调试以了解终结器

这篇文章涵盖了Java内置概念之一,称为Finalizer 。 这个概念实际上是众所周知的,也是众所周知的,这取决于您是否有足够的时间来仔细研究一下java.lang.Object类。 就在java.lang.Object本身中,有一个名为finalize()的方法。 该方法的实现是空的,但是基于这种方法的存在,力量和危险都取决于JVM内部行为。

当JVM检测到该类具有finalize()方法时,魔术就开始发生。 因此,让我们继续使用非平凡的finalize()方法创建一个类,这样我们就可以了解JVM在这种情况下处理对象的方式。 为此,让我们开始构建一个示例程序:

终结类的示例

import java.util.concurrent.atomic.AtomicInteger;class Finalizable {static AtomicInteger aliveCount = new AtomicInteger(0);Finalizable() {aliveCount.incrementAndGet();}@Overrideprotected void finalize() throws Throwable {Finalizable.aliveCount.decrementAndGet();}public static void main(String args[]) {for (int i = 0;; i++) {Finalizable f = new Finalizable();if ((i % 100_000) == 0) {System.out.format("After creating %d objects, %d are still alive.%n", new Object[] {i, Finalizable.aliveCount.get() });}}}
}

该示例在一个无终止循环中创建新对象。 这些对象使用静态aliveCount变量来跟踪已创建的实例数。 每当创建新实例时,计数器都会递增,并且在GC之后每次调用finalize()时 ,计数器值都会减少。

那么,从这样一个简单的代码片段中您会得到什么呢? 由于没有从任何地方引用新创建的对象,因此它们应立即可以用于GC。 因此,您可能希望代码与程序的输出一起永久运行,类似于以下内容:

After creating 345,000,000 objects, 0 are still alive.
After creating 345,100,000 objects, 0 are still alive.
After creating 345,200,000 objects, 0 are still alive.
After creating 345,300,000 objects, 0 are still alive.

显然并非如此。 现实是完全不同的,例如,在我的Mac OS X上的JDK 1.7.0_51上,我看到程序因java.lang.OutOfMemoryError而失败:创建约120万个对象后, GC开销限制已超出 :

After creating 900,000 objects, 791,361 are still alive.
After creating 1,000,000 objects, 875,624 are still alive.
After creating 1,100,000 objects, 959,024 are still alive.
After creating 1,200,000 objects, 1,040,909 are still alive.
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceededat java.lang.ref.Finalizer.register(Finalizer.java:90)at java.lang.Object.(Object.java:37)at eu.plumbr.demo.Finalizable.(Finalizable.java:8)at eu.plumbr.demo.Finalizable.main(Finalizable.java:19)

垃圾收集行为

要了解发生了什么,我们需要在运行时查看示例代码。 为此,让我们在打开-XX:+ PrintGCDetails标志的情况下运行示例:

[GC [PSYoungGen: 16896K->2544K(19456K)] 16896K->16832K(62976K), 0.0857640 secs] [Times: user=0.22 sys=0.02, real=0.09 secs] 
[GC [PSYoungGen: 19440K->2560K(19456K)] 33728K->31392K(62976K), 0.0489700 secs] [Times: user=0.14 sys=0.01, real=0.05 secs] 
[GC-- [PSYoungGen: 19456K->19456K(19456K)] 48288K->62976K(62976K), 0.0601190 secs] [Times: user=0.16 sys=0.01, real=0.06 secs] 
[Full GC [PSYoungGen: 16896K->14845K(19456K)] [ParOldGen: 43182K->43363K(43520K)] 60078K->58209K(62976K) [PSPermGen: 2567K->2567K(21504K)], 0.4954480 secs] [Times: user=1.76 sys=0.01, real=0.50 secs] 
[Full GC [PSYoungGen: 16896K->16820K(19456K)] [ParOldGen: 43361K->43361K(43520K)] 60257K->60181K(62976K) [PSPermGen: 2567K->2567K(21504K)], 0.1379550 secs] [Times: user=0.47 sys=0.01, real=0.14 secs] 
--- cut for brevity---
[Full GC [PSYoungGen: 16896K->16893K(19456K)] [ParOldGen: 43351K->43351K(43520K)] 60247K->60244K(62976K) [PSPermGen: 2567K->2567K(21504K)], 0.1231240 secs] [Times: user=0.45 sys=0.00, real=0.13 secs] 
[Full GCException in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded[PSYoungGen: 16896K->16866K(19456K)] [ParOldGen: 43351K->43351K(43520K)] 60247K->60218K(62976K) [PSPermGen: 2591K->2591K(21504K)], 0.1301790 secs] [Times: user=0.44 sys=0.00, real=0.13 secs] at eu.plumbr.demo.Finalizable.main(Finalizable.java:19)

