本篇为JVM中垃圾回收相关知识总结
Java语言实现了自动的垃圾回收机制,使得程序员在编写程序的过程中可以将精力更多地放在业务逻辑的实现上,而不用时刻关注内存的使用与回收。这里主要总结下JVM内存中堆区对象的回收。
对象的存储与堆区的划分
按照垃圾回收机制中常用的分代回收算法可以将堆中的内存区域简单划分为年轻代(Young区),老年代(Old区),永久代(Perm区):
在JVM 中,新创建的对象存放在Eden区中,Old区存放的是Young区触发minor GC后仍然存活的对象或者Young区连续空间无法放置的较大的对象。
垃圾标记的算法
1.引用计数器算法
在对象被创建时,会在堆中为对象分配内存空间,同时产生一个引用计数器并且计数器加1,当有新的该对象的引用,则计数器加1;而当一个引用销毁的时候,引用计数器减1,当引用计数器为0表示该对象无引用,可标记为垃圾对象并且可回收了。
但是会出现一种比较bug的情况是:当两个对象相互引用,并且没有其他引用,此时两个对象计数器都为1,但对象却是没有额外引用的垃圾对象,导致回收不了这两个垃圾对象。
2.根搜索算法
只要某个对象不再被其他活动对象引用,则该对象就为垃圾对象,可以被回收。那么,如何判断一个对象是否为活动对象?能够被一个根对象集合到达的对象称之为活动对象。如图所示,除了g,h,i,其他对象都为活动对象,h虽然被g引用,但也到达不了根集合,所以也是垃圾对象,可被回收。
那么JVM中如何定义根集?
(1).在方法中局部变量区的对象的引用;
(2).在java操作栈中的对象引用;
(3).在常量池中的对象引用;
(4).在本地方法中持有的对象引用;
(5).类的Class对象
垃圾回收的算法
此部分所述垃圾回收算法,均是基于垃圾标记中的根搜索算法而来。
1.标记-清除算法
标记-清除算法从根集合开始扫描,将引用的对象进行标记,未被标记的对象则为非存活对象即垃圾对象;如图所示,对象B没有从根集的引用,因而没有被标记,在进行GC的时候,会将该对象回收。
2.复制算法
复制算法从根集合开始扫描,将存活的对象复制到一块没有使用过的内存区域中,并将原来的内存空间释放。
3.标记-整理算法
标记-整理算法与标记-清除算法相似,其是在标记-清除算法的基础上,再增加一个步骤,即将内存空间回收后,会将所有的存活对象向左边空闲区域移动,并相应地更新其指针。
综合以上来看,标记-清除算法的效率较标记-整理算法来看,是比较高的,缺点是会产生内存碎片,而标记-整理算法有一个对内存的整理过程,不会产生内存碎片问题;标记-清除算法是标记存活对象,将非存活的对象清除,故而当非存活对象较少时,其效率较高,而复制算法是将存活的对象进行复制,所以在存活的对象较少时,其效率更高,适合存活对象较少的内存区域,如Eden区。
垃圾回收器
目前常用的垃圾回收器有如下几种:
- 串行垃圾回收器(Serial Garbage Collector)
- 并行垃圾回收器(Parallel Garbage Collector)
- 并发标记扫描垃圾回收器(CMS Garbage Collector)
- G1垃圾回收器(G1 Garbage Collector)
这里只简单介绍前3种垃圾回收器,G1为商用收费回收器。
1.串行垃圾回收器(Serial)
串行垃圾回收器顾名思义,在JVM做垃圾回收时,由于是串行的,所以是单线程的,在JVM中其他应用程序会全部暂停。
2.并行垃圾回收器(Parallel)
并行垃圾收集器可以看作多线程的Serial收集器,并行回收的线程数可以通过-XX:ParallelGCThreads来指定,如果CPU和核数小于8,线程数可以和核数一样,如果大于8,值为 3+(cpu core*5)/8。
3.并发垃圾回收器(CMS)
相较于前两种垃圾回收器,CMS垃圾回收器比较难以理解,在此作稍详细的介绍。
首先CMS垃圾回收器分为几个阶段:
初始标记 ——>并发标记——>预清理——>重新标记——>并发清理——>并发重置
首先,初始阶段两个主要任务:扫描从根集合直接到老年代的对象; 扫描从新生代到老年代的对象。那之后还有老年代到老年代的对象;
并发标记阶段,注意该阶段是GC线程和用户线程并发,扫描老年代到老年代对象,扫描新晋升的老年代,直接在老年代,老年代引用关系变化的对象,标记为dirty;
到预清理阶段,标记新的Eden区对象A到老年代B的对象,标记B为活对象;扫描dirtyCard区,进行对象标记;
重新标记阶段,对Eden区,DirtyCard,GCRoot 3个途径重新扫描并标记,重新标记阶段没有扫描老年代区域,这应该是并发标记响应时间优先的关键所在;
最后进行并发清理和并发重置。
年轻代和老年代对应的垃圾回收器
年轻代所采用的收集器有:
Serial收集器
ParNew收集器
ParallelScavenge收集器
Serial收集器对CPU消耗不高,但会阻塞用户线程,适用用户交互少的系统;ParNew收集器可以看作多线程版Serial,在多CPU模式下适用,但注意在单CPU下,该收集器效率低于Serial。ParallelScavenge收集器,是吞吐量优先的收集器,即用户程序运行时间占用总时间(用户程序运行时间+GC时间)的比重要大,当今多核并行云计算的发展要求程序尽可能的使用CPU和内存资源,尽快的计算出最终结果,因此在交互不多的云端,比较适合使用该回收器。
老年代所使用的收集器有:
SerialOld收集器
ParallelOld收集器
CMS收集器
SerialOld是client下老年代的默认收集器,也是CMS回收失败后的备用收集器;ParallelOld收集器是多线程执行的回收器;CMS收集器在并发处理内存回收,保证了用户线程的响应实时性,但会产生内存碎片,需要注意内存的压缩整理。
下图中展示了新老年代对应的回收器,并表明了不同年代回收器可组合使用的关系,两个回收器之间用直线相连表示可以组合使用。