JVM中一个垃圾回收线程,它的优先级较低,正常情况下不会执行。JVM空闲或者当前内存不足时,才会触发垃圾回收线程执行,扫描内没有被引用的对象,将这些对象添加到要回收的集合中进行回收。
GC介绍
Garbage Collection 垃圾收集,监测对象是否可用进而实现自动回收对象的目的。
GC调用方法:System.gc() 或Runtime.getRuntime().gc() 。
GC作用:防止内存泄漏。
GC运行方式:作为优先级低的线程运行,不可预知的情况下清除/回收内存中已经死亡或长时间未使用的对象。程序员无法实时调用GC回收对象。
说明 | |
---|---|
-Xms | -Xmx | -Xmn | 堆的初始大小 | 堆的最大值 | 堆中年轻代值 |
-XX:-DisableExplicitGC | 禁用System.gc() |
-XX:NewSize | -XX:MaxNewSize | 新生代大小 | 新生代最大大小 |
-XX:NewRation | 老生代与新生代的比例 |
-XX:InitialTenuringThreshold | -XX:MaxTenuringThreshold | 老年代阀值 | 老年代最大值 |
GC触发条件
方法一:引用计数器
为每个对象创建一个引用计数,有对象引用时计数器 +1,引用被释放时计数 -1,当计数器为 0 时就可以被回收。它有一个缺点不能解决循环引用的问题;
方法二:可达性分析
从 GC Roots 开始向下搜索,搜索所走过的路径称为引用链。当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是可以被回收的。
引用计数器 1.为对象创建一个引用计数器,当该对象被引用时,计数器就加1;当该对象的引用被释放后,计数器就减1。 2.当计数器为0时就表明该对象没有被引用,可以被GC回收。但是无法解决循环依赖的问题。
可达性分析法
可达性分析的操作,必须确保一致性的快照中进行,保证所有引用在此期间不存在变化的情况。
可达性分析的操作会触发所有用户线程暂停(挂起),官方称为STW。
GC何时调用
-
对象没有引用(引用计数器=0,没有可达GC Root对象)
-
作用域捕获异常
-
程序意外终止 (例如:kill -9)
-
执行System.exit()
-
程序正常执行结束
GC如何调用
-
System.gc()
-
Runtime.getRuntime().gc()
GC 降低开销
-
少用静态变量,静态变量属于全局变量,不会被GC回收,一直占用内存。
-
尽量使用基本类型,而不是包装类(int / Integer),基本类型占用的资源少。
-
StringBuffer替代String来增加字符串。
-
对象尽量不显示设为null,对象=null,会被作为垃圾处理。将不用的对象设置为null,有利于GC回收。
-
尽量不显示调用System.gc()。System.gc()不会立即触发Full GC,但是大多时候会触发GC,从而增加了GC频率、增加了STW的次数、影响系统性能。
GC算法
标记-清除算法
第一步,标记需要回收的对象;
第二步,清除回收。
优点:操作简单方便、无需移动对象。
缺点:造成空间碎片,提高垃圾回收的频率。
复制算法
按容量划分为2个大小相等的内存区域,当其中一块用完时将存活的对象复制到另一块中,将已使用的内存空间一次清理掉。
优点:不会产生内存碎片。
缺点:降低了内存空间的使用率,生存周期长达的对象容易被重复复制,浪费CPU资源。
标记-整理算法
标记无用对象,将所有可用的对象移动到一端,直接除掉边界外的对象。
优点:不会存在内存碎片
缺点:需要局部移动,会降低效率。
分代算法
根据对象存活周期的不同,将内存分为年轻代、老年代、永久代。
新生代基本采用复制算法,老年代采用标记-整理算。
GC Collector
描述 | ||
---|---|---|
Serial | 最早的单线程串行垃圾回收器 | 新生代 |
Serial Old | Serial 垃圾回收器的老年版本,也是单线程的 | 老年代 |
ParNew | Serial 的多线程版本 | 新生代 |
Parallel | 吞吐量优先的收集器(多线程),牺牲等待时间换取吞吐量 | 新生代 |
Parallel Old | Parallel 老生代版本。采用标记-整理算法 | 老年代 |
CMS | 以获得最短停顿时间为目标的收集器,适用 B/S 系统。 | 老年代 |
G1 | 兼顾吞吐量和停顿时间的 GC 实现,是 JDK 9 以后的默认 GC 选项 | 整体 |
-
串行 (必须掌握)
-
并行(必须掌握)
-
并发 CMS(必须掌握)
-
G1(必须掌握)
JDK1.7默认:Parallel Scavenge(新生代) + Parallel Old(老年代)
JDK1.8默认:Parallel Scavenge(新生代) + Parallel Old(老年代)
JDK1.9默认:G1
串行垃圾回收器
比喻:顾客在吃饭,服务员让其起来离开位置,需要打扫,顾客吃饭的行为被中断。
-
单线程环境,只运行一个线程回收垃圾。
-
回收时会暂停所有的用户线程 STW
并行垃圾回收器
比喻:客在吃饭,出现几个清洁工打扫卫生,顾客先出去,一会回来用餐。
-
多个垃圾收集线程执行垃圾收集工作
-
用户线程暂停 STW
CMS 并发垃圾回收器
比喻:顾客在吃饭,出现几个清洁工打扫卫生,顾客先去1号卓用餐,一会回到原位置。
-
垃圾收集与用户线程同时运行
串行垃圾回收器
-
JVM 开启串行垃圾回收器:-XX:+UseSerialGC
-
JVM开启串行垃圾回收器之后,新生代、老年代会使用相关的组合
-
新生代:Serial,使用复制算法
-
老年代:Serial Old,使用标记-整理算法
-
并行垃圾回收器
新生代并行收集器,是Serial收集器的多线程版本,在多核cpu环境下使用。
-
JVM开启并行垃圾回收器:-XX:+UseParNewGC
-
JVM开启并行垃圾回收器之后,新生代、老年代会使用相关的组合
-
新生代:ParNew,使用复制算法
-
老年代:Serial Old,使用标记-整理算法
-
并发垃圾回收器
又称为吞吐量收集器(Throughout Collector),追求高吞吐量、高效运行CPU。
吞吐量=用户线程时间/(用户线程时间+垃圾回收时间)
使用场景:订单处理、工资支付等
-
-XX:+UseParallelGC:开启此模式,使用ParallelScavenge + Serial Old组合进行垃圾回收。
-
-XX:+UseParallelOldGC: 开启此模式,使用Parallel Scavenge + Parallel Old的收集器组合进行垃圾回收。
CMS
老年代并行收集器,追求最短卡顿时间。
CMS执行步骤
-
初始标记 STW ①
-
并发标记 ②
-
预清理
-
重新标记 STW ③
-
并发清除 ④
-
重置
1. 初始标记 STW
标记从GC Roots直接可达的老年代对象、新生代引用的老年代对象。
整个过程是单线程,其他线程会被暂停执行。2. 并发标记
开始tracing,标记可达对象。
垃圾回收线程与应用程序并行执行,GC Roots对象会发生改变(例如:对象从新生代进入老年代,老年代中的引用发生改变,之前的引用被标记为dirty)。3. 预清理
4. 重新标记 STW
暂停所有应用线程,重新标记并发标记遗漏的对象。5. 并发清除
激活应用程序线程,将未被标记为存活的对象记为不可达。6. 重置
CMS内部重置回收器状态,准备进入下一个回收周期。CMS收集器在Minor GC时会暂停所有的应用线程,并以多线程的方式进行垃圾回收。在Full GC时不再暂停应用线程,而是使用若干个后台线程定期的对老年代空间进行扫描,及时回收其中不再使用的对象。
G1特点
-
不产生内存碎片。G1会压缩空闲内存使之足够紧凑,做法是用regions代替细粒度的空闲列表进行分配,减少内存碎片的产生。
-
可精确控制停顿时间。G1的STW更可控,G1在停顿时间上添加了预测机制,用户可以指定期望停顿时间,避免同一时刻回收垃圾过多,造成雪崩。
G1开启
-
第一步:开启G1垃圾收集器 -XX:+UseG1GC
-
第二步:设置堆的最大内存 -XX Xms 32g
-
第三步:设置最大停顿时间 -XX:MaxGCPauseMillis=100
G1 Region
G1取消了年轻代、老年代的物理划分,将堆划分为若干个region,这些region包含了逻辑上的年轻代、老年代区域。
Humongous 巨大: 对象占用空间超过分区容量50%,G1认为这个对象是一个巨型对象。Humongous区专门存放巨型对象,一个H区装不下一个巨型对象,G1会寻找连续的H区来存储。
寻找连续的H区时,会触发Full GC。
G1全局并发标记
第一步:初始标记 (initial mark, STW):标记从根节点直接可达的对象,该阶段执行一次年轻代GC,产生STW。
第二步:根区域扫描(root region scan)
-
在初始标记活动区扫描对老年代的引用,标记被引用的对象。
-
该阶段与程序同时运行,只有完成该阶段,才能开始下一次STW年轻代垃圾回收。
第三步:并发标记(Cocurrent Marking):G1 GC在整个堆中查找可访问(存活的)的对象,该阶段与程序同时运行,可以被STW年轻代垃圾回收中断。
第四步:重新标记(Remark, STW):产生STW,针对上一次的标记进行修正。
第五步:清除垃圾(Cleanup,STW):清点和重置标记状态,产生STW。该阶段实际不会做GC,等待evacuation阶段来回收。
G1优化
G1 GC吞吐量目标:90%应用程序时间 + 10% 垃圾回收时间
G1模式
1. Young GC
Eden区使用达到阀值,并且无法申请足够内存时会触发Young GC。
每次Young GC会回收所有Eden和Survior区,将存活对象复制到老年代和Survior区。
2. Mixed GC
收集整个Young Gen和部分Old Gen。G1独有的模式
MixedGC是G1GC特有的,跟FullGC不同的是MixedGC只回收部分老年代的Region。
MixedGC一般会发生在一次YoungGC后面,为了提高效率,MixedGC会复用YoungGC的全局的根扫描结果,因为这个STW过程是必须的,整体上来说缩短了暂停时间。
GC 练习
public class Math {
public static final int initData = 666;
public static User user = new User(); // 实际在堆中分配内存空间,元空间中user指向堆中user的实际地址。
public int math() {
int a = 1;
int b = 2;
int c = (a+b) * 10;
return c;
}
public static void main(String[] args) {
Math demo1 = new Math(); // 实际在堆中分配地址,局部变量表中math是堆中math的存储地址的引用。
demo1.math();
}
}
class User{}
// math()对应的汇编代码如下
public int math();
Code:
0: iconst_1
1: istore_1
2: iconst_2
3: istore_2
4: iload_1
5: iload_2
6: iadd
7: bipush 10
9: imul
10: istore_3
11: iload_3
12: ireturn
Math.java 操作数栈过程
Math.java内存分配过程
Math.class 加载过程
0.加载:将bin-JVM-Math.class加载至JVM内存区域
加载方式:懒加载
war包有1万个文件,实际使用不足10%。实际,90%的文件不会被加载(未使用)。
Math.class加载至JVM内存之前有一些列步骤:
1. 验证
Math.class第1行:cafe babe 0000 0034 0029 0700 0201 0008
如果修改内容:cafe babe 0000 0034 0029 0700 0201 1118
新的字节码文件不符合格式,验证格式错误。则不加载Math.class
2.准备
将静态变量赋予默认值(JVM规定:boolean->false, Integer->0, Object->null 等)
3.解析
3.1 静态链接:符号引号替换为直接引用。(main被称为符号,直接引用就是内存地址。由方法名获取变为直接通过物理地址获取。)
3.2 动态链接:在程序运行期间完成的将符号引用替换为直接引用。
4.初始化
实际赋值,initData 从0变为666。