目录
-
引用计数法
-
可达性分析算法
-
可达性分析算法实现方式
-
堆对象回收
-
方法区回收
-
分代收集理论
-
标记-复制算法
-
标记-清除算法
-
标记-整理算法
-
Serial收集器
-
Parallel Scavenge 收集器
-
ParNew收集器
-
CMS收集器(老年代)
判断对象为可回收状态的方法
引用计数法
引用计数法实现简单,并且效率高,原理是在对象中添加一个引用计数器啊,当有对象引用到它时,计数器加1,当不引用时,计数器减1。在GC时,只需要判断引用计数器是否为0,就可以判断该对象是否能被回收。
引用计数法并不被主流虚拟机使用,原因是很难解决对象循环引用的问题,两个对象互相引用导致引用计数器一直不为0,使得GC无法回收这些对象。
可达性分析算法
把GC Roots
对象作为起点,从这些节点向下查找引用对象,这些对象都标记为非垃圾对象,其余非标记对象即为垃圾对象。
可以作为GC Roots
对象:
-
线程栈局部变量表的对象 -
静态变量 -
本地方法栈变量
可达性分析算法实现方式
作为GC Roots的对象会很多,如果在GC前进行扫描会消耗很多时间。JVM采用了OopMap数据结构保存GC Roots对象,当需要GC时,直接从OopMap中获取即可。
当线程运行到safe point时,会保存GC Roots对象到OopMap中。
当需要GC时,GC线程会等待所有用户线程到达safe point后,再进行回收,这样才能保证OopMap是最新的。
如何让用户线程到达safe point?
-
抢断中断:先暂停所有用户线程,如果发现线程没有到达safe point,则重新运行直到safe point为止 -
主动中断:设置一个标志位,线程运行时轮询直到为真,就表示运行到safe point(主流使用)
如果GC时线程处于wait或者block状态,则无法主动中断。JVM引入了safe region(安全区)。安全区是指代码片段中,引用关系是不变的,在这个区域任何位置GC都是安全的。当用户线程执行到safe region的代码时,会标注自己进入了安全区,如果线程要离开安全区,则需要等待GC完成。
通过暂停用户线程、安全点和安全区机制,保证了JVM在GC时,GC Roots引用关系不变,可以获取最新的OopMap。
垃圾回收过程
堆对象回收
可达性分析算法会经历两次标记过程,第一次标记垃圾对象会判断对象是否执行finalize
方法,如果没有覆盖,对象直接回收。
如果覆盖了finalize
方法,则可以在finalize
方法中与GC Roots
对象建立引用链逃脱回收。
方法区回收
方法区回收主要回收两个部分:
-
无用的类 -
无用的常量,例如如果一个字符串在常量池中,但没有 String
对象引用常量池中的该字符串,则会被GC回收
如何判断一个类是无用的类?
-
类的所有实例已经被回收 -
加载类的 ClassLoader
被回收 -
类对应的 Class
对象没有被引用(即无法通过反射创建类)
GC算法
分代收集理论
一般把Java堆分为新生代和老年代,根据两代的特点选择不同的GC算法进行回收。
-
新生代,每次GC会有大量的对象被回收,所以可以选择复制算法,效率高 -
老年代,对象存活几率高,使用标记-清除或者标记-整理算法等进行回收
标记-复制算法
将内存分为大小相同的两块,每次只使用其中的一块,当GC时,将存活的对象移动到另外一块可用内存,清除掉使用的这块内存。
-
优点:快 -
缺点:浪费一半内存
图示:

标记-清除算法
标记-清除算法分为两个步骤:
-
标记:标记存活的对象 -
清除:统一回收未被标记的对象
这种算法有两个明显的问题:
-
效率问题,需要标记的对象太多,效率低 -
空间问题,清除后产生不连续内存碎片
图示:

标记-整理算法
根据老年代特点的标记算法,分为三个步骤:
-
标记,标记存活对象 -
整理,所有存活对象挪到一边 -
清除,清除掉边界外的内存
图示:

垃圾收集器
Serial收集器
JVM参数:-XX:+UseSerialGC -XX:+UseSerialOldGC
Serial收集器是单线程收集器,收集过程中会STW。
分代算法使用:
-
新生代:复制算法 -
老年代:标记-整理算法
图示:

Parallel Scavenge 收集器
JVM参数:-XX:+UseParallelGC -XX:+UseParallelOldGC
Parallel收集器是Serial收集器的多线程版本,使用多线程进行垃圾回收。默认的收集线程数和CPU核数相同(可以通过-XX:ParallelGCThreads
修改)。
分代算法使用:
-
新生代:复制算法 -
老年代:标记-整理算法
图示:

ParNew收集器
JVM参数:-XX:+UseParNewGC
与Parallel Scavenge收集器原理一样,除了Serial收集器外,只有ParNew收集器可与CMS收集器配合使用。
CMS收集器(老年代)
JVM参数:-XX:+UseConcMarkSweepGC(old)
CMS(Concurrent Mark Sweep)收集器是并发的标记清楚算法实现,可以基本让用户线程和GC线程并发工作,产生较短的STW,注重用户体验。
分为四个步骤:
-
初始标记:STW并记录GC Roots直接引用的对象 -
并发标记:三色标记法遍历GC Roots,耗时长,但不会STW(由于用户线程同时运行,可能会导致对象状态发生改变) -
重启标记:STW并记录并发标记过程中产生的对象状态变更,比初始阶段耗时长,但远远低于并发标记时间 -
并发清理:并发清理内存空间,这个阶段新增对象不做任何处理 -
并发重置:重置GC过程中的标记数据
图示:

CMS收集器特点:
-
并发收集 -
低停顿
CMS收集器缺点:
-
会和用户线程抢CPU资源 -
无法处理浮动垃圾(并发标记和并发清理阶段产生的垃圾),浮动垃圾等待下次GC清理 -
产生内存碎片,可以使用 -XX:+UseCMSCompactAtFullCollection
参数使用整理算法 -
执行过程不确定,有可能一次GC还没做完,又触发了GC(并发标记和并发清理阶段出现),触发 concurrent mode failure
错误,此时STW并使用Serial Old收集器
参数:
-XX:+UseConcMarkSweepGC
:启用cms-XX:ConcGCThreads
:并发的GC线程数-XX:+UseCMSCompactAtFullCollection
:FullGC之后做压缩整理(减少碎片)-XX:CMSFullGCsBeforeCompaction
:多少次FullGC之后压缩一次,默认是0,代表每次FullGC后都会压缩一 次-XX:CMSInitiatingOccupancyFraction
: 当老年代使用达到该比例时会触发FullGC(默认是92,这是百分比)-XX:+UseCMSInitiatingOccupancyOnly
:只使用设定的回收阈值(-XX:CMSInitiatingOccupancyFraction
设 定的值),如果不指定,JVM仅在第一次使用设定值,后续则会自动调整-XX:+CMSScavengeBeforeRemark
:在CMS GC前启动一次Young GC,目的在于减少老年代对年轻代的引 用,降低CMS GC的标记阶段时的开销,一般CMS的GC耗时 80%都在标记阶段-XX:+CMSParallellnitialMarkEnabled
:表示在初始标记的时候多线程执行,缩短STW-XX:+CMSParallelRemarkEnabled
:在重新标记的时候多线程执行,缩短STW;
参考资料
-
CMS垃圾收集器 -
Java JVM的引用计数和可达性分析垃圾收集算法 -
JVM源码分析之安全点safepoint -
JVM可达性分析算法是怎么实现的?
原文始发于微信公众号(erpang coding):JVM内存回收
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/37325.html