zl程序教程

您现在的位置是:首页 >  后端

当前栏目

JVM垃圾回收(Hotspot)

JVM 垃圾 回收 HotSpot
2023-09-14 09:04:04 时间

垃圾判断算法

程序计数器由于不会发生oom,所以不需要垃圾回收。虚拟机栈和本地方法栈随着线程的死亡而释放内存,所以也不需要垃圾回收。只有堆和方法区中内存的生命周期具有不确定性,需要垃圾回收管理。

另外

垃圾回收之前,需要找到垃圾。所以如何判断一个对象是垃圾是第一步。

引用计数算法

在对象中添加一个引用计数器,每当有一个地方引用该对象时,计数器值就加1,当引用失效时,计数器值就减1。当一个对象的引用计数器值为0时,则可以判定该对象是垃圾。

优点:引用计数法实现简单

缺点:

  1. 需要耗费一点内存去记录对象被引用数
  2. 存在两个对象相互引用的情况,即这两个对象的引用计数器值至少为1,造成内存泄漏

可达性分析算法

GC Roots (根对象) 定义

  1. 虚拟机栈中引用的对象
  2. 方法区中静态变量引用的对象
  3. 方法区中常量引用的对象
  4. 本地方法栈中JNI(Native方法)引用的对象
  5. Java虚拟机内部的引用
  6. 同步锁持有的对象

     总结一下GC Roots的特点:就是被堆外直接引用的堆内对象

跨代引用

Hotspot堆内存是分代的,所以可能出现跨代引用,被跨代引用的堆对象其实也算是一种GC Root。

比如上面A,X,Y都是堆外直接引用的堆内对象,是常规的GC Root

但是B作为新生代对象,他没有被堆外直接引用,但是却被老年代对象X引用了(跨代引用),而老年代X是GC Root,所以B不是垃圾。

但是分代垃圾回收可以只对新生代进行回收(Minor GC),而垃圾回收前,需要查找被垃圾回收内存区域的GC Root,如果只考虑堆外直接引用堆内对象为GC Root的话,那么只有A是GC Root,B是垃圾。但是实际上B不是垃圾,所以我们可以将 代外直接引用的代内对象 也当成一种“分代级别”的GC Root.

那么上面这种情况,B是否就是垃圾了?

整体来看,B确实是垃圾。但是如果只有新生代发生垃圾回收的话,B还是不会被回收,即B不会被当成垃圾。只有X被回收了,B才会被回收。所以我们可以将 代外直接引用的代内对象 也当成代GC Root.

如果存在上面这种情况,X也存在跨代引用,则X也不能被当成垃圾。

如果发生上面这种情况,整体来看B,X都是垃圾,如果只发生老年代的垃圾回收(Majar GC),则X不会被垃圾回收。这样其实就造成了空间浪费。所以在回收老年代前,通常回收一次新生代,这样B就会被回收,之后X也会被回收。

可达性分析算法

从GC Roots(根对象)开始,根据引用关系向下搜索,搜索过程所走过的路径称为”引用链“,如果某个对象到GC Roots间没有任何引用链,则该对象可以判定为垃圾。

优点:两个对象相互引用时,也能被识别为垃圾。不需要对象保存引用计数信息。

缺点:当GC Roots数量过多时,该算法耗时较长。

Java中的四种引用

强引用(Strong Reference)

当对象被一个或一个以上的引用变量引用时,它处于可达状态,不可能被垃圾回收。

软引用(Soft Reference)

软引用需要通过SoftReference类来实现,当一个对象只有软引用时,它有可能被垃圾回收机制回收。对于只有软引用的对象而言,当系统内存足够时,它不会被系统回收,程序也可使用该对象;当系统内存不足时,系统可能会回收它。软引用通常用于对内存敏感的程序中。

// java -Xmx10m Main
public class Main {
    public static void main(String[] args) {
        soft();
    }

