一、对象回收
引用计数法
- 在对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加一;当引用失效时,计数器值就减一;任何时刻计数器为零的对象就是不可能再被使用的
public class ReferenceCoutingGC { public Object instance = null; private static final int _1MB = 1024*1024; private byte[] bigSize = new byte[2 * _1MB]; public static void testGC(){ ReferenceCoutingGC objA = new ReferenceCoutingGC(); ReferenceCoutingGC objB = new ReferenceCoutingGC(); objA.instance = objB; objB.instance = objA; objA = null; objB = null; System.gc(); } public static void main(String[] args) { testGC(); } }
- 意味着虚拟机并没有因为这两个对象互相引用就放弃回收它们,这也从侧面说明了Java虚拟机并不是通过引用计数算法来判断对象是否存活的。
可达性分析算法
- 如果从GC Roots到某个对象是不可达的,则证明该对象不可能再被使用
- 固定可作为GC Roots的对象有:
- 在方法区中类静态属性引用的对象
- 在方法区中常量引用的对象。如字符串常量池里的引用
- 在本地方法栈中引用的对象
- Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象(比如
NullPointExcepiton
、OutOfMemoryError
)等,还有系统类加载器 - 所有被同步锁(
synchronzied
关键字)所持有的对象 - 反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等
二、四种引用
强引用
- 指代码间的引用赋值(
Object obj = new Object()
),只有所有的GC Roots对象都不引用该对象,才能被垃圾回收
软引用
- 软引用用来描述一些还有用但非必须的对象
- 只被软引用关联着的对象,在系统将要发生内存溢出异常前,会把这些对象列进回收范围之中进行第二次回收,如果这次回收还没有足够的内存,才会抛出内存溢出异常
//-Xmx20m -XX:+PrintGCDetails -verbose:gc public class Demo2_3 { private static final int _4MB = 4 * 1024 * 1024; public static void main(String[] args) throws IOException { //没有使用软引用对象 /*List<byte[]> 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[] List<SoftReference<byte[]>> 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()); } } }
//运行结果 [B@45ee12a7 1 [B@330bedb4 2 [B@2503dbd3 3 //第三次循环结束时,内存已经紧张,触发一次新生代回收 [GC (Allocation Failure) [PSYoungGen: 2351K->488K(6144K)] 14639K->13130K(19968K), 0.0022554 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [B@4b67cf4d 4 //一次新生代回收仍不够,触发全面回收 [GC (Allocation Failure) --[PSYoungGen: 4696K->4696K(6144K)] 17338K->17338K(19968K), 0.0022631 secs] [Times: user=0.05 sys=0.00, real=0.00 secs] [Full GC (Ergonomics) [PSYoungGen: 4696K->4508K(6144K)] [ParOldGen: 12642K->12612K(13824K)] 17338K->17121K(19968K), [Metaspace: 3513K->3513K(1056768K)], 0.0058797 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] //仍内存不够,触发软引用回收 [GC (Allocation Failure) --[PSYoungGen: 4508K->4508K(6144K)] 17121K->17121K(19968K), 0.0007782 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC (Allocation Failure) [PSYoungGen: 4508K->0K(6144K)] [ParOldGen: 12612K->719K(8704K)] 17121K->719K(14848K), [Metaspace: 3513K->3513K(1056768K)], 0.0078229 secs] [Times: user=0.00 sys=0.02, real=0.01 secs] [B@7ea987ac 5 循环结束:5 null null null null [B@7ea987ac //null值即为软引用,占用内存,可配合引用队列释放 Heap PSYoungGen total 6144K, used 4264K [0x00000000ff980000, 0x0000000100000000, 0x0000000100000000) eden space 5632K, 75% used [0x00000000ff980000,0x00000000ffdaa100,0x00000000fff00000) from space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000) to space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000) ParOldGen total 8704K, used 719K [0x00000000fec00000, 0x00000000ff480000, 0x00000000ff980000) object space 8704K, 8% used [0x00000000fec00000,0x00000000fecb3c48,0x00000000ff480000) Metaspace used 3520K, capacity 4506K, committed 4864K, reserved 1056768K class space used 391K, capacity 394K, committed 512K, reserved 1048576K Process finished with exit code 0
- 软引用本身也会占用少量内存,可配合引用队列来释放软引用本身
public class Demo2_4 { private static final int _4MB = 4 * 1024 * 1024; 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()); } } }
[B@45ee12a7 1 [B@330bedb4 2 [B@2503dbd3 3 [B@4b67cf4d 4 [B@7ea987ac 5 =========================== [B@7ea987ac
弱引用
- 仅有弱引用引用该对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用对象
public class Demo2_5 { private static final int _4MB = 4 * 1024 * 1024; public static void main(String[] args) { // list --> WeakReference --> byte[] List<WeakReference<byte[]>> list = new ArrayList<>(); for (int i = 0; i < 5; i++) { WeakReference<byte[]> ref = new WeakReference<>(new byte[_4MB]); list.add(ref); for (WeakReference<byte[]> w : list) { System.out.print(w.get()+" "); } System.out.println(); } System.out.println("循环结束:" + list.size()); } }
[B@45ee12a7 [B@45ee12a7 [B@330bedb4 [B@45ee12a7 [B@330bedb4 [B@2503dbd3 [GC (Allocation Failure) [PSYoungGen: 2351K->488K(6144K)] 14639K->13062K(19968K), 0.0015034 secs] [Times: user=0.06 sys=0.02, real=0.00 secs] [B@45ee12a7 [B@330bedb4 [B@2503dbd3 [B@4b67cf4d [GC (Allocation Failure) [PSYoungGen: 4696K->504K(6144K)] 17270K->13102K(19968K), 0.0012889 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [B@45ee12a7 [B@330bedb4 [B@2503dbd3 null [B@7ea987ac //回收第四个,才放得下第五个 循环结束:5 Heap PSYoungGen total 6144K, used 4768K [0x00000000ff980000, 0x0000000100000000, 0x0000000100000000) eden space 5632K, 75% used [0x00000000ff980000,0x00000000ffdaa130,0x00000000fff00000) from space 512K, 98% used [0x00000000fff80000,0x00000000ffffe010,0x0000000100000000) to space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000) ParOldGen total 13824K, used 12598K [0x00000000fec00000, 0x00000000ff980000, 0x00000000ff980000) object space 13824K, 91% used [0x00000000fec00000,0x00000000ff84d8d0,0x00000000ff980000) Metaspace used 3518K, capacity 4504K, committed 4864K, reserved 1056768K class space used 390K, capacity 392K, committed 512K, reserved 1048576K Process finished with exit code 0
虚引用
- 必须配合引用队列使用,主要配合 ByteBuffer 使用,被引用对象回收时,会将虚引用入队,由 Reference Handler 线程调用虚引用相关方法释放直接内存
三、垃圾回收算法
标记-清除算法
- 首先标记出所有需要回收的对象,在标记完成后,统一回收掉所有被标记的对象
- 缺点:会导致内存空间碎片化
标记-复制算法
- 将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉
标记-整理算法
- 让所有存活的对象都向内存空间一端移动,然后直接清理掉边界以外的内存
四、分代垃圾回收
-
分代回收过程:
- 对象在新生代Eden区中分配
- 新生代内存不足,会触发minor gc,Eden区和From幸存区存活的对象使用copy复制到To幸存区,存活对象年龄加一,并且交换From区和To区
- 当对象寿命超过阈值,会晋升至老年代,最大寿命15(4bit)
- 当老年代空间不足,先触发minor gc,若空间仍不足,触发full gc,最后空间若仍不足,报错
OutOfMemoryError
- minor gc 会引发STW(stop the world),暂停其他用户线程,等垃圾回收结束,用户线程才恢复运行
-
大对象直接进入老年代
public class Demo2_1 { 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 { ArrayList<byte[]> list = new ArrayList<>(); list.add(new byte[_8MB]); list.add(new byte[_8MB]); } }
可以看到新生代中几乎没有内存占用,老年代占用了8MB,然而集合中加了两个8MB,最后一个加不进去,所以报了OutOfMemoryError
错误 -
VM参数
含义 参数 堆初始大小 -Xms 堆最大大小 -Xmx 或 -XX:MaxHeapSize=size 新生代大小 -Xmn 或 (-XX:NewSize=size + -XX:MaxNewSize=size ) 幸存区比例(动态) -XX:InitialSurvivorRatio=ratio 和 -XX:+UseAdaptiveSizePolicy 幸存区比例 -XX:SurvivorRatio=ratio 晋升阈值 -XX:MaxTenuringThreshold=threshold 晋升详情 -XX:+PrintTenuringDistribution GC详情 -XX:+PrintGCDetails -verbose:gc FullGC 前 MinorGC -XX:+ScavengeBeforeFullGC 幸存区中的ratio,默认为8,也就是说Eden占新生代的8/10,From幸存区和To幸存区各占新生代的1/10
五、垃圾回收器
串行
- 单线程垃圾回收器,堆内存较小
-XX:+UseSerialGC = Serial + SerialOld
,新生代回收器Serial
采用标记-复制算法,老年代回收器SerialOld
采用标记-整理算法
吞吐量优先
Parallel Scavenge
新生代回收器与Parallel Old
老年代回收器配合使用- 吞吐量就是处理器用于运行用户代码的时间与处理器总消耗时间(运行代码与垃圾回收之和)的比值
参数 含义 -XX:+UseParallelGC ~ -XX:+UseParallelOldGC 开关 -XX:GCTimeRatio=ratio 垃圾收集时间占总时间的比率:1/(1+ratio),默认ratio为99 -XX:MaxGCPauseMillis 最大垃圾回收停顿时间 -XX:+UseAdaptiveSizePolicy 自适应调整策略 -XX:ParallelGCThreads=n 指定并发线程数目
响应时间优先
- 新生代
ParNew
和老年代CMS
配合使用,当CMS
并发失败,则将多线程退化到单线程,使用SerialOld
- 初始标记:仅仅标记以下GC Roots能直接关联到的对象,速度很快
- 并发标记:从GC Roots的直接关联对象开始遍历整个对象图,耗时较长但不需要停顿用户线程,可与垃圾回收线程并发运行
- 重新标记:为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那部分对象的标记记录
- 并发清除:清理删除掉标记阶段判断的已经死亡的对象,可与用户线程并发执行
六、Garbage First垃圾回收器
- G1可以面向堆内存任何部分来组成回收集,衡量标准不再是它属于哪个分代,而是哪块内存中存放的垃圾数量最多,回收收益最大,这就是G1收集器的Mixed GC模式
回收过程
- Young Collection:新生代垃圾收集,会STW
- Young Collection + CM:在 Young GC 时会进行 GC Root 的初始标记,老年代占用堆空间比例达到阈值时,进行并发标记(不会 STW)
- Mixed Collection:对E、S、O进行全面垃圾回收
跨代引用
- 在寻找GC Roots过程中,老年代中有很多对象,查找效率低
- 利用卡表技术,将老年代分为一个个的卡,当该卡引用了新生代则将其标记为脏卡
- 每次引用更新时,执行写屏障,将该操作加入到队列中,异步执行