jvm之垃圾收集器(gc)与内存分配策略

     2022-04-24     360

关键词:

1.为什么要学习GC?

GC (Garbage Collection)早于java出现,60年代出现的Lisp中最早使用了GC。

当需要排查各种内存溢出、内存漏斗问题时,当垃圾回收成为系统达到更高并发量的瓶颈时,就需要用到gc了。

总之,写出高性能的Java程序需要懂GC。

2.GC在JVM的体系结构中的位置

HotSpot JVM体系结构。

技术分享

和应用性能相关的部分用紫色标出,调优从它们着手!

技术分享

3.什么是性能?

在对Java应用程序进行调优时,主要关注两点:响应速度和吞吐量。

3.1响应速度

响应速度是应用或系统返回指定数据的速度。举例:

  • 桌面UI相应速度。
  • 网站返回网页速度。
  • 数据库查询速度。

3.2吞吐量

吞吐量关注一段时间内应用完成的工作量。举例:

  • 给定时间段内事务数。
  • 一小时内批量处理的作业量。
  • 一小时内数据库查询量。

4.自动垃圾回收

简而言之,自动垃圾回收的过程就是到堆内存中查看哪个对象还被使用,哪些不被使用了,并删掉不被使用的对象。

被使用的对象在程序某处有引用指向它,不被使用的对象不再被引用。在C中,分配和回收内存是手动的。在Java中垃圾回收由GC完成。

4.1 垃圾回收的基本过程

第一步:标记

标记被引用和不再被引用的对象。

技术分享

如果系统中的所有对象都被扫描,这个过程会很费时。(伏笔)

第二步 (一种选择):正常清除。

删除所有不被引用的对象,保留被引用对象,并将指针指向空闲空间。

技术分享

内存分配器持有指向空闲空间的指针。

第二步 (另一种选择) : 清除并整理。

为了提高性能,在删除不被引对象之后,我们将所有剩下的被引对象整理在一起,这样空闲空间也能成为连续的大块。这样让新的内存分配更简洁更快速。

技术分享

4.2大部分对象都是短命的

经过大量的分析,大部分的对象是短命的。如下图所示,Y轴表示存活对象的字节数,X轴表示GC随着时间已分配的字节数。

技术分享

从图中可看成,大部分对象存活的时间很短。

4.3JVM的分代

上面的伏笔讲到,扫描SVM中所有对象,对其进行标记整理效率很低。对象数量增加的话,GC的耗时也会增加。

在4.2中我们讲到大部分对象都是短命的。所以我们可以将JVM分代回收以提高性能。将堆分成几个部分:新生代、老年代和永久代。

技术分享

4.3.1新生代

所有新对象被分配到这里。当新生代被占满,会出发minor garbage collection (简称minor GC),回收一个充满“死”对象的新生代会很快。部分幸存对象变老并在年龄达到一定阈值后放到老年代。

Stop the World Event:所有minor GC都是Stop the World Event。就是说会停掉所有的线程直到GC完成。

4.3.2老年代

老年代存放长期存活对象。新生代的对象年龄超过一定阈值时,会进入老年代。对老年代回收叫做major garbage collection (major GC)。

major GC也是Stop the World Event。一般来说major GC比minor GC要慢很多,因为会涉及到所有存活的对象。所以对于响应速度要求高的应用,应该尽可能避免major GC。

另外,major GC的时长也取决于GC使用何种回收策略进行老年代回收。

4.3.3永久代

永久代存放描述类和方法的元数据。

5. 对象已死吗?

如何判断哪些对象还“存活”?哪些已经“死去”呢?一般用引用计数算法和可达性分析算法。

5.1 引用计数算法

是什么?给对象添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。

这种算法实现简单,判定效率高。Python就是用了这种算法管理内存。但是主流Java虚拟机里面没有选用引用计数算法来管理内存。主要原因是因为它很难解决对象之间的相互循环引用的问题。

/**
 * testGC()方法执行后,objA和objB会不会被GC呢? 
 * @author zzm
 */
public class ReferenceCountingGC {
    public Object instance = null;
    private static final int _1MB = 1024 * 1024;
    /**
     * 这个成员属性的唯一意义就是占点内存,以便在能在GC日志中看清楚是否有回收过
     */
    private byte[] bigSize = new byte[2 * _1MB];
    public static void testGC() {
        ReferenceCountingGC objA = new ReferenceCountingGC();
        ReferenceCountingGC objB = new ReferenceCountingGC();
        objA.instance = objB;
        objB.instance = objA;
        objA = null;
        objB = null;
        // 假设在这行发生GC,objA和objB是否能被回收?
        System.gc();
    }
}

5.2 可达性分析算法

这个算法的基本思想就是通过一系列称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时 (用图论中的话来说,就是GC Root到这个对象不可达),则证明此对象是不可用的。如下图。

技术分享

