文章为 深入理解java虚拟机 第三版 周志明 著 读书笔记 仅供学习交流
1 为什么需要垃圾收集器
垃圾收集(Garbage Collection 简称GC)。Java内存运 行时区域的各个部分,其中程序计数器、虚拟机栈、本地方法栈3个区域随线程而生,随线程而灭,栈 中的栈帧随着方法的进入和退出而有条不紊地执行着出栈和入栈操作。每一个栈帧中分配多少内存基 本上是在类结构确定下来时就已知的,这几个区域的内存分配和回收都具备确定性, 在这几个区域内就不需要过多考虑如何回收的问题。而Java堆和方法区这两个区域则有着很显著的不确定性,这部分内存的分配和回收是动态的。垃圾收集器 所关注的正是这部分内存该如何管理。
2 如何判断对象已死
引用计数算法
在对象中添加一个引用计数器,每当有一个地方 引用它时,计数器值就加一;当引用失效时,计数器值就减一;任何时刻计数器为零的对象就是不可 能再被使用。引用计数算法(Reference Counting)虽然原理简单判定方式也很高但是无法解决对象相互循环引用问题。
/** * @Author: QinMY * @Date: 2021/3/27 11:50 * @Des: 引用计数算法的缺陷 * vm args:-XX:+PrintGCDetails */ public class ReferenceCountingGC { public Object instance = null; /*** 这个成员属性的唯一意义就是占点内存,以便能在GC日志中看清楚是否有回收过 */ private static final int _1m = 1024 * 1024; public static void main(String[] args) { ReferenceCountingGC objA = new ReferenceCountingGC(); ReferenceCountingGC objB = new ReferenceCountingGC(); objA.instance = objB; objB.instance = objA; objA = null; objB = null; //假设发生GC 看对象objA 和 objB 是否回收 System.gc(); } }
[GC (System.gc()) [PSYoungGen: 6774K->1374K(75776K)] 6774K->1382K(249344K), 0.0013808 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC (System.gc()) [PSYoungGen: 1374K->0K(75776K)] [ParOldGen: 8K->1160K(173568K)] 1382K->1160K(249344K), [Metaspace: 3308K->3308K(1056768K)], 0.0054540 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] Heap PSYoungGen total 75776K, used 1951K [0x000000076b600000, 0x0000000770a80000, 0x00000007c0000000) eden space 65024K, 3% used [0x000000076b600000,0x000000076b7e7c80,0x000000076f580000) from space 10752K, 0% used [0x000000076f580000,0x000000076f580000,0x0000000770000000) to space 10752K, 0% used [0x0000000770000000,0x0000000770000000,0x0000000770a80000) ParOldGen total 173568K, used 1160K [0x00000006c2200000, 0x00000006ccb80000, 0x000000076b600000) object space 173568K, 0% used [0x00000006c2200000,0x00000006c23223f0,0x00000006ccb80000) Metaspace used 3315K, capacity 4496K, committed 4864K, reserved 1056768K class space used 359K, capacity 388K, committed 512K, reserved 1048576K
-
从结果看内存日志中 6774K->1374K,说明虚拟机并没有因为这两 个对象互相引用就放弃回收它们,这也从侧面说明了Java虚拟机并不是通过引用计数算法来判断对象 是否存活的
可达性分析
当前主流的商用程序语言都是通过可达性分析(Reachability Analysis)算法来判定对象是否存活的。这个算法的基本思路就是通过 一系列称为“GC Roots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过 程所走过的路径称为“引用链”(Reference Chain),如果某个对象到GC Roots间没有任何引用链相连, 或者用图论的话来说就是从GC Roots到这个对象不可达时,则证明此对象是不可能再被使用的。
GC Root的对象包括一下几种: * 虚拟机栈中引用的对象 比如各个线程被调用的方法堆栈中使用到的参数 局部变量 临时变量 * 方法区中类静态属性引用的对象 比如 java类的引用类型静态变量 * 方法区中常量引用的对象 比如字符串常量池中的引用 * 在本地方法栈中JNI引用的对象 * java虚拟机的内部引用 如基本数据类型对应的Clasa对象 一些常驻的异常对象 还有系统类加载器 * 所有被同步锁(Synchronized)持有的对象 * 反应java虚拟机内部情况的JMXBean JVMTI中注册的回调 ,本地代码缓存等
3 再谈引用
java将引用分为四中
-
强引用(Strongly Reference)
强引用是最传统的“引用”的定义,是指在程序代码之中普遍存在的引用赋值,即类似“Object obj=new Object()”这种引用关系。无论任何情况下,只要强引用关系还存在,垃圾收集器就永远不会回
收掉被引用的对象。
-
软引用(Soft Reference)
·软引用是用来描述一些还有用,但非必须的对象。只被软引用关联着的对象,在系统将要发生内 存溢出异常前,会把这些对象列进回收范围之中进行第二次回收,如果这次回收还没有足够的内存, 才会抛出内存溢出异常。在JDK 1.2版之后提供了SoftReference类来实现软引用。
-
弱引用(Weak Reference)
·弱引用也是用来描述那些非必须对象,但是它的强度比软引用更弱一些,被弱引用关联的对象只 能生存到下一次垃圾收集发生为止。当垃圾收集器开始工作,无论当前内存是否足够,都会回收掉只 被弱引用关联的对象。在JDK 1.2版之后提供了WeakReference类来实现弱引用。
-
虚引用(Phantom Reference)
虚引用也称为“幽灵引用”或者“幻影引用”,它是最弱的一种引用关系。一个对象是否有虚引用的 存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚 引用关联的唯一目的只是为了能在这个对象被收集器回收时收到一个系统通知。在JDK 1.2版之后提供 了PhantomReference类来实现虚引用。
4 生存还是死亡
即使在可达性分析算法中判定为不可达的对象,也不是“非死不可”的,这时候它们暂时还处于“缓 刑”阶段,要真正宣告一个对象死亡,至少要经历两次标记过程:如果对象在进行可达性分析后发现没 有与GC Roots相连接的引用链,那它将会被第一次标记,随后进行一次筛选,筛选的条件是此对象是 否有必要执行finalize()方法。假如对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用 过,那么虚拟机将这两种情况都视为“没有必要执行”。
5 方法区(元空间)回收
方法区的垃圾回收主要回收两部分内容:废弃的常量和不在使用的类型
-
废弃的常量 已经没有任何字符串对象引用常量池的常量 在虚拟机中也没有其他地方引用这个字面量
-
不再使用的类型
-
该类的所有实例都已经被回收 java堆中不存在该类及其任何派生子类的实例
-
加载该类的类加载器都已经回收
-
该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问类的方法
-