【Java】神奇的内存占用大户FinalizerReference

问题起因

在解决应用内存占用久高不下的问题时,常常会发现排名第一的是一个叫做FinalizerReference的东西

下图是项目中内存快照在AndroidStudio2.4中打开看到的样子

可以看到,占用排名第一的类FinalizerReference占用了59M之巨(内存快照前有使用AS强制GC多次)

这就很让人好奇了

FinalizerReference是什么?

我们来看源码:

https://android.googlesource.com/platform/libcore/+/master/luni/src/main/java/java/lang/ref/FinalizerReference.java

在讲Java 虚拟机 JVM垃圾回收机制时会涉及到一个概念,即finalize方法,这是Sun公司在Java设计之初,为照顾c++程序员有写析构函数的习惯而做的一种妥协

在《深入理解Java虚拟机》一书中,讲到GC在最终回收对象之前,会去检查对象是否override了finalize方法,如果有覆盖此方法,对象就会被添加到finalize执行队列中,该书中没有指明是什么队列,实际上finalize执行队列就是我们这里讨论的FinalizerReference(书中还有提到finalize方法只会执行一次,并且有超时保护,因与本文主题无关,不做展开讨论)

也就是说,有重写Java根基类Object的finalize方法的所有对象,在被最终被GC回收之前,都会被添加到这个队列中,等待被执行

为什么不能被GC

这里要谈到JVM设计的一个基本标准

GC的finalize执行队列会在一个单独的守护线程中运行,这一线程的优先级极低;

一旦用户创建的对象速度过快,含finalize的对象速度快于finalize队列移除各元素的速度,FinalizerReference就会越来越大,而它获取CPU时间又少的可怜

测试中发现,1~2h过去后,FinalizerReference的大小并没有按照预计的回落到较低水准

这就迫使我们一定要解决这一问题,否则,随着应用开辟的内存越来越大,很容易引发喜闻乐见的OutOfMemoryError

如何解决

  1. 不要重写finalize()方法,实在要释放资源,请到其它destroy一类函数中处理

实际上Java库中不少方法都有重写finalize方法(如Input/outputStream、Canvas、Paint)

这就引出了第二条指导原则:

  1. 重复利用资源(避免反复创建)

  2. 杀器FinalizerHelper

    • 此工具来源于公司同事的技术分享
    • 指导思想:在场景退出的安全节点,利用反射强制调用FinalizerReference的remove方法,将对象从队列中移除掉
    • 对具体实施细节的同学联系我

Powered by KyleCe

Copyright © 2015 - 2019 KyleCe All Rights Reserved.

访客数 : | 访问量 :