从日志中我们可以看到,仅用几个次要GC清理了Eden之后,JVM便转而使用了昂贵得多的Full GC周期来清理使用期限和旧空间。 为什么这样? 由于没有东西引用我们的对象,难道所有实例都不应该在伊甸园中早逝吗? 我们的代码有什么问题?

要理解GC行为的原因,让我们对代码做些微改动,并删除finalize()方法的主体。 现在,JVM检测到不需要终结我们的类,并将行为更改回“正常”状态。 查看GC日志,我们只会看到廉价的次要GC永远运行。

Java内存模型
就像在此修改示例中一样,没有任何东西确实指向Eden中的对象(所有对象都在其中诞生),GC可以做非常有效的工作并立即丢弃整个Eden。 因此,我们立即清洗了整个伊甸园,而永无止境的循环将永远持续下去。

另一方面,在我们原始的示例中,情况有所不同。 JVM会为每个Finalized实例创建一个个人看门狗,而不是没有任何引用的对象 这个看门狗是Finalizer的一个实例 。 而所有这些实例又被Finalizer类引用。 因此,由于有了这个参考链,整个团伙仍然活着。

现在,伊甸园已满,所有对象都已被引用,GC除了将所有内容复制到Survivor空间之外,别无选择。 或更糟糕的是,如果“幸存者”中的自由空间也受到限制,则扩展到“终身制”空间。 您可能还记得,终身制空间中的GC是完全不同的野兽,并且比用于清理伊甸园的“让一切都扔掉”方法昂贵得多。

终结器队列

只有在GC完成之后,JVM才知道除终结器之外,没有任何东西可以引用我们的实例,因此它可以标记所有指向这些实例的终结器以供处理。 因此,GC内部将所有Finalizer对象添加到位于java.lang.ref.Finalizer.ReferenceQueue的特殊队列中。

只有完成所有这些麻烦之后,我们的应用程序线程才能继续进行实际工作。 这些线程之一现在对我们特别有趣- “ Finalizer”守护程序线程。 您可以通过jstack进行线程转储来查看该线程的运行情况:

My Precious:~ demo$ jps
1703 Jps
1702 Finalizable
My Precious:~ demo$ jstack 1702--- cut for brevity ---
"Finalizer" daemon prio=5 tid=0x00007fe33b029000 nid=0x3103 runnable [0x0000000111fd4000]java.lang.Thread.State: RUNNABLEat java.lang.ref.Finalizer.invokeFinalizeMethod(Native Method)at java.lang.ref.Finalizer.runFinalizer(Finalizer.java:101)at java.lang.ref.Finalizer.access$100(Finalizer.java:32)at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:190)
--- cut for brevity ---

从上面我们可以看到“ Finalizer”守护程序线程正在运行。 “最终确定器”线程是仅具有单一责任的线程。 线程运行一个未终止的循环,该循环被阻塞,等待新实例出现在java.lang.ref.Finalizer.ReferenceQueue队列中。 每当“ Finalizer”线程在队列中检测到新对象时,它将弹出对象,调用finalize()方法并从Finalizer类中删除引用,因此,下次GC运行Finalizer时 ,现在可以将引用的对象设为GCd。

因此,我们现在有两个未终止的循环在两个不同的线程中运行。 我们的主线程正在忙于创建新对象。 这些对象都有自己的个人看门狗,称为终结器 ,它们已由GC添加到java.lang.ref.Finalizer.ReferenceQueue中 。 然后,“ Finalizer ”线程正在处理该队列,从该队列中弹出所有实例,并在这些实例上调用finalize()方法。

在大多数情况下,您会避免这样做。 调用finalize()方法应该比我们实际创建新实例更快。 因此,在许多情况下, “ Finalizer”线程将能够赶上并清空下一个队列,然后再将下一个GC注入更多Finalizer 。 就我们而言,这显然没有发生。

为什么这样? “终结器”线程的运行优先级低于主线程。 这意味着它将减少CPU时间,因此无法跟上正在创建的对象的步伐。 到这里,我们有了对象-创建对象的速度比“ Finalizer”线程能够完成这些对象的finalize()更快,从而消耗了所有可用堆。 结果–我们亲爱的朋友java.lang.OutOfMemoryError的口味不同。

