目录

  • 1. JAVA堆内存是如何划分的
    • 1.1 为什么移除永久代?
    • 1.2 分代概念
    • 1.3 为什么分代?
    • 1.4 为什么survivor分为两块相等大小的幸存空间?
    • 1.5 JVM堆内存常用参数
  • 2. 怎么确定哪些对象可以GC?
    • 2.1 引用计数法
      • 2.1.1 JAVA中四种引用类型
        • 2.1.1.1 强引用
        • 2.1.1.2 软引用
        • 2.1.1.3 弱引用
        • 2.1.1.4 虚引用
    • 2.2 可达性分析
      • 2.2.1 根搜索算法
  • 3. JVM GC算法有哪些(GC,Garbage Collection)?
    • 2.1 标记-清除算法
    • 2.2 复制算法
    • 2.3 标记-整理算法
    • 2.4 分代收集算法
  • 3. 垃圾收集器汇总
    • 3.1 Serial垃圾收集器(新生代-单线程-复制算法)
    • 3.2 ParNew垃圾收集器(新生代-多线程-复制算法)
    • 3.3 Parallel Scavenge(新生代-多线程<自适应策略注重吞吐量>-复制算法)
    • 3.4 Serial Old收集器(老年代-单线程-标记整理算法)
      • 3.4.1 新生代Serial与老年代Serial Old搭配垃圾收集过程
      • 3.4.2 新生代Parallel Scavenge/ParNew与老年代Serial Old搭配垃圾收集过程
    • 3.5 Parallel Old收集器(老年代-多线程-标记整理算法)
      • 3.5.1 新生代Parallel Scavenge和老年代Parallel Old收集器搭配运行过程
    • 3.6 Concurrent mark sweep(CMS)收集器(老年代-多线程-标记清除算法)
      • 3.6.1 CMS收集器工作过程
      • 3.6.2 CMS收集器有以下三个不足
    • 3.7 G1收集器(新老年代-多线程-标记整理算法)
  • 4. Java虚拟机常用的垃圾收集器相关参数
  • 5. JDK版本默认垃圾收集器

1. JAVA堆内存是如何划分的

  • JVM内存划分为堆内存和非堆内存,堆内存分为年轻代(Young Generation)、老年代(Old Generation),非堆内存就一个永久代(Permanent Generation)。
  • 年轻代又分为Eden和Survivor区。Survivor区由FromSpace和ToSpace组成。Eden区占大容量,Survivor两个区占小容量,默认比例是8:1:1。
  • 堆内存用途:存放的是对象,垃圾收集器就是收集这些对象,然后根据GC算法回收。
  • 非堆内存用途:永久代,也称为方法区,存储程序运行时长期存活的对象,比如类的元数据、方法、常量、属性等。

在JDK1.8版本废弃了永久代,替代的是元空间(MetaSpace),元空间与永久代上类似,都是方法区的实现,他们最大区别是:元空间并不在JVM中,而是使用本地内存。

1.1 为什么移除永久代?

移除永久代原因:为融合HotSpot JVM与JRockit VM(新JVM技术)而做出的改变,因为JRockit没有永久代。
有了元空间就不再会出现永久代OOM问题了!

1.2 分代概念

新生成的对象首先放到年轻代Eden区,当Eden空间满了,触发Minor GC,存活下来的对象移动到Survivor0区,Survivor0区满后触发执行Minor GC,Survivor0区存活对象移动到Suvivor1区,这样保证了一段时间内总有一个survivor区为空。经过多次Minor GC仍然存活的对象移动到老年代。
老年代存储长期存活的对象,占满时会触发Major GC=Full GC,GC期间会停止所有线程等待GC完成,所以对响应要求高的应用尽量减少发生Major GC,避免响应超时。

  • Minor GC : 清理年轻代
  • Major GC : 清理老年代
  • Full GC : 清理整个堆空间,包括年轻代和永久代
    所有GC都会停止应用所有线程。

1.3 为什么分代?

将对象根据存活概率进行分类,对存活时间长的对象,放到固定区,从而减少扫描垃圾时间及GC频率。针对分类进行不同的垃圾回收算法,对算法扬长避短。

1.4 为什么survivor分为两块相等大小的幸存空间?