Java语言中,可作为GC Roots的对象包括下面几种:

  • 虚拟机栈 (栈帧中的本地变量表) 中引用的对象。
  • 方法区中类静态属性引用的对象。
  • 方法区中常量引用的对象。
  • 本地方法栈中JNI (即一般说的Native方法) 引用的对象。 

6. GC过程

上面阐述了堆为何要分成不同的回收代,下面看不同回收代之间如何协作已完成GC过程。以下图片讲述对象分配和在回收代之间的流转。

第一步,所有新对象都分配在eden空间,两个surviror空间刚开始为空。如下图。

技术分享

第二步,eden空间被占满后,触发minor GC。如下图。

技术分享

第三步,被引用对象被移到S0空间,未被引用对象在清除eden时被删除。如下图。

技术分享

第四步,在下一次minor GC时,eden操作与第三步相同,即未被引用对象被删除,被引对象移到survivor空间,但是是移到S1。

另外,在S0空间中在上次minor GC存活的对象年龄增长并移到S1。当所有的存活对象移到S1后,将S0和eden清除。如下图。

技术分享

第五步,在接下来的minor GC中,重复和第四步同样的过程,只不过survivor空间互换。将存活对象移到S0,存活对象年龄增长,Eden和S1被清除。如下图。

技术分享

第六步,在每次minor GC时对象年龄都增长,当对象年龄超过某个阈值时 (假设为8) ,那么这些对象从新生代移到老年代。如下图。

技术分享

第七步,随着minor GC不断发生,不断有对象从新生代移到老年代。如下图。

技术分享

第八步,从上面看出,GC的大部分过程是在新生代进行的。直到触发major GC,老年代被清除并整理。如下图。

技术分享

7. 垃圾收集器

技术分享

7.1Serial 收集器

技术分享

技术分享

Serial GC一般不用于服务器端。但是虚拟机运行在Client模式下的默认新生代收集器,因为它简单高效 (与其他收集器的单线程相比)。

7.2ParNew 收集器

技术分享

7.3 Parallel Scavenge收集器

技术分享

7.4 Serial Old收集器

技术分享

7.5 Parallel Old收集器

技术分享

7.6 CMS 收集器

技术分享

技术分享

7.7 G1收集器

技术分享

8. 内存分配和回收策略

8.1 对象优先在Eden分配

技术分享

技术分享

技术分享

 1 private static final int _1MB = 1024 * 1024;
 2 /**
 3  * VM参数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
 4   */
 5 public static void testAllocation() {
 6      byte[] allocation1, allocation2, allocation3, allocation4;
 7      allocation1 = new byte[2 * _1MB];
 8      allocation2 = new byte[2 * _1MB];
 9      allocation3 = new byte[2 * _1MB];
10      allocation4 = new byte[4 * _1MB];  // 出现一次Minor GC
11  }

8.2 大对象直接进入老年代

技术分享

1 private static final int _1MB = 1024 * 1024;
2 /**
3  * VM参数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
4  * -XX:PretenureSizeThreshold=3145728
5  */
6 public static void testPretenureSizeThreshold() {
7     byte[] allocation;
8     allocation = new byte[4 * _1MB];  //直接分配在老年代中
9 }

8.3长期存活的对象将进入老年代

技术分享

技术分享

8.4动态对象年龄判定

技术分享

8.5空间分配担保

技术分享

9. 总结

本文介绍垃圾收集作用、过程、垃圾收集算法、垃圾收集器和各种原理。GC在很多时候是影响系统性能、并发能力的主要因素之一。总之,想要写出高性能JAVA程序,懂GC是必要的!

 

参考资料:

深入理解JVM 周志明

http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/index.html

http://www.cubrid.org/blog/dev-platform/understanding-java-garbage-collection/

 

jvm初探-内存分配gc原理与垃圾收集器

JVM初探-内存分配、GC原理与垃圾收集器标签:JVMJVM内存的分配与回收大致可分为如下4个步骤:何时分配->怎样分配->何时回收->怎样回收.除了在概念上可简单认为new时分配外,我们着重介绍后面的3个步骤:I.怎样分配-JVM内存分... 查看详情

垃圾收集器与内存分配策略之垃圾日志与常见参数

