JVM简单总结

2020年6月30日
JVM简单总结插图

本文出自明月工作室:https://www.freebytes.net/it/java/jvm-simplesum.html

jvm内存区域

JVM简单总结插图

1、程序计数器:每个线程都有自己的一个程序计数器,记录所执行的字节码的行号。

2、虚拟机栈:每个线程独有的,生命周期与线程相同。一个方法被执行的时候就会创建一个栈帧,方法的执行至结束对应着栈帧的入栈至出栈的过程。

3、本地方法栈: 与虚拟机栈作用类似,但它只为本地native方法服务。

4、堆:在虚拟机启动时创建,被所有线程共享,存放对象的实例,堆中可细分为新生代和老年代,默认比例为1:2。新生代中又细分为eden区和from survivor、to servivor区,默认比例是8:1:1。

5、方法区:方法区被所有线程共享。存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等。元空间(jdk8)和永久代(jdk8以前)都是Hotspot虚拟机对方法区的实现。 元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。

6、运行时常量池:一般存放编译期生成的各种字面量和符号引用。

对象存活的判断

垃圾收集器在对堆进行回收前,先确定对象是否存活。   

引用计数法:    在对象中添加一个引用计数器,每有一个地方引用它时,计数器+1,当引用失效时,计数器-1。任何时刻计数器都为0的对象就是不可能再被使用的。但这种算法无法解决对象之间互相引用的问题。

跟搜索算法( 可达性分析算法 ):即是通过一系列GC ROOTS对象作为起始点,向下搜索,走过的路径叫做引用链,没有被引用链相连的对象被判定为可回收对象。

GC ROOTS :在java 中,可判定为GC ROOTS的对象为:
1.虚拟机栈中引用的对象。
2.方法区中类静态属性引用的对象。
3.方法区中的常量引用对象。
4.本地方法栈中JNI引用的对象。

垃圾收集算法:

1、标记-清除算法:从 根集合(GC ROOTS)向下搜索,没有与任何引用链相连的对象,被标记为可回收对象。在标记完成后统一对被标记对象进行清除工作。

但是这个算法,标记和清除的效率都不高,而且容易导致大量不连续的内存碎片,使得再下次分配大内存对象时,因找不到足够的连续内存空间而不得不进行一次gc。

2、复制算法:将内存分为两等份,一份作为对象分配区,一份作为空闲区,当对象分配区内存接近饱满,就将还存活的对象复制到空闲区,清理分配区的所有对象至此分配区和空闲区互换。

但是这样划分内存区域的代价太大,一般会将内存做更加细致的划分。所谓新生代中eden区和两个survior区的划分也是基于复制算法。

3、标记-整理算法:与标记-清除算法相似,但是在标记之后,不是直接清除,而是将所有存活对象向一端移动,然后直接清理掉端边界之外的内存。

4、分代收集算法:将内存划分为新生代和老年代,对象存活率较低的新生代使用复制算法,复制成本较低。老年代中对象存活率较高,使用标记-清除或者标记-整理算法。

Eden区和survivor区

新生代和老年代的默认比例是1:2 ;

eden和两个survivor(from和to)区分为默认的8:1:1比例;

当对象分配时,进入eden和from区,进行gc时,将存活对象复制到to区,清理eden和from区,再交换from和to区,保证to区为空,如此反复。

每经过一次gc,to区的对象年龄+1,到达阈值后会放入老年代,太大的对象不会进入to会直接进入老年代。to区满了就会将全部对象放入老年代。

为什么需要两个幸存区?

之所以要分配两个survivor区,其实主要还是为了避免在造成内存空间的碎片化。

如果只有1个eden区和1个survivor区(假设是from区),那么第一次gc时,from区为空,进行第一次gc后,enden区的存活对象复制到from区。

在第二次gc时,eden和from都不是空的,都有对象需要被清除。eden区的存活对象依然被复制算法复制到from区;但是from区本身却只能使用标记清除算法清除无法存活的对象,因此会变得内存空间不连续,可能造成下一次gc的提前到来。

当然from区也可以使用标记整理算法,这样子确实不需要两个幸存区,但是年轻代的对象回收,规范上来说本就是基于复制算法的,不应出现标记清除和标记整理算法。

而如果有两个幸存区,from区和to区,那就在第二次回收时,就可以直接将eden和from区的存活对象复制到to区即可,清除from区和eden区,这样就不会造成内存空间碎片化。

GC类型

minor gc:发生在年轻代(eden)的gc,频率较频繁。

major gc:发生在老年代的gc。

full gc:清理年轻代和老年代。触发场景:

  1. 调用System.gc()
  2. 方法区空间不足
  3. 老年代空间不足