关于JVM学还是不学

JVM学习

Posted by ohcomeyes on July 20, 2018

前言

说起JVM大多数给予的回应是JAVA虚拟机,是JAVA语言跨平台的武器。而且我们在开发中遇到和JVM有关的问题基本上就是OutOfMemoryError(内存溢出),然后一般解决方法就是找到相关的代码进行优化或者调整-Xms(初始内存)和-Xmx(最大能够使用内存大小)。尤其现在很多语言、框架概括来说就是以特有的方式抽象出来,隐藏具体的细节实现。那关于底层的这块学还是不学呢?

误区

其实很多人在谈论到JVM的时候没有一个很清晰的认识,而且不常用到,所以会有要不要学的必要! 一方面觉得接触到底很厉害,一方面又觉得用不着,不是有句话说,不以结婚为目的谈恋爱都是在耍流氓。其实是一个道理,所有的底都是在为上层服务,学不学取决于工作和个人,如果单纯只是觉得学了很厉害,这个就是在扯蛋了,毕竟就算学完JVM还有操作系统核心以及cpu指令这些更底层的东西。 所有建议要了解JVM,但根据实际情况再考虑要不要深究。

掌握的好处

  • 更清晰:目前很多高级语言都是解释型语言,但谈到JAVA的时候就可以说是解释与编译并存语言,JVM中解释器以及即时编译器就可以回答这个问题。
  • 更理解:理解动态编译和静态编译的区别,我们都知道常通过 javac 将程序源代码编译,转换成 java 字节码,JVM 通过解释字节码将其翻译成对应的机器指令,逐条读入,逐条解释翻译,JVM JIT(即时编译编译器)会把翻译过的机器码保存起来,以备下次使用,提高执行效率。
  • 更高效:能够清楚Java应用在运行时堆的布局情况,观察gc频率优化内存的分配策略,通过调整JVM相关参数提高Java应用的性能。
  • 更明白:知道Java程序是如何执行的,为什么具备可移植性强的特性。

JVM架构图

JVM = 类加载器(Classloader) + 执行引擎( Execution Engine) + 运行时数据区域 (Runtime Data Areas)

JVM-Architecture

如果看的迷糊,就先看看翻译过来的吧,建议相关的计算机单词还是要掌握的

JVM架构图

类加载器(Classloader)

  1. 装载:查找和导入class文件 查找的时候通过启动类(classpath下)、扩展类(jre\lib下)、应用程序类(自定义的类),加载时会有委派分层算法,通常我们见到的就是双亲委派模型。 双亲委派:每次通过先委托父类加载器加载,当父类加载器无法加载(没有)时,再自己加载。源码ClassLoader类的loadClass方面就默认实现,感兴趣的可以去看看,要说有啥作用,其实个人理解一方面避免了重复加载,一方面安全一些,好比自定义String类动态加载替换了核心类,那要是在里面植入不安全代码怎么处理。

  2. 链接:把类的二进制数据合并到JRE中
  3. 校验:检查载入class文件数据的正确性
  4. 准备:给类的静态变量分配存储空间,并设置默认值
  5. 解析:将符号引用转变成直接引用
  6. 初始化: 最后阶段,所有的静态变量将被分配原始值,静态块将被执行。

Java的动态类加载功能由类加载器处理的,有两种装载class的方式

  • 隐式:new方式生成对象时,隐式调用classLoader到JVM
  • 显式:通过class.forname()动态加载

运行时数据区域 (Runtime Data Areas)
主要被分为5个模块

  1. 方法区:各个线程共享的内存区域,所有的类级数据将被存储在这里,包括静态变量,通过class对象调用方法获取信息的时候,那些数据都是来自这里,每个JVM只有一个方法区。
  2. 堆区:各个线程共享的内存区域,存储对象实例以及数组值,由于方法区和堆区共享多个线程的内存,所以存储的数据不是线程安全的,每个JVM只有一个堆区。由后面说到的GC自动管理,堆区会分成两块:新生代New Generation和旧生代Old Generation,可以动态扩展,像上面说的内存溢出啊,设置这个(-Xms和-Xmx)。 堆区

  3. 栈区:每个线程都会创建一个独立的栈区,当每个方法调用时会在栈区创建一个栈帧(条目),用于存储局部变量表、操作栈、动态链接、方法出口等信息,方法的调用到执行完成可以看成是以栈帧为单位,进行压栈和出栈,因为不是共享内存,所以是线程安全的。看图:

栈区

  1. 程序计数器(PC):每个线程都有一个独立的PC,可以理解为PC是执行的字节码指令的指示器,告诉你下一条指令是哪个,PC是存储在计算机处理器的内部,而且JAVA应用是控制不了的,但是字节码解析器可以通过控制PC的值来选择下一条要执行的字节码指令,这样就能完成分支、循环、跳转、异常处理、线程恢复等基础功能。
  2. 本地方法栈:保存了本地方法信息,和栈区一样,每一个线程,将创建一个单独的本地方法栈。为本地(Native)方法服务。

执行引擎( Execution Engine)
执行运行时数据区 (Runtime Data Areas)分配的字节码,或者执行本地方法。

  1. 解析器:解析字节码
  2. 即时编译编译器(JIT):提高性能的东西,因为每个方法重复调用的话解析器需要重新去解析,而解析器解析字节码发现重复的时候可以丢给JIT,JIT来把它生产本地方法,供重复调用。里面的分析器就是一个探查器,用来发现某个方法被多次调用或者未被调用。
  3. 垃圾回收器(GC):这个东西大家应该听的挺多的把,就是用来内存回收的,主要是管理堆区,针对堆区的新生代和旧生代都由不同的回收机制。具体的回收技术感兴趣的可以去了解一些,比较高级,可以针对不同环境选择不同机制。
  4. 本地方法接口(JNI):JNI将和本地方法库相互作用,为执行引擎提供需要。
  5. 本地方法库:执行引擎所需的本地库的集合。

结语

关于里面一些详细以后再慢慢补充吧,毕竟对整个的架构有了一定了解后,对个人在设计方面以及在优化方面还是有帮助的~
个人博客~
简书~