日前查看某个程序的日志,发现一直在报GC相关的信息,不确定这样的信息是代表正确还是不正确,所以正好借此机会再复习下GC相关的内容:
以其中一行为例来解读下日志信息:
[GC (Allocation Failure) [ParNew: 367523K->1293K(410432K), 0.0023988 secs] 522739K->156516K(1322496K), 0.0025301 secs] [Times: user=0.04 sys=0.00, real=0.01 secs]
GC:
表明进行了一次垃圾回收,前面没有Full修饰,表明这是一次Minor GC ,注意它不表示只GC新生代,并且现有的不管是新生代还是老年代都会STW。
Allocation Failure:
表明本次引起GC的原因是因为在年轻代中没有足够的空间能够存储新的数据了。
ParNew:
表明本次GC发生在年轻代并且使用的是ParNew垃圾收集器。ParNew是一个Serial收集器的多线程版本,会使用多个CPU和线程完成垃圾收集工作(默认使用的线程数和CPU数相同,可以使用-XX:ParallelGCThreads参数限制)。该收集器采用复制算法回收内存,期间会停止其他工作线程,即Stop The World。
367523K->1293K(410432K):单位是KB
三个参数分别为:GC前该内存区域(这里是年轻代)使用容量,GC后该内存区域使用容量,该内存区域总容量。
0.0023988 secs:
该内存区域GC耗时,单位是秒
522739K->156516K(1322496K):
三个参数分别为:堆区垃圾回收前的大小,堆区垃圾回收后的大小,堆区总大小。
0.0025301 secs:
该内存区域GC耗时,单位是秒
[Times: user=0.04 sys=0.00, real=0.01 secs]:
分别表示用户态耗时,内核态耗时和总耗时
分析下可以得出结论:
该次GC新生代减少了367523-1293=366239K
Heap区总共减少了522739-156516=366223K
366239 – 366223 =16K,说明该次共有16K内存从年轻代移到了老年代,可以看出来数量并不多,说明都是生命周期短的对象,只是这种对象有很多。
我们需要的是尽量避免Full GC的发生,让对象尽可能的在年轻代就回收掉,所以这里可以稍微增加一点年轻代的大小,让那17K的数据也保存在年轻代中。
GC时,用什么方法判断哪些对象是需要回收:
- 引用计数法(已经不用了)
- 可达性分析法
前一种简而言之就是给对象添加一个引用计数器,有其他地方引用时这个计数器+1,引用失效时-1,为0时就可以删除掉了。但是它不能解决循环引用的问题,所以一般使用的都是后一种算法。
可达性分析法的基本思路就是通过一系列名为GC Roots的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的,那就可以回收掉了。
GC Roots一般都是些堆外指向堆内的引用,例如:
- JVM栈中引用的对象
- 方法区中静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中引用的对象
以CMS为例,补充一些知识点介绍:
复制算法介绍:
因为新生代对象生命周期一般很短,现在一般将该内存区域划分为三块部分,一块大的叫Eden,两块小的叫Survivor。他们之间的比例一般为8:1:1。
使用的时候只使用Eden + 一块Survivor。用Eden区用满时会进行一次minor gc,将存活下面的对象复制到另外一块Survivor上。如果另一块Survivor放不下(对应虚拟机参数为 XX:TargetSurvivorRatio,默认50,即50%),对象直接进入老年代。
(使用CMS时,默认的新生代收集器是ParNew)(有时新生代GC时,需要找到老年代中引用的新生代对象,这个时候会用到一种叫“卡表”的技术,避免老年代的全表扫描,具体怎么操作的暂时还不知道……)
Survivor区的意义:
如果没有survivor,Eden每进行一次minor gc,存活的对象就会进入老年代,老年代很快被填满就会进入major gc。由于老年代空间一般很大,所以进行一次gc耗时要长的多!尤其是频繁进行full GC,对程序的响应和连接都会有影响!
Survivor存在就是减少被送到老年代的对象,进而减少Full gc的发生。默认设置是经历了16次minor gc还在新生代中存活的对象才会被送到老年代。
为什么要有两个Survivor:
主要是为了解决内存碎片化和效率问题。如果只有一个Survivor时,每触发一次minor gc都会有数据从Eden放到Survivor,一直这样循环下去。注意的是,Survivor区也会进行垃圾回收,这样就会出现内存碎片化问题。如下图所示:
碎片化会导致堆中可能没有足够大的连续空间存放一个大对象,影响程序性能。如果有两块Survivor就能将剩余对象集中到其中一块Survivor上,避免碎片问题。如下图所示:
Minor GC和Full GC的区别以及触发条件:
Minor GC:
对于复制算法来说,当年轻代Eden区域满的时候会触发一次Minor GC,将Eden和From Survivor的对象复制到另外一块To Survivor上。
注意:如果某个对象存活的时间超过一定Minor gc次数会直接进入老年代,不在分配到To Survivor上(默认15次,对应虚拟机参数 -XX:+MaxTenuringThreshold)。
Full GC:
用于清理整个堆空间。它的触发条件主要有以下几种:
- 显式调用System.gc方法(建议JVM触发)。
- 方法区空间不足(JDK8及之后不会有这种情况了,详见下文)
- 老年代空间不足,引起Full GC。这种情况比较复杂,有以下几种:
3.1 大对象直接进入老年代引起,由-XX:PretenureSizeThreshold参数定义
3.2 Minor GC时,经历过多次Minor GC仍存在的对象进入老年代。上面提过,由-XX:MaxTenuringThreashold参数定义
3.3 Minor GC时,动态对象年龄判定机制会将对象提前转移老年代。年龄从小到大进行累加,当加入某个年龄段后,累加和超过survivor区域 * -XX:TargetSurvivorRatio的时候,从这个年龄段往上的年龄的对象进入老年代
3.4 Minor GC时,Eden和From Space区向To Space区复制时,大于To Space区可用内存,会直接把对象转移到老年代
JVM的空间分配担保机制可能会触发Full GC:
在进行Minor GC之前,JVM的空间担保分配机制可能会触发3.2、3.3和3.4发生,即触发一次Full GC。
空间担保分配是指在发生Minor GC之前,虚拟机会检查老年代最大可用的连续空间是否大于新生代所有对象的总空间。
如果大于,则此次Minor GC是安全的。
如果小于,则虚拟机会查看HandlePromotionFailure设置值是否允许担保失败。如果HandlePromotionFailure=true,那么会继续检查老年代最大可用连续空间是否大于历次晋升到老年代的对象的平均大小,如果大于,则尝试进行一次Minor GC,但这次Minor GC依然是有风险的,失败后会重新发起一次Full gc;如果小于或者HandlePromotionFailure=false,则改为直接进行一次Full GC。
所有才会说一次Full GC很有可能是由一次Minor GC触发的。
PS:JDK8中HotSpot为什么要取消永久代
JDK8取消了永久代,新增了一个叫元空间(Metaspace)的区域,对应的还是JVM规范中的方法区(主要存放一些class和元数据的信息)。区别在于元空间使用的并不是JVM中的内存,而是使用本地内存。
而这么做的原因大致有以下几点:
1、字符串存在永久代中,容易出现性能问题和内存溢出。
2、类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出。
3、永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。
4、Oracle 可能会将HotSpot 与 JRockit 合二为一。
补充下JDK8内存模型图:
参考:
https://blog.csdn.net/antony9118/article/details/51425581(两个Survivor的意义)
https://zhidao.baidu.com/question/1111800566588999699.html(Full GC什么时候触发)
https://blog.csdn.net/quinnnorris/article/details/75040538(可达性分析算法解析)
https://segmentfault.com/a/1190000007726689(卡表是什么)
相关推荐
在IT行业中,JVM(Java Virtual Machine)是Java应用程序的核心组成部分,它负责解析并执行Java字节码,为开发者提供了一种跨平台的运行环境。然而,如同任何其他软件一样,JVM也可能出现各种异常情况,影响程序的...
2020-02-17T10:17:24.672+0800: 48172.920: [GC (Allocation Failure) 2020-02-17T10:17:24.672+0800: 48172.920: [ParNew: 1756536K->14685K(1922432K), 0.0272831 secs] 1850643K->108791K(4019
本文将深入探讨JVM的主要知识点,包括内存模型、类加载机制、垃圾收集器及其算法、内存调优工具,以及相关配置选项。 1. **JVM内存模型** - **对象创建与内存分配**:JVM根据对象大小和生命周期将其分配到堆内存的...
Memory Allocation Failed(处理方案).md
项目中碰到的问题
### 马士兵JVM调优笔记知识点梳理 #### 一、Java内存结构 Java程序运行时,其内存被划分为几个不同的区域,包括堆内存(Heap)、方法区(Method Area)、栈(Stack)、程序计数器(Program Counter Register)以及...
Java虚拟机(JVM)的垃圾收集(Garbage Collection, GC)是其内存管理的关键部分,用于自动回收不再使用的对象占用的内存空间。...熟练掌握这些知识点能帮助开发者更好地理解和解决问题,避免不必要的性能瓶颈。
5. MetaSpace区域(对应Java 8及以后版本的永久代)加载类过多,引发Full GC。 6. 代码中误用`System.gc()`直接触发Full GC。 针对上述问题,可以采取以下优化策略: 1. 使用JVM工具如`jstat`进行Full GC分析,合理...
以下是一些关键的JVM知识点: 1. **Java内存划分**: - **堆内存**:分为年轻代(新生代:Eden + 2个Survivor区)和老年代。年轻代主要存放新创建的对象,老年代则存储生命周期较长的对象。 - **程序计数器**:每...
[GC (Allocation Failure) [PSYoungGen: 1168K->480K(1536K)] 1168K->1064K(3752K), 0.0012940 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC (Ergonomics) [PSYoungGen: 480K->0K(1536K)] ...
### Sun JVM原理与内存管理 #### 一、Sun JDK 1.6 GC (Garbage Collector) Sun JDK 1.6 的垃圾收集器(GC)是其内存管理的关键组成部分,它负责自动地回收不再使用的对象所占用的内存。本文将详细介绍Sun JDK 1.6 GC...
**JVM历史发展** Java虚拟机(JVM)自1995年随着Java语言的发布而诞生,至今已有二十多年的历史。它的发展历程可以分为以下几个主要阶段: ...理解这些知识点,有助于开发者编写出更高效、更稳定的代码。
此外,JVM还使用了线程本地分配缓存区(Thread Local Allocation Buffer, TLAB),每个线程都有自己的小内存块用于分配,减少了同步开销。 内存分配完成后,JVM会将分配的空间初始化为零值,提供默认的初始值。这...
### JVM虚拟机基础概念 ...通过深入学习JVM虚拟机的相关知识,不仅能够帮助开发者更好地理解Java程序的运行机制,还能够在实际开发过程中针对具体问题采取有效的优化措施,提升程序的运行效率和用户体验。
在提供的代码示例中,我们看到当分配给`allocation1`的大对象超过Eden区的容量时,JVM执行了Minor GC。初始时,Eden区被完全使用,而老年代未使用。当我们尝试为`allocation2`分配内存时,由于Eden区已满, Minor ...
### JVM调优攻略 #### 一、概述 《JVM调优攻略》是一份详尽的文档,旨在帮助开发者理解并掌握Java虚拟机(JVM)的优化技巧。本指南不仅适用于初学者,对于有一定基础的开发人员来说也同样具有很高的参考价值。文档中...
在Sun HotSpot JVM中,为了提高对象分配的速度,引入了TLAB(Thread Local Allocation Buffer)的概念。TLAB是每个线程私有的缓冲区,用于快速分配对象。当对象过大或TLAB已满时,JVM会直接在堆上分配。 - **新生代...
但是, Allocation Tracker 也存在一些局限性,例如需要源码支持,否则 Jump To Source 按钮不可用。 Allocation Tracker 是一个非常有用的工具,帮助开发者更好地了解内存的使用情况,快速定位内存分配问题。但是...
3. 低总分配成本(Low total allocation cost):JVM 的内存分配机制能够尽量减少内存分配的成本,提高系统的性能。 4. 高质量的机器代码生成(High Quality Machine Code Generation):JVM 的 JIT 编译器能够生成...
Java虚拟机(JVM)是Java程序运行的基础,它的核心组成部分之一就是垃圾收集器(Garbage Collector, GC)。本文将全面解析JVM中的七种垃圾收集器,分析它们的特性和适用场景,帮助开发者理解如何优化Java应用的内存...