主要为了解决碎片化。如果内存碎片化严重,也就是两个对象占用不连续的内存,已有的连续内存不够新对象存放,就会触发GC。

1.5 JVM堆内存常用参数

2. 怎么确定哪些对象可以GC?

2.1 引用计数法

在 Java 中,引用和对象是有关联的。如果要操作对象则必须用引用进行。因此,很显然一个简单的办法是通过引用计数来判断一个对象是否可以回收。简单说,即一个对象如果没有任何与之关联的引用,即他们的引用计数都为 0,则说明对象不太可能再被用到,那么这个对象就是可回收对象。

2.1.1 JAVA中四种引用类型

2.1.1.1 强引用

在 Java 中最常见的就是强引用,把一个对象赋给一个引用变量,这个引用变量就是一个强引用。当一个对象被强引用变量引用时,它处于可达状态,它是不可能被垃圾回收机制回收的,即使该对象以后永远都不会被用到 JVM 也不会回收。因此强引用是造成 Java 内存泄漏的主要原因之一。

2.1.1.2 软引用

软引用需要用 SoftReference 类来实现,对于只有软引用的对象来说,当系统内存足够时它不会被回收,当系统内存空间不足时它会被回收。软引用通常用在对内存敏感的程序中。

2.1.1.3 弱引用

弱引用需要用 WeakReference 类来实现,它比软引用的生存期更短,对于只有弱引用的对象来说,只要垃圾回收机制一运行,不管 JVM 的内存空间是否足够,总会回收该对象占用的内存。

2.1.1.4 虚引用

虚引用需要 PhantomReference 类来实现,它不能单独使用,必须和引用队列联合使用。虚引用的主要作用是跟踪对象被垃圾回收的状态。

2.2 可达性分析

为了解决引用计数法的循环引用问题,Java 使用了可达性分析的方法。通过一系列的“GC roots”对象作为起点搜索。如果在“GC roots”和一个对象之间没有可达路径,则称该对象是不可达的。

要注意的是,不可达对象不等价于可回收对象,不可达对象变为可回收对象至少要经过两次标记过程。两次标记后仍然是可回收对象,则将面临回收。

2.2.1 根搜索算法

根搜索算法是从离散数学中的图论引入的,程序把所有的引用关系看作一张图,从一个节点GC ROOT开始,寻找对应的引用节点,找到这个节点以后,继续寻找这个节点的引用节点,当所有的引用节点寻找完毕之后,剩余的节点则被认为是没有被引用到的节点,即无用的节点。

目前java中可作为GC Root的对象有
1、 虚拟机栈中引用的对象(本地变量表)
2、 方法区中静态属性引用的对象
3、 方法区中常量引用的对象
4、 本地方法栈中引用的对象(Native对象)

3. JVM GC算法有哪些(GC,Garbage Collection)?

JVM垃圾收集算法有四种:标记-清除算法、复制算法、标记-整理算法、分代收集算法

红色是标记的非活动对象,绿色是活动对象。

2.1 标记-清除算法

该算法如同它的名字一样,分为两个阶段:标记、清除。首先标记出所有需要回收的对象,然后,统一清除这些被标记的对象。
该算法的缺点是:

  • 1、效率不高;
  • 2、产生大量不连续的内存碎片,导致有大量内存剩余的情况下,由于,没有连续的空间来存放较大的对象,从而触发了另一次垃圾收集动作。

2.2 复制算法

由于标记-清除算法的效率不高,从而提出了复制算法。复制算法将可用的内存分成两样大小的两块,每次只使用其中一块内存。当这块内存用完之后,就把还存活的对象复制到另外一块上面,然后,把这块清空。复制算法克服了标记-清除算法的两个缺点,但是太浪费内存,相当于内存空间减小了一半。
随着时间的积累,现在使用的复制算法的虚拟机,不再是把内存分为1:1的两块。因为98%的对象是寿命很短的,创建之后,很快就被回收了,存活下来的只有2%,所以,用来存储存活对象的内存区,可以小一些。现在的商业虚拟机是把可用内存分为一个较大的Eden空间和两个较小的Survivor空间,每次使用Eden和其中的一块Survivor。当回收时,把Eden和Survivor中的存活对象一次复制到另一块Survivor内存区上,然后把Eden和刚才用过的Survivor空间清空。HotSpot虚拟机默认Eden和Survivor的大小比例是8:1,这样,每次新产生的对象可以使用90%的内存空间。 缺点需要两倍的内存空间。

