二、垃圾回收
垃圾回收
1. 如何判断对象可以回收/是垃圾
1.1 引用计数法
- 早期python虚拟机使用这种方法
- 存在2个互相引用,进而不能释放的问题
- Java虚拟机没有采用这种方法
1.2 可达性分析算法
- Java 虚拟机中的垃圾回收器采用可达性分析来探索所有存活的对象
- 扫描堆中的对象,看是否能够沿着 GC Root对象 为起点的引用链找到该对象,找不到,表示可以 回收
- 哪些对象可以作为 GC Root(是一系列对象) ?
Memory Analyzer(MAT) - eclipse提供的著名的专业的Java heap 分析工具
/**
* 演示GC Roots,通过看list1没值和有值前后对比,看到底哪些对象可以作为根对象
*/
public class Demo2_2 {
public static void main(String[] args) throws InterruptedException, IOException {
List
- 使用
MAT
先用jps
看进程id - 分别用
jmap
在list1有值和null时抓取内存快照:jmap -dump:format=b,live,file=1.bin 21384
- live参数表示只抓存活的,并且抓取之前会进行一次GC
- format=b 表示格式为二进制
- file=1.bin表示将抓取的数据存在当前文件夹下1.bin文件中
- 21384 是进程ID
- 用MAT打开上面的2个快照文件
- 查看当前快照的GC Roots对象有哪些
- GC Roots对象是堆中的对象,不是栈帧中的局部变量引用
- Busy Monitor:被加锁的对象
- System Class:系统类对象
- Thread:活动线程栈帧内使用的对象,比如下图中list1引用的对象就是,方法参数引用的也是
1.3 四种引用
- 强引用 (
StrongReference
) :只有所有GC Roots
对象都不通过【强引用】引用该对象,该对象才能被垃圾回收- 软引用(
SoftReference
):- 仅有软引用引用该对象时,在垃圾回收后,内存仍不足时会再次出发垃圾回收,回收软引用 对象
- 可以配合引用队列来释放软引用自身
- 弱引用(
WeakReference
):- 仅有弱引用引用该对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用对象
- 可以配合引用队列来释放弱引用自身
- 虚引用(
PhantomReference
): 必须配合引用队列使用,主要配合ByteBuffer
使用,被引用对象回收时,会将虚引用入队, 由Reference Handler
线程调用虚引用相关方法释放直接内存 - 终结器引用(
FinalReference
): 无需手动编码,但其内部配合引用队列使用,在垃圾回收时,终结器引用入队(被引用对象 暂时没有被回收),再由Finalizer
线程通过终结器引用找到被引用对象并调用它的 finalize 方法,第二次 GC 时才能回收被引用对象
//加上虚拟机参数 -Xmx20m -XX:+PrintGCDetails -verbose:gc 再执行下面代码terminal中观察内存使用情况 public class Demo2_3 { private static final int _4MB = 4 * 1024 * 1024; public static void main(String[] args) throws IOException { /*List
[]>> list = new ArrayList<>(); for (int i = 0; i < 5; i++) { SoftReference<byte[]> ref = new SoftReference<>(new byte[_4MB]); System.out.println(ref.get()); list.add(ref); System.out.println(list.size()); } System.out.println("循环结束:" + list.size()); for (SoftReference<byte[]> ref : list) { System.out.println(ref.get()); } } }list = new ArrayList<>(); for (int i = 0; i < 5; i++) { list.add(new byte[_4MB]); } System.in.read();*/ soft(); } public static void soft() { // list --> SoftReference --> byte[] Listbyte - 软引用(
2.1 标记清除
定义: Mark Sweep
- 速度较快 会造成内存碎片
2.2 标记整理
定义:Mark Compact
- 速度慢 没有内存碎片
2.3 复制
定义:Copy
- 不会有内存碎片
- 需要占用双倍内存空间
3. 分代垃圾回收
- 对象首先分配在伊甸园区域
- 新生代空间不足时,触发 minor gc,伊甸园和 from 存活的对象使用 copy 复制到 to 中,存活的 对象年龄加 1并且交换 from to
- minor gc 会引发 stop the world(STW),暂停其它用户的线程,等垃圾回收结束,用户线程才恢复运行
- 当对象寿命超过阈值时,会晋升至老年代,最大寿命是15(4bit)
- 当老年代空间不足,会先尝试触发 minor gc,如果之后空间仍不足,那么触发 full gc,STW的时 间更长
3.1 相关 VM 参数
含义 参数
堆初始大小 -Xms
堆最大大小 -Xmx 或 -XX:MaxHeapSize=size
新生代大小 -Xmn 或 (-XX:NewSize=size + -XX:MaxNewSize=size )
- 括号中分别为新生代初始大小和最大大小
幸存区比例(动态) -XX:InitialSurvivorRatio=ratio 和 -XX:+UseAdaptiveSizePolicy
幸存区比例 -XX:SurvivorRatio=ratio
- eg:ratio为8,新生代为10MB,则伊甸园为8M,from to 各1M
晋升阈值 -XX:MaxTenuringThreshold=threshold
- 新生代晋升到老年代的阈值
晋升详情 -XX:+PrintTenuringDistribution
GC详情 -XX:+PrintGCDetails -verbose:gc
FullGC 前 MinorGC -XX:+ScavengeBeforeFullGC
3.2 示例
/**
* 演示内存的分配策略
*/
public class Demo2_1 {
private static final int _512KB = 512 * 1024;
private static final int _1MB = 1024 * 1024;
private static final int _6MB = 6 * 1024 * 1024;
private static final int _7MB = 7 * 1024 * 1024;
private static final int _8MB = 8 * 1024 * 1024;
// -Xms20M -Xmx20M -Xmn10M -XX:+UseSerialGC -XX:+PrintGCDetails -verbose:gc -XX:-ScavengeBeforeFullGC
//堆初始大小和最大大小都为20M,新生代10M(输出的新生代大小为9M,包含伊甸园和from区域)
//UseSerialGC表示用这个垃圾回收器,JDK1.8默认不是这个
//ScavengeBeforeFullGC:表示FullGC前进行一次GC,默认也是开启的
public static void main(String[] args) throws InterruptedException {
}
}
初始main函数为空,输出如下
- 对于大对象,新生代放不下,老年代能放下,会直接放入老年代,不会触发GC
对面上面的类,若main函数如下
public static void main(String[] args) public static void main(String[] args) throws InterruptedException {
list.add(new byte[_8MB]);
}
输出
- 如果新生代和老年代都放不下,会触发OOM
- 一个线程OOM不会导致整个进程终止
public static void main(String[] args) public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
ArrayList<byte[]> list = new ArrayList<>();
list.add(new byte[_8MB]);
list.add(new byte[_8MB]);
}).start();
System.out.println("sleep....");
Thread.sleep(1000L);
}
4. 垃圾回收器
4.1. 串行
- 单线程
- 堆内存较小,适合个人电脑
4.2. 吞吐量优先
- 多线程
- 堆内存较大,多核 cpu
- 让单位时间内,STW 的时间最短(比如1H内发生2次,共 0.2S 0.2S = 0.4S),垃圾回收时间占比最低,这样就称吞吐量高
4.3 响应时间优先
- 多线程
- 堆内存较大,多核 cpu 尽可能让单次 STW 的时间最短比如(1h内发生了5次gc,共 0.1 0.1 0.1 0.1 0.1 = 0.5s)
5. 垃圾回收调优
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!