如果您仍然不相信我,请进行堆转储并查看内部。 例如,当使用-XX:+ HeapDumpOnOutOfMemoryError参数启动我们的代码片段时,我在Eclipse MAT Dominator Tree中看到以下图片:
统治者树Java终结器 从屏幕快照中可以看到,我的64m堆完全充满了Finalizers

结论

综上所述, Finalizable对象的生命周期与标准行为完全不同,即:

  • JVM将创建Finalizable对象的实例
  • JVM将创建java.lang.ref.Finalizer的实例,指向我们新创建的对象实例。
  • java.lang.ref.Finalizer类保留刚刚创建的java.lang.ref.Finalizer实例。 这会阻止下一个较小的GC收集我们的对象并使它们保持活动状态。
  • 次要GC无法清洁伊甸园,并扩展到幸存者和/或保有权空间。
  • GC检测到这些对象有资格完成,并将这些对象添加到java.lang.ref.Finalizer.ReferenceQueue
  • 该队列将由“ Finalizer ”线程处理,一个接一个地弹出对象并调用其finalize()方法。
  • 在调用finalize()之后,“ Finalizer ”线程从Finalizer类中删除该引用,因此在下一个GC中可以将对象限定为GCd。
  • Finalizer ”线程与我们的“ main ”线程竞争,但是由于优先级较低,CPU时间减少,因此永远无法跟上。
  • 该程序将耗尽所有可用资源,并抛出OutOfMemoryError 。

故事的道德启示? 下次,当您认为finalize()优于通常的清理,拆除或finally块时,请再考虑一下。 您可能对所生成的干净代码感到满意,但是,不断增长的Finalizable对象队列使您的老一辈人受挫,这可能表明需要重新考虑。

翻译自: https://www.javacodegeeks.com/2014/05/debugging-to-understand-finalizers.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/363703.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++ constexpr变量和constexpr函数

constexpr 类型变量必须用常量表达式或 constexpr 函数来初始化: constexpr int a10;   constexpr int ba10;   constexpr int cd();  //当 d()为一个 constexpr 函数时才可以 constexpr 函数的形参和返回值都只能是字面型类型,且只能有一条 retur…

Zookeeper实现注册与发现

1.Zookeeper的数据模型 (1) Zookeeper的数据模型,类似于树形结构: (2) Zookeeper的每一个节点成为称为Znode,主要用来存储数据。 data : 存储数据信息。acl : 记录Znode的访问权限。child : 当前节点的子节点引用。stat :包含Zn…

class 命名规范

本文是从简书复制的, markdown语法可能有些出入, 想看"正版"和更多内容请关注 简书: 小贤笔记 注: 文章摘自 penggelies07- 简书, super晴天 - CSDN 常见class关键词 布局类:header, footer, container, main, content, aside, page, section 包裹类&am…

web策略类游戏开发(四)一个可以承载万人在线的架构

web策略类游戏开发(四)一个可以承载万人在线的架构 Webgame现在已经开始需要进入大统一服务器时代,每个游戏区域容纳的玩家数量将从现在的几万人发展到几十万人,因此在新的背景下,webgame如何处理大量用户的请求将成为问题。目前一台asp.net做…

htc g7 android 4.4,HTC G7刷机,从WM手机刷到了安卓,开启了新的刷机体验....

2011.0914.晚上22:00使用2010的教程刷机,降级时黑屏。使用老的教程,一开始降级从094降到0.83(2010技术)黑屏,但是未板砖,还有开机声音,无法控制机器。没办法,只能使用RUU技术,window…

Java的编年史和低延迟

总览 我正在看Typesafe的Rolan Kuhn在介绍反应流方面的出色演讲,乍一看似乎与《纪事报》有一些相似的目标,但是当您深入研究细节时,对我来说显然有一些关键假设是根本不同。 关键假设 《纪事》设计的主要假设是 低延迟是您的问题&#xff0c…

复制物料时不复制安全库存

1.打开bos,选择物料-功能控制 2.把允许复制去掉 转载于:https://www.cnblogs.com/RogerLu/p/10441588.html

CSS实现水平垂直居中

1、需求分析 子元素在父元素中水平垂直居中 2、技术分析 基础的css、html 3、详细分析 如图: 3.1 HTML部分 如图所示&#xff0c;大边框内包含一个小边框两部分&#xff0c;设置一个父元素div和一个子元素div。 <div class"container">父元素<div class…

