文章目录
- 前言
- 哪些内存需要回收
- 方法区的垃圾回收
- 垃圾收集算法
- 垃圾收集器
- 年轻代进入老年代条件
- 内存担保机制
- FullGC 触发时机
- GC日志解析
- 日志参数
前言
JVM包含内容:
- 类装载子系统(Class Load SubSystem)
- 运行时数据区(Run-Time Data Areas)
- 堆
- 栈
- 局部变量表
- 操作数栈
- 动态链接
- 方法返回地址
- 程序计数器
- 方法区
- 本地方法接口(Native Method Stack)
- PC寄存器(Programe Counter Register)
- 执行引擎(Execution Engine)
- 字节码解释器
对字节码采用逐行解释的方式执行 - JIT(Just In Time)编译器
- 字节码解释器
JIT(Just In Time)编译器
- 方法调用计数器:统计方法调用次数
统计方法调用的次数。默认阈值时Client模式下1500次,在Server模式下是10000次。超过这个阈值就会触发JIT编译。这个阈值可以通过-XX:CompileThreshold设定 - 回边计数器:统计循环体执行的循环次数
jvm内存分配
- 栈上分配与TLAB/内存分配的两种方法
jdk1.8默认垃圾回收器
JDK1.8中,Parallel Scavenge 被设置为年轻代(Young Generation)的默认垃圾回收器,而 Parallel Old 是用于老年代(Tenured Generation)的垃圾回收器
哪些内存需要回收
所谓“要回收的垃圾”无非就是那些不可能再被任何途径使用的对象。
寻找回收对象的两种方式。
- 引用计数法
给对象中添加一个引用计数器,每当一个地方引用这个对象时,计数器值+1;当引用失效时,计数器值-1。任何时刻计数值为0的对象就是不可能再被使用的。 - 可达性分析法
通过一系列称为GC Roots
的对象作为起始点,从这些节点向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链(即GC Roots到对象不可达)时,则证明此对象是不可用的。
可以作为GCRoots的对象包括下面几种:
- 虚拟机栈(栈帧中的局部变量区,也叫做局部变量表)中引用的对象。
- 方法区中的类静态属性引用的对象。
- 方法区中常量引用的对象。
- 本地方法栈中JNI(Native方法)引用的对象。
方法区的垃圾回收
方法区的垃圾回收主要回收两部分内容:
- 废弃常量。
以字面量回收为例,如果一个字符串“abc”已经进入常量池,但是当前系统没有任何一个String对象引用了叫做“abc”的字面量,那么,如果发生垃圾回收并且有必要时,“abc”就会被系统移出常量池。常量池中的其他类(接口)、方法、字段的符号引用也与此类似。 - 无用的类。既然进行垃圾回收,就需要判断哪些是废弃常量,哪些是无用的类,需要满足以下三个条件:
- 该类的所有实例都已经被回收,即Java堆中不存在该类的任何实例。
- 加载该类的ClassLoader已经被回收。
- 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
垃圾收集算法
- 标记-清除(Mark-Sweep)算法
- 复制(Copying)算法
- 标记-整理(Mark-Compact)算法
- 分代收集算法
垃圾收集器
- Serial收集器()
需要STW(Stop The World),停顿时间长。
简单高效,对于单个CPU环境而言,Serial收集器由于没有线程交互开销,可以获取最高的单线程收集效率。 - Serial Old收集器
Serial收集器的老年代版本 - ParNew收集器
ParNew收集器其实就是Serial收集器的多线程版本 - Parallel Scavenge收集器
- Parallel Old收集器
- CMS收集器
- G1收集器
垃圾收集器讲解
垃圾收集器讲解2
年轻代进入老年代条件
- 躲过15次gc,达到15岁高龄之后进入老年代;
- 动态年龄判定规则,如果Survivor区域内年龄1+年龄2+年龄3+年- 龄n的对象总和大于Survivor区的50%,此时年龄n以上的对象会进入老年代,不一定要达到15岁
- 如果一次Young GC后存活对象太多无法放入Survivor区,此时直接计入老年代
- 大对象直接进入老年代
内存担保机制
- 什么是老年代空间担保机制?担保的过程是什么?
JVM有这么一个参数:-XX:-HandlePromotionFailure(1.8默认设置)
年轻代每次GC前都,JVM都会计算老年代剩余可用空间,如果这个剩余空间小于年轻代里所有对象大小之和(包括垃圾对象),那么JVM就会看是否设置前面这个参数。如果设置这个参数,且老年代剩余空间是否小于之前每一次MInorGC后进入老年代对象的平均大小。
如果没设置参数,或者小于平均大小,会先触发一次FullGC,将老年代和年轻代的垃圾对象一起回收掉,如果回收后还是没有空间存放对象,则会发生OOM。
- 老年代空间担保机制是谁给谁担保?
我理解的是老年代给新生代的S区做担保。 - 为什么要有老年代空间担保机制?或者说空间担保机制的目的是什么?
目的:避免频繁的进行FullGC。 - 如果没有老年代空间担保机制会有什么不好?
如果没有这个担保机制,就会直接执行Full GC,这样对性能的影响频次会增加。
FullGC 触发时机
Full GC(Full Garbage Collection)是指对整个Java堆进行垃圾回收,包括新生代和老年代。触发Full GC的情况有以下几种:
-
老年代空间不足:当老年代中没有足够的空间来分配一个大对象时,会先尝试进行Minor GC,如果仍然无法获得足够的空间,则会触发Full GC。
-
调用System.gc()方法:虽然使用System.gc()方法不能保证立即进行垃圾回收,但是这个方法可以提示JVM进行垃圾回收。如果此时需要更多的内存空间,那么就可能会触发Full GC。
-
Perm区空间不足:Perm区是存放类信息和常量池等元数据的区域,如果Perm区没有足够的空间来存放这些信息,就会触发Full GC。
-
CMS GC出现Concurrent Mode Failure:CMS(Concurrent Mark Sweep)是一种以最小化停顿时间为目标的垃圾收集器,在CMS执行过程中,如果应用程序产生了大量更新,导致CMS回收速度跟不上对象生成速度,那么就可能会出现Concurrent Mode Failure,此时会启动Full GC来清理整个堆空间。
-
分配担保失败:在Minor GC后,如果survivor区无法容纳所有幸存对象,那么就要将部分幸存对象转移到老年代。如果老年代剩余空间不足以容纳这些对象,就需要进行Full GC。
需要注意的是,Full GC通常比Minor GC和CMS GC的停顿时间长,同时对于大型应用程序,Full GC可能会影响性能,因此应该尽量避免Full GC的发生。
GC日志解析
GC日志内容
日志内容解析及GC案例
不同垃圾收集器的不同日志打印示例
G1垃圾收集器日志解析
日志参数
- -XX:+PrintGC: 输出GC日志。类似:java -verbose:gc
- -XX:+PrintGCDetails : 输出GC的详细日志
- -XX:+PrintGCTimestamps : 输出GC的时间戳(以基准时间的形式)
- -XX:+PrintGCDatestamps : 输出GcC的时间戳(以日期的形式,如2013-05-04T21:53:59.234+0800)
- -XX:+PrintHeapAtGC: 在进行GC的前后打印出堆的信息
- -Xloggc:./logs/gc.log: 日志文件的输出路径
-XX:+PrintGC :
这个只会显示总的GC堆的变化,如下:
[GC (Allocation Failure) 80832K->19298K(227840K),0.0084018 secs]
[GC (Metadata GC Threshold) 109499K->21465K(228352K),0.0184066 secs]
[Full GC (Metadata GC Threshold) 21465K->16716K(201728K),0.0619261 secs]
参数解析:
GC、Full GC:GC的类型,GC只在新生代上进行,Full GC包括永生代,新生代,老年代。
Allocation Failure:GC发生的原因。
80832K->19298K:堆在GC前的大小和GC后的大小。
228840k:现在的堆大小。
0.0084018 secs:GC持续的时间。
-XX:+PrintGCDetails
[GC (Allocation Failure) [PSYoungGen:70640K->10116K(141312K)] 80541K->20017K(227328K),0.0172573 secs] [Times:user=0.03 sys=0.00,real=0.02 secs]
[GC (Metadata GC Threshold) [PSYoungGen:98859K->8154K(142336K)] 108760K->21261K(228352K),0.0151573 secs] [Times:user=0.00 sys=0.01,real=0.02 secs]
[Full GC (Metadata GC Threshold)[PSYoungGen:8154K->0K(142336K)]
[ParOldGen:13107K->16809K(62464K)] 21261K->16809K(204800K),[Metaspace:20599K->20599K(1067008K)],0.0639732 secs]
[Times:user=0.14 sys=0.00,real=0.06 secs]
参数解析:
GC,Full FC:同样是GC的类型
Allocation Failure:GC原因
PSYoungGen:使用了Parallel Scavenge并行垃圾收集器的新生代GC前后大小的变化
ParOldGen:使用了Parallel Old并行垃圾收集器的老年代GC前后大小的变化
Metaspace: 元数据区GC前后大小的变化,JDK1.8中引入了元数据区以替代永久代
xxx secs:指GC花费的时间
Times:user:指的是垃圾收集器花费的所有CPU时间sys:花费在等待系统调用或系统事件的时间real:GC从开始到结束的时间,包括其他进程占用时间片的实际时间。
-XX:+PrintGCTimestamps & -XX:+PrintGCDatestamps
带上日期:
2019-09-24T22:15:24.518+0800: 3.287: [GC (Allocation Failure) [PSYoungGen:136162K->5113K(136192K)] 141425K->17632K(222208K),0.0248249 secs] [Times:user=0.05 sys=0.00,real=0.03 secs]2019-09-24T22:15:25.559+0800: 4.329: [GC (Metadata GC Threshold) [PSYoungGen:97578K->10068K(274944K)] 110096K->22658K(360960K),0.0094071 secs] [Times: user=0.00 sys=0.00,real=0.01 secs]
2019-09-24T22:15:25.569+0800: 4.338: [Full GC (Metadata GC Threshold) [PSYoungGen:10068K->0K(274944K)][ParoldGen:12590K->13564K(56320K)] 22658K->13564K(331264K),[Metaspace:20590K->20590K(1067008K)],0.0494875 secs] [Times: user=0.17 sys=0.02,real=0.05 secs]
总结 :
[GC
和[Full GC
说明了这次垃圾收集的停顿类型,如果有Full则说明GC发生了"Stop The World"
不同的垃圾收集器在日志中的名称:
- 使用Serial收集器在新生代的名字是
Default New Generation
,因此显示的是[DefNew
- 使用ParNew收集器在新生代的名字会变成
[ParNew
,意思是Parallel New Generation
- 使用Parallel Scavenge收集器在新生代的名字是
[PSYoungGen
- 使用Parallel Old收集器收集器在老年代显示
[ParoldGen
- 使用G1收集器的话,会显示为
garbage-first heap
Allocation Failure:表明本次引起GC的原因是因为在年轻代中没有足够的空间能够存储新的数据了。
Metadata GCThreshold:Metaspace区不够用了
FErgonomics:JVM自适应调整导致的GC
System:调用了System.gc()方法
一般日志格式:
GC日志格式的规律一般都是:GC前内存占用->GC后内存占用(该区域内存总大小)
[PSYoungGen:5986K->696K(8704K) ] 5986K->704K(9216K)
- 中括号内:GC回收前年轻代大小,回收后大小,(年轻代总大小)
- 括号外:GC回收前年轻代和老年代大小,回收后大小,(年轻代和老年代总大小)
GC日志中有三个时间:user,sys和real
- user:进程执行用户态代码(核心之外)所使用的时间。这是执行此进程所使用的实际CPU 时间,其他进程和此进程阻塞的时间并不包括在内。在垃圾收集的情况下,表示GC线程执行所使用的 CPU 总时间。
- sys:进程在内核态消耗的 CPU 时间,即在内核执行系统调用或等待系统事件所使用的CPU 时间
- real:程序从开始到结束所用的时钟时间。这个时间包括其他进程使用的时间片和进程阻塞的时间(比如等待 I/O 完成)。对于并行gc,这个数字应该接近(用户时间+系统时间)除以垃圾收集器使用的线程数。
日志分析原文