到底谁是辣鸡?(对象是否存活和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

相关文章

检查 Nginx 是否启动的几种方法

《检查Nginx是否启动的几种方法》本文主要介绍了检查Nginx是否启动的几种方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学... 目录1. 使用 systemctl 命令(推荐)2. 使用 service 命令3. 检查进程是否存在4

Spring Boot Interceptor的原理、配置、顺序控制及与Filter的关键区别对比分析

《SpringBootInterceptor的原理、配置、顺序控制及与Filter的关键区别对比分析》本文主要介绍了SpringBoot中的拦截器(Interceptor)及其与过滤器(Filt... 目录前言一、核心功能二、拦截器的实现2.1 定义自定义拦截器2.2 注册拦截器三、多拦截器的执行顺序四、过

Python中4大日志记录库比较的终极PK

《Python中4大日志记录库比较的终极PK》日志记录框架是一种工具,可帮助您标准化应用程序中的日志记录过程,:本文主要介绍Python中4大日志记录库比较的相关资料,文中通过代码介绍的非常详细,... 目录一、logging库1、优点2、缺点二、LogAid库三、Loguru库四、Structlogphp

C# GC回收的方法实现

《C#GC回收的方法实现》本文主要介绍了C#GC回收的方法实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录 一、什么是 GC? 二、GC 管理的是哪部分内存? 三、GC 什么时候触发?️ 四、GC 如何判断一个对象是“垃圾

C++ scoped_ptr 和 unique_ptr对比分析

《C++scoped_ptr和unique_ptr对比分析》本文介绍了C++中的`scoped_ptr`和`unique_ptr`,详细比较了它们的特性、使用场景以及现代C++推荐的使用`uni... 目录1. scoped_ptr基本特性主要特点2. unique_ptr基本用法3. 主要区别对比4. u

Nginx内置变量应用场景分析

《Nginx内置变量应用场景分析》Nginx内置变量速查表,涵盖请求URI、客户端信息、服务器信息、文件路径、响应与性能等类别,这篇文章给大家介绍Nginx内置变量应用场景分析,感兴趣的朋友跟随小编一... 目录1. Nginx 内置变量速查表2. 核心变量详解与应用场景3. 实际应用举例4. 注意事项Ng

Java多种文件复制方式以及效率对比分析

《Java多种文件复制方式以及效率对比分析》本文总结了Java复制文件的多种方式,包括传统的字节流、字符流、NIO系列、第三方包中的FileUtils等,并提供了不同方式的效率比较,同时,还介绍了遍历... 目录1 背景2 概述3 遍历3.1listFiles()3.2list()3.3org.codeha

JAVA Log 日志级别和使用配置示例

《JAVALog日志级别和使用配置示例》本文介绍了Java中主流的日志框架,包括Logback和Log4j2,并详细解释了日志级别及其使用场景,同时,还提供了配置示例和使用技巧,如正确的日志记录方... 目录一、主流日志框架1. Logback (推荐)2. Log4j23. SLF4J + Logback

C++简单日志系统实现代码示例

《C++简单日志系统实现代码示例》日志系统是成熟软件中的一个重要组成部分,其记录软件的使用和运行行为,方便事后进行故障分析、数据统计等,:本文主要介绍C++简单日志系统实现的相关资料,文中通过代码... 目录前言Util.hppLevel.hppLogMsg.hppFormat.hppSink.hppBuf

GO语言zap日志库理解和使用方法示例

《GO语言zap日志库理解和使用方法示例》Zap是一个高性能、结构化日志库,专为Go语言设计,它由Uber开源,并且在Go社区中非常受欢迎,:本文主要介绍GO语言zap日志库理解和使用方法的相关资... 目录1. zap日志库介绍2.安装zap库3.配置日志记录器3.1 Logger3.2 Sugared