一、如何判断对象可以回收
1. 引用计数法2. 可达性分析算法3. 四种引用 二、 垃圾回收算法
1. 标记清除2. 标记整理3. 复制 三、 分代垃圾回收
相关VM参数 四、垃圾回收器
1. 串行的垃圾回收器2. 吞吐量优先的垃圾回收器3. 响应时间优先的垃圾回收器
一、如何判断对象可以回收 1. 引用计数法(参考视频:黑马)
只要一个对象被其他变量所引用,那么这个对象计数+1;
如果某一个变量不再引用它了,计数-1
当对象的引用计数变为0,那么就可以被当做垃圾回收。
存在的问题:循环引用
这两个对象的引用计数不能归零(其他对象都无法引用它们了) 所以不能被当做垃圾回收掉(内存泄露)
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> list = new ArrayList<>();
// 引用队列
ReferenceQueue queue = new ReferenceQueue<>();
for (int i = 0; i < 5; i++) {
// 关联了引用队列, 当软引用所关联的 byte[]被回收时,软引用自己会加入到 queue 中去
SoftReference 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 reference : list) {
System.out.println(reference.get());
}
}
弱引用 WeakReference
只要发生了垃圾回收,就会把弱引用的对象回收
软、弱引用被回收,可以进入引用队列
虚引用
虚引用和终结器引用必须配合引用队列来使用。
回收后进入引用队列。
终结器引用
(类似析构函数 当对象被回收后 终结器引用调用finalize()方法善后)
扫描堆对象的过程中,如果发现对象确实被引用了,则保留,如果没有被GC root直接或间接引用,则要回收。
两个阶段: 先标记,再清除(释放空间)
注意并不是将内存中的字节清零,而是记录这些块的起始结束地址,然后放入空闲链表里。当有新对象时候,就去空闲链表里找,有没有足够的空间容纳新对象,如果有,就进行分配。
优点
速度快
缺点
容易造成内存碎片(不整理内存啦 大对象放不进去)
两个阶段: 标记,整理
整理:避免内存碎片问题,将对象向前移动(紧凑)
缺点:速度慢 对象要向前移动
标记,然后将from中存活的对象复制到to中。然后将from全部清空,并且交换from和to
双倍内存空间;无碎片
实际的垃圾回收,会结合前面的三种算法。具体为分代垃圾回收机制。
需要长时间使用的对象放在老年代,对于用完了就可以丢弃的对象放到新生代。这样可以根据不同生命周期的特点,采用不同的垃圾回收策略。
对象首先分配在伊甸园区域新生代空间不足,触发minor gc,伊甸园和from存活的对象使用copy复制到to中,存活的对象年龄+1并且交换from和tominor 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 list = new ArrayList<>();
list.add(new byte[_8MB]);//新生代根本不够用 如果老年代足够,那么直接放入老年代
}).start();
System.out.println("sleep....");//内存溢出了还能运行。。。一个线程内的内存溢出不会导致主线程停止运行
Thread.sleep(1000L);
}
}
GC:新生代gc
Full GC:老年代
常量池在方法区中
新生代内存紧张,即使没有到阈值,也可以晋升到老年代新生代根本不够用 如果老年代足够,那么直接放入老年代
新生代和老年代都放不下对象–内存溢出
单线程垃圾回收
适合:堆内存较小,适合个人电脑
-XX:+UseSerialGC = Serial + SerialOld 打开串行的垃圾回收器
复制算法 标记整理算法(老年代)
此图既适合新生代的gc,也适合老年代的gc(这俩区别是使用的回收算法不同)
多线程堆内存较大,适用于多核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在重新标记阶段,可能有新生代的对象引用老年代的对象。使用这个标签,在重新标记阶段之前,对新生代做一次垃圾回收。
碎片过多,会并发失败,退化为串行垃圾回收,使得响应时间++



