1 什么是垃圾?
1 没有任何引用指向的一个对象或者多个对象(循环引用)
2 申请、释放内存:
C语言:malloc、free
C++: new、delete
Java:new、自动回收
3 自动回收与手动回收优缺点:
自动回收:编程简单,系统不容易出错
手动回收:可能出现忘记回收(内存泄露)、多次回收(回收了有用的数
2.如何定位垃圾?
引用计数(reference counting):对象上存放引用它的引用数量,当引用数量为0,认为是垃圾,但这种方案找不到循环引用
根可达(root searching):以静态变量、线程栈中变量、常量池(指向Class对象)、JNI指针(指向native方法用到的类或对象),作为根引用,顺着根引用能找到的对象都不是垃圾,其他都是垃圾,Hotspot就使用这种方式定位垃圾
3.常见的垃圾回收算法
标记清除(Mark-Sweep):
两遍扫描:
mark:将根对象可以访问到的对象都打上一个标识,表示可达
sweep:遍历堆内存,将不可达对象清理
位置不连续,产生内存碎片,存活对象多时,效率高,因为需要清理的少
拷贝算法(Copying):
将堆内存对半分为两个半区,只用其中一个半区来进行对象内存的分配,如果在这个半区内存不够给新的对象分配了,那么就开始进行垃圾收集,将这个半区中的所有可达对象都拷贝到另外一个半区中去。需要移动和复制对象,因此对同一个对象,需修改引用指向存活对象少,效率高,因为需要复制的对象少。没有碎片,浪费空间
标记压缩(Mark-Compact):
两遍扫描:
mark:将根对象可以访问到的对象都打上一个标识,表示可达
compact:移动所有的可达对象到堆内存的同一个区域中,使他们紧凑的排列在一起,从而将所有非可达对象释放出来的空闲内存都集中在一起
无碎片,不会产生内存减半,不浪费空间,但效率低:
1 算法复杂
2 多线程移动还需要时间进行同步
4 堆内存逻辑分区(也叫JVM内存分代模型,用于分代垃圾回收算法)
1 从分代GC的角度看,堆分为新生代与老年代
2 有些垃圾回收器逻辑上并不是如此划分的:
除Epsilon、ZGC、Shenandoah之外的垃圾回收期都是使用逻辑分代模型
G1是逻辑分代,物理不分代
除此之外,不仅逻辑分代,而且物理分代
3 新生代 = eden + 2个survivor区
YGC(MinorGC):无法在新生代为对象分配空间时产生,对新生代的内容进行回收,由于新生代内容通常全能被回收掉,因此YGC采用拷贝算法进行回收,而拷贝算法需要额外空间,因此产生了survivor区
第1次YGC:大多数的对象会被回收,eden -> s0
第2次YGC:eden + s0 -> s1
第3次YGC:eden + s1 -> s0
年龄足够或者survivor区装不下:老年代
年龄指对象复制的次数
-XX:MaxTenuringThreshold:设置年龄阈值
PS默认15,因为gc的age是4位,最大是15,CMS是6,G1是15
动态年龄判定:Hotspot遍历所有对象时,按照年龄从小到大对其所占用的大小进行累积,当累积的某个年龄大小超过了survivor区的一半时,取这个年龄和MaxTenuringThreshold中更小的一个值,作为新的晋升年龄阈值
内存分配担保:YGC时,JVM会首先检查老年代最大的可用连续空间,是否大于新生代所有对象的总和,如果大于,那么这次YGC是安全的,如果不大于的话,JVM就需要判HandlePromotionFailure是否允许空间分配担保
允许空间分配担保:JVM继续检查老年代最大的可用连续空间是否大于历次晋升到老年代的所有对象总的平均大小,如果大于表示此处YGC相对安全,正常进行一次YGC,如果小于,进行FGC
不允许空间分配担保:FGC
4 老年代
FGC(Major GC、Full GC):老年代满了会产生FGC,整个内存都回收,效率低,会暂停所有当前运行的线程,产生stw(stop-the-world)停顿,进行垃圾清理
FGC本身采用标记压缩算法
5 GC Tuning(Generation):尽量减少FGC(MajorGC)
6 -Xmn:指定新生代内存大小,-Xms:指定堆内存小值,-Xmx:指定堆内存最大值。-X:非标准参数 m:memory
5 栈上分配、TLAB
1 一些小的、无逃逸的、线程私有的对象,会使用标量替换,以标量形式将该对象存放于栈内存中,而不是在堆内存中
无逃逸:某个对象的引用不会传递给其他线程或方法拿到
线程私有:不是线程私有,就意味着该对象一定会被其他线程访问到,也就一定是逃逸对象了
标量替换:例如User会被替换为一个int和一个String
// -XX:-DoEscapeAnalysis -XX:-EliminateAllocations -XX:-UseTLAB -Xlog:c5_gc* // 去掉逃逸分析、标量替换、线程专有对象分配 public class TestTLAB { class User { // user只有两个属性,都是基本类型变量,因此是支持标量替换的 int id; String name; public User(int id, String name) { this.id = id; this.name = name; } } void alloc(int i) { // 如果该方法会将新的User对象返回给main方法,就属于逃逸 new User(i, "name " + i); } 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); } }
2 栈上分配比堆上分配更快,栈中的内存不需要垃圾回收,因为变量出栈,就没了
3 TLAB(Thread Local Allocation Buffer)线程本地分配
为防止各线程对堆内存的征用导致效率降低
提前为每个线程分配独有的一块空间,eden区的1%,线程分配空间时,先往这块空间里分配
6 常见的垃圾回收器
1. Serial 年轻代 串行回收
2. Parallel Scavenge 简称PS 年轻代 并行回收
3. ParNew 年轻代 配合CMS的并行回收
4. SerialOld
5. ParallelOld
6. ConcurrentMarkSweep 简称CMS 老年代 并发的, 垃圾回收和应用程序同时运行,降低STW的时间(200ms)
7. G1(10ms)
8. ZGC (1ms) PK C++
9. Shenandoah
10. Eplison
1.8默认的垃圾回收:PS + ParallelOld
7 JVM调优第一步,了解生产环境下的垃圾回收器组合
1 JVM的命令行参数参考:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html
2 JVM参数分类:
标准:- 开头,所有的HotSpot都支持,输入java查看
非标准:-X 开头,特定版本HotSpot支持特定命令,输入java -X查看
不稳定:-XX 开头,下个版本可能取消
3 -XX相关参数查看
-XX:+PrintCommandLineFlags 打印启动时真正使用的命令行参数
-XX:+PrintFlagsFinal 最终参数值,可以查看java所有参数
-XX:+PrintFlagsInitial 默认参数值
8 GC常用参数
1 常用垃圾回收器组合设定参数(1.8)
-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的日志来分辨
2 GC通用参数
-Xmn -Xms -Xmx -Xss 年轻代 最小堆 最大堆 栈空间
-XX:+UseTLAB 使用TLAB,默认打开
-XX:+PrintTLAB 打印TLAB的使用情况
-XX:TLABSize 设置TLAB大小
-XX:+DisableExplictGC System.gc()不管用(FGC),线上系统都启用
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintHeapAtGC
-XX:+PrintGCTimeStamps
-XX:+PrintGCApplicationConcurrentTime (低) 打印应用程序时间
-XX:+PrintGCApplicationStoppedTime (低) 打印暂停时长
-XX:+PrintReferenceGC (重要性低) 记录回收了多少种不同引用类型的引用
-verbose:class 类加载详细过程
-XX:+PrintVMOptions 打印JVM启动时参数
-XX:+PrintFlagsFinal -XX:+PrintFlagsInitial 必须会用
-Xloggc:opt/log/gc.log
-XX:MaxTenuringThreshold 升代年龄,最大值15
-XX:PreBlockSpin 锁自旋多久升级为重量级锁(不建议设置)
-XX:CompileThreshold 热点代码检测参数多少次后编译为热点代码,逃逸分析 标量替换(不建议设置)