title: 垃圾收集算法
tag: 笔记 JVM
![image-20240305204940086](https://i-blog.csdnimg.cn/blog_migrate/508c84d65ef0a0f9c73ef77c64cb16ae.png)
概述
垃圾收集应当完成三件事情:
- 哪些内存需要回收
- 什么时候回收
- 如何回收
在之前我们了解的Java运行时内存的各个区域中,其中的程序计数器,虚拟机栈和本地方法栈都是线程私有的,它们随着线程生和灭。因此这些线程私有的区域一般是不需要考虑垃圾回收。
而在Java堆和方法区这两个区域有显著的不确定性,只有处于运行期间,我们才能知道程序究竟会创建哪些对象,创建多少个对象,这部分内存的分配和回收是动态的。因此JVM的垃圾收集器主要都是针对堆和方法区的内存管理。
检测对象存活
在堆里基本存放Java中所有的对象实例,因此要进行垃圾收集就需要知道这些对象中哪些是"死"的,哪些是“活”的。
引用计数算法
此种算法会在每一个对象上记录这个对象被引用的次数,只要有任何一个对象引用了次对象,这个对象的计数器就+1,取消对这个对象的引用时,计数器就-1。任何一个时刻,如果该对象的计数器为0,那么这个对象就是可以回收的。
这种方法虽然简单,效率也较高,但这种方法也存在一些问题比如当出现循环依赖时,两个互相引用的对象它们的计数器都为1,哪怕它们没有被任何其它对象引用它们也不会被回收。
因此在Java的垃圾收集器中都没有使用这种方式来确定对象的存活。
可达性分析算法
大部分程序语言均用在可达性分析算法来判断一个对象是存活。
这个算法的基本思路就是通过 一系列称为“GC Roots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为“引用链”(Reference Chain),如果某个对象到GC Roots间没有任何引用链相连, 或者用图论的话来说就是从GC Roots到这个对象不可达时,则证明此对象是不可能再被使用的。
![image-20240305204940086](https://i-blog.csdnimg.cn/blog_migrate/508c84d65ef0a0f9c73ef77c64cb16ae.png)
而可以作为GC Roots节点的对象通常是以下几种:
- 在虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如各个线程被调用的方法堆栈中使用到的 参数、局部变量、临时变量等。
- ·在方法区中类静态属性引用的对象,譬如
Java
类的引用类型静态变量。 · - 在方法区中常量引用的对象,譬如字符串常量池(
String Table
)里的引用。 - ·在本地方法栈中JNI(即通常所说的
Native
方法)引用的对象。 - ·Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象(比如
NullPointExcepiton
、OutOfMemoryError
)等,还有系统类加载器。 · - 所有被同步锁(synchronized关键字)持有的对象。
- ·反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等。
引用类型
在JDK1.2之后为了描述这样的对象:当内存空间还足够时,能保留在内存之中,如果内存空间在进行垃圾收集后仍然非常紧张,那就可以抛弃这些对象。因此对Java的引用概念进行了扩充,将引用分为
- 强引用
这是"引用"最传统的定义,指在程序代码中普遍存在的引用赋值,如似“Object obj=new Object()
”这种引用关系。这样的引用关系只要还存在就永远不会被垃圾收集器回收掉。
- 软引用
描述一些还有用,但非必须的对象。只被软引用关联着的对象,在系统将要发生内存溢出异常前,会把这些对象列进回收范围之中进行第二次回收,如果这次回收还没有足够的内存, 才会抛出内存溢出异常。在JDK 1.2版之后提供了SoftReference
类来实现软引用。
- 弱引用
用来描述那些非必须对象,但是它的强度比软引用更弱一些,被弱引用关联的对象只 能生存到下一次垃圾收集发生为止。当垃圾收集器开始工作,无论当前内存是否足够,都会回收掉只 被弱引用关联的对象。在JDK 1.2版之后提供了WeakReference
类来实现弱引用。
- 虚引用
也称为“幽灵引用”或者“幻影引用”,它是最弱的一种引用关系。一个对象是否有虚引用的 存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚 引用关联的唯一目的只是为了能在这个对象被收集器回收时收到一个系统通知。在JDK 1.2版之后提供 了PhantomReference
类来实现虚引用。
这四种引用强度依次减弱。
方法区的回收
相较于对堆的回收,方法区的回收由于其较苛刻的规定,因此方法区的回收性价比通常是比较低的。
方法区主要回收两部分内容:
- 废弃的常量
- 不再使用的类型
判定一个常量是否“废弃”还是相对简单,而要判定一个类型是否属于“不再被使用的类”的条件就 比较苛刻了。需要同时满足下面三个条件:
- 该类所有的实例都已经被回收,也就是Java堆中不存在该类及其任何派生子类的实例。
- 加载该类的类加载器已经被回收,这个条件除非是经过精心设计的可替换类加载器的场景,如 OSGi、JSP的重加载等,否则通常是很难达成的。
- 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
而在满足上面的条件之后这个不再使用的类型也不一定会被回收,我们可以通过JVM的参数来设置是否对其进行回收。