Code前端首页关于Code前端联系我们

C++程序开发:JVM GC垃圾收集算法及调优参数

terry 2年前 (2023-09-25) 阅读数 44 #后端开发
  • 在C++程序中,对于每个通过new动态创建的对象,当不再需要时,必须通过delete删除该对象,以释放其占用的内存空间目的。 ,否则会发生内存泄漏。在Java中,为了简化内存管理,JVM提供了自动垃圾收集机制,JVM统一对对象进行垃圾收集。在Java应用程序中,您只需要根据需要创建对象,无需手动删除应用程序中的对象。
  • 从上一篇文章的分析可以看出,在JVM的运行时数据区中,新创建的对象主要存储在堆中,因此JVM主要在堆上进行垃圾收集。也就是说,由于每个JVM进程的堆大小通常是固定的,当堆存储大量对象而没有足够的空闲内存来存储新创建的对象时,就会触发垃圾回收,不再需要的对象,将被销毁并释放相应的对象。内存空间。

GC算法

对象可达性

  • 当JVM对堆进行垃圾回收时,首先要判断堆中哪些对象是可重用的,哪些对象还需要被引用,即。成为确定对象是否存活的机制。
  • 对于对象的生存,一种方法是使用引用计数,即为每个对象维护一个引用计数。每次引用该对象时,它都会根据使用而递增或递减。当引用计数为0时,表示该对象不再被引用,可以被重用;
  • 第二个是根据对象的可达性分析确定的。当Hotspot虚拟机执行垃圾收集时,它会根据对象可用性分析来确定哪些对象可用。可以重复使用。
  • GC Roots:对象可访问性分析首先要确定一个分析的起点,即GC根节点对象。对于任意对象,判断是否存在从GC根可达的路径,即GC根对象本身直接引用或间接引用一个对象。如果存在,则说明该对象可用,不能在本次GC中使用。回收,否则可以回收。成为GC Root的对象首先在JVM内存模型中使用堆外的对象,即方法区和堆栈中的对象,而不是存储在堆本身的对象,因为GC Root对象不使用垃圾回收。从堆栈中弹出时,堆栈的对象引用会自动销毁。主要包括以下内容:
  1. 栈(或栈帧)中的对象引用:当前执行方法的对象类型的参数、局部变量以及中间变量对应的对象引用;
  2. 本地方法栈中引用的JNI对象,即JVM 自身方法的对象引用;
  3. 方法区的静态和常量对象引用:类的静态属性的对象引用,类的常量的对象引用。

对象引用

  • GC 根是堆栈或方法范围内的对象引用,而不是这些对象引用所引用的对象。对象引用和对象引用引用的对象是两个不同的实体。你一定不能混淆他们。对象引用所引用的对象也在堆上。
  • 要特别注意类的静态和常量对象引用所引用的对象。在Hotspot实现中,在JDK8之前,类的静态和常量属性的对象引用所引用的对象都存储在方法区,即PermGen中。 JDK8之后,这些对象都放在堆上。
  • 关于Java对象引用的更多分析,请参见:Java对象的强引用、软引用、弱引用、虚引用

