调试以了解终结器

这篇文章涵盖了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,一经查实,立即删除!

相关文章

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做…

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

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…

从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;则自动会创建…

Vuex的第一次接触

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

java IO流小结

Java流操作有关的类或接口&#xff1a; Java流类图结构&#xff1a; 流的概念和作用 流是一组有顺序的&#xff0c;有起点和终点的字节集合&#xff0c;是对数据传输的总称或抽象。即数据在两设备间的传输称为流&#xff0c;流的本质是数据传输&#xff0c;根据数据传输特性将流…

华为android是什么型号,华为手机机型众多,目前这几款最值得入手

华为手机机型众多&#xff0c;目前这几款最值得入手2020-09-22 15:00:033点赞0收藏0评论华为手机可以说是国家手机的代名词。受某种感情的影响&#xff0c;很多人都用华为取代了iPhone。为了表达感情&#xff0c;很多人也纷纷效仿&#xff0c;购买华为手机。但我想说的是支持华…

pt-online-schema-change VS oak-online-alter-table【转】

前言 在上篇文章中提到了MySQL 5.6 Online DDL&#xff0c;如果是MySQL 5.5的版本在DDL方面是要付出代价的&#xff0c;虽然已经有了Fast index Creation&#xff0c;但是在添加字段还是会锁表的&#xff0c;而且在添加删除辅助索引是会加S锁&#xff0c;也就是无法进行写操作。…

vue命令行错误处理

全局安装vue/cli时&#xff1a;npm install -g vue/cli &#xff08;1&#xff09;Error: EACCES: permission denied, access /usr/local/lib/node_modules/vue/cli 原因: 执行命令时没有获得管理员权限 解决办法: 在命令前面加上sudo即可.然后输入电脑的管理员密码操作即可…

RAC(ReactiveCocoa)介绍(一)

最近在学习RAC&#xff0c;之前在iOS工作中&#xff0c;类之间的传值&#xff0c;无非是block、delegate代理、KVO和Notification等这几种方法。在RAC中&#xff0c;同样具备替代block、delegate代理、KVO和Notification&#xff0c;UI target、定时器timer、数据结构等各种方式…

一段简单的html 5 音频,5个用于处理HTML5音频的库和API

在过去的几个月中&#xff0c;我遇到了许多不同的库&#xff0c;它们利用了相对较新的HTML5 Audio API以及更著名的HTML5 Audio Element及其更简单的API。我以为我会在本文中分享这些库中的一小部分&#xff0c;以向您展示如果选择创建需要操纵声音文件的游戏或应用程序&#x…

WinAPI: SetRect 及初始化矩形的几种办法

本例分别用五种办法初始化了同样的一个矩形, 运行效果图:unit Unit1;interfaceusesWindows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,Dialogs, StdCtrls;typeTForm1 class(TForm)Button1: TButton;Button2: TButton;Button3: TButton;Button4: TBu…

备忘录——通过RVA计算文件位置

备忘录——通过RVA计算文件位置 原创&#xff1a;Anders Liu 摘要&#xff1a;本文介绍了如何通过PE文件中某一项的RVA来计算其在文件中的位置。 参考文献 ECMA-335——Common Language Infrastructure (CLI) 4th Edition, June 2006 范畴 该备忘录描述了在分析PE&#xff08;可…

中后端管理系统前后分离、前端框架的实现拙见

一、实现思路 在实践中后台管理系统的前后端分离时&#xff0c;往往会因为业务量的增加使其前端项目难以维护&#xff0c;以及打包时间不理想&#xff0c;还有业务系统与框架之间区分不在明显。本文是本人从另一个角度提出的一种解决方案&#xff0c;希望各位提出宝贵的建议。…

初见mobX

先看如下的代码 const {observable} mobox; const {observer}mobxReact; const {Component}React; const appStateobservable({count:0 }) appState.incrementfunction(){this.count } appState.decrementfunction(){this.count-- } observer class Counter extends Component{…

【留言板】可编辑输入框操作总结

闲暇之余&#xff0c;用于加深自己对基础的了解&#xff0c;徒手撸了一个留言板&#xff1a;输入框。废话少说&#xff0c;进入正题。简陋的效果如下(下载代码)&#xff1a; 一、定义需求 可输入文本&#xff0c;以及插入表情。兼容性&#xff1a;IE与标准浏览器 二、详细设计…

2021年兰州师大附中高考成绩查询,2021年兰州重点高中名单及排名,兰州高中高考成绩排名榜...

”一千个人眼中&#xff0c;就有一千个哈姆雷特“。关于兰州高职学校排名&#xff0c;每个人的观点也是各不相同&#xff0c;今天就给大家分享一下我心中的兰州高中排名及格局分布&#xff0c;主要参考依据是近年中考录取分数线及高考成绩。数据仅供参考&#xff01;希望对你有…