这篇文章我们主要关注这些问题::Java程序执行完后,堆中的对象什么时候被回收?如何回收?
堆又叫做 “GC堆" 由于现在收集器基本都采用分代收集算法,所以Java堆还可以细分为:新生代和老年代,比例是1:2;再细致一点新生代内部又划分为Eden区、Survivor区,比例为8:1。下图显示了堆的结构:
对象在堆中内存的分配是有严格规定的,策略为:
- 对象优先在新生代Eden区分配内存;
- 大对象直接进老年代,主要是长字符串和数组这些需要大量连续内存空间的对象;
- 长期存活的对象进入老年代。Eden区内存不够时,JVM发起一次MinorGC,对象的年龄加一,默认对象年龄到15时进入老年代;
- 动态年龄判定。相同年龄所有对象大小的总和大于 Survivor 空间的一半,大于等于该年龄的对象进入老年代。
- 老年代空间不足;
- 方法区空间不足;
- 调用System.gc(),建议JVM进行full gc;
- 长期存活的对象转入老年代,空间不足;
- 没有足够的连续空间分配给大对象;
- 新生代垃圾回收存活的对象太多,S1放不下,老年代担保空间不足,担保空间指的是老年代最大可用的连续空间是否大于新生代所有对象总空间。
堆里面几乎放了所有的对象,那我们怎么知道这些对象是否还有用呢?JVM提供了两种方法来判定:
- 强引用,new出来的对象,垃圾回收器绝不会回收它;
- 软引用,在系统将要发生OMM前会回收这些对象的内存;一些不太重要的资源,可以使用软引用包装,内存不够时来回收。使用场景可以是下面这种:
- 软引用的使用是创建SoftReference对象,下面的代码 soft 对象 关联了byte数组。当byte对象被回收了,软引用对象还是存活的,值是null,虽然占用空间比较小,但还需要回收。可以使用引用队列。
ReferenceQueue<byte[]> list = new ReferenceQueue<>(); for (int i = 0; i < 4; i++) { // soft关联了引用队列,当软引用关联的byte数组被回收时,软引用对象本身会加入到队列中去, SoftReference<byte[]> soft = new SoftReference<>(new byte[_4MB],queue); list.add(soft) } // 再从队列中将无用的软引用对象移除 Reference<? extends byte[]> poll = queue.poll(); while (poll != null) { list.remove(poll); poll = queue.poll(); }
- 弱引用,垃圾收集器工作时只要发现,马上回收;
WeakReference<byte[]> soft = new WeakReference<>(new byte[4]);
- 虚引用,形同虚设,任何时候都可能被回收。
我们已经知道对象什么时候被回收了,那如何回收呢?介绍四种最常用的垃圾回收算法:
垃圾收集算法是一种内存回收的思想,具体的实现是垃圾收集器。简要介绍下常用的垃圾收集器:
- serial串行收集器:单线程,垃圾回收的时候,必须暂停其他工作。新生复制,老年标记整理,简单高效;
- ParNew 收集器:serial的多线程版本;
- Parallel Scavenge 收集器:复制算法的多线程收集器。注重吞吐量,cpu运行代码时间/cpu耗时总时间。新生复制,老年标记整理;
- Serial Old 收集器:老年代版本;
- Parallel Old 收集器:Parallel Scavenge老年代版本;
- CMS 收集器,注重最短时间停顿。并发收集器,垃圾收集线程与用户线程(基本上)同时工作。 标记清除算法
关于垃圾收集器更多的细节可以阅读周志明老师的书。
参考资料:《深入理解Java虚拟机》第二版 周志明
《深入拆解Java虚拟机》郑雨迪
《JVM虚拟机底层原理分析与性能调优》程序员诸葛