2008/5/5

go haywire 非常生气Imagination is the source of creation. 想象是创造之源.转载于:https://www.cnblogs.com/fishert/archive/2008/05/06/1184243.html

鸿蒙3部曲先看哪部,讨论雪鹰与鸿蒙三部曲的关系

1、相信番茄的每部小说出来&#xff0c;心里都在想这会不会是鸿蒙三部曲的最后一部呢&#xff0c;鸿蒙金榜最后一位掌控者呢。2、但是隔了这么多部&#xff0c;番茄依旧未写&#xff0c;第三部&#xff0c;让人不禁思考&#xff0c;会不会第三部是番茄的封山之作呢。3、如果说前…

python装饰器概念与应用

格式一&#xff1a;装饰器外层不传参&#xff0c;内层传参 user_status False # 用户登录了就把这个改成Truedef login(func): # 把要执行的henan模块从这里传进来def inner(*args, **kwargs): # 再定义一层函数&#xff0c;参数为henan的参数_username "alex" …

从Java连接到Cassandra

在我的帖子Hello Cassandra中 &#xff0c;我研究了如何下载Cassandra NoSQL数据库并使用cqlsh连接到Cassandra数据库。 在本文中&#xff0c;我将介绍从Java客户端连接到Cassandra数据库的基础知识。 尽管有几种可用于从Java访问Cassandra数据库的 框架 &#xff0c;但我将在…

Django---Model操作

一、字段 1 AutoField(Field)2 - int自增列&#xff0c;必须填入参数 primary_keyTrue3 4 BigAutoField(AutoField)5 - bigint自增列&#xff0c;必须填入参数 primary_keyTrue6 7 注&#xff1a;当model中如果没有自增列&#xff0c;则自动会创建…

8. Action过滤

Action过滤原文: http://quickstarts.asp.net/3-5-extensions/mvc/ActionFiltering.aspx1. 介绍一般Action与用户动作是一对一的关系,用户的某一个动作对应一个特定的Action.然而,有时你可能会希望在Action执行的前后执行指定的操作,MVC中可以通过Action过滤器来实现这个功能.A…

android运行时状态,Android 如何保存Android 运行时状态

Android 如何保存Android 运行时状态使用 SaveInstanceState去保存运行时数据首先&#xff0c;我们需要重写一下系统的public void onSaveInstanceState(Bundle savedInstanceState)方法并在onSaveInstanceState方法中添加需要保存的数据。最后我们可以在onRestoreInstanceStat…

程序文件分类及编写要求

一级&#xff1a;管理体系手册 由质量管理部负责编制&#xff0c;最高管理者批准后发布生效。举例如&#xff1a;XX公司管理手册 二级&#xff1a;管理体系程序文件 由各相关部门进行编写&#xff0c;质量管理部体系负责人审核通过各部门评审会签&#xff0c;由管理者代表批…

仿制药的美丽与陌生

最近&#xff0c;我正在为Oracle认证专家Java SE 7程序员考试做准备&#xff0c;而我恰巧在Java泛型领域遇到了一些看起来很奇怪的结构。 但是&#xff0c;我也看到了一些巧妙而优雅的代码。 我发现这些示例值得分享&#xff0c;这不仅是因为它们可以使您的设计选择更容易&…

C# 判断txt文件编码格式

/// <summary> /// 获取文件的编码格式 /// </summary> public class EncodingType{/// <summary> /// 给定文件的路径&#xff0c;读取文件的二进制数据&#xff0c;判断文件的编码类型 /// </summary> /// <param name“FILE_NAME“>文件路径&…

Vuex的第一次接触

前言&#xff1a;最近在做Vue实现去哪网&#xff0c;想要实现在城市列表页面&#xff0c;点击某个城市的时候&#xff0c;主页的头部的城市会随着改变&#xff0c;就是首页和城市页面有共用的数据要分享&#xff0c;这里使用Vuex 1. Vuex是什么&#xff1f; 是Vue官方推荐的数…

乐刻运动 app android,乐刻运动

乐刻运动是一款广受欢迎的运动健身软件&#xff0c;不论你是想打造完美身材还是减脂减重&#xff0c;乐刻运动都会为你制定严格而又科学的运动健身计划&#xff0c;相当于一个掌上私人的健身教练&#xff0c;时刻关注你的健身状况&#xff0c;快来下载试试吧。乐刻运动软件优势…