垃圾回收( Garbage Collection 以下简称 GC)诞生于1960年 MIT 的 Lisp 语言,有半个多世纪的历史。在Java 中,JVM 会对内存进行自动分配与回收,其中 GC 的主要作用就是清楚不再使用的对象,自动释放内存。
GC 相关的研究者们主要是思考这3件事情。
哪些内存需要回收?
什么时候回收?
如何回收?
本文也大致按照这个思路,为大家描述垃圾回收的相关知识。因为会有很多内存区域相关的知识,希望读者先学习完精美图文带你掌握 JVM 内存布局再来阅读本文。
在这里先感谢周志明大佬的新鲜出炉的大作:《深入理解Java 虚拟机》- 第3版
拜读之后对JVM有了更深的理解,强烈推荐大家去看。
本文的主要内容如下(建议大家在阅读和学习的时候,也大致按照以下的思路来思考和学习):
哪些内存需要回收?即GC 发生的内存区域?
如何判断这个对象需要回收?即GC 的存活标准?
这里又能够引出以下的知识概念:
引用计数法
可达性分析法
引用的种类和特点、区别 (强引用、软引用、弱引用、虚引用)
延伸知识:(WeakHashMap) (引用队列)
有了对象的存活标准之后,我们就需要知道GC 的相关算法(思想)
标记-清除(Mark-Sweep)算法
复制(Copying)算法
标记-整理(Mark-Compact)算法
在下一步学习之前,还需要知道一些GC的术语,防止对一些概念描述出现混淆
知道了算法之后,自然而然我们到了JVM中对这些算法的实现和应用,即各种垃圾收集器(Garbage Collector)
串行收集器
并行收集器
CMS 收集器
G1 收集器
一句话:GC 主要关注 堆和方法区
在精美图文带你掌握 JVM 内存布局一文中,理解介绍了Java 运行时内存的分布区域和特点。
其中我们知道了程序计数器、虚拟机栈、本地方法栈3个区域是随线程而生,随线程而灭的。栈中的栈帧随着方法的进入和退出而有条不紊地执行着出栈和入栈操作。每一个栈帧中分配多少内存基本上是在类结构确定下来时就已知的(尽管在运行期会由JIT编译器进行一些优化,但在本章基于概念模型的讨论中,大体上可以认为是编译期可知的),因此这几个区域的内存分配和回收都具备确定性,在这几个区域内就不需要过多考虑回收的问题,因为方法结束或者线程结束时,内存自然就跟随着回收了。
而堆和方法区则不一样,一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也可能不一样,我们只有在程序处于运行期间时才能知道会创建哪些对象,这部分内存的分配和回收都是动态的。GC 关注的也就是这部分的内存区域。
知道哪些区域的内存需要被回收之后,我们自然而然地想到了,如何去判断一个对象需要被回收呢?对于如何判断对象是否可以回收,有两种比较经典的判断策略。
在对象头维护着一个 counter 计数器,对象被引用一次则计数器 +1;若引用失效则计数器 -1。当计数器为 0 时,就认为该对象无效了。主流的Java虚拟机里面没有选用引用计数算法来管理内存,其中最主要的原因是它很难解决对象之间相互循环引用的问题。发生循环引用的对象的引用计数永远不会为0,结果这些对象就永远不会被释放。
从GC Roots 为起点开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots 没有任何引用链相连时,则证明此对象是不可用的。不可达对象。Java 中,GC Roots 是指:
Java对引用的概念进行了扩充,将引用分为强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)4种,这4种引用强度依次逐渐减弱。这样子设计的原因主要是为了描述这样一类对象:当内存空间还足够时,则能保留在内存之中;如果内存空间在进行垃圾收集后还是非常紧张,则可以抛弃这些对象。很多系统的缓存功能都符合这样的应用场景。也就是说,对不同的引用类型,JVM 在进行GC 时会有着不同的执行策略。所以我们也需要去了解一下。
MyClass obj = new MyClass(); // 强引用
obj = null // 此时‘obj’引用被设为null了,前面创建的'MyClass'对象就可以被回收了
只要强引用存在,垃圾收集器永远不会回收被引用的对象,只有当引用被设为null的时候,对象才会被回收。但是,如果我们错误地保持了强引用,比如:赋值给了 static 变量,那么对象在很长一段时间内不会被回收,会产生内存泄漏。
软引用是一种相对强引用弱化一些的引用,可以让对象豁免一些垃圾收集,只有当 JVM 认为内存不足时,才会去试图回收软引用指向的对象。JVM 会确保在抛出 OutOfMemoryError 之前,清理软引用指向的对象。软引用通常用来实现内存敏感的缓存,如果还有空闲内存,就可以暂时保留缓存,当内存不足时清理掉,这样就保证了使用缓存的同时,不会耗尽内存。
SoftReference<MyClass> softReference = new SoftReference<>(new MyClass());
弱引用的强度比软引用更弱一些。当 JVM 进行垃圾回收时,无论内存是否充足,都会回收只被弱引用关联的对象。
WeakReference<MyClass> weakReference = new WeakReference<>(new MyClass());
弱引用可以引申出来一个知识点, WeakHashMap&ReferenceQueueReferenceQueue 是GC回调的知识点。这里因为篇幅原因就不细讲了,推荐引申阅读:ReferenceQueue的使用
虚引用也称为幽灵引用或者幻影引用,它是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。
PhantomReference<MyClass> phantomReference = new PhantomReference<>(new MyClass(), new ReferenceQueue<>());
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!