JVM调优是一层窗户纸,只是看起来很难。学完本节课,让你:
熟悉 GC 常用算法,熟悉常见垃圾回收器,具有实际 JVM 调优实战经验
java和c++垃圾回收的区别
java:
由GC处理垃圾,一般都不是马上回收.GC有自己的想法
开发效率高,执行效率低
c++
代码中手动处理垃圾
可能忘记回收,导致内存泄漏
可能回收多次,导致非法访问并回收了别人正在用的内存
开发效率低,执行效率高
对象头上有个reference count,上面记着有几个引用,RC为0时这个对象就是垃圾
py就是使用了引用计数
缺点:不能解决循环引用
何为根对象
从根(Root)上的对象开始,顺着线往下捋,看根对象引用谁,能找到的都是有用的,剩下的就是垃圾
一般这些有用的对象,就称作存活对象.
概念:
把有用的和没用的对象用标记标出来(RootSearching),然后清除没用的对象
特点:
概念:
把区域分成两半:a区存放对象,b区留着啥也不放
GC时,先从a区找到存活对象,把它拷贝到b区,然后把a区的对象全部回收;
下次回收时从b拷贝到a,然后回收b
特点:
概念:
把存活对象往区域的前边压缩,剩下的全部清除回收
特点
新生代:老年代 = 1:2
// 查看老年代和新生代占用的空间比例
java -XX:+PrintFlagsFinal -version | grep NewRatio
新生代
老年代
老年代存活对象多,使用的是标记压缩算法,或者标记清除算法
经常听到MinorGC,YGC,FGC等词,他们是啥意思呢?
MinorGC/YGC,新生代空间耗尽时触发
MajorGC/FullGC/FGC,老年代无法继续分配空间时触发,新生代和老年代同时进行回收,比较慢,重量级.
一般垃圾回收的过程:
YGC回收之后,Eden中大多数的对象会被回收,活着的进入s0
再次YGC,活着的对象eden + s0 -> s1
再次YGC,eden + s1 -> s0
年龄足够 -> 老年代 (15 CMS 6)
s区装不下 -> 老年代
老年代满了FGC Full GC
JVM调优指什么?GC Tuning (Generation)
尽量减少FGC
MinorGC = YGC
MajorGC = FGC
栈上分配的技术基础:
直接分配在Eden区的话,会存在多线程的竞争,效率较低.
为了提高效率,减少多线程的竞争,会优先考虑分配在栈上和线程本地 TLAB(ThreadLocal Allocation Buffer)上
什么是TLAB
Java常见面试题—栈分配与TLAB
C语言struct都可以在栈上分配;
为了对标C,Java中的小对象、无逃逸(就在某段代码中使用)、支持标量替换(可以用普通的int等属性代替整个对象)、无需调整这样的对象分配在栈上。
如果栈上分配不下的话,会把它们分配到 线程本地 TLAB(ThreadLocal Allocation Buffer)
线程本地空间是线程独有的,避免多线程的争用,效率较高
小实验验证一下:
package com.mashibing.jvm.c5_gc;
//-XX:-DoEscapeAnalysis -XX:-EliminateAllocations -XX:-UseTLAB -Xlog:c5_gc*
// -代表去掉属性
// -XX:-DoEscapeAnalysis 去掉逃逸分析
// -XX:-EliminateAllocations 去掉标量替换
// -XX:-UseTLAB 去掉线程专有对象分配
public class TestTLAB {
//User u; // 如果在这里写User u,方法里面 u=new User(), 这个叫就是有逃逸,因为这个对象被外面的给引用了。
class User {
int id;
String name;
public User(int id, String name) {
this.id = id;
this.name = name;
}
}
void alloc(int i) {
new User(i, "name " + i); // 逃逸:u=new User()
}
public static void main(String[] args) {
TestTLAB t = new TestTLAB();
long start = System.currentTimeMillis();
for (int i = 0; i < 1000_0000; i++) t.alloc(i);
long end = System.currentTimeMillis();
System.out.println(end - start);
}
}
跟年龄有关:
JVM的参数分为三种:
名词解释:
Serial 单线程
Parallel 多线程
STW:stop-the-world,时间停止!只有我(GC)能动! 即GC时需要暂时停止JVM中的程序指令
safe point安全点:要GC时,程序中很多线程在运行,并不马上stop the world,而是让各个线程都做好准备再去暂停,暂停的点就是安全点.
垃圾收集器跟内存大小的关系
Serial 几十兆
PS 上百兆 - 几个G
CMS - 20G左右
G1 - 上百G
ZGC - 4T - 16T(JDK13)
只要虚线连在一起的,就能进行组合
垃圾回收器的历史:
a stop-the-world,copying collector which uses a single GC thread
STW的,使用coping算法的,单线程的
单CPU效率最高
虚拟机是Client模式的默认垃圾回收器
a stop-the-world,mark-sweep-compact collector which uses a single GC thread
STW的,使用mark-sweep 或 mark-compact算法的,单线程的
a stop-the-world,copying collector which uses multiple GC threads
stw的,使用coping算法的,多线程的
a compacting collector which uses multiple GC threads
使用mark-compact算法的,多线程的
其实就是Parallel Scavenge 的增强版,可以和CMS配合使用
PS 和 PN区别的延伸阅读
CMS:ConcurrentMarkSweep
a mostly concurrent,low-pause collector
以前垃圾回收时其他的工作线程都要暂停,等GC完毕后才继续
JDK1.4后期引入了CMS,开启了并发回收,垃圾回收线程和其他工作线程可以同时进行.
垃圾回收和应用程序同时运行,降低STW的时间(200ms)
算法:三色标记 + Incremental Update
CMS问题比较多,所以现在没有一个版本默认是CMS,只能手工指定
CMS既然是MarkSweep,就一定会有碎片化的问题,碎片到达一定程度,CMS的老年代分配对象分配不下的时候,使用SerialOld 进行老年代回收
想象一下:
PS + PO -> 加内存 换垃圾回收器 -> PN + CMS + SerialOld(几个小时 - 几天的STW)
几十个G的内存,单线程回收 -> G1 + FGC 几十个G -> 上T内存的服务器 ZGC
Memory Fragmentation 内存碎片问题: 因为标记清除会产生碎片化,如果老年代已经没有地方可以装了,CMS会请出Serial Old让它来进行清理,Serial Old是单线程的,效率很低
-XX:+UseCMSCompactAtFullCollection
-XX:CMSFullGCsBeforeCompaction 默认为0 指的是经过多少次FGC才进行压缩
Floating Garbage 浮动垃圾问题:老年代满了,浮动垃圾没有清理完。这时会请出Serial Old让它来进行清理
Concurrent Mode Failure
产生:if the concurrent collector is unable to finish reclaiming the unreachable objects before the tenured generation fills up, or if an allocation cannot be satisfiedwith the available free space blocks in the tenured generation, then theapplication is paused and the collection is completed with all the applicationthreads stopped
这个现象在日志里打印出来是PromotionFailed
以上两个问题的解决方案类似:降低触发CMS的阈值,保持老年代有足够的空间。
–XX:CMSInitiatingOccupancyFraction 92 可以理解为,老年代内存到达92% 的时候,触发CMS垃圾回收期。
可以降低这个值,让CMS保持老年代足够的空间
可以使用命令查看默认值:
java -XX:+PrintFlagsFinal -version | grep CMSInitiatingOccupancyFraction
这个值在1.8和11.0.3版本默认都是-1;
算法:三色标记 + SATB
算法:ColoredPointers + LoadBarrier(读屏障)
算法:ColoredPointers + WriteBarrier(写屏障)
-XX:+UseSerialGC = Serial New (DefNew) + Serial Old
小型程序。默认情况下不会是这种选项,HotSpot会根据计算及配置和JDK版本自动选择收集器
-XX:+UseParNewGC = ParNew + SerialOld
这个组合已经很少用(在某些版本中已经废弃)
原因
-XX:+UseConc(urrent)MarkSweepGC = ParNew + CMS + Serial Old
-XX:+UseParallelGC = Parallel Scavenge + Parallel Old (1.8默认) 【PS + SerialOld】
-XX:+UseParallelOldGC = Parallel Scavenge + Parallel Old
-XX:+UseG1GC = G1
Linux中没找到默认GC的查看方法,而windows中会打印UseParallelGC
java +XX:+PrintCommandLineFlags -version
通过GC的日志来分辨
Linux下1.8版本默认的垃圾回收器到底是什么?
1.8.0_181 默认(看不出来)Copy MarkCompact
1.8.0_222 默认 PS + PO