    private static void soft(){
        SoftReference<byte[]> sf = new SoftReference<>(new byte[1024 * 1024 * 6]);
        System.out.println("======");
        SoftReference<byte[]> sf1 = new SoftReference<>(new byte[1024 * 1024 * 6]);
        System.out.println("======");
        System.out.println(sf.get());//null
        System.out.println(sf==null);//false
        System.out.println(sf1.get());//[B@1b6d3586
    }
}

 当一次Full GC后,堆内存任然不足时,会再次进行一次Full GC,将new byte[1024 * 1024 * 6]对象所占内存回收。

此时只剩下软引用对象new SoftReference(),因为它被sf强引用,所以无法被垃圾回收。

此时我们可以通过以下方式回收软引用对象new SoftReference()

if(sf.get()==null){
     sf = null;
}

 

也可以借助另一种方式,SoftReference对象本身可以绑定一个引用队列ReferenceQueue对象,当软引用指向的对象被回收了,则软引用会被自动加入绑定的引用队列中。这种情况适用于要回收的软引用对象较多时。

import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args){
        soft();
    }

    private static void soft(){
        ReferenceQueue<byte[]> queue = new ReferenceQueue<>();

        List<SoftReference<byte[]>> list = new ArrayList<>();
        list.add(new SoftReference<>(new byte[1024 * 1024],queue));
        list.add(new SoftReference<>(new byte[1024 * 1024 * 2],queue));
        list.add(new SoftReference<>(new byte[1024 * 1024 * 3],queue));
        list.add(new SoftReference<>(new byte[1024 * 1024 * 6],queue));

        Reference<? extends byte[]> poll = queue.poll();
        while (poll!=null){
            list.remove(poll);
            poll = queue.poll();
        }

        System.out.println(list.size());// 1
        list.forEach(t-> System.out.println(t.get()));// [B@404b9385
    }
}

弱引用(Weak Reference)

弱引用需要通过WeakReference类来实现,弱引用和软引用很想,但弱引用级别更低。对于只有弱引用的对象而言,当系统垃圾回收机制运行时,不管系统内存是否足够,总会回收该对象所占用的内存。

弱引用具体使用和软引用差不多,只是二者引用对象的回收时机不一样,只要发生垃圾回收,无论内存是否充足,都会回收弱引用引用的对象。

虚引用(Phantom Reference)

虚引用通过PhantomReference类实现,虚引用完全类似于没有引用。虚引用对对象本身没有太大影响,对象甚至感觉不到虚引用的存在。如果一个对象只有虚引用时,那么它和没有引用的效果大致相同。虚引用主要用于跟踪对象被垃圾回收的状态,虚引用不能单独使用,虚引用必须和引用队列联合使用。

虚引用引用的对象不会因为内存不足而被垃圾回收,虚引用引用的对象和没有虚引用引用的对象效果相同。即虚引用并不是为了应对内存紧张场景。

虚引用必须配合引用队列使用,因为虚引用引用的对象无法通过get()获取,所以无法判断虚引用引用的对象是否已经被回收。只能通过检查是否虚引用本身是否入队,若入队,则说明虚引用引用的对象已经被回收。

由于虚引用引用的对象被回收后,会自动入队,所以只要检查队中是否有虚引用就可以跟踪对象被垃圾回收的状态。

public class Main {
    public static class Test{
        @Override
        protected void finalize() throws Throwable {
            System.out.println("finalize方法被调用");
            super.finalize();
        }
    }

    public static void main(String[] args){
        phantom();
    }

    private static void phantom(){
        ReferenceQueue<Test> queue = new ReferenceQueue<>();

        Test test = new Test();
        PhantomReference<Test> pr = new PhantomReference<>(test, queue);
        test = null;

        while (true){
            Reference<? extends Test> poll = queue.poll();
            if (poll!=null){
                System.out.println(poll==pr);
                break;
            } else {
                System.out.println("=");
                System.gc();
            }
        }
    }

 

垃圾回收算法

标记-清除

标记-整理

标记-复制

分代垃圾回收

堆内存分代

分代垃圾回收流程

相关VM参数

垃圾回收器

串行垃圾回收器

吞吐量垃圾回收器

响应时间垃圾回收器

G1垃圾回收器

垃圾回收调优

调优目标

代码审查

新生代调优

老年代调优