生成和垃圾回收算法

  • 垃圾回收主要发生在堆没有足够内存空间的情况下。保存新创建的对象时触发。由于堆中对象的生命周期不同,所以在进行垃圾回收时并不需要回收所有对象,只回收不再可用的对象。同时,应用程序在垃圾收集期间必须暂停,这将导致应用程序在暂停期间不可用。垃圾收集持续的时间越长,应用程序不可用的时间就越长。由于垃圾收集是根据对象可用性的分析来决定回收哪些对象,因此持续时间与要扫描和分析的内存区域的大小有关。
  • 基于上面的分析,为了尽量减少应用程序停顿时间,JVM根据对象生命周期的长短和生成思路,将堆进一步划分为新生代和老年代两个区域,从而减少扫描和扫描垃圾收集的需要。要分析的内存区域。新生代和老年代的大小比例可以通过JVM参数控制:-XX:NewRatio。默认新生代与老年代的大小比例为1:2,新生代占据堆的1/3,即-XX:NewRatio=2。或者使用 -XX:NewSize 指定新生代的起始大小,并使用 -XX:MaxNewSize 指定新生代的最大大小。如果NewRatio和NewSize同时存在,NewSize和MaxNewSize会覆盖NewRatio。
  • 每次将新创建的对象放入新生代时,如果新生代没有足够的内存空间来存储新对象,则只会对新生代进行垃圾回收以获得内存空间。这个过程称为Minor GC。如果新生代垃圾回收后内存空间仍然不足,则新生代和老年代,即整个堆同时被垃圾回收。这个过程称为Full GC。 Minor GC 持续时间较短,对应用影响较小,而 Full GC 持续时间较长,对应用影响较大。
  • 老年代主要存储较旧或需要较大内存空间的对象:
  • 使用JVM参数:-XX:MaxTenuringThreshold=xx提前与老年代不同的对象的年龄。 GC收集器的实现是不同的。 Parallel Scavenge 中默认值为 15,CMS 中默认值为 6,G1 中默认值为 15。如果设置为 0,则新对象将直接放置在老年代中。每进行一次Minor GC,存活对象的年龄就加一;
  • 使用JVM参数:-XX:PretenureSizeThreshold=xx(单位为字节)来控制超过此大小的对象直接在老年代分配内存。空间,默认为0,表示在新生代中首先分配。
  • 根据生成思路对堆进行细分后,由于新生代和老年代存储的对象属性不同,新生代和老年代的垃圾回收算法也存在差异。相应的新生代是基于复制算法的。为了实现复用,基于标记-扫描或标记-压缩算法来复用老年代。

新一代垃圾收集算法

复制算法

  • 每个新创建的对象首先在新一代中分配内存进行存储。新生代对象的特点是生命周期很短,即大部分是存储的。如果一段时间后不再可用,可以重新使用。因此,在新一代进行废物收集时,大多数物品都可以回收利用。
  • 基于新生代存储的对象生命周期短、每次存活对象较少的特点,新生代主要实现基于复制算法的垃圾回收。算法实现如下:新生代进一步分为三个区域:Eden、From Survivor和To Survivor,新创建的对象存储在Eden中。 From 和 To 的大小相同。可以通过JVM参数控制Eden和Survivor的大小比例:-XX:SurvivorRatio。 Eden和Survivor默认为8:2,-XX:SurvivorRatio=8,即Eden大小为新生代的4/5,From和TIL各占1/10。
  • 算法执行过程为:进行Minor GC时,对Eden区的对象进行可访问性分析后,将幸存对象移至From Survivor,并增加幸存对象的年龄。如果对象的年龄超过 MaxTenuringThreshold ,或者对象太大而无法被 From Survivor 存储,则该对象将被移动到老年代。然后清理伊甸园区域。至此,Eden区和To Survivor区都空了,Minor GC完成。此时,对于下一次Minor GC,从Eden移动的对象的From Survivor变成了To Survivor,为空。 To Survivor 变成 From Survivor,即相当于基于复制算法的 Minor GC,每次将 Eden 中幸存的对象移动到 From Survivor。

Old代垃圾收集算法

  • Old代垃圾收集主要在进行Full GC时触发。触发full GC主要包括四种:
  1. 老年代空间不足:新生代Minor GC后空间不够,用来存放新对象,新对象放在老年代。当老年代没有足够的空间来存储对象时,老年代会触发垃圾回收,即Full GC。此时,新生代和老年代同时被垃圾回收。 ;
  2. 老年代空间不足:进行Minor GC时,对象从Eden移动到From Survivor,而From Survivor没有足够的空间来存放对象。对象被移动到老年代,老年代无法存储该对象。 ,则触发Full GC;
  3. 当Minor GC时晋升到老年代的对象平均大小大于老年代当前的可用空间时,也会触发Full GC。
  4. 程序中调用()函数时,默认也会进行Full GC,但可以通过JVM参数禁用:-XX:-+DisableExplicitGC
  • 老年代主要存储较老的对象,即生命周期较长的对象,以及需要较多内存空间的对象。由于每次执行垃圾收集时,通常都会有几个幸存的对象。如果使用复制算法,首先需要复制大量的对象,其次如果幸存的对象太大,From Survivor会需要更大的内存空间。由于老年代中存活对象多、大对象多的特点,不适合使用复制算法,否则会导致应用程序回收慢、宕机时间长。因此,老年代的垃圾回收通常采用标签压缩和标签清除算法。两种算法都不需要额外的内存空间,比如复制算法中的Survivor,从而提高了内存消耗。

