对象引用计算器,引用加1,失效减1。计数为0表示对象死亡。
JVM不采用,因为互相引用导致循环引用问题
以GC Roots为起点,从这些起点开始向下搜索,经过的路径称为引用链。若一个对象到GC Roots之间没有任何引用链,则该对象是不可达的。
在Java语言中,可作为GC Roots的对象包括以下几种:
在可达性分析过程中,对象引用类型会对对象的生命周期产生影响
主要包括废弃常量和无用类的回收。
满足以下三个条件,类才可以被回收(卸载):
HotSpot虚拟机通过 -Xnoclassgc 参数进行控制是否启用类卸载功能。在大量使用反射、动态代理、CGLib等框架,需要虚拟机具备类卸载功能,避免方法区发生内存溢出。
分为两个阶段
缺点
把内存分成大小相同的两块,当一块的内存用完了,就把可用对象复制到另一块上,将使用过的一块一次性清理掉
缺点:浪费了一半内存
标记后,让所有存活的对象移到一端,然后直接清理掉端边界以外的内存
把堆分为新生代和老年代
将新生代内存分为一块大的Eden区和两块小的Survivor;每次使用Eden和一个Survivor,回收时将Eden和Survivor存活的对象复制到另一个Survivor(HotSpot的比例Eden:Survivor = 8:1)。可以通过JVM参数调整。
存活判定与收集算法仅是理论上,JVM实现时必须考虑效率,才能保证JVM高效运行。
GC Roots的节点主要在全局性引用(常量,静态属性)与执行上下文(本地变量表)中,逐个检查消耗很多时间。
可达性分析对执行时间的敏感还表现在GC停顿上,GC时必须停顿所有Java执行线程(“STOP THE WORLD”)
OopMap数据结构记录哪些位置是引用。类加载完成时或JIT编译时,GC时直接扫描OopMap。
在OopMap协助下,HotSpot可以快速准确完成GC Roots枚举。
特定位置记录信息,称为安全点。程序执行时并非在所有地方都可以停顿下来开始GC,只有在到达安全点时才能暂停。
安全点选定以程序“是否具有让程序长时间执行的特征”为标准。“长时间执行”的最明显特征是指令序列复用。例如方法调用,循环跳转,异常跳转。
如果在GC发生时让所有线程(不包括执行JNI的线程)到最近的安全点上再停顿下来。2种方案:
首选中断所有线程,有线程未在安全点中断,恢复线程运行到中断点。(几乎不采用)
主动式中断(Voluntary Suspension)
需要GC时,不中断线程,设置标志位,由线程主动轮询,发现中断标识时主动挂起。(轮询标识的地方和安全点重合)。
程序不执行时,即没有分配到CPU时间,线程处于Sleep或者Blocked状态,线程无法响应JVM中断,JVM也不会等待线程重新获取CPU时间。对于这种情况采用安全区域来解决。
安全区域是指在一段代码片段中,引用关系不会发生变化,在这个区域任何地方开始GC都是安全的,可看作扩展安全点。
在线程执行到安全区域时,首先标识自己已进入安全区域,GC时就不考虑这些线程了,在线程离开安全区域时,要检查系统是否已完成根节点枚举(或整个GC),如果完成了,线程就继续执行,否则就必须等待接收到可以离开安全区域的信号。
收集算法是内存回收的理论,而垃圾回收器是内存回收的实践。
如果两个收集器之间存在连线说明他们之间可以搭配使用
这是一个单线程收集器。意味着它只会使用一个 CPU 或一条收集线程去完成收集工作,并且在进行垃圾回收时必须暂停其它所有的工作线程直到收集结束。
新生代采用复制算法
老年代采用标记-整理
可以认为是 Serial 收集器的多线程版本。
并行:Parallel,指多条垃圾收集线程并行工作,此时用户线程处于等待状态
这是一个新生代收集器,也是使用复制算法实现,同时也是并行的多线程收集器。
CMS 等收集器的关注点是尽可能地缩短垃圾收集时用户线程所停顿的时间,而 Parallel Scavenge 收集器的目的是达到一个可控制的吞吐量(Throughput = 运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间))。
参数:
作为一个吞吐量优先的收集器,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整停顿时间。这就是 GC 的自适应调整策略(GC Ergonomics)。
Serial 收集器的老年代版本,单线程,使用标记-整理
。
这个收集器的主要意义:给Client模式下的虚拟机使用。
Server模式下,它还有2大用途:
Parallel Old 是 Parallel Scavenge 收集器的老年代版本。多线程,使用 标记-整理
CMS (Concurrent Mark Sweep) 收集器是一种以获取最短回收停顿时间为目标的收集器。基于 标记-清除
算法实现。
运作步骤:
缺点:
默认回收线程数:(CPU数量+3) /4
第一次标记后出现的未处理垃圾称为浮动垃圾,不能等到老年代填满了再收集,如果预留内存无法满足用户线程需要,会产生Concurrent Mode Failure异常。
标记-清除
算法带来的空间碎片更多细节参考图解 CMS 垃圾回收机制原理
优点:
其他收集器的范围在某个代,G1不再这样,它将整个内存分为多个大小相等的独立区域(Region),虽然还保留新生代,老年代,但是新生代,老年代不再物理隔离,都是一部分Region(不需要连续)的结合。
运作步骤:
阅读 GC 日志是处理 JVM 内存问题的基础技能,它只是一些人为确定的规则,每一种收集器的日志形式都是由它们自身的实现所决定的,但 JVM 设计者为了方便用户阅读,将各个收集器的日志都维持一定的共性,例如下面两段典型的日志:
33.125: [GC [DefNew: 3324K->152K (3712K), 0.0025925secs] 3324K->152K (11904K), 0.0031680 secs]
100.667: [FullGC [Tenured: 0K->210K (10240K), 0.0149142secs] 4603K->210K (19456K), [Perm: 2999K->2999K (21248K)], 0.0150007 secs] [Times: user=0.01 sys=0.00, real=0.02 secs]
数字 “33.125” 和 “100.667” 表示从 JVM 启动以来经过的秒数。
“[GC” 和 “[Full GC” 说明了这次垃圾收集的停顿类型,而不是用来区分新生代 GC 还是老年代 GC 的。如果有 “Full”,说明这次 GC 是发生了 “Stop The World” 的,例如下面这段新生代收集器 ParNew 的日志也会出现 “[Full GC” (一般是因为出现了分配担保失败之类的问题,导致 STW)。如果是调用 System.gc() 方法所触发的 GC,那么在这里将显示 “[Full GC (System)”。
[Full GC 283.736: [ParNew: 261599K->261599K (261952K),0.0000288 secs]
“[DefNew”、”[Tenured”、”[Perm” 表示GC发生的区域,这里显示的区域名称与使用的 GC 收集器是密切相关的,例如上面样例所使用的 Serial 收集器中的新生代名为 “Default New Generation”,所以显示的是 “[DefNew”。
如果是 ParNew 收集器,新生代名称就会变为 “[ParNew”。
如果是 Parallel Scavenge 收集器,新生代名称就会变为 “PSYoungGen”,老年代和永久代同理,名称也是由收集器决定的。
后面方括号内部的 “3324K->152K (3712K)” 即 “GC 前该内存区域已使用容量->GC 后该内存区域已使用容量 (该内存区域总容量)”。
而在方括号之外的 “3324K->152K (11904K)” 即 “GC 前 Java 堆已使用容量->GC 后 Java 堆已使用容量 (Java堆总容量)”。
再往后,”0.0025925 secs” 表示该内存区域 GC 所占用的时间,单位是秒。
有的收集器会给出更具体的时间数据,如 “[Times: user=0.01 sys=0.00,real=0.02 secs]”,这里面的 user、sys 和 real 与 Linux 的 time 命令所输出的时间含义一致,分别代表用户态消耗的CPU时间、内核态消耗的CPU事件和操作从开始到结束所经过的墙钟时间 (Wall Clock Time)。
CPU 时间与墙钟时间的区别是,墙钟时间包括各种非运算的等待耗时,例如等待磁盘 I/O、等待线程阻塞,而 CPU 时间不包括这些耗时,但当系统有多 CPU 或者多核的话,多线程操作会叠加这些 CPU 时间,所以读者看到 user 或 sys 时间超过 real 时间是完全正常的。
名称 | 说明 | 备注 |
---|---|---|
UseSerialGC | 开启此参数使用serial & serial old搜集器(client模式默认值)。 | |
UseParNewGC | 开启此参数使用ParNew & serial old搜集器(不推荐)。 | |
UseConcMarkSweepGC | 开启此参数使用ParNew & CMS(serial old为替补)搜集器。 | |
UseParallelGC | 开启此参数使用parallel scavenge & parallel old搜集器(server模式默认值)。 | |
UseParallelOldGC | 开启此参数在年老代使用parallel old搜集器(该参数在JDK1.5之后已无用)。 |
名称 | 说明 | 备注 |
---|---|---|
Xms | 堆的初始值。默认为物理内存的1/64,最大不超1G。 | |
Xmx | 堆的最大值。默认为物理内存的1/4,最大不超1G。 | |
Xmn | 新生代的大小。 | |
Xss | 线程栈大小。 | |
PermSize | 永久代初始大小。默认为物理内存的1/64,最大不超1G。 | |
MaxPermSize | 永久代最大值。默认为物理内存的1/4,最大不超1G。 | |
NewRatio | 新生代与年老代的比例。比如为3,则新生代占堆的1/4,年老代占3/4。 | |
SurvivorRatio | 新生代中调整eden区与survivor区的比例,默认为8,即eden区为80%的大小,两个survivor分别为10%的大小。 |
名称 | 说明 | 备注 |
---|---|---|
PretenureSizeThreshold | 晋升年老代的对象大小。默认为0,比如设为10M,则超过10M的对象将不在eden区分配,而直接进入年老代。 | |
MaxTenuringThreshold | 晋升老年代的最大年龄。默认为15,比如设为10,则对象在10次普通GC后将会被放入年老代。 | |
DisableExplicitGC | 禁用System.gc()。 |
名称 | 说明 | 备注 |
---|---|---|
ParallelGCThreads | 回收时开启的线程数。默认与CPU个数相等。 | |
GCTimeRatio | 设置系统的吞吐量。比如设为99,则GC时间比为1/1+99=1%,也就是要求吞吐量为99%。若无法满足会缩小新生代大小。 | |
MaxGCPauseMillis | 设置垃圾回收的最大停顿时间。若无法满足设置值,则会优先缩小新生代大小,仍无法满足的话则会牺牲吞吐量。 |
名称 | 说明 | 备注 |
---|---|---|
CMSInitiatingOccupancyFraction | 触发CMS收集器的内存比例。比如60%的意思就是说,当内存达到60%,就会开始进行CMS并发收集。 | |
UseCMSCompactAtFullCollection | 在每一次CMS收集器清理垃圾后送一次内存整理。 | |
CMSFullGCsBeforeCompaction | 设置在几次CMS垃圾收集后,触发一次内存整理。 |
对象主要分配在新生代的 Eden 区上,如果启动了本地线程分配缓冲区,将线程优先在 (TLAB) 上分配。少数情况会直接分配在老年代中。
大对象指需要大量连续内存空间的对象,最典型的是很长的字符串以及数组。
-XX:PretenureSizeThreshold(只对Serial,ParNew有效)。大于此设置值的对象直接在老年代分配。
对象在Survivor中,每“熬过”一次Minor GC,年龄加1,当增加到一定程度(默认15),就会晋升到老年代。-XX:MaxTenuringThreshold设置此值。
虚拟机并不是永远要求对象年龄达到MaxTenuringThreshold才晋升老年代,如果Survivor空间中相同年龄的所有对象大小超过Survivor的一半,年龄大于等于该年龄的对象直接进入老年代。
在发生Minor GC前,虚拟机会先检查老年代最大可用连续空间是否大于新生代对象总空间,如果条件成立,那么Minor GC是安全的,如果不成立,虚拟机会查看HandlePromotionFailure设置是否允许担保。如果允许,则继续检查老年代最大可用连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,尝试一次Minor GC,否则进行Full GC。