2.3 标记-整理算法

从名字可以看出,该算法是对“标记-清除”算法的改进升级版。同样的该算法分为两个阶段:标记、整理。标记阶段同“标记-清除”算法。整理阶段,不是直接对标记对象进行清理,而是让所有存活的对象都移动到一端,然后,直接把边界以外的内存清空。这就解决了“标记-清除”算法会造成大量不连续内存碎片的问题。

2.4 分代收集算法

分代收集算法是根据对象的存活周期的不同,将内存划分为几块。当前的商业虚拟机的垃圾收集都采用了该算法。一般把Java堆分成新生代(年轻代)和老年代(年老代)。这样就可以根据各年代中对象的存活周期来选择最合适的收集算法了。新生代,由于只有少量的对象能存活下来,所以选用“复制算法”,只需要付出少量存活对象的复制成本。老年代,由于对象的存活率高,没有额外的空间分担,就必须使用“标记-清除”或“标记-整理”算法。

3. 垃圾收集器汇总

Java堆内存被划分为新生代和老年代两部分,新生代主要使用复制和标记-清除垃圾回收算法,老年代主要使用标记-整理垃圾回收算法,因此java虚拟中针对新生代和老年代分别提供了多种不同的垃圾收集器,JDK1.6中Sun HotSpot虚拟机的垃圾收集器如下:

图中如果两个垃圾收集器直接有连线,则表明这两个垃圾收集器可以搭配使用。

3.1 Serial垃圾收集器(新生代-单线程-复制算法)

Serial是最基本、历史最悠久的垃圾收集器,使用复制算法,曾经是JDK1.3.1之前新生代唯一的垃圾收集器。

Serial是一个单线程的收集器,它不仅仅只会使用一个CPU或一条线程去完成垃圾收集工作,并且在进行垃圾收集的同时,必须暂停其他所有的工作线程,直到垃圾收集结束。

Serial垃圾收集器虽然在收集垃圾过程中需要暂停所有其他的工作线程,但是它简单高效,对于限定单个CPU环境来说,没有线程交互的开销,可以获得最高的单线程垃圾收集效率,因此Serial垃圾收集器依然是java虚拟机运行在Client模式下默认的新生代垃圾收集器。

3.2 ParNew垃圾收集器(新生代-多线程-复制算法)

ParNew垃圾收集器其实是Serial收集器的多线程版本,也使用复制算法,除了使用多线程进行垃圾收集之外,其余的行为和Serial收集器完全一样,ParNew垃圾收集器在垃圾收集过程中同样也要暂停所有其他的工作线程。

ParNew收集器默认开启和CPU数目相同的线程数,可以通过-XX:ParallelGCThreads参数来限制垃圾收集器的线程数。

ParNew虽然是除了多线程外和Serial收集器几乎完全一样,但是ParNew垃圾收集器是很多java虚拟机运行在Server模式下新生代的默认垃圾收集器。

3.3 Parallel Scavenge(新生代-多线程<自适应策略注重吞吐量>-复制算法)

Parallel Scavenge收集器也是一个新生代垃圾收集器,同样使用复制算法,也是一个多线程的垃圾收集器,它重点关注的是程序达到一个可控制的吞吐量(Thoughput,CPU用于运行用户代码的时间/CPU总消耗时间,即吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)),高吞吐量可以最高效率地利用CPU时间,尽快地完成程序的运算任务,主要适用于在后台运算而不需要太多交互的任务。

Parallel Scavenge收集器提供了两个参数用于精准控制吞吐量:

  • -XX:MaxGCPauseMillis:控制最大垃圾收集停顿时间,是一个大于0的毫秒数。
  • -XX:GCTimeRation:直接设置吞吐量大小,是一个大于0小于100的整数,也就是程序运行时间占总时间的比率,默认值是99,即垃圾收集运行最大1%(1/(1+99))的垃圾收集时间。