标记压缩算法和标记清除算法

  • 标记压缩和标记清除算法首先必须对老年代中的对象进行可达性分析,标记可达(生存)对象,其中标记过程是从GC root 引用的对象开始,首先将那些被GC Root引用的对象标记为存活对象,然后继续从这些对象中寻找存活对象。如果一个对象有从GC root可达的路径,则该对象是存活的,否则需要回收垃圾对象;
  • 标记压缩和标记清除算法的区别在于,标记压缩算法会将所有幸存的对象移动到一侧,然后越界清除所有内存空间,从而减少老年代碎片的内存。标记和清除算法只是简单地清除和销毁这些垃圾对象,并没有压缩老年代的内存空间,因此会存在内存碎片问题。然而,由于不需要移动幸存的对象,因此标记和清除算法执行得更快。快速地。

GC垃圾收集器

  • 垃圾收集器主要是在上述GC算法的实现基础上,增加了执行垃圾收集的线程数,并考虑该方法是否应该与应用程序并发执行。

新一代垃圾收集器

  • 新一代垃圾收集器主要基于复制算法实现。在此基础上,根据执行垃圾收集的线程数量,可以分为串行垃圾收集器和并行垃圾收集器。 。

1。串行垃圾收集器:-XX:+UseSerialGC

  • 串行垃圾收集器是一种单线程垃圾收集器,使用一个线程来执行Minor GC。串行垃圾收集器是运行在客户端模式下的 JVM 进程的新一代默认垃圾收集器。执行Minor GC时,所有应用程序线程都必须暂停。
  • JVM 配置参数为:-XX:+UseSerialGC。通过使用此配置参数,新生代和老年代都使用串行垃圾收集器。老年代是基于品牌压缩算法的Serial Old。

2。并行垃圾收集器:-XX:+UseParallelGC

  • 并行垃圾收集器是基于复制算法的垃圾收集器的多线程版本,使用多个线程同时执行垃圾收集。与单线程垃圾收集器相比,多个垃圾收集线程并行执行可以加速Minor GC并减少应用程序停顿时间。特别是对于多CPU的服务器,一般采用并行垃圾收集器进行垃圾收集。
  • JVM配置参数为:-XX:+UseParallelGC。该配置参数仅对新生代有效,即新生代使用并行垃圾收集器,老年代使用Serial Old串行收集器。这也是以服务器模式运行的 JVM 进程的默认垃圾收集器配置,即新生代中的 Parallel 和老年代中的 Serial Old 。
  • 吞吐量优先:并行并行垃圾收集器除了提供更多线程执行垃圾收集外,主要目的是提供高吞吐量,即应用程序执行时间占总执行时间的比例较高,其中吞吐量的计算方式为:吞吐量=应用程序执行时间/(程序执行时间+垃圾收集器执行时间)。因此,Parallel关注的是应用程序整体的执行时间而不是应用程序的单一响应速度。它更适合对CPU利用率要求较高的应用程序,例如需要大量计算和科学计算的后台任务,以确保这些任务尽快完成,而不是用户交互非常频繁且需要快速完成的应用程序用户响应,例如网络游戏、电子商务等网络应用。
  • 吞吐量目标:为了实现可控的吞吐量,并行垃圾收集器通过 JVM 参数控制最大垃圾收集暂停时间: -XX:MaxGCPauseMillis、、 控制吞吐量大小直接地。
  • 可控吞吐量的实现:通过JVM参数:-XX:+UseAdaptiveSizePolicy实现堆大小的动态调整,达到吞吐量控制的目的。这时候就不需要配置新生代的堆了。分代的大小,如新生代的大小 - 通过JVM参数配置并行收集器的线程数:-XX:ParallelGCThreads=20,一般设置为与处理器的数量。

