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


一次生产环境内存高分析
在这里插入图片描述

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

在这里插入图片描述

可达性分析算法

再说可达性分析算法,基本思路是通过一系列成为GC ROOTS 的对象作为起点,当一个对象到 GC ROOTS 没有任何相连,证明此对象是不可达,即被判断为可回收的对象。
在这里插入图片描述
之后的过程是:

  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

结合本次案例分析
在这里插入图片描述

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%

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

关于引用

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

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

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

内存泄漏

在这里插入图片描述

  • 3
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
MAT工具使用说明.docx MAT(Memory Analyzer Tool)工具入门 一MAT简介 MAT(Memory Analyzer Tool),一个基于Eclipse的内存分析工具,是一个快速、功能丰富的JAVA heap分析工具,它可以帮助我们查找内存泄漏和减少内存消耗。使用内存分析工具从众多的对象中进行分析,快速的计算出在内存中对象的占用大小,看看是谁阻止了垃圾收集器的回收工作,并可以通过报表直观的查看到可能造成这种结果的对象。 二 使用MAT意义 当服务器应用占用了过多内存的时候,会遇到OutOfMemoryError。如何快速定位问题呢?Eclipse MAT的出现使这个问题变得非常简单。它能够离线分析dump的文件数据。 四 MAT操作流程 1先调用jdk的工具得到heap使用情况 我安装的是jdk1.6 C:/>java -version java version "1.6.0_11" Java(TM) SE Runtime Environment (build 1.6.0_11-b03) Java HotSpot(TM) Client VM (build 11.0-b16, mixed mode, sharing) 2调用jdk工具jps查看当前的java进程 C:/>jps 3504 Jps 3676 Bootstrap 3496 org.eclipse.equinox.launcher_1.0.201.R35x_v20090715.jar 3调用jmap工具得到信息 C:/>jmap -dump:format=b,file=heap.bin 3676 Dumping heap to C:/heap.bin ... Heap dump file created 这时,我们的C盘根目录,就生成了heap.bin文件。 4用eclipse的file---->open打开这个文件,首先是一个启动图: 这里可以选择查看 (1)内存泄露报表,自动检查可能存在内存泄露的对象,通过报表展示存活的对象以及为什么他们没有被垃圾收集; (2)对象报表,对可颖对象的分析,如字符串是否定义重了,空的collection、finalizer以及弱引用等。 我这里选择的是查看内存报表,以下是截的简略图: 通过报表展示,蛮清楚的,下面还有详细的说明,这里就没有帖图了,有兴趣的可以继续探究。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值