mark: 标记 sweep: 清除、清扫Mutator: 图片 Collector: 收集器compacting: 压实的,把……紧压在一起(或压实);(物质)变紧密;紧密地形成generational: 分代 Threshold: 阈值Ratio: 比率、比例Tenured Space、generation、enden、survivor、ratioserial
1. 概念认识
1.1 垃圾回收解决了什么问题?
- 对象内存分配的问题
- 回收分配给对象的内存的问题
1.2 简单的认识
一个对象称为垃圾之后,暂时的存储在(也可以说是堆内存)内存中,当存储到一定的量之后,虚拟机会自动启动垃圾回收将这些对象释放,可以主动调用system.gc()方法,开启垃圾回收,并且当一个对象被回收之后会自动调用finalize()```java public class Person { public void finalize() {
} }System.out.println("我被GC回收了,finalize被调用");
public class Ex() { public static void main(String[] args) { Person p1 = new Person(); Person p2 = new Person();
p1 = null;p2 = null;System.gc();for (int i = 0; i < 100000; i++) {// 为了让系统运行时间长一点}}
}
<a name="nczdR"></a># 2. 标记算法<a name="dg4Xd"></a>## 2.1 什么是垃圾对象1. 对象被判定为垃圾的标准,当一个对对象没有被其它对象所引用,那么就可以判断它是垃圾<a name="cc1Ih"></a>## 2.2 引用计数算法1. 通过判断对象的引用数量来决定对象是否可被`GC`1. 每个对象实例都有一个引用计数器,被引用则`+1`,完成引用则`-1`1. 任何计数器为`0`的对象实例都可以被`GC`回收1. 优点: 执行效率高,程序受影响较小1. 缺点: 无法检测出循环引用的情况,导致内存泄漏1. 举例:在某个方法中定义了引用变量指向该对象实例,当方法结束的时候,由于该引用变量是局部变量,它存储在虚拟机栈中,方法结束后就自动销毁,此时该实例对象的引用计数器便会`-1````java// 如下所示,两个对象循环引用,则引用计数器就无法检测出来MyObject o1 = new MyObjec();MyObject o2 = new MyObjec();o1.childNode = o2;o2.childNode = o1;
2.3 可达性分析算法
2.3.1 定义
- 虚拟机栈中引用的对象(栈帧中的本地变量表)
- 方法区的常量引用对象
- 方法区中的类静态引用的对象
- 本地方法栈中的引用对象
-
3. 回收算法
3.1 标记-清除算法
英文名称
mark and sweep- 标记:通过可达性分析算法从根对象进行扫描,对存活对象进行标记
- 清除:对堆内存从头到尾进行线性遍历,回收不可达对象内存
- 缺点:容易碎片化,易产生大量碎片化的内存地址块,当一个较大对象需要存储空间时,会不断的触发垃圾回收,知道产生足够存储的内存地址
- 举例:如下图所示,目前空间最大的内存为
2个单位,现在需要为一个3个单位的对象赋值,那么Mutator(指的是当前的应用程序)一直是暂停状态,Collector一直在尝试进行收集,导致当前应用程序在GC时处于停滞状态,直到系统提示内存溢出 -
3.2 复制算法
英文名称:copying
- 个人理解:镜面复制,拿空间换时间,特点一半一半,适合年轻代
- 算法逻辑:
- 将内存空间分为较大额度的
对象块和空闲块,对象只在在对象快上建立 - 当系统触发垃圾回收的时候,将
对象块的对象全部按照顺序复制到空闲块 - 此
时空闲块变成下一次的对象块 - 然后直接清除
对象快中所有的对象,此时对象块变成空闲块
- 将内存空间分为较大额度的
- 优点:
- 解决碎片化问题
- 顺序分配内存,简单高效
- 该算法适用于,声明周期较短,存活率底的对象区域,这样复制的东西较少,比如年轻代
- 事实上现在的商用虚拟机都是采用,复制算法回收年轻代,研究表明,年轻代,每次回收只有
10%左右的对象存活
-
3.3 标记-整理算法
英文名称:
compacting- 标记: 从跟集合进行扫描,对存活对象进行标记
- 清除: 移动所有存活对象,且按照内存地址依次排列,然后将末端内存地址以后的内存全部收回
- 优点
- 避免内存的不连续
- 不用设置两块内存块互换,浪费内存
- 适用于存活率较高的场景,比如老年代
-
3.4 分代收集算法
将堆内存进行模块划分:按照声明周期不同,划分区域以采用不同的垃圾回收算法
- 分代收集算法是一套组合拳,不同的区域采用不同的收集算法
-
4. 常见的GC
4.1 对象内存划分
4.2 年轻代GC
4.2.1 基础认识
基本思想:尽可能快速的收集掉那些声明周期短的对象
- 年轻代的垃圾回收,也叫
Minor GC - 年轻代的区域划分
- 一个
Eden Space区域,占比80% - 两个
Survivor区域,分别是From Space占比10%,To Space占比10% - 为什么
Eden Space占比特别大,因为年轻代每次GC之后存活的对象特别少,基本上小于10%
- 一个
Minor GC触发的时间视频介绍:翔仔面试课
7-2的第10分钟- 第一次
Minor GC- 这时全部的对象都在
Eden Space区域,通过可达性分析算法,标记出所有的垃圾对象 - 通过复制算法,将不是垃圾的对象复制到
From Space空间,并且复制的对象年龄+1 - 然后直接回收
Eden Space区域的垃圾
- 这时全部的对象都在
- 第二次
Minor GC- 上一次的信息介绍:这时候
Eden Space区域会产生新的对象,From Space存留上次GC对象 - 通过可达性分析算法,标记出
Eden Space、From Space所有的垃圾对象 - 通过复制算法,将不是垃圾的对象复制到
To Space空间,并且复制的对象年龄+1 - 然后直接回收
Eden Space、From Space区域的垃圾
- 上一次的信息介绍:这时候
- 之后就是无限的
Minor GC循环 To Space、From Space区域的空间不足怎么办?多次
Minor GC存活下来的对象- 一次
Minor GC存活下来的对象,年龄会+1 - 直到达到某个阈值默认是
15,这些对象就会成为老年代对象 - 这个阈值可以通过这个参数设置
-XX:MaxTenuringThreshold,但是不能超过15
- 一次
- 年轻代的内存区域存不下的对象,例如
Edne Space、Survuvor装不下的对象 - 新生成的大对象
-xx:+PretenuerSizeThreshold 这个阈值为什么是15次,25次可以吗?
- 首先要清除对象在堆内存中的存储结构,查看笔记:并发编程 - 锁相关中4.2.5章节的描述
- 对象的分代年龄是存储在对象头的
Mark Word中 - 它占据了
4个bit位置,所以它能存储的最大数值是154.2.4 常见的参数设置
-XX:SurvivorRatio:用来设置Eden Space和其中一个Survivor区域的比值-XX:NowRatio:老年代和年轻代的内存比例- 默认是
2:1 - 总内存是如何设置的呢?是由前面的
-Xms -Xmx -Xss控制
- 默认是
-XX:MaxTenuringThreshold:设置新生代晋升到老年代最大Minor GC次数的阈值4.3 老年代GC
4.3.1 基础认识
老年代一般是标记整理算法、标记整理算法,由于对老年代的垃圾收集,一般会伴随着年轻代的
Minor GC,所以老年代的垃圾收集叫Full/Major GC一般这两个描述是一样的,但是由于各种解释不确定Major GC也可以特指老年代的垃圾回收4.3.2 触发Full GC的条件
老年代空间不足
- 针对
JDK8之前的,永久代空间不足也会触发Full GC CMS GC时出现promotion failed,concurrent mode failure,使用CMS GC的时候要注意日志中是否有这两个错误promotion failed:中文“晋升失败”,是在进行Minor GC的时候,Survivor区域放不下了,对象只能放入老年代,而老年代也放不下了concurrent mode failure:中文“并发模式失败”,是指在进行CMS GC的过程中,同时有对象要放入老年代中,而此时老年代空间不足
Minor GC晋升到老年代的平均大小大于老年代的剩余空间- 调用
System.gc() 使用
RMI来进行RPC或管理的JDK应用,每小时执行一次Full GC5. 垃圾搜集器
5.1 前置概念
5.1.1 stop the world
虚拟机由于要执行
GC而停止了应用程序的执行- 这个情况可能在任何一种
GC算法中发生 - 当
stop the world发生的时候,处理GC线程所有的线程都是处于阻塞状态 多数情况下的
GC优化,都是值减少stop the world发生的时候来提高程序的性能5.1.2 safe point
举例说明:
GC就像保洁打扫卫生,不允许出现,边打扫,边扔垃圾的情况,所以保洁打扫的时候就和所有人说,我开始打扫了,不要扔垃圾,这个时间点就是安全点- 在
GC的可达性分析算法中,要保证分析程序不会产生新的垃圾,所以所有的线程在这个点都被阻塞了 - 安全点产生的地方:方法调用、循环跳转、异常跳转等地方
-
5.1.3 虚拟机的运行模式
server模式:采用重量级的虚拟机,启动慢,但是程序平稳运行之后速度快client模式:采用轻量级的虚拟机,启动快,但是程序平稳运行之后速度慢java -version即可查看当前jvm采用的模式
5.2 年轻代常见垃圾收集器
5.2.1 serial
- 中文意思:顺序排列的,串行的、单行的
- 启动设置:
-XX:+UseSerialGC - 采用的算法:复制算法
- 该收集器是虚拟机历史最悠久的年轻代垃圾搜集器,在
JDK3之前是年轻代的唯一选择 - 单线程搜集,进行垃圾搜集时,必须阻塞所有工作线程
- 简单高效,它依然是
client模式下的默认年轻代搜集器 -
5.2.2 ParNew
启动设置:
-XX:UseParNewGC- 使用算法:复制算法
- 它是多线程搜集,其它的行为和serial是一样的
- 它是
server模式下的虚拟机首选的年轻代搜集器 - 单核执行效率不如
serial,多核情况下才会突出优势,因为存在可用线程的切换开销,但是随着cpu数量的增多,多核情况下会抹除这些不足 - 默认开启的线程数是和
cpu的数量相同,可以使用ParNewGCThread限制垃圾搜集的线程数 - 可以和
CMS搜集器配合工作 -
5.2.3 parallel scavenge
吞吐量:运行用户代码的时间/(运行用户代码的时间+垃圾回收的时间)
中文意思:并行清除
- 启动设置:
-XX:UseParallelGC - 使用算法:复制算法
- 使用多线程垃圾回收
- 相对前两面两个搜集器,减少用户线程停顿时间,本收集器更关注系统的吞吐量
- 减少停顿时间:适用于和用户交互的程序,提高用户体验
- 高吞吐量:则会高效利用
cpu时间,尽可能快的完成运算任务,主要适合在后台运算的程序
该搜集器是
Server模式下的默认年轻代垃圾收集器,注意ParNew的描述是首选搜集器5.3 老年代常见的垃圾搜集器
5.3.1 serial old
启动设置:
-XX:UseSerialOldGC- 使用算法:标记-整理算法
- 单线程搜集,进行垃圾搜集时,必须阻塞所有工作线程
- 简单高效,它依然是
client模式下的默认老年代搜集器 -
5.3.2 parallel old
启动设置:
-XX:UseParallelOldGC- 使用算法:标记-整理算法
- 多线程,吞吐量优先
- 它出现的意义
- 它是
JDK6才提供的垃圾搜集器 - 在此之前,年轻代的
parallel scavenge处于比较尴尬的地位 - 原因是,如果年轻代选择
parallel scavenge,老年代只能选择serial old - 由于
serial old是单线程的,它会拖累parallel scavenge - 导致这种组合也未必能在整体上获取到最大吞吐量的效果,一定情况下还不如
ParNew + cms性能高
- 它是
-
5.3.3 cms
启动配置:
-XX:UseConcMarkSweepGC- 使用算法:标记-清除算法
- 简单认知
- 它是
JDK5中提供的,一出生它几乎占据了老年代垃圾回收的半壁江山 - 它划时代的意义,就是垃圾回收线程几乎可以和用户线程同时进行工作
- 几乎就是指它还是不能做到完全不需要
stop the world,只是它尽可能的缩短了停顿时间 - 如果你的应用程序对停顿比较敏感,并且在程序运行的时候提供流弊的内存,使用cms是更好的选择
- 如果在
JVM中有相对较多存活时间较长的对象会更适合cms
- 它是
执行流程
启动配置:
-XX:UseG1GC- 使用算法:复制算法 + 标记-整理算法