3。并行ParNew垃圾收集器:-XX:+UseParNewGC

  • ParNew是串行垃圾收集器的多线程版本。它也是基于复制算法,主要与老一代并行并发垃圾收集器CMS一起使用。使用。
  • 与并行垃圾收集器不同,ParNew只是串行垃圾收集器的简单多线程版本,不具备可控的吞吐量和动态调整新旧代堆大小的能力。
  • 配置方法:如果老年代配置为使用CMS垃圾收集器,则新生代默认使用ParNew,无需显式配置。如果需要显示配置,JVM参数为:-XX:+UseParNewGC。 ParNew 和 CMS 的组合优先考虑响应时间。如果不想启用年轻代并行GC,可以通过设置-XX:-UseParNewGC将其关闭。

老一代垃圾收集器

  • 老一代垃圾收集器是基于标签压缩或标记算法实现的。

1。 Serial Old 垃圾收集器:-XX:+UseSerialGC

  • Serial Old 是老年代的垃圾收集器。它使用单线程标签压缩算法来实现老年代的垃圾收集。
  • 当JVM进程运行在客户端模式时,默认使用Serial Old作为老年代垃圾收集器。
  • 配置方法为:-XX:+UseSerialGC。此时,老年代和新生代都使用串行垃圾收集器。
  • Serial Old 垃圾收集器还充当 CMS 的备份垃圾收集器。即使用CMS对老年代进行垃圾回收时,如果出现并发状态错误,则会降级为使用Serial Old对老年代进行垃圾回收。 。

2。 Parallel 并行老年代垃圾收集器:-XX:+UseParallelOldGC

  • Parallel Old 是一个基于品牌压缩算法的多线程老年代垃圾收集器。通常与新一代并行并行垃圾收集器配合使用,以达到高容量可控的目的。
  • 新生代开启时:-XX:+UseParallelGC,老年代仍然使用Serial Old,所以配置必须显示:else-XX:+G指定老年代。使用并行并行旧垃圾收集器。

3。并行并发 CMS 垃圾收集器:-xx:+useconcmarksweepgc

  • 详细分析请参阅:并行并发 CMS 垃圾收集器:-xx:+Useconcmarksweepgc

g1 垃圾收集器:-xx:+useconcmarksweepgc

  • G1 垃圾收集器是一种更智能的垃圾收集器。在实现层面,消除了新生代和老年代之间的物理区别,而是将堆划分为相同大小的区域。但从逻辑上讲,它仍然属于代际回收。新生代和老年代是由几个这样的区域组成的。
  • G1 的设计原则是可以通过简单明了的方式进行性能调整。典型的配置只需要如下配置:指定堆的最大大小和GC的最大暂停时间,G1垃圾收集器就会找到满足要求的方法。这个目标。如果我们需要在内存大小安全时进行调整,我们只需要更改最大暂停时间即可。
-XX:+UseG1GC -Xmx32g -XX:MaxGCPauseMillis=200
  • G1 是 JDK9 的默认垃圾收集器。关于原项目是否应该升级到G1,根据官方建议,如果当前项目使用CMS或者ParalleOldGC运行良好,不会因为GC问题造成长时间停顿,建议维持现状,就这样没有必要升级到G1。如果存在以下情况,可以尝试升级到G1,根据GC日志分析效果:
  1. 实时数据占用堆空间一半以上;
  2. 对象分配速度或对象前进速度发生显着变化;
  3. 期望消除长时间停顿或GC(0.5s ~ 1s)

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门