java.lang.ref.Finalizer占用高内存

文章目录

      • 可达性分析算法
        • 情景复现
        • 测试finalize方法的调用
          • GC overhead limit exceeded
        • 关于引用
        • 方法区中所谓的回收无用的类,那什么样的类会被判定为无用的类
        • 内存泄漏

一次生产环境内存高分析
java.lang.ref.Finalizer占用高内存_第1张图片

先复习一下基本知识
Shallow Size
对象自身占用的内存大小,不包括它引用的对象
Retained Size
Retained Size=当前对象大小+当前对象可直接或间接引用到的对象的大小总和

java.lang.ref.Finalizer占用高内存_第2张图片

可达性分析算法

再说可达性分析算法,基本思路是通过一系列成为GC ROOTS 的对象作为起点,当一个对象到 GC ROOTS 没有任何相连,证明此对象是不可达,即被判断为可回收的对象。
java.lang.ref.Finalizer占用高内存_第3张图片
之后的过程是:

  1. 被标记不可达的对象以后,进行第一次标记,和第一次筛选,条件是该对象 有没有必要执行finalize方法
  2. 没有必要执行finalize方法的情况是
    1.finalize已经执行过(finalize 方法只会被执行一次)
    2.该对象没有重写finalize方法
  3. 如果要执行finalize方法,该对象进入一个F-Queue队列,稍后有 一个优先级为8的 finalizer线程来执行(注意:如果一个对象在 finalize 方法中运行缓慢,将会导致队列后的其他对象永远等待,严重时将会导致系统崩溃)
  4. GC对队列中进行第二次标记,如果在执行finalize方法的时候将自己和GC ROOTS关联上,该对象即可逃离回收,否则,被回收掉
  5. 重要
    对象是在已经被GC识别为是垃圾后才丢到Queue中的,在queue中依然占用内存

结合代码来测试,手动触发GC时,FinalizerThread 执行了finalize方法,Finalizer类在静态代码块中初始 FinalizerThread ,优先级设为 8,daemon线程

static {
        ThreadGroup tg = Thread.currentThread().getThreadGroup();
        for (ThreadGroup tgn = tg;
             tgn != null;
             tg = tgn, tgn = tg.getParent());
        Thread finalizer = new FinalizerThread(tg);
        finalizer.setPriority(Thread.MAX_PRIORITY - 2);
        finalizer.setDaemon(true);
        finalizer.start();
    }

一个对象的finalize方法最多会执行一次

private volatile boolean running
 public void run() {
            if (running)
                return;

这个线程的任务则是死循环从 Finalizer 的队列中,取出 Finalizer 对象,然后调用这些对象的 runFinalizer 方法,其中捕捉了所有的InterruptedException,继续执行
这个队列是一个 ReferenceQueue 队列 。里面存放的就是 Finalizer 对象,当一个对象需要执行 finalize 方法(未执行过且重写了该方法)的时候, JVM 会将这个对象包装成 Finalizer 实例,然后链接到 Finalizer 链表中,并放入这个队列,执行finalize方法,最终清空 Finalizer。

 for (;;) {
                try {
                    Finalizer f = (Finalizer)queue.remove();
                    f.runFinalizer(jla);
                } catch (InterruptedException x) {
                    // ignore and continue
                }
            }

猜想,可能是finalize方法执行跟不上创建对象的速度, 才会导致finalize queue一直增大, 占用内存,,最终OOM

结合本次案例分析
java.lang.ref.Finalizer占用高内存_第4张图片

UnmarshallerImpl类确实重写了finalize方法

 protected void finalize() throws Throwable {
        try {
            ClassFactory.cleanCache();
        } finally {
            super.finalize();
        }

    }

情景复现

jvm参数

-XX:+PrintGCDetails -Xmx20m -Xms20m -Xmn10m -XX:MetaspaceSize=20m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=D:\oomdum
 for(int i=0;;i++) {
            UnmarshallerImpl unmarshaller = new UnmarshallerImpl(null, null);
            if ((i % 100_000) == 0) {
                System.out.format("After creating %d objects.%n", new Object[] {i });
            }
        }

虽然没有发生oom,通过jmap dump的日志来看,和之前出的问题基本一致

被类加载器"bootstrap class loader"加载的11,689个"java.lang.ref.Finalizer"实例占了12,162,280 (85.90%)字节. 
关键字
java.lang.ref.Finalizer

测试finalize方法的调用

  1. 从一个对象变得不可达开始,到执行它的finalizer方法,时间可能任意长
  2. 使用finalize方法,性能损失

合理用途:

  1. 充当安全网,即当所有对象忘记关闭某些资源时,作为最后一道防线,晚点执行总比不执行好
  2. 与对象的本地对等体关联
static LongAdder aliveCount = new LongAdder();

    @Override
    protected void finalize() throws Throwable {
        FinalizeTest.aliveCount.decrement();
    }

    public FinalizeTest() {
        aliveCount.increment();
    }

    public static void main(String args[]) {
        for (int i = 0;; i++) {
            FinalizeTest f = new FinalizeTest();
            if ((i % 100_000) == 0) {
                System.out.format("After creating %d objects, %d are still alive.%n", new Object[] {i, FinalizeTest.aliveCount.intValue() });
            }
        }
    }

7100000 个对象,发生OOM:GC overhead limit exceeded
jvm参数:-XX:+PrintGCDetails -Xmx20m -Xms20m -Xmn10m

After creating 7100000 objects, 269131 are still alive.
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
GC overhead limit exceeded

原因:执行垃圾收集的时间比例太大,有效的运算量太小,默认情况下,如果GC花费的时间超过 98%, 并且GC回收的内存少于 2%

java.lang.ref.Finalizer占用高内存_第5张图片
java.lang.ref.Finalizer占用高内存_第6张图片
java.lang.ref.Finalizer占用高内存_第7张图片

关于引用

强引用:通过new创建出来的对象。只要强引用存在,垃圾回收器将不会回收
软引用:通过SoftReference实现软引用,系统发生OOM之前,进行回收
弱引用:通过WeakReference实现弱引用,无论当内存是否足够,GC运行时都会进行回收。
虚引用:通过PhantomReference实现,通过虚引用无法回去对象的实例,虚引用的作用就是当此对象被回收时,会收到一个系统通知

方法区中所谓的回收无用的类,那什么样的类会被判定为无用的类

(1)java堆中不存在该类的任何实例。
(2)加载该类的ClassLoader已经被回收。
(3)该类的class对象没有任何地方被引用。
满足以上三个条件的类可以被回收,而不是和java堆中的对象一样必然会被回收。

内存泄漏

java.lang.ref.Finalizer占用高内存_第8张图片

你可能感兴趣的:(java.lang.ref.Finalizer占用高内存)