到底谁是辣鸡?(对象是否存活和GC日志分析)

2024-05-31 11:58

本文主要是介绍到底谁是辣鸡?(对象是否存活和GC日志分析),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

垃圾收集要搞清楚的三件事(除了方法区回收其他都针对对象也就是堆区的回收)

垃圾收集(Carbage Collection,GC),垃圾收集需要考虑三件事:
1.哪些内存需要回收

方法计数器、虚拟机栈、本地方法栈三个区域随着线程生和灭,每一个栈帧所分配的内存在编译器大体上都是可知的,内存的回收和分配都具备确定性,所以不需要过多的考虑回收问题,因为方法结束或者是线程结束内存自动就会回收了。

方法区和堆区(垃圾收集区域需要回收)的内存分配则不一样,接口的多个实现、方法的多个分支占用的内存都可能不一样,只有在运行期才知道哪些对象会创建,这部分的内存回收和分配都是动态的,所以垃圾收集器关注的是这部分内存。

2.什么时候回收

对象死亡时回收(不被任何途径使用)。

3.如何回收

垃圾收集算法。

判断对象是否存活(什么时候回收和GC日志开启方式以及日志分析)

1.引用计数法

原理:给对象添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能被再使用的。

优点:实现简单、判定效率高。

缺点:对象间相互循环引用问题无法解决,
在下边的栗子里objA和objB的instance字段相互持有对方的引用,除此之外没有别的任何引用,而且两个对象已经不可能被访问,但是相互引用着对方,计数器都不是0,于是引用计数法无法告知GC去回收内存。

举个官方栗子:

public class GCObj{public Object instance = null;private static final int _1MB = 1024*1024;//判断对象的这部分内存是否会被回收,判断是不是使用的引用计数法private byte[] isGCByte = new byte[2*_1MB];public static void main(String[] args) {GCObj objA = new GCObj();GCObj objB = new GCObj();objA.instance = objB;objB.instance = objA;objA = null;objB = null;//假设在此GC,objA和objB是否会被回收System.gc();}
}

GC日志

[GC (System.gc()) [PSYoungGen: 7270K->664K(36864K)] 7270K->664K(121856K), 0.0181725 secs] [Times: user=0.02 sys=0.00, real=0.03 secs] [Full GC (System.gc()) [PSYoungGen: 664K->0K(36864K)] [ParOldGen: 0K->616K(84992K)] 664K->616K(121856K), [Metaspace: 3155K->3155K(1056768K)], 0.0915562 secs] [Times: user=0.11 sys=0.00, real=0.09 secs] 

观察日志可分析出(此处涉及到GC日志的分析,以后单独介绍),虚拟机并没有因为两个对象相互引用就不回收他们,这也说明虚拟机没有使用引用计数法判断对象存活。

GC日志分析:
首先如何让代码运行时打印GC日志:
日志分析

点击Edit Configurations…
设置VM optional参数

然后设置VM optinals参数:-XX:+PrintGCDetails
新生代老年代,GC,对内存分配

堆设置
-Xms :初始堆大小
-Xmx :最大堆大小
-XX:NewSize=n :设置年轻代大小
-XX:NewRatio=n: 设置年轻代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4
-XX:SurvivorRatio=n :年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5
-XX:MaxPermSize=n :设置持久代大小 收集器设置
-XX:+UseSerialGC :设置串行收集器
-XX:+UseParallelGC :设置并行收集器
-XX:+UseParalledlOldGC :设置并行年老代收集器
-XX:+UseConcMarkSweepGC :设置并发收集器 垃圾回收统计信息
-XX:+PrintHeapAtGC GC的heap详情
-XX:+PrintGCDetails GC详情
-XX:+PrintGCTimeStamps 打印GC时间信息
-XX:+PrintTenuringDistribution 打印年龄信息等
-XX:+HandlePromotionFailure 老年代分配担保(true or false) 并行收集器设置
-XX:ParallelGCThreads=n :设置并行收集器收集时使用的CPU数。并行收集线程数。
-XX:MaxGCPauseMillis=n :设置并行收集最大暂停时间
-XX:GCTimeRatio=n :设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n) 并发收集器设置
-XX:+CMSIncrementalMode :设置为增量模式。适用于单CPU情况。
-XX:ParallelGCThreads=n :设置并发收集器年轻代收集方式为并行收集时,使用的CPU数。并行收集线程数。

更多设置参考:IDEA和Eclipse GC日志分析打印开启方法和参数设置

