Debugging to Understand Finalizer--reference
This post is covering one of the Java built-in concepts called Finalizer. This concept is actually both well-hidden and well-known, depending whether you have bothered to take a look at the java.lang.Object class thoroughly enough. Right in the java.lang.Object itself, there is a method called finalize(). The implementation of the method is empty, but both the power and dangers lie on the JVM internal behaviour based upon the presence of such method.
When JVM detects that class has a finalize() method, magic starts to happen. So, lets go forward and create a class with a non-trivial finalize() method so we can see how differently JVM is handling objects in this case. For this, lets start by constructing an example program:
Example of Finalizable class
01.
import
java.util.concurrent.atomic.AtomicInteger;
02.
03.
class
Finalizable {
04.
static
AtomicInteger aliveCount =
new
AtomicInteger(
0
);
05.
06.
Finalizable() {
07.
aliveCount.incrementAndGet();
08.
}
09.
10.
@Override
11.
protected
void
finalize()
throws
Throwable {
12.
Finalizable.aliveCount.decrementAndGet();
13.
}
14.
15.
public
static
void
main(String args[]) {
16.
for
(
int
i =
0
;; i++) {
17.
Finalizable f =
new
Finalizable();
18.
if
((i % 100_000) ==
0
) {
19.
System.out.format(
"After creating %d objects, %d are still alive.%n"
,
new
Object[] {i, Finalizable.aliveCount.get() });
20.
}
21.
}
22.
}
23.
}
The example is creating new objects in an unterminated loop. These objects use static aliveCount variable to keep track how many instances have already been created. Whenever a new instance is created, the counter is incremented and whenever the finalize() is called after GC, the counter value is reduced.
So what would you expect from such a simple code snippet? As the newly created objects are not referenced from anywhere, they should be immediately eligible for GC. So you might expect the code to run forever with the output of the program to be something similar to the following:
1.
After creating
345
,
000
,
000
objects,
0
are still alive.
2.
After creating
345
,
100
,
000
objects,
0
are still alive.
3.
After creating
345
,
200
,
000
objects,
0
are still alive.
4.
After creating
345
,
300
,
000
objects,
0
are still alive.
Apparently this is not the case. The reality is completely different, for example in my Mac OS X on JDK 1.7.0_51, I see the program failing with java.lang.OutOfMemoryError: GC overhead limitt exceeded just about after ~1.2M objects have been created:
01.
After creating
900
,
000
objects,
791
,
361
are still alive.
02.
After creating
1
,
000
,
000
objects,
875
,
624
are still alive.
03.
After creating
1
,
100
,
000
objects,
959
,
024
are still alive.
04.
After creating
1
,
200
,
000
objects,
1
,
040
,
909
are still alive.
05.
Exception in thread
"main"
java.lang.OutOfMemoryError: GC overhead limit exceeded
06.
at java.lang.ref.Finalizer.register(Finalizer.java:
90
)
07.
at java.lang.Object.(Object.java:
37
)
08.
at eu.plumbr.demo.Finalizable.(Finalizable.java:
8
)
09.
at eu.plumbr.demo.Finalizable.main(Finalizable.java:
19
)
Garbage Colletion behaviour
To understand what is happening, we would need to take a look at our example code during the runtime. For this, lets run our example with -XX:+PrintGCDetails flag turned on:
01.
[GC [PSYoungGen: 16896K-&
gt
;2544K(19456K)] 16896K-&
gt
;16832K(62976K), 0.0857640 secs] [Times: user=0.22 sys=0.02, real=0.09 secs]
02.
[GC [PSYoungGen: 19440K-&
gt
;2560K(19456K)] 33728K-&
gt
;31392K(62976K), 0.0489700 secs] [Times: user=0.14 sys=0.01, real=0.05 secs]
03.
[GC-- [PSYoungGen: 19456K-&
gt
;19456K(19456K)] 48288K-&
gt
;62976K(62976K), 0.0601190 secs] [Times: user=0.16 sys=0.01, real=0.06 secs]
04.
[Full GC [PSYoungGen: 16896K-&
gt
;14845K(19456K)] [ParOldGen: 43182K-&
gt
;43363K(43520K)] 60078K-&
gt
;58209K(62976K) [PSPermGen: 2567K-&
gt
;2567K(21504K)], 0.4954480 secs] [Times: user=1.76 sys=0.01, real=0.50 secs]
05.
[Full GC [PSYoungGen: 16896K-&
gt
;16820K(19456K)] [ParOldGen: 43361K-&
gt
;43361K(43520K)] 60257K-&
gt
;60181K(62976K) [PSPermGen: 2567K-&
gt
;2567K(21504K)], 0.1379550 secs] [Times: user=0.47 sys=0.01, real=0.14 secs]
06.
---
cut
for
brevity---
07.
[Full GC [PSYoungGen: 16896K-&
gt
;16893K(19456K)] [ParOldGen: 43351K-&
gt
;43351K(43520K)] 60247K-&
gt
;60244K(62976K) [PSPermGen: 2567K-&
gt
;2567K(21504K)], 0.1231240 secs] [Times: user=0.45 sys=0.00, real=0.13 secs]
08.
[Full GCException
in
thread
"main"
java.lang.OutOfMemoryError: GC overhead limit exceeded
09.
[PSYoungGen: 16896K-&
gt
;16866K(19456K)] [ParOldGen: 43351K-&
gt
;43351K(43520K)] 60247K-&
gt
;60218K(62976K) [PSPermGen: 2591K-&
gt
;2591K(21504K)], 0.1301790 secs] [Times: user=0.44 sys=0.00, real=0.13 secs]
10.
at eu.plumbr.demo.Finalizable.main(Finalizable.java:19)
From the logs we see that after just a few minor GCs cleaning Eden, the JVM turns to a lot more expensive Full GC cycles cleaning tenured and old space. Why so? As nothing is referring our objects, shouldn’t all the instances die young in Eden? What is wrong with our code?
To understand, the reasons for GC behaving as it does, let us do just a minor change to the code and remove the body of the finalize() method. Now the JVM detects that our class does not need to be finalized and changes the behaviour back to “normal”. Looking at the GC logs we would see only cheap minor GCs running forever.
As in this modified example nothing indeed refers to the objects in Eden (where all objects are born), the GC can do a very efficient job and discard the whole Eden at once. So immediately, we have cleansed the whole Eden, and the unterminated loop can continue forever.
In our original example on the other hand, the situation is different. Instead of objects without any references, JVM creates a personal watchdog for each and every one of the Finalizableinstances. This watchdog is an instance of Finalizer. And all those instances in turn are referenced by the Finalizer class. So due to this reference chain, the whole gang stays alive.
Now with the Eden full and all objects being referenced, GC has no other alternatives than to copy everything into Survivor space. Or worse, if the free space in Survivor is also limited, then expand to the Tenured space. As you might recall, GC in Tenured space is a completely different beast and is a lot more expensive than “lets throw away everything” approach used to clean Eden.
Finalizer queue
Only after the GC has finished, JVM understands that apart from the Finalizers nothing refers to our instances, so it can mark all Finalizers pointing to those instances to be ready for processing. So the GC internals add all Finalizer objects to a special queue atjava.lang.ref.Finalizer.ReferenceQueue.
Only when all this hassle is completed our application threads can proceed with the actual work. One of those threads is now particularly interesting for us – the “Finalizer” daemon thread. You can see this thread in action by taking a thread dump via jstack:
01.
My Precious:~ demo$ jps
02.
1703 Jps
03.
1702 Finalizable
04.
My Precious:~ demo$ jstack 1702
05.
06.
---
cut
for
brevity ---
07.
"Finalizer"
daemon prio=5 tid=0x00007fe33b029000 nid=0x3103 runnable [0x0000000111fd4000]
08.
java.lang.Thread.State: RUNNABLE
09.
at java.lang.ref.Finalizer.invokeFinalizeMethod(Native Method)
10.
at java.lang.ref.Finalizer.runFinalizer(Finalizer.java:101)
11.
at java.lang.ref.Finalizer.access$100(Finalizer.java:32)
12.
at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:190)
13.
---
cut
for
brevity ---
From the above we see the “Finalizer” daemon thread running. “Finalizer” thread is a thread with just a single responsibility. The thread runs an unterminated loop blocked waiting for new instances to appear in java.lang.ref.Finalizer.ReferenceQueue queue. Whenever the “Finalizer” threads detects new objects in the queue, it pops the object, calls the finalize() method and removes the reference from Finalizer class, so the next time the GC runs the Finalizer and the referenced object can now be GCd.
So we have two unterminated loops now running in two different threads. Our main thread is busy creating new objects. Those objects all have their personal watchdogs called Finalizer which are being added to the java.lang.ref.Finalizer.ReferenceQueue by the GC. And the “Finalizer” thread is processing this queue, popping all the instances from this queue and calling the finalize() methods on the instances.
Most of the time you would get away with this. Calling the finalize() method should complete faster than we actually create new instances. So in many cases, the “Finalizer” thread would be able to catch up and empty the queue before the next GC pours more Finalizers into it. In our case, it is apparently not happening.
Why so? The “Finalizer” thread is run at a lower priority than the main thread. In means that it gets less CPU time and is thus not able to catch up with the pace objects are being created. And here we have it – the objects are created faster than the “Finalizer” thread is able to finalize() them, causing all the available heap to be consumed. Result – different flavours of our dear friendjava.lang.OutOfMemoryError.
If you still do not believe me, take a heap dump and take a look inside. For example, when our code snipped is launched with -XX:+HeapDumpOnOutOfMemoryError parameter, I see a following picture in Eclipse MAT Dominator Tree:
As seen from the screenshot, my 64m heap is completely filled with Finalizers.
Conclusions
So to recap, the lifecycle of Finalizable objects is completely different from the standard behaviour, namely:
- The JVM will create the instance of Finalizable object
- The JVM will create an instance of the java.lang.ref.Finalizer, pointing to our newly created object instance.
- java.lang.ref.Finalizer class holds on to the java.lang.ref.Finalizer instance that was just created. This blocks next minor GC from collecting our objects and is keeping them alive.
- Minor GC is not able to clean the Eden and expands to Survivor and/or Tenured spaces.
- GC detects that the objects are eligible for finalizing and adds those objects to thejava.lang.ref.Finalizer.ReferenceQueue
- The queue will be processed by “Finalizer” thread, popping the objects one-by-one and calling their finalize() methods.
- After finalize() is called, the “Finalizer” thread removes the reference from Finalizer class, so during the next GC the objects are eligible to be GCd.
- The “Finalizer” thread competes with our “main” thread, but due to lower priority gets less CPU time and is thus never able to catch up.
- The program exhausts all available resources and throws OutOfMemoryError.
Moral of the story? Next time, when you consider finalize() to be superior to the usual cleanup, teardown or finally blocks, think again. You might be happy with the clean code you produced, but the ever-growing queue of Finalizable objects thrashing your tenured and old generations might indicate the need to reconsider.
reference from :http://java.dzone.com/articles/debugging-understand-finalizer
Debugging to Understand Finalizer--reference的更多相关文章
- debugging tools
https://blogs.msdn.microsoft.com/debugdiag/ https://blogs.msdn.microsoft.com/debuggingtoolbox/2012/1 ...
- 基于dalvik模式下的Xposed Hook开发的某加固脱壳工具
本文博客地址:http://blog.csdn.net/qq1084283172/article/details/77966109 这段时间好好的学习了一下Android加固相关的知识和流程也大致把A ...
- Cheatsheet: 2014 06.01 ~ 06.30
Mobile Developing iOS8 Apps Using Swift – Part 1- Hello World The Insider's Guide to Android Intervi ...
- 【转】java线上程序排错经验2 - 线程堆栈分析
前言 在线上的程序中,我们可能经常会碰到程序卡死或者执行很慢的情况,这时候我们希望知道是代码哪里的问题,我们或许迫切希望得到代码运行到哪里了,是哪一步很慢,是否是进入了死循环,或者是否哪一段代码有问题 ...
- 一些方便系统诊断的bash函数
原文地址:一些方便系统诊断的bash函数 一些方便系统诊断的bash函数:http://hongjiang.info/common-bash-functions/ 这段脚本包含100多个bash函数, ...
- FinalizableReference, FinalizablePhantomReference, FinalizableReferenceQueue
FinalizableReference /* * Copyright (C) 2007 The Guava Authors * * Licensed under the Apache License ...
- 通过Windows Visual Studio远程调试WSL2中的.NET Core Linux应用程序
最近两天在Linux中调试.NET Core应用程序,同时我发现在Linux中调试.NET Core应用程序并不容易.一直习惯在Visual Studio中进行编码和调试.现在我想的是可以简单快速的测 ...
- Hibernate Validator 6.0.9.Final - JSR 380 Reference Implementation: Reference Guide
Preface Validating data is a common task that occurs throughout all application layers, from the pre ...
- Sphinx 2.2.11-release reference manual
1. Introduction 1.1. About 1.2. Sphinx features 1.3. Where to get Sphinx 1.4. License 1.5. Credits 1 ...
随机推荐
- C 语言函数指针
c代码: #include <stdio.h> int add(int x,int y); int subtract(int x,int y); int domath(int (*math ...
- C++中的类所占内存空间总结
C++中的类所占内存空间总结 最近在复习c++的一些基础,感觉这篇文章很不错,转载来,大家看看! 类所占内存的大小是由成员变量(静态变量除外)决定的,成员函数(这是笼统的说,后面会细说)是不计算 ...
- 国内的一些开源镜像站汇总,EPEL源
国内的一些开源软件的镜像站 汇总 CentOS6.5安装 EPEL 源 国内高校的开源镜像站 中国科学技术大学(debian.ustc.edu.cn) 上海交通大学(ftp.stju.edu.c ...
- BZOJ_1778_[Usaco2010_Hol]_Dotp_驱逐猪猡_(期望动态规划+高斯消元+矩阵)
描述 http://www.lydsy.com/JudgeOnline/problem.php?id=1778 炸弹从1出发,有\(\frac{P}{Q}\)的概率爆炸,如果不爆炸,等概率移动到连通的 ...
- POJ_2566_Bound_Found_(尺取法+前缀和)
描述 http://poj.org/problem?id=2566 给出一个整数序列,并给出非负整数t,求数列中连续区间和的绝对值最接近k的区间左右端点以及这个区间和的绝对值. Bound Found ...
- windows下nginx安装、配置与使用
目前国内各大门户网站已经部署了Nginx,如新浪.网易.腾讯等:国内几个重要的视频分享网站也部署了Nginx,如六房间.酷6等.新近发现Nginx 技术在国内日趋火热,越来越多的网站开始部署Nginx ...
- Word对象模型 (.Net Perspective)
本文主要针对在Visual Studio中使用C# 开发关于Word的应用程序 来源:Understandingthe Word Object Model from a .NET Developer' ...
- Linux中ifreq 结构体分析和使用
结构原型: struct ifreq{#define IFHWADDRLEN 6 union { char ifrn_name[IFNAMSIZ]; } ifr_ifrn; union { ...
- [Bhatia.Matrix Analysis.Solutions to Exercises and Problems]ExI.1.3
Use the QR decomposition to prove Hadamard's inequality: if $X=(x_1,\cdots,x_n)$, then $$\bex |\det ...
- 【转】CUDA5/CentOS6.4
转载自http://wenzhang.baidu.com/article/view?key=076f36658dd0828c-1393896446 This article explains how ...