文章目录
一、如何判断对象可以回收
1. 引用计数法
(参考视频:黑马)
只要一个对象被其他变量所引用,那么这个对象计数+1;
如果某一个变量不再引用它了,计数-1
当对象的引用计数变为0,那么就可以被当做垃圾回收。
存在的问题:循环引用
这两个对象的引用计数不能归零(其他对象都无法引用它们了) 所以不能被当做垃圾回收掉(内存泄露)
2. 可达性分析算法
- Java虚拟机中的垃圾回收器采用可达性分析来探索所有存活的对象
- 扫描堆中的对象,看是否能够沿着GC Root对象 为起点的引用链找到该对象,找不到,表示可以回收
- 哪些对象可以作为GC Root? 工具:Memory Analyzer(MAT) 帮助分析堆,防止内存泄露之类的…
要把堆内存dump下来:
jmap -dump:format=b,live,file=1.bin 5580
然后将文件 1.bin放进工具
3. 四种引用
-
强引用
-
软引用 softReference
没有其他强引用指向,回收完后还是内存不足,那么就回收软引用所指对象
(软引用本身也要占用一定内存空间) 如果要清理软引用本身,那么需要配合使用引用队列
public static void main(String[] args) {
List<SoftReference<byte[]>> list = new ArrayList<>();
// 引用队列
ReferenceQueue<byte[]> queue = new ReferenceQueue<>();
for (int i = 0; i < 5; i++) {
// 关联了引用队列, 当软引用所关联的 byte[]被回收时,软引用自己会加入到 queue 中去
SoftReference<byte[]> ref = new SoftReference<>(new byte[_4MB], queue);
System.out.println(ref.get());
list.add(ref);
System.out.println(list.size());
}
// 从队列中获取无用的 软引用对象,并移除
Reference<? extends byte[]> poll = queue.poll();
while( poll != null) {
list.remove(poll);
poll = queue.poll();
}
System.out.println("===========================");
for (SoftReference<byte[]> reference : list) {
System.out.println(reference.get());
}
}
-
弱引用 WeakReference
只要发生了垃圾回收,就会把弱引用的对象回收
软、弱引用被回收,可以进入引用队列 -
虚引用
虚引用和终结器引用必须配合引用队列来使用。
回收后进入引用队列。 -
终结器引用
(类似析构函数 当对象被回收后 终结器引用调用finalize()方法善后)
二、 垃圾回收算法
1. 标记清除
扫描堆对象的过程中,如果发现对象确实被引用了,则保留,如果没有被GC root直接或间接引用,则要回收。
两个阶段: 先标记,再清除(释放空间)
注意并不是将内存中的字节清零,而是记录这些块的起始结束地址,然后放入空闲链表里。当有新对象时候,就去空闲链表里找,有没有足够的空间容纳新对象,如果有,就进行分配。
优点
速度快
缺点
容易造成内存碎片(不整理内存啦 大对象放不进去)
2. 标记整理
两个阶段: 标记,整理
整理:避免内存碎片问题,将对象向前移动(紧凑)
缺点:速度慢 对象要向前移动
3. 复制
标记,然后将from中存活的对象复制到to中。然后将from全部清空,并且交换from和to
双倍内存空间;无碎片
三、 分代垃圾回收
实际的垃圾回收,会结合前面的三种算法。具体为分代垃圾回收机制。
需要长时间使用的对象放在老年代,对于用完了就可以丢弃的对象放到新生代。这样可以根据不同生命周期的特点,采用不同的垃圾回收策略。
- 对象首先分配在伊甸园区域
- 新生代空间不足,触发minor gc,伊甸园和from存活的对象使用copy复制到to中,存活的对象年龄+1并且交换from和to
- minor gc会引发stop the world(停止其他用户线程,只保留垃圾回收线程,因为发送对象复制…地址都移动了) 垃圾回收结束,用户线程才恢复运行
- 对象寿命超过阈值时,会晋升到老年代,最大寿命是15(存寿命的是4bit 对象头中四位)
- 当老年代空间不足,会先进行minor gc看能不能腾出一点空间。发现空间仍旧不足,就触发一次full gc,STW(stop the world)的时间更长
- 如果还是不足,out of memory
相关VM参数
VM options:-Xms20M -Xmx20M -Xmn10M -XX:+UseSerialGC -XX:+PrintGCDetails -verbose:gc -XX:-ScavengeBeforeFullGC
可以看到堆内存详情:
幸存区的一个1024k是不计入总数的。
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
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
ArrayList<byte[]> list = new ArrayList<>();
list.add(new byte[_8MB]);//新生代根本不够用 如果老年代足够,那么直接放入老年代
}).start();
System.out.println("sleep....");//内存溢出了还能运行。。。一个线程内的内存溢出不会导致主线程停止运行
Thread.sleep(1000L);
}
}
GC:新生代gc
Full GC:老年代
常量池在方法区中
- 新生代内存紧张,即使没有到阈值,也可以晋升到老年代
- 新生代根本不够用 如果老年代足够,那么直接放入老年代
- 新生代和老年代都放不下对象–内存溢出
四、垃圾回收器
1. 串行的垃圾回收器
单线程垃圾回收
适合:堆内存较小,适合个人电脑
-XX:+UseSerialGC = Serial + SerialOld
打开串行的垃圾回收器
复制算法 标记整理算法(老年代)
此图既适合新生代的gc,也适合老年代的gc(这俩区别是使用的回收算法不同)
2. 吞吐量优先的垃圾回收器
- 多线程
- 堆内存较大,适用于多核cpu(单核的话,切换成本就非常高)
- 让单位时间内,STW的时间最短 0.2 0.2 总时间上会更优。
(吞吐量:垃圾回收时间占程序运行时间的占比)
-XX:+UseParallelGC ~ -XX:+UseParallelOldGC
同上,新生代使用复制算法,老年代使用标记整理算法。
停下来以后(安全点),垃圾回收器会开启多个线程来进行gc
垃圾回收线程的个数与CPU核数相同
工作时候cpu使用曲线:占用率一下100%(4核都去回收垃圾了)
线程数的控制:-XX:ParallelGCThreads=n
- 一些选项
-XX:+UseAdaptiveSizePolicy 动态调整新生代,调整Eden和Survival区的大小
目标设定:
-XX:GCTimeRatio=ratio 调整吞吐量 垃圾回收时间/总时间 1/(1+ratio) 调整这个堆大小使其达到目标 一般设置19
-XX:MaxGCPauseMillis=ms 最大暂停毫秒数
上面这两个目标是矛盾的><
3. 响应时间优先的垃圾回收器
- 多线程
- 堆内存较大,多核cpu
- 尽可能让STW的时间最短
0.1 0.1 0.1 0.1 0.1 ……
开启:-XX:+UseConcMarkSweepGC
concurrent 并发标记清除
用户进程和垃圾清除进程可以同时工作,这样减少Stop the world的时间。
cms工作在老年代的垃圾回收器
-
工作流程
工作在老年代垃圾回收的时候,标记清除算法(有内存碎片)
初始标记的时候,仍然需要stop the world
等到初始标记完成后,用户线程恢复运行,垃圾回收进程还能同时并发标记,这样响应时间是很短的。
并发结束后,还需要重新标记,要stop the world;然后用户线程恢复运行,进行清理。 -
补充
-
初始标记时候受以下参数影响
XX:ParallelGCThreads=n ~ -XX:ConcGCThreads=threads
一般4 1 -
对CPU占用不高,四核cpu只取了1核做回收
用户占用3/4 吞吐量下降一点点 -
并发清理时候用户线程还在运行,还会产生垃圾,叫做浮动垃圾。要预留一些参数来保留浮动垃圾。参数:
XX:CMSInitiatingOccupancyFraction=percent
内存占比,设置为80,就是指:老年代的空间已经占用80%时,就进行垃圾回收。这个值越小,CMS触发垃圾回收的时机越早 -
-XX:+CMSScavengeBeforeRemark
在重新标记阶段,可能有新生代的对象引用老年代的对象。使用这个标签,在重新标记阶段之前,对新生代做一次垃圾回收。 -
碎片过多,会并发失败,退化为串行垃圾回收,使得响应时间++
-