聊聊 JVM 的垃圾回收机制

 2023-01-19
原文作者:silly8543 原文地址:https://juejin.cn/post/6977883686725222407

在Java堆内存中创建的对象,都是占用资源,毕竟内存资源有限,当没有任何方法的局部变量在引用这个实例对象,就会被当做垃圾处理,这个时候需要 JVM的垃圾回收机器 来清理这些不在引用的对象

只需要启动一个JVM进程,就会自带一个垃圾回收的后台线程,这个线程会在后台不断检查JVM堆内存中的各个实例对象

202301011609452191.png

如果某个实例对象没有任何一个方法的局部变量指向他,也没有任何一个类的静态变量,包括常量等地方在指向他。

那么这个垃圾回收线程,就会把这个没人指向的“Demo”实例对象给回收掉,从内存里清除掉,让他不再占用任何内存资源,这样的话,这些不再被人指向的对象实例,即JVM中的“ 垃圾 ”,就会定期的被后台垃圾回收线程清理掉,不断释放内存资源

如何判断对象已死,不在引用

引用计数法

引用计数法描述的算法为:给对象增加一个引用计数器,每当有一个地方引用它时,计数器就+1;当引用失效时,计数器就-1;任何时刻计数器为0的对象就是不能再被使用的,即对象已“死”。 引用计数法实现简单,判定效率也比较高,在大部分情况下都是一个比较好的算法。比如Python语言就是采用的引用计数法来进行内存管理的。 但是,在主流的JVM中没有选用引用计数法来管理内存,最主要的原因是引用计数法无法解决对象的循环引用问题。

可达性分析算法

Java并不采用引用计数法来判断对象是否已“死”,而采用“可达性分析”来判断对象是否存活(同样采用此法的还有C#、Lisp-最早的一门采用动态内存分配的语言)。 此算法的核心思想:通过一系列称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索走过的路径称为“引用链”,当一个对象到 GC Roots 没有任何的引用链相连时(从 GC Roots 到这个对象不可达)时,证明此对象不可用。以下图为例:

202301011609458432.png

对象Object5 —Object7之间虽然彼此还有联系,但是它们到 GC Roots 是不可达的,因此它们会被判定为可回收对象。

在Java语言中,可作为GC Roots的对象包含以下几种:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象
  • 方法区中静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中(Native方法)引用的对象

在JVM的规范中, 静态变量也可以看做是一种GC Roots

只要你的对象被方法的局部变量、类的静态变量给引用了,就不会回收他们

方法区内会不会进行垃圾回收

在以下几种情况下,方法区里的类会被回收

  • 首先该类的所有实例对象都已经从Java堆内存里被回收
  • 其次加载这个类的ClassLoader已经被回收
  • 最后,对该类的Class对象没有任何引用

Java中对象不同的引用类型

关于引用和垃圾回收的关系, 分别是强引用、软引用、弱引用和虚引用

强引用

     private static Demo1 demo1 = new Demo1();

这个就是比较普遍的代码,一个变量引用一个对象, 只要是强引用的类型,那么垃圾回收的时候绝对不会去回收这个对象

软引用

     private static SoftReference<Demo1> softReference = new SoftReference<>(new Demo1());

就是把Demo1实例对象用一个SoftReference软引用类型的对象给包裹起来,此时softReference变量对Demo1对象的引用就是软引用

正常情况下垃圾回收是不会回收软引用对象的,但是如果你进行垃圾回收之后,发现内存空间还是不够存放新的对象,内存都快溢出,此时就会把这些软引用对象给回收掉,哪怕他被变量引用了,但是因为他是软引用,所以还是要回收

弱引用

    public static WeakReference<Demo1> demo1WeakReference = new WeakReference<Demo1>(new Demo1());

弱引用就跟没引用是类似的,如果发生垃圾回收,就会把这个对象回收掉

强引用、软引用、弱引用三者:

  • 比较常用的就是 强引用和软引用 ,强引用就是代表绝对不能回收的对象,软引用就是说有的对象可有可无,如果内存实在不

够了,可以回收软引用,弱引用只要发生垃圾回收,则会被回收掉

finalize()方法的作用

在垃圾回收之前,会先调用下对象中的finalize()方法, 所以在没有GC Roots引用的对象,不一定会被立马被回收 ,可以使用下面的代码拯救自己

    public class HelloWorld {
    
        public static  HelloWorld instance;
    
        @Override
        protected void finalize() throws Throwable {
            HelloWorld.instance=this;
        }
    }

假设有一个HelloWorld对象要被垃圾回收了,那么假如这个对象重写了Object类中的finialize()方法

此时会先尝试调用一下他的finalize()方法,看是否把自己这个实例对象给了某个GC Roots变量,比如说代码中就给了HelloWorld类的静态变量

如果重新让某个GC Roots变量引用了自己,那么就不用被垃圾回收了

什么时候触发垃圾回收

垃圾回收判断条件:

  • 有GC Roots引用的对象不能回收,没有GC Roots引用的对象可以回收
  • 如果有GC Roots引用,但是如果是软引用或者弱引用的,也有可能被回收掉。