jvm虚拟机总结

看了很多网上的博客,来写一下我自己的理解

jvm主要结构

jvm虚拟机总结jvm虚拟机总结jvm虚拟机总结
jvm虚拟机总结
网上找的图,可以看出jvm主要包含图中灰色背景的5个部分,绿色的堆和方法区是所有线程共享的,橙色的java栈、本地方法栈和程序计数器三个部分是线程私有的,也就是说每个线程都会有这三个部分。

程序计数器

表面上看是计数用的,也不完全是,下面说说我的理解。每个线程运行的过程jvm会翻译成一系列指令,程序计数器用于记录线程执行到哪一条指令的状态,现在电脑大是多核cpu,用时间轮询的方法执行多个线程,我们看来是并行执行的,当线程的时间片用完的时候,这时候应该暂停当前线程,并给下一个线程分配时间片,这时候程序计数器用来记录这一暂停状态的,等待下次分配到了时间片以便从上次暂停的地方继续向下执行指令。

java栈(虚拟机栈)和本地方法栈

这两个栈的功能是一样的,区别就是本地方法栈是执行本地方法时使用的,因为之前做过安卓我知道的本地方法就是c语言写的方法用native修饰,虚拟机本身要用到这部分的方法。
java栈是没有线程都各自有的一块内存空间,记录了函数的运行过程,我理解的基本单位就是栈帧,也就是说一个函数调用则对应一个栈帧入栈,一个函数返回也就对应着一个栈帧出栈,每个栈帧中包含了局部变量表,操作数栈和帧数据区。现在暂时理解到了栈帧中包含了函数中的局部变量的引用指向堆中和函数的参数。
jvm虚拟机总结


堆就是用来存放对象实例的地方,垃圾回收也是回收这部分内存。堆的结构又是这个样子的:
jvm虚拟机总结
分为新生代和老年代,新生代又分为Eden区和s0s1区(拯救区)。新创建的对象会在Eden区,经过新生代的回收之后存活下来的对象会进入s0或者s1区,再经过多次回收如果还存活就放入老年代中。

jvm虚拟机总结

这里还形象的画出不同区域的配置参数的方法

控制参数
-Xms设置堆的最小空间大小。

-Xmx设置堆的最大空间大小。

-XX:NewSize设置新生代最小空间大小。

-XX:MaxNewSize设置新生代最大空间大小。

-XX:PermSize设置永久代最小空间大小。

-XX:MaxPermSize设置永久代最大空间大小。

-Xss设置每个线程的堆栈大小。




方法区

用于存储虚拟机加载的类信息、常量、静态变量、及时编译器编译后的代码数据。这是博客中的描述,重要的一点是常量池在这个地方。这个部分是平常说的永久代(在HotSpot Jvm下是这样的)。
jvm虚拟机总结
上图中最后的部分是永久代,也就是方法区部分,上面的部分是堆,两个部分都是线程共享的。

附上一张堆、方法区、java栈的关系图,很直观

jvm虚拟机总结

垃圾回收算法

标记清除算法:标记出需要回收的对象,在标记完成后回收掉所有标记的对象。这个算法存在的问题就是导致内存空间碎片太多,会导致后面的对象无法找到连续的存储空间。
jvm虚拟机总结
复制算法:将内存分成两个大小相同的部分,就是上面说到的s0和s1区,每次只是用一个部分,当前部分满了,将还存活的对象复制到另一块内存中,并清空当前内存。解决了产生空间碎片的问题,缺点就是需要一块辅助内存的空间。
jvm虚拟机总结
标记整理算法:和上面的算法一样先标记要清除的对象,把存活的对象向内存的一端移动,清理移动后存活对象边界意外的区域。这样就不需要一块辅助内存空间进行回收。
jvm虚拟机总结

针对于堆中的不同区域,用不同的算法进行垃圾回收。新生代会进行频繁的回收,每次回收都会存活少量对象,这里用的是复制算法。老年代中对象存活率高,回收不频繁、没有额外的辅助空间用标记整理算法。

垃圾收集器

