先复习一下基本知识
Shallow Size
对象自身占用的内存大小,不包括它引用的对象
Retained Size
Retained Size=当前对象大小+当前对象可直接或间接引用到的对象的大小总和
再说可达性分析算法,基本思路是通过一系列成为GC ROOTS 的对象作为起点,当一个对象到 GC ROOTS 没有任何相连,证明此对象是不可达,即被判断为可回收的对象。
之后的过程是:
结合代码来测试,手动触发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
合理用途:
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花费的时间超过 98%, 并且GC回收的内存少于 2%
强引用:通过new创建出来的对象。只要强引用存在,垃圾回收器将不会回收
软引用:通过SoftReference实现软引用,系统发生OOM之前,进行回收
弱引用:通过WeakReference实现弱引用,无论当内存是否足够,GC运行时都会进行回收。
虚引用:通过PhantomReference实现,通过虚引用无法回去对象的实例,虚引用的作用就是当此对象被回收时,会收到一个系统通知
(1)java堆中不存在该类的任何实例。
(2)加载该类的ClassLoader已经被回收。
(3)该类的class对象没有任何地方被引用。
满足以上三个条件的类可以被回收,而不是和java堆中的对象一样必然会被回收。