垃圾收集器与内存分配策略(五)——垃圾日志与常见参数理解GC日志每个收集器的日志格式都可以不一样,但各个每个收集器的日志都维持一定的共性。如下面二段日志:33.125:[GC[DefNew:3324K->152K(3712K),0.0025925secs]3324K->152K(11904K),... 查看详情

垃圾收集器与内存分配策略之内存分配与回收策略

垃圾收集器与内存分配策略(六)——内存分配与回收策略对象的内存分配,一般来说就是在堆上的分配(但也可能经过JIT编译后被拆散为标量类型并间接地栈上分配),对象分配的细节取决于当前使用的是哪一种垃圾收集器组合... 查看详情

jvm之内存分配与回收策略

...是百分之百固定的,其细节决定于当前使用的是哪种垃圾收集器组合,当然还有虚拟机中与内存相关的参数。垃圾收集器组合一般就是Serial+SerialOld和Parallel+SerialOld,前者是Client模式下的默认垃圾收集器组合,后者是Server模式下 查看详情

jvm系列jvm垃圾收集器与内存分配策略

...理的。因此内存的分配和回收也是jvm三大功能之一。垃圾收集器(GC)需要完成三件事情:哪些内存需要回收?什么时候进行回收?如何回收?本篇博客将解答jvm是如何处理以上三个问题的。值得注意的是,java运行时数据区中的... 查看详情

垃圾收集器与内存分配策略(深入理解jvm二)

1.概述垃圾收集(GarbageCollection,GC).当需要排查各种内存溢出、内存泄露问题时,当垃圾收集成为系统达到更高并发量的瓶颈时,我们就需要对这些“自动化”的技术实施必要的监控和调节。Java内存运行时,程序计数器、... 查看详情

jvm内存分配策略

...会伴随至少一次的MinorGC(并非绝对,比如在ParallelScavenge收集器的收集策略里就有直接进行MajorGC的策 查看详情

垃圾收集器与内存分配策略

1.    垃圾收集器与内存分配策略垃圾回收机制(GarbageCollection,GC),GC的历史要比java悠久。1960年诞生于MIT的Lisp是第一个真正使用内存动态分配和垃圾收集技术的语言。当时人们考虑GC需要解决三件事:哪些内存需要回收... 查看详情

理解jvm之垃圾收集器概述

前言很多人将垃圾收集(GarbageCollection)视为Java的伴生产物,实际1960年诞生的Lisp是第一门真正使用内存动态分配与垃圾手机技术的语言。在目前看来,内存的动态分配与内存回收已经相当成熟,但了解GC与内存分配还是非常有必要... 查看详情

《深入理解jvm——gc算法与内存分配策略》

...—GC算法与内存分配策略 PostedbyCrowonAugust10,2017说起垃圾收集(GarbageCollection,GC),想必大家都不陌生,它是JVM实现里非常重要的一环,JVM成熟的内存动态分配与回收技术使Java(当然还有其他运行在JVM上的语言,如Scala等)... 查看详情

垃圾收集器与内存分配策略(代码片段)

1.引用计数GC算法每个对象都会有对应的计数器来计算对象引用,但JVM不会采用该策略,因为不能解决对象相互引用的回收。publicclassReferenceCountingGCpublicObjectinstance=null;privatestaticfinalint_1M=1024*1024;privatebyte[]bigSize=newbyte[2*_1M];publicstat... 查看详情

垃圾收集器与内存分配策略之篇一:简要概述和垃圾收集算法

一、了解垃圾收集的意义   经过半个多世纪的发展,目前内存的动态分配与内存回收技术已经相当成熟,一切看起来已经进入了"自动化时代",那么我们为什么还要研究和去了解GC和内存分配呢。答案很简单:当需要... 查看详情

《深入理解jvm——gc算法与内存分配策略》(代码片段)

...—GC算法与内存分配策略 PostedbyCrowonAugust10,2017说起垃圾收集(GarbageCollection,GC),想必大家都不陌生,它是JVM实现里非常重要的一环,JVM成熟的内存动态分配与回收技术使Java(当然还有其他运行在JVM上的语言,如Scala等)... 查看详情

jvm--垃圾回收

...有一定了解的基础上,接下来进行JVM垃圾收集的学习垃圾收集器与内存分配策略1.概述  内存的动态分配与内存回收技术已经很成熟了,了解GC和内存分配:一方面为了当出现内存溢出,内存泄漏的时候排查问题,另一方面垃... 查看详情

jvm系列---垃圾收集器与内存分配策略

回顾上文介绍了jvm的内存区域以及介绍了内存的溢出情况。jvm区域分为5个,线程独有:虚拟机栈,本地方法栈,程序计数器。线程共享:方法区,堆两种溢出:栈溢出(StackOverflowError),OutOfMemoryError(OOM)为什么学习垃圾收集... 查看详情

jvm垃圾收集器与内存分配策略

垃圾收集器与内存分配策略对象存活判断引用计数算法给对象添加一个计数器,每有一个引用+1,当引用失效-1,若为0则不在被使用.可达性分析算法对象是否可到达GCroots或者说GCroots是否是对象的上层节点(祖父节点,父节点)GCroots虚拟... 查看详情

垃圾收集器与内存分配策略之垃圾收集器

垃圾收集器与内存分配策略(四)——垃圾收集器 收集算法是内存回收的方法论,垃圾收集器则是内存回收的具体实现。 垃圾收集器介绍在垃圾收集器的层面上对并行与并发的解释:并行(Parallel):指多条垃圾收集线程... 查看详情

jvm-内存分配与回收策略

...问题。回收部分通过之前的《GC设计思路分析》和《垃圾收集器》这两篇博文进行了总结,那么接下来主要就是谈谈自己对JVM是如何给对象分配内存这一部分的理解。JVM的内存空间是有限的,并且堆内存是共享的,那么不同线程... 查看详情