串行收集器:只有一个线程进行回收,回收的过程会产生停顿(stop the world)
参数控制:-XX:+UseSerialGC
jvm虚拟机总结
并行收集器:串行收集器的多线程版本,回收时还是会有停顿
参数控制:-XX:+UseParNewGC  ParNew收集器
         -XX:ParallelGCThreads 限制线程数量
jvm虚拟机总结
并发收集器:垃圾回收线程和影响程序线程同时进行,垃圾回收的过程不会产生停顿,适应于对响应时间要求高的场景。
参数控制:-XX:+UseParallelGC 


cms收集器:是一种并发收集器,主要过程下图六个步骤。下图看出初始标记和重新标记的时候依旧会产生停顿,但是比起上面的停顿来说这个停顿可以忽略。
初始标记,只是从GCRoot关联直达对象,只关联一层,所以会很快
并发标记,通过关联对象扫描全部对象进行标记
重新标记,修正并发标记时,程序运行产生的修改

jvm虚拟机总结
G1收集器
特点:使用标记整理算法、可以预测停顿、同样是分代回收。
将堆分为大小相等的若干个区域,还是保留了以前的分代收集。
年轻代回收和老年代回收都是用了标记整理的算法进行回收的。

cms收集器和G1收集器可以参考这篇博客,这里讲的很详细。


jvm运行过程

jvm虚拟机总结
如上图所示:jvm运行分为3个部分,类加载器、运行时数据区和执行引擎部分,上面所总结的是运行时数据区。

类加载的过程

jvm虚拟机总结
类加载的过程包括了上图的加载、链接(验证、准备、解析)、初始化五个阶段
加载:1.通过类的全路径名得到类的Class文件
          2.将Class文件中的静态存储结构(常量、静态变量)转化为方法区运行的数据结构。
          3.在堆中生成这个类的Class对象
链接:1.验证:验证加载类的正确性。确保加载类的符合javac编译规范
          2.准备:为类的静态变量分配内存,并赋默认值(这里不是赋值,是赋默认值static int a = 10 此时给a赋值为0)
          3.解析:将Class中的符号引用换为直接引用(符号引用是Java文件被编译时,在不清楚被引用类的实际地址,用一串符号来暂时代替被引用类的地址,直接引用则是直接指向被引用类的地址,引用是和地址是有区别的先这样理解)
初始化:给类中的静态变量赋初始值这时候a的值才是10
          

类加载的过程一定会用到类加载器,类加载器可以分为两种,一种使可以被Java程序调用的(扩展类加载器和应用类加载器),一种使不可以被Java程序调用的属于jvm私有的加载器(启动类加载器)。

jvm虚拟机总结
不同的加载是加载不同路径下的类到jvm内存中
启动类加载器: 加载JAVA_HOME\lib 目录中的类库
扩展类加载器: 加载JAVA_HOME\ext目录下的类库
应用类加载器: 加载Java应用程序的类
类加载的过程是用双亲委派模型,具体过程是:类加载器要加载某个类时,首先提交给父类让父类去加载,父类再向他的父类提交上去,也就是最终提交到启动类加载器来加载,当启动类加载器无法完成加载的时候会向下传递,有他的子类尝试加载,如果还是完成不了的话则继续向下传递。这样会避免同一个类被加载多次,在堆中产生不同的Class对象。

我们自己写的类时不会和Java类库都加载道jvm中的,是在创建这个类的对象的时候才会加载,在主动引用该类对象的时候才会进行加载操作
主动引用
  1.new一个对象
  2.调用类的静态成员和静态方法(不包含静态常量)
  3.通过反射进行调用
  4.初始化一个类,若父类没有被初始化,则先初始化父类
  5.虚拟机启动,main方法所在类被提前初始化
被动引用
  1.子类调用父类的静态变量,只有父类初始化。子类不初始化
  2.通过数组定义引用
  3.final修饰的变量

Java类初始化顺序:父类静态变量-->父类静态代码块-->子类静态代码块-->父类静态变量-->父类非静态代码块-->父类构造函数-->子类非静态变量-->子类非静态代码块-->子类构造函数

参考博客:

java类的加载机制

jvm内存结构

垃圾回收算法

jvm基本结构详解

jvm相关知识整理

jvm架构解析