书上的GC日志
针对以上书中的例子来具体分析:
33.125 和 100.667 代表了GC发生的时间,这个数字代表从Java虚拟机启动以来经过的秒数。
[GC 和[Full GC 代表了GC的停顿类型并不是来区分GC发生在新生代还是老年代的,如果有Full说明本次GC发生了Stop The World (简称STW)的停顿。

ParNew收集器中的GC
ParNew收集器中的GC也出现Full,这一般是出现了担保失败之类的问题出现了STW
如果是调用System.gc()方法触发的GC,则如我们上边运行的程序中的那样,[GC (System.gc()) ,[Full GC (System.gc())

[DefNew(新生代)、[Tenured(老年代)、[Perm(永久代)表示GC发生的区域
在Serial收集器中显示[DefNew(Default New Generation)
ParNew收集器中显示[ParNew(Parallel New Generation)
Parallel Scavenge收集器中显示[PSYoungGen
老年代和永久带一致都是跟收集器决定的

后边方括号中的3324k->152k(3712k)含义是:“GC前该内存区域使用量->GC后该内存区域使用量(该内存区域总容量)”

方括号之外的3324k->152k(11904k)的含义是:“GC前Java堆的已使用量”->GC后Java堆的已使用量(Java堆的总容量)

再往后,0.0025925secs表示此次GC所占时间单位是秒,有的收集器(需要在VM optional中设置)可以显示更具体的时间数据,如:[Time:
user=0.01 sys=0.01,real=0.02 secs]这里边的 user、sys、real和Linux的time命令输出的时间含义一致分别代表:用户态消耗的CPU时间(user)、内核态消耗的CPU时间(sys)、操作从开始到结束所经过的墙钟时间(Wall Clock Time)(real)
墙钟时和CPU时间的区别:
墙钟时间包括各种非运算的等待耗时,例如磁盘IO、等待线程阻塞
而CPU时间则不包括这些耗时
但是当有多CPU或者多核时多线程操作会叠加这些CPU时间
所以有时user或者sys的事件会超过real的事件。

2.可达性分析算法

通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时(就是从GC Roots 到这个对象是不可达),则证明此对象是不可用的。所以它们会被判定为可回收对象(例如图中的Obj5、Obj6、Obj7对象既是不可达的)。

可达性分析图

在Java语言中,可作为GC Roots对象的包括下面一下几种:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象
  • 方法区中类能静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中JNI(Native方法)引用的对象

在可达性分析算法中,要真正宣告一个对象死亡,至少要经历两次标记过程:

1.(缓刑)如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链,那它将会被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。当对象没有 覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过,虚拟机将这两种情况都视为“没有必要执行”。

2.(对象自我拯救)如果这个对象被判定为有必要执行finalize()方法,那么这个对象将会放置在一个叫做F-Queue队列之中,并在稍后由一个由虚拟机自动建立的、低优先级的Finalizer线程去执行它。finalize()方法是对象逃脱死亡命运的最后一次机会,稍候GC将对F-Queue中的对象进行第二次小规模的标记,如果对象要在finalize()中成功拯救自己——只要重新与引用链上的任何一个对象建立关联即可,譬如把自己(this关键字)赋值给某个类变量或者对象的成员变量,那在第二次标记时它将会被移除出“即将回收”的集合;如果对象这时候还没有逃脱,那基本上它就真的被回收了。

对象可以在被GC时自我拯救,这种自救机会只有一次,因为一个对象的finalize()方法最多只会被系统调用一次。

判断对象是否存活与“引用”有关(补充)

在JDK1.2之后,Java对引用的概念进行了扩充,将引用分为强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)四种,这四种引用强度依次逐渐减弱。

强引用(Strong Reference):就是指在程序代码之中普遍存在的,类似“Object obj = new Object()”这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。

软引用(Soft Reference):用来描述一些还有用但并非必须的对象。在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。

弱引用(Weak Reference):用户描述非必须对象的。被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。

虚引用(Phantom Reference):一个对象是否有虚引用存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用的唯一目的就是能在这个对象被收集器回收时刻得到一个系统通知。

方法区回收(扩展)

方法区(在Hotpot虚拟机中的永久代),垃圾收集效率要低于堆中对象的收集效率(70%-90%),永久代中回收的主要内容为两块:废弃常量和无用类

  • 废弃常量回收:以常量池中的字面量为例,如字符串常量“abc”,没有任何String对象引用常量池中的“abc”通俗点说就是没有任何String字符串叫“abc”,此时abc就会被清理出常量池,其他类(接口)、方法、字段的符号引用也是如此。
  • 无用类的回收:满足三个条件①该类的所有实例都已经被回收,也就是Java堆中不存在此类的任何实例②加载该类的ClassLoarder已经被回收③该对象的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。此三项只代表该类可以回收,要想真正回收类,还要在虚拟机参数中设置:-Xnoclassgc

参考:《深入理解Java虚拟机第二版:JVM高级特性与最佳实践》
http://blog.csdn.net/u014381710/article/details/48554465

这篇关于到底谁是辣鸡?(对象是否存活和GC日志分析)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1017863

相关文章

SpringBoot日志级别与日志分组详解

《SpringBoot日志级别与日志分组详解》文章介绍了日志级别(ALL至OFF)及其作用,说明SpringBoot默认日志级别为INFO,可通过application.properties调整全局或... 目录日志级别1、级别内容2、调整日志级别调整默认日志级别调整指定类的日志级别项目开发过程中,利用日志

JavaScript中比较两个数组是否有相同元素(交集)的三种常用方法

《JavaScript中比较两个数组是否有相同元素(交集)的三种常用方法》:本文主要介绍JavaScript中比较两个数组是否有相同元素(交集)的三种常用方法,每种方法结合实例代码给大家介绍的非常... 目录引言:为什么"相等"判断如此重要?方法1:使用some()+includes()(适合小数组)方法2

如何通过try-catch判断数据库唯一键字段是否重复

《如何通过try-catch判断数据库唯一键字段是否重复》在MyBatis+MySQL中,通过try-catch捕获唯一约束异常可避免重复数据查询,优点是减少数据库交互、提升并发安全,缺点是异常处理开... 目录1、原理2、怎么理解“异常走的是数据库错误路径,开销比普通逻辑分支稍高”?1. 普通逻辑分支 v

深度剖析SpringBoot日志性能提升的原因与解决

《深度剖析SpringBoot日志性能提升的原因与解决》日志记录本该是辅助工具,却为何成了性能瓶颈,SpringBoot如何用代码彻底破解日志导致的高延迟问题,感兴趣的小伙伴可以跟随小编一起学习一下... 目录前言第一章:日志性能陷阱的底层原理1.1 日志级别的“双刃剑”效应1.2 同步日志的“吞吐量杀手”

Linux实现查看某一端口是否开放

《Linux实现查看某一端口是否开放》文章介绍了三种检查端口6379是否开放的方法:通过lsof查看进程占用,用netstat区分TCP/UDP监听状态,以及用telnet测试远程连接可达性... 目录1、使用lsof 命令来查看端口是否开放2、使用netstat 命令来查看端口是否开放3、使用telnet

使用Java读取本地文件并转换为MultipartFile对象的方法

《使用Java读取本地文件并转换为MultipartFile对象的方法》在许多JavaWeb应用中,我们经常会遇到将本地文件上传至服务器或其他系统的需求,在这种场景下,MultipartFile对象非... 目录1. 基本需求2. 自定义 MultipartFile 类3. 实现代码4. 代码解析5. 自定

java -jar example.jar 产生的日志输出到指定文件的方法

《java-jarexample.jar产生的日志输出到指定文件的方法》这篇文章给大家介绍java-jarexample.jar产生的日志输出到指定文件的方法,本文给大家介绍的非常详细,对大家的... 目录怎么让 Java -jar example.jar 产生的日志输出到指定文件一、方法1:使用重定向1、

c++日志库log4cplus快速入门小结

《c++日志库log4cplus快速入门小结》文章浏览阅读1.1w次,点赞9次,收藏44次。本文介绍Log4cplus,一种适用于C++的线程安全日志记录API,提供灵活的日志管理和配置控制。文章涵盖... 目录简介日志等级配置文件使用关于初始化使用示例总结参考资料简介log4j 用于Java,log4c

Android 缓存日志Logcat导出与分析最佳实践

《Android缓存日志Logcat导出与分析最佳实践》本文全面介绍AndroidLogcat缓存日志的导出与分析方法,涵盖按进程、缓冲区类型及日志级别过滤,自动化工具使用,常见问题解决方案和最佳实... 目录android 缓存日志(Logcat)导出与分析全攻略为什么要导出缓存日志?按需过滤导出1. 按

nginx配置错误日志的实现步骤

《nginx配置错误日志的实现步骤》配置nginx代理过程中,如果出现错误,需要看日志,可以把nginx日志配置出来,以便快速定位日志问题,下面就来介绍一下nginx配置错误日志的实现步骤,感兴趣的可... 目录前言nginx配置错误日志总结前言在配置nginx代理过程中,如果出现错误,需要看日志,可以把