到底谁是辣鸡?(对象是否存活和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应用中出现的Full GC问题的场景与解决

《SpringBoot应用中出现的FullGC问题的场景与解决》这篇文章主要为大家详细介绍了SpringBoot应用中出现的FullGC问题的场景与解决方法,文中的示例代码讲解详细,感兴趣的小伙伴可... 目录Full GC的原理与触发条件原理触发条件对Spring Boot应用的影响示例代码优化建议结论F

Qt spdlog日志模块的使用详解

《Qtspdlog日志模块的使用详解》在Qt应用程序开发中,良好的日志系统至关重要,本文将介绍如何使用spdlog1.5.0创建满足以下要求的日志系统,感兴趣的朋友一起看看吧... 目录版本摘要例子logmanager.cpp文件main.cpp文件版本spdlog版本:1.5.0采用1.5.0版本主要

Python 迭代器和生成器概念及场景分析

《Python迭代器和生成器概念及场景分析》yield是Python中实现惰性计算和协程的核心工具,结合send()、throw()、close()等方法,能够构建高效、灵活的数据流和控制流模型,这... 目录迭代器的介绍自定义迭代器省略的迭代器生产器的介绍yield的普通用法yield的高级用法yidle

C++ Sort函数使用场景分析

《C++Sort函数使用场景分析》sort函数是algorithm库下的一个函数,sort函数是不稳定的,即大小相同的元素在排序后相对顺序可能发生改变,如果某些场景需要保持相同元素间的相对顺序,可使... 目录C++ Sort函数详解一、sort函数调用的两种方式二、sort函数使用场景三、sort函数排序

kotlin中const 和val的区别及使用场景分析

《kotlin中const和val的区别及使用场景分析》在Kotlin中,const和val都是用来声明常量的,但它们的使用场景和功能有所不同,下面给大家介绍kotlin中const和val的区别,... 目录kotlin中const 和val的区别1. val:2. const:二 代码示例1 Java

Go标准库常见错误分析和解决办法

《Go标准库常见错误分析和解决办法》Go语言的标准库为开发者提供了丰富且高效的工具,涵盖了从网络编程到文件操作等各个方面,然而,标准库虽好,使用不当却可能适得其反,正所谓工欲善其事,必先利其器,本文将... 目录1. 使用了错误的time.Duration2. time.After导致的内存泄漏3. jsO

SpringBoot日志配置SLF4J和Logback的方法实现

《SpringBoot日志配置SLF4J和Logback的方法实现》日志记录是不可或缺的一部分,本文主要介绍了SpringBoot日志配置SLF4J和Logback的方法实现,文中通过示例代码介绍的非... 目录一、前言二、案例一:初识日志三、案例二:使用Lombok输出日志四、案例三:配置Logback一

Spring事务中@Transactional注解不生效的原因分析与解决

《Spring事务中@Transactional注解不生效的原因分析与解决》在Spring框架中,@Transactional注解是管理数据库事务的核心方式,本文将深入分析事务自调用的底层原理,解释为... 目录1. 引言2. 事务自调用问题重现2.1 示例代码2.2 问题现象3. 为什么事务自调用会失效3

在java中如何将inputStream对象转换为File对象(不生成本地文件)

《在java中如何将inputStream对象转换为File对象(不生成本地文件)》:本文主要介绍在java中如何将inputStream对象转换为File对象(不生成本地文件),具有很好的参考价... 目录需求说明问题解决总结需求说明在后端中通过POI生成Excel文件流,将输出流(outputStre

golang 日志log与logrus示例详解

《golang日志log与logrus示例详解》log是Go语言标准库中一个简单的日志库,本文给大家介绍golang日志log与logrus示例详解,感兴趣的朋友一起看看吧... 目录一、Go 标准库 log 详解1. 功能特点2. 常用函数3. 示例代码4. 优势和局限二、第三方库 logrus 详解1.