1 什么是垃圾
- 没有任何引用指向的一个对象或者多个对象(循环引用)
- 申请、释放内存
- C语言:malloc、free
- C++: new、delete
- Java:new、自动回收
- 自动回收与手动回收优缺点
- 自动回收:编程简单,系统不容易出错
- 手动回收:可能出现忘记回收(内存泄露)、多次回收(回收了有用的数据)
2 定位垃圾
- 引用计数(reference counting):对象上存放引用它的引用数量,当引用数量为0,认为是垃圾,但这种方案找不到循环引用
- 根可达(root searching):以静态变量、线程栈中变量、常量池(指向Class对象)、JNI指针(指向native方法用到的类或对象),作为根引用,顺着根引用能找到的对象都不是垃圾,其他都是垃圾,Hotspot就使用这种方式定位垃圾
3 垃圾回收算法
- 标记清除(Mark-Sweep)
- 两遍扫描:
- mark:将根对象可以访问到的对象都打上一个标识,表示可达
- sweep:遍历堆内存,将不可达对象清理
- 产生内存碎片
- 存活对象多时,效率高,因为需要清理的少
- 两遍扫描:
- 拷贝算法(Copying)
- 将堆内存对半分为两个半区,只用其中一个半区来进行对象内存的分配,如果在这个半区内存不够给新的对象分配了,那么就开始进行垃圾收集,将这个半区中的所有可达对象都拷贝到另外一个半区中去
- 需要移动和复制对象,因此对同一个对象,需修改引用指向
- 存活对象少,效率高,因为需要复制的对象少
- 标记压缩(Mark-Compact),无碎片,不浪费空间,但效率低
- 两遍扫描
- mark:
- compact:移动所有的可达对象到堆内存的同一个区域中,使他们紧凑的排列在一起,从而将所有非可达对象释放出来的空闲内存都集中在一起
- 无碎片,不会产生内存减半
- 效率低
- 算法复杂
- 多线程移动还需要时间进行同步
- 两遍扫描
4 堆内存逻辑分区(也叫分代模型)
- 从分代GC的角度看,堆分为新生代与老年代
- 有些垃圾回收器逻辑上并不是如此划分的
- 除Epsilon、ZGC、Shenandoah之外的垃圾回收期都是使用逻辑分代模型
- G1是逻辑分代,物理不分代
- 除此之外,不仅逻辑分代,而且物理分代
- 新生代 = eden + 2个survivor区
- YGC(Minor GC):无法在新生代为对象分配空间时产生,对新生代的内容进行回收,由于新生代内容通常全能被回收掉,因此YGC采用拷贝算法进行回收,而拷贝算法需要额外空间,因此产生了survivor区
- 第1次YGC:大多数的对象会被回收,eden -> s0
- 第2次YGC:eden + s0 -> s1
- 第3次YGC:eden + s1 -> s0
- 年龄足够:老年代
- 年龄指对象复制的次数
- -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
- 老年代
- FGC(Major GC、Full GC):老年代满了会产生FGC,整个内存都回收,效率低,会暂停所有当前运行的线程,产生stw(stop-the-world)停顿,进行垃圾清理
- FGC本身采用标记压缩算法
- GC Tuning(Generation):尽量减少FGC
- -Xmn:指定新生代内存大小,-Xms:指定堆内存小值,-Xmx:指定堆内存最大值
- -X:非标准参数
- m:memory
5 栈上分配、TLAB
- 一些小的、无逃逸的、线程私有的对象,会使用标量替换,以标量形式将该对象存放于栈内存中,而不是在堆内存中
- 无逃逸:某个对象的引用不会传递给其他线程或方法拿到
- 线程私有:不是线程私有,就意味着该对象一定会被其他线程访问到,也就一定是逃逸对象了
- 标量替换:例如User会被替换为一个int和一个String
- 栈上分配比堆上分配更快,栈中的内存不需要垃圾回收,因为变量出栈,就没了
- TLAB(Thread Local Allocation Buffer)线程本地分配
- 为防止各线程对堆内存的征用导致效率降低
- 提前为每个线程分配独有的一块空间,eden区的1%,线程分配空间时,先往这块空间里分配
//-XX:-DoEscapeAnalysis -XX:-EliminateAllocations -XX:-UseTLAB -Xlog:c5_gc*
//去掉逃逸分析、标量替换、线程专有对象分配
public class TestTLAB