mark: 标记 sweep: 清除、清扫
Mutator: 图片 Collector: 收集器
compacting: 压实的,把……紧压在一起(或压实);(物质)变紧密;紧密地形成
generational: 分代 Threshold: 阈值
Ratio: 比率、比例
Tenured Space、generation、enden、survivor、ratio
serial
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
位置,所以它能存储的最大数值是15
4.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 GC
5. 垃圾搜集器
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
- 使用算法:复制算法 + 标记-整理算法