Parallel Scavenge是吞吐量优先的垃圾收集器,它还提供一个参数:-XX:+UseAdaptiveSizePolicy,这是个开关参数,打开之后就不需要手动指定新生代大小(-Xmn)、Eden与Survivor区的比例(-XX:SurvivorRation)、新生代晋升老年代对象年龄(-XX:PretenureSizeThreshold)等细节参数,虚拟机会根据当前系统运行情况收集性能监控信息,动态调整这些参数以达到最大吞吐量,这种方式称为GC自适应调节策略,自适应调节策略也是ParallelScavenge收集器与ParNew收集器的一个重要区别。

3.4 Serial Old收集器(老年代-单线程-标记整理算法)

Serial Old是Serial垃圾收集器老年代版本,它同样是个单线程的收集器,使用标记-整理算法,这个收集器也主要是运行在Client默认的java虚拟机默认的老年代垃圾收集器。

在Server模式下,主要有两个用途:

  • 在JDK1.5之前版本中与新生代的Parallel Scavenge收集器搭配使用。

  • 作为老年代中使用CMS收集器的后备垃圾收集方案。

3.4.1 新生代Serial与老年代Serial Old搭配垃圾收集过程


新生代Parallel Scavenge收集器与ParNew收集器工作原理类似,都是多线程的收集器,都使用的是复制算法,在垃圾收集过程中都需要暂停所有的工作线程。

3.4.2 新生代Parallel Scavenge/ParNew与老年代Serial Old搭配垃圾收集过程

3.5 Parallel Old收集器(老年代-多线程-标记整理算法)

Parallel Old收集器是Parallel Scavenge的老年代版本,使用多线程的标记-整理算法,在JDK1.6才开始提供。

在JDK1.6之前,新生代使用ParallelScavenge收集器只能搭配老年代的Serial Old收集器,只能保证新生代的吞吐量优先,无法保证整体的吞吐量,Parallel Old正是为了在老年代同样提供吞吐量优先的垃圾收集器,如果系统对吞吐量要求比较高,可以优先考虑新生代Parallel Scavenge和老年代Parallel Old收集器的搭配策略。

3.5.1 新生代Parallel Scavenge和老年代Parallel Old收集器搭配运行过程

3.6 Concurrent mark sweep(CMS)收集器(老年代-多线程-标记清除算法)

Concurrent mark sweep(CMS)收集器是一种老年代垃圾收集器,其最主要目标是获取最短垃圾回收停顿时间,和其他老年代使用标记-整理算法不同,它使用多线程的标记-清除算法。

最短的垃圾收集停顿时间可以为交互比较高的程序提高用户体验,CMS收集器是Sun HotSpot虚拟机中第一款真正意义上并发垃圾收集器,它第一次实现了让垃圾收集线程和用户线程同时工作

CMS工作机制相比其他的垃圾收集器来说更复杂,整个过程分为以下4个阶段:

  • 初始标记:只是标记一下GC Roots能直接关联的对象,速度很快,仍然需要暂停所有的工作线程。
  • 并发标记:进行GC Roots跟踪的过程,和用户线程一起工作,不需要暂停工作线程。
  • 重新标记:为了修正在并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,仍然需要暂停所有的工作线程。
  • 并发清除:清除GC Roots不可达对象,和用户线程一起工作,不需要暂停工作线程。

由于耗时最长的并发标记和并发清除过程中,垃圾收集线程可以和用户现在一起并发工作,所以总体上来看CMS收集器的内存回收和用户线程是一起并发地执行。

3.6.1 CMS收集器工作过程

