JVM java虚拟机-垃圾收集

引用计数法

对象增加一个引用计数加1,失去减1
判断对象是可以被回收
1.引用计数为0
2.无法链接到“GC Roots”对象,GC Roots对象有:静态常量,静态对象,native对象,虚拟机栈对象(因此两个对象a,b如果互相引用,然后a=null,b=null,会被判定可以被垃圾回收;不需要stop the word,因为OopMap)

四种引用类型

1.强引用:即一般普遍的引用
2.软引用:用SorftRefernce实现,在内存即将用完时,会被清理,使用场景网页缓存、图片缓存
3.弱引用:弱引用通过WeakReference类实现,在垃圾收集器工作时都会被清理,
4.虚引用: 通过PhantomReference类来实现,无法通过虚引用来获得实例,不能单独使用,唯一目的是在对象被垃圾回收时可以收到通知
JVM java虚拟机-垃圾收集

垃圾收集算法

标记清除算法

算法分标记和清除两个阶段。首先标记要清除的对象,在标记完成后同一清除。
方法缺点:1.效率低,2.会产生大量碎片,以后存储大对象不方便

复制算法:

把内存分成大小相等的两块,当一块用完把,把没有被删除的对象移动到另一块,余下的全部删除。一般用于新生代的算法。
方法有点:没有碎片;缺点:因为分成了两块,浪费内存资源,对象存活率高时,复制操作较多

标记-整理算法

和标记清除算法类似,增加了一步让存活对象移动的过程,已减少内存碎片。适合老年代。

HotSpot算法实现

枚举节点

在判断对象是否存活时,需要寻找GC Roots节点,此时需要保持对象关系不变,因此需要stop the world。为了减少GC Roots消耗的时间导致的stop the world 时间太长,使用一组OopMap来存储对象引用信息,从而快速的完成对象可达性校验。
安全点
安全点为程序运行时记录OopMap的点,如方法调用,循环跳转,异常跳转。

安全区域

即对象关系不会发生变化的区域,可以看成安全点的扩充。如sleep状态的线程。

中断方式

1.抢先式中断:发生gc时,把所有线程中断,没有运行到安全点的程序运行到安全点后在做gc。(一般不用)
2.主动式中断:为线程在安全点处和需要分配内存的地方做一个标记,线程执行时主动轮寻这个标志,发现这个标志时主动挂起,执行gc。

内存分配策略

内存分区配置

设置新生代老年代内存分配:-Xms20M,-Xmx20M,-Xmn10M(java堆内存大小20M,其中10M为新生代内存)
设置新生代内存survivor区(survivor区包括from和to两个相同大小的区域)的比例:-XX:SurvivorRatio=8(即:eden:from:to = 8:1:1)

对象分配流程

新生代:

1.如果启动了本地线程分配缓存,会先分配在TLAB上,否则一般先会分配到Eden区上。
2.在第一次Minor GC时把eden区存活的对象移动到survivor区的from处
3.在第二次Minor GC时eden区存活的对象被移动到to区,from区存活的对象,如果没有进入老年,区也被移动到to区
4.重复2,3的过程
JVM java虚拟机-垃圾收集

老年代:

1.大对象直接进入老年代:如对象很大survivor区内存不够无法放入的情况,这种情况可以设置-XX:PretenureSizeThreshold参数,使大于这个值的对象直接进入老年区,已避免在eden区和survivor区间的内存复制。
2.长期存活的对象进入老年代:没熬过一次minor GC,年龄就增加1,当年龄达到一定程度就晋升到老年代。晋升老年代的年龄通过:-XX:MaxTenuringThreshold设置(默认15)。
3.当survivor区所有对象的大小总和超过其一半时,该区年龄大于等于平均年龄的对象进入老年区。
4.当发生minor GC时,如果HandlePromotionFailure= false老年代的可用连续内存放不下新生代的所有对象,将发生时full gc;如果 HandlePromotionFailure= true老年代的可用连续内存放不下新生代的所有对象,但是大于历次晋升到老年代的对象平均大小,就不会full gc。
在这总情况下,老年代可能无法放下本次晋升老年代的对象,导致HandlePromotionFailure失败,此时会重新Full GC后再存储。一般情况下选择HandlePromotionFailure = true,因为可以避免频繁的full GC。

垃圾收集器

JVM java虚拟机-垃圾收集

Serial

只使用一个cpu或者一条收集线程去完成gc。
新生代用复制算法;老年代用标记-整理算法。
由于没有线程交互的开销,适合客户端模式下的虚拟机。

ParNew

Serial的多线程版本,gc时因为是多线程,速度更快。可以与CMS收集器配合工作,是很多server模式下的首选收集器。

Parallel Scavenge

与ParNew类似,为新生代收集器,但Parallel Scavenge主要追求吞吐量,可以通过 -XX:GCTimeRatio来设置吞吐量的大小。通过-XX:MaxGCPauseMillis来设置停顿时间。 吞吐量 = 运行用户时间/(运行代码时间+垃圾收集时间)

Serial Old

Serial老年代版本,与Parallel Scavenge搭配使用,或者做为CMS的后备方案。

Parallel Old

Parallel Scavenge老年代收集器。

CMS(java1.8默认)

详情见:https://blog.csdn.net/wangyy130/article/details/88758055sCMS GC的终极目标是降低垃圾回收时的暂停时间,所以在该阶段要尽最大的努力去处理那些在并发阶段被应用线程更新的老年代对象,这样在暂停的 重新标记阶段就可以少处理一些,暂停时间也会相应的降低。
CMS收集器在Minor GC时会暂停所有的应用线程,并以多线程的方式进行垃圾回收。在Full GC时不再暂停应用线程,而是使用若干个后台线程定期的对老年代空间进行扫描,及时回收其中不再使用的对象。

缺点:
1.因并发设计对cpu敏感,默认启动回收线程数(cpu数量+3)/4。
2.CMS收集器无法处理浮动垃圾,需要预留空间提供并发收集时的程序操作,通过 -XX:CMSInitiatingOccupancy设置老年区内存比,当达到触发比时触发gc,如果设置太高导致“concurrent mode failure”导致触发失败,可以临时启用 serial old 来完成老年代的垃圾收集
3.因为 标记-清理 算法,会有内存碎片,通过设置 -XX:CMSFullGCsBeforeCompaction,来确定执行多少吃full gc 跟着来一次压缩。

G1(java1.9默认)

JVM java虚拟机-垃圾收集

JVM java虚拟机-垃圾收集

G1将新生代,老年代的物理空间划分取消了。取而代之的是,G1算法将堆划分为若干个区域(Region),其中Humongous区域用来存储短期巨型对象超过50%分区容积以上。
Gc时把存活对象从一个区域拷贝到另一个区域。