Bootstrap

Java成神之路——JVM垃圾回收概览

如何确认对象可以被回收

枚举根节点,来确认, 搜索对象的引用链. 当一个对象的引用不能到达根节点,那么就认为这个对象是垃圾.

根节点可以为: 虚拟机栈中引用的对象,方法区中类静态属性引用的遍历,方法区中常量引用的对象,本地方法栈中JNI 也就是native方法 引用的对象


常见的垃圾回收算法

标记清除:

首先标记出需要回收的对象,然后进行清除,效率不高,会产生大量的内存碎片,内存碎片太多可能在分配大对象时找不到连续的内存空间从而提前导致提前gc。

清理前:
在这里插入图片描述
标记清除后:
在这里插入图片描述

标记整理:

标记出需要回收的对象,所有存活的对象向一端移动,然后清理掉端边界外的内存,避免内存碎片的产生。

清理前:
在这里插入图片描述
标记整理后:
在这里插入图片描述

复制算法:

将内存分为两块,每次只使用其中的一块,当其中的一块用完了,把存活的对象复制到另一块,然后清除此块内存。复制算法在对象存活率较高的情况下回进行较多的复制,效率变低。

清理前:
在这里插入图片描述
复制算法清理后:
在这里插入图片描述

分代收集:

一种综合的方式, 根据生命周期的不同,将堆分为新生代和老年代,新生代中容易产生垃圾,采用复制算法,每次只需要少量复制即可,老年代对象存过概率高,采用标记清除或标记整理

回收算法优缺点

标记清除缺点: 标记和清除两个过程的效率都不高,另一个是空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致之后再程序运行过程中需要分配大对象的时候,无法找到足够的连续内存而不可不提前触发一次垃圾收集动作.

标记整理优缺点: 不会产生内存碎片,但是整理需要花费额外的时间

复制算法优缺点: 没有内存碎片,但原有的内存缩小了一半(可优化调整)


Stop the world

在进行垃圾回收,枚举根节点的时候,对象之间的引用必须在一致性快照中(保证关系引用不能还是在变化的),如果不满足这点要求那么就无法保证,准确的回收垃圾.这就导致GC进行时必须停顿所有的java执行线程。停止java执行线程称为Stop the world。

安全点与安全区域

在进行GC的过程中,当执行系统停顿下来后,并不需要对上下文和全局的引用位置进行检索,而是可以直接找到那些地方存在对象的引用,通过一种OopMap的数据结构来实现,OopMap记录,栈和寄存器那些位置是应用。在生成本地代码时就插入一些记录Oomap指令记录对象引用

安全点

很多命令都可能导致引用的变化,不可能每执行一次这种指令就再记录一下OoMap,那么将会占用大量的额外空间,所以OoMap数据结构存储在一个特殊的点,这个存储的位置就是安全点,程序执行到达安全点停止,GC通过OoMap寻找垃圾,进行回收.

如何让所有线程都在安全点停下来?

主动式中断,设置一个标识,各个线程执行时不断轮询这个标志,发现标志时就自动挂起,轮询标志的地方和安全点重合。

安全区域

当线程处于Sleep状态或Blocked状态, 这时线程无法响应JVM的请求,走到安全点去中断.安全区域是指在一段代码片段之中,引用关系不会发生变化.在这个区域的任意位置开始GC都是安全的.
当线程执行到安全区域时候标识一下,这样在GC的时候就不管在安全区中的线程了,当线程要离开安全区的之前先检查一下GC是否完成,如果完成继续执行,否则等待完成再继续执行。

垃圾收集器

垃圾回收器适用区域与可用搭配如下:在这里插入图片描述

Serial

新生代收集器 复制算法

单线程收集器,只使用一个cpu或一个线程,在清理过程中,其他工作线程暂停,Stop the world.简单而高效,在单个CPU情况下,由于没有多线程切换的开销,往往更高效.

Parnew

新生代收集器 复制算法

其实就是Serial的多线程版本

Parallel Scavenge

新生代收集器 复制算法

采用复制算法,才用多线程,它与Parnew的区别在于关注的点不同,Parallel Scavenge 目是达到一个可控制的吞吐量,保证用户代码运行时间

吞吐量= 运行用户代码时间/(运行用户代码时间+垃圾收集时间)

如果,虚拟机运行总时间为 100分钟, 运行客户代码时间为 99,垃圾回收为 1, 那么吞吐量就是 99%

常用可控参数

-XX:MaxGCPauseMillis (最大垃圾收集停顿时间,过小会到时频繁GC)

-XX:GCTimeRatio 大于0小于100的整数, 垃圾收集时间占总运行时间的比值.

-XX:UseAdaptiveSizePolicy 开关参数,打开后无需指定新生代大小,老年代大小,晋升老年代对象大小等细节参数,虚拟机根据系统运行情况自动调节

Serial Old

老年代收集器 使用标记整理算法

Serial 收集器的老年代版本, 使用单线程

Parallel Old

老年代收集器 使用标记整理

Paralle 的老年代版本多线程

CMS收集器

老年代收集器

Concurrent Mark Sweep, 以获取最短回收停顿时间为目标的收集器,基于标记整理实现

整理过程分为4个步骤:

  1. 初始化标记(CMS initial mark)
  2. 并发标记(CMS concurrent mark)
  3. 重新标记(CMS remark)
  4. 并发清除(CMS concurrent sweep)

在这里插入图片描述
初始化标记,重新标记任然需要 Stop the world

cmd 收集器三个特点:

  1. 对cpu资源非常敏感,在并发阶段,虽然不会停止用户线程,但是会因为占用了一些线程资源,导致应用变慢,总吞吐量降低.
  2. 无法处理浮动垃圾,在并发清理阶段,应用程序还会产生垃圾,这些垃圾是没有标记过的需要等到下一次gc才能清理,因此 cms 收集器不能等到老年区满了再开始回收,在到一定阈值时就开始回收1.6默认为92%,如果在清理期间内存不足,那么jvm会启动serial old收集器来收集,这样停顿的时间更长,所以需要合理的设置阈值
  3. 最后一个缺点,CMS基于标记清除,收集过会产生内存碎片,在CMS内存碎片过多时,JVM进行fullGC(清理整个堆)进行内存碎片整理,这个过程也将造成用户线程停顿

-XX:CMSInitiatingOccupancyFraction 设置阈值(老年区内存用到百分之多少进行垃圾回收)

G1收集器会单独再写一篇博客


咸鱼IT技术交流群:89248062,在这里有一群和你一样有爱、有追求、会生活的朋友! 大家在一起互相支持,共同陪伴,让自己每天都活在丰盛和喜乐中!同时还有庞大的小伙伴团体,在你遇到困扰时给予你及时的帮助,让你从自己的坑洞中快速爬出来,元气满满地重新投入到生活中
;