3.6.2 CMS收集器有以下三个不足

  • CMS收集器对CPU资源非常敏感,其默认启动的收集线程数=(CPU数量+3)/4,在用户程序本来CPU负荷已经比较高的情况下,如果还要分出CPU资源用来运行垃圾收集器线程,会使得CPU负载加重。

  • CMS无法处理浮动垃圾(Floating Garbage),可能会导致Concurrent ModeFailure失败而导致另一次Full GC。由于CMS收集器和用户线程并发运行,因此在收集过程中不断有新的垃圾产生,这些垃圾出现在标记过程之后,CMS无法在本次收集中处理掉它们,只好等待下一次GC时再将其清理掉,这些垃圾就称为浮动垃圾。
    CMS垃圾收集器不能像其他垃圾收集器那样等待老年代机会完全被填满之后再进行收集,需要预留一部分空间供并发收集时的使用,可以通过参数-XX:CMSInitiatingOccupancyFraction来设置老年代空间达到多少的百分比时触发CMS进行垃圾收集,默认是68%。
    如果在CMS运行期间,预留的内存无法满足程序需要,就会出现一次ConcurrentMode Failure失败,此时虚拟机将启动预备方案,使用Serial Old收集器重新进行老年代垃圾回收。

  • CMS收集器是基于标记-清除算法,因此不可避免会产生大量不连续的内存碎片,如果无法找到一块足够大的连续内存存放对象时,将会触发因此Full GC。CMS提供一个开关参数-XX:+UseCMSCompactAtFullCollection,用于指定在Full GC之后进行内存整理,内存整理会使得垃圾收集停顿时间变长,CMS提供了另外一个参数-XX:CMSFullGCsBeforeCompaction,用于设置在执行多少次不压缩的Full GC之后,跟着再来一次内存整理。

3.7 G1收集器(新老年代-多线程-标记整理算法)

Garbage first垃圾收集器是目前垃圾收集器理论发展的最前沿成果,相比与CMS收集器,G1收集器两个最突出的改进是:

  • 基于标记-整理算法,不产生内存碎片。

  • 可以非常精确控制停顿时间,在不牺牲吞吐量前提下,实现低停顿垃圾回收。

G1收集器将堆内存划分多个大小相等的独立区域(Region),并且能预测暂停时间,能预测原因它能避免对整个堆进行全区收集。G1跟踪各个Region里的垃圾堆积价值大小(所获得空间大小以及回收所需时间),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region,从而保证了再有限时间内获得更高的收集效率。

区域划分和优先级区域回收机制,确保G1收集器可以在有限时间获得最高的垃圾收集效率。

G1收集器工作工程分为4个步骤,包括:

  • 初始标记(Initial Mark)
  • 并发标记(Concurrent Mark)
  • 最终标记(Final Mark)
  • 筛选回收(Live Data Counting and Evacuation)

初始标记与CMS一样,标记一下GC Roots能直接关联到的对象。并发标记从GC Root开始标记存活对象,这个阶段耗时比较长,但也可以与应用线程并发执行。而最终标记也是为了修正在并发标记期间因用户程序继续运作而导致标记产生变化的那一部分标记记录。最后在筛选回收阶段对各个Region回收价值和成本进行排序,根据用户所期望的GC暂停时间来执行回收。

4. Java虚拟机常用的垃圾收集器相关参数


5. JDK版本默认垃圾收集器

jdk1.7 默认垃圾收集器Parallel Scavenge(新生代)+Serial Old(老年代)

jdk1.8 默认垃圾收集器Parallel Scavenge(新生代)+Serial Old(老年代)

jdk1.9 默认垃圾收集器G1

jdk10 默认垃圾收集器G1

-XX:+PrintCommandLineFlagsjvm参数可查看默认设置收集器类型

-XX:+PrintGCDetails亦可通过打印的GC日志的新生代、老年代名称判断

java虚拟机的 -XX:+PrintGCDetails 参数可以打印垃圾收集器的日志信息。

-verbose:gc 可以查看Java虚拟机垃圾收集结果。

Over,Enjoy!

JVM堆内存介绍、垃圾收集算法、垃圾回收器汇总相关推荐

  1. JVM堆内存与垃圾收集器

    3.1 谈谈Java中不同的引用类型? Java里有不同的引用类型,分别是强引用.软引用.弱引用和虚引用; 强引用:Object object = new Object();即使内存溢出了也不会回收这 ...

  2. JVM堆内存控制/分代垃圾回收

    JVM的堆的内存, 是通过下面面两个参数控制的 -Xms 最小堆的大小, 也就是当你的虚拟机启动后, 就会分配这么大的堆内存给你  -Xmx 是最大堆的大小 当最小堆占满后,会尝试进行GC,如果GC之 ...

  3. java8堆内存模型_「GC系列」JVM堆内存分代模型及常见的垃圾回收器

    1. 内存分代模型 为什么要说JVM的内存分代模型呢,因为内存分代和垃圾回收器的运行是有关系的. 现在大部分用到的垃圾回收器在逻辑上是分代的,除了G1之外的其他垃圾回收器在逻辑上和物理上都是分代的. ...

  4. JVM 堆内存设置原理

    堆内存设置 原理 JVM堆内存分为2块:Permanent Space 和 Heap Space. Permanent 即 持久代(Permanent Generation),主要存放的是Java类定 ...

  5. JVM学习(四):垃圾回收器

    JVM学习(四):垃圾回收器 前面我们学习了JVM的垃圾回收算法,如果说,垃圾回收算法是内存回收的理论,那么今天我们要讲的垃圾回收器,就是内存回收的具体实现了.在Java虚拟机规范中对垃圾回收器应该如 ...

  6. Elasticsearch JVM 堆内存使用率飙升,怎么办?

    1.引言 本系列文章介绍如何修复 Elasticsearch 集群的常见错误和问题. 这是系列文章的第四篇,主要探讨:Elasticsearch JVM 堆内存使用率飙升,怎么办? 第一篇:Elast ...

  7. 压测工具Ab(ApacheBench)使用入门,JVM堆内存调优及GC优化

    压测工具AB Ab(ApacheBench) 测试工具是 Apache 提供的一款测试工具,具有简单易上手的特点,在测试 Web 服务时非常实用. ab 一般都是在Linux上用. 安装非常简单,只需 ...

  8. JVM堆内存(heap)详解

    很好的一篇文章,转载了http://blog.51cto.com/lizhenliang/2164876?wx= JAVA堆内存管理是影响性能主要因素之一. 堆内存溢出是JAVA项目非常常见的故障,在 ...

  9. JVM堆内存与非堆内存(heap)官方详解

    JVM堆内存与非堆内存(heap)官方详解 JAVA堆内存管理是影响性能主要因素之一. 堆内存溢出是JAVA项目非常常见的故障,在解决该问题之前,必须先了解下JAVA堆内存是怎么工作的. 先看下JAV ...

最新文章

  1. Spring Boot Spring MVC 异常处理的N种方法
  2. win10中cmd如何编译和运行c/c++程序?
  3. 【Python 爬虫】 1、爬虫基础概念
  4. zookeeper基本原理及适用场景 转:http://blog.chinaunix.net/uid-26748613-id-4536290.html
  5. aix系统服务器限制ftp访问,AIX 限制ftp用户只能访问其主目录
  6. 依赖注入参考文章(1)
  7. Linux从入门到精通——数据库
  8. Notion,一款优秀的程序员记笔记软件,值得推荐!
  9. 国内百兆独立服务器哪里的比较便宜镇江电信好吗
  10. html手机端在线制作,HTML5制作,手机H5页面制作,H5炫酷效果,前端制作 | 纬博赛特...
  11. IBinder对象在进程间传递的形式(一)
  12. SQL 注释语句 (--与/*...*/)
  13. 12-integer-to-roman
  14. VUE如何导入开源项目及报错处理gyp ERR! stack Error: Command failed: D:\Python\python.EXE -c import sys; print “%s.
  15. 以字典为基础建立一个通讯录,向字典中添加和删除通讯人(名字、电话、email、工作单位等),查询某个人的信息,然后输出通讯录中所有人的信息。
  16. 《Photoshop Lightroom4 经典教程》—第1课1.4节Lightroom工作流程
  17. 个人学习笔记(备份)
  18. Windows Server 2022 英文版、简体中文版下载 (2021 年 11 月更新)
  19. 基于Linux系统实现西门子PLC的数据采集
  20. 【达内课程】Android中的Notification

热门文章

  1. PMOS和NMOS在开关应用中高侧和低侧驱动的对比
  2. 化解Android 和iOS“恩怨”的Flutter ,凭的是什么?
  3. oracle sql查询字段中以“;”分割的字符串,转化成对应的,以“;”分割的文字串
  4. [BZOJ 2844]albus就是要第一个出场
  5. firewall-cmd --permanent --get-zone-of-interface=ens33显示no zone
  6. React性能优化SCU | PureComponent | memo
  7. NetInside网络攻击分析帮您轻松发现可疑主机
  8. 查询无限级/三级分销的简单易用SQL
  9. html+css实战144-banner-精品推荐-内容
  10. 三维地图前端arcgis_【ArcGIS JS API + eCharts系列】实现二、三维网络路径图的绘制...