JVM 垃圾收集算法和垃圾收集器(上)

简介: 本文主要讲述垃圾收集算法和常用的几种垃圾收集器

垃圾收集算法


标记-清除算法(Mark-Sweep)


  • 算法分为 "标记" 和 "清除" 两阶段,首先标记出所有需要回收的对象,然后回收所有需要回收的对象


  • 缺点


  • 效率问题, 标记和清理两个过程效率都不高
  • 空间问题, 标记清理之后会产生大量不连续的内存碎片,空间碎片太多可能会导致后续使用中无法找到足够的连续内存而提前触发一次的垃圾收集动作。


  • 效率不高,需要扫描所有的对象,堆越大,GC 越慢


  • 存在内存碎片问题。GC 次数越多,碎片越严重


标记-整理算法 (Mark-Compact)


  • 标记过程仍然一样,但后续步骤不是进行直接清理,而是令所有存活的对象一端移动,然后直接清理掉这端边界意外的内存。


  • 没有内存碎片


  • 对 Mark-Sweep 耗费更多的时间进行 compact


标记-复制算法 (Copying)


  • 将可用内存划分为2块,每次只使用其中的一块,当半区内存用完了,仅将还存活的独享复制到另外一块上,然后就把原来整块内存空间一次性清理掉


  • 这样使得每次内存对是对真个半区的回收,内存分配时也就不用考虑内存碎片等复杂情况,只要一动堆顶指针,按循序分配就可以了,实现简单,运行效率高。 只是这种算法的代价是将内存缩小为原来的一半。代价高昂。


  • 现在的商业虚拟机中都是采用这种算法来回收新生代


  • 将内存分一块较大的eden空间和2块较少的survivor空间,每次使用eden和其中一块survivor(存活者) 空间,当回收时将eden和survivor还存活的对象一次性拷贝 到另外一个块survivor 空间上,然后清理掉eden和用过的survivor


  • Oracle Hotspot 虚拟机默认eden 和 survivor 的大小比例是8:1 也就是每次只有10%的内存是"浪费"的。


  • 复制收集算法在独享存活率高的时候,效率有所下降


  • 如果不想浪费50%的空间,就需要有额外的空间进行分配担保用于应付半区内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选用这种算法。


  • 只需要扫描存活对象,效率更高。


  • 不会产生碎片


  • 需要浪费额外的内存作为复制区


  • 复制算法非常适合生命周期比较短的对象,因为每次GC总能回收大部分的对象,复制的开销比较小。


  • 根据IBM的专门研究, 98%的java 对象只会存活1个GC周期,对这些对象很适合复制算法。而且不用1:1 划分工作区和复制区的空间。


分代算法 (Generational)


  • 当前商业虚拟机的垃圾收集都是采用"分代收集" (Generational Collecting)算法,根据独享不同的存活周期将内存划分为几块


  • 一般是把Java堆分作新生代和老年代, 这样就可以根据各个年代的特点采用最适当的收集算法,譬如新生代每次GC都有大批量对象死去,只有烧流量存活, 那就选用复制算法只需要付出少量存活对象的复制成本就可以完成收集。


  • 综合前面几种GC算法的优缺点,针对不同生命周期的对象采用不同的GC算法


  • 新生代采用 Copying
  • 老年代采用 Mark-Sweep or Mark-Compact


  • Hotspot JVM 6中共分为三个代: 年轻代(Young Generation)、年老代(Old Generation)和永久代(Permanent Generation).


  • 年轻代(Young Generation)


  • 新生成的对象都放在新生代。年轻代用复制算法记性GC(理论上,年轻代对象生命周期非常短,所以适合复制算法)
  • 年轻代分三个区。一个Eden 区, 两个Survivor区(可以通过参数设置Survivor个数)。对象在Eden区中生成。 当Eden区满时, 还存活的对象 将被复制到另外一个Survivor区, 当第一个Survivor区也满的时候,从第一个Survivor区复制过来的并且此时还存活的独享,将被复制到老年代。2个Survivor 的完全对称,轮流替换。
  • Eden 和2个Survivor的缺省比例是8:1:1, 也就是10%的空间将被浪费,可以根据GC log的信息调整大小的比例。


  • 老年代(Old Generation)


  • 存放了经过一次或多次GC还存活的对象
  • 一般采用 Mark-Sweep或 Mark-Compact 算法进行GC
  • 有很多垃圾收集器可以选择。每种垃圾收集器可以看做是一个GC算法的具体实现。可以更具具体应用的需求选用合适的垃圾收集器(追求吞吐量?追求最短的响应时间)。


  • 永久代


  • 并不属于堆 (Heap) 但是GC也会涉及到这个区域
  • 存放了每个Class的结构信息, 包括常量池、字段描述、方法描述。与垃圾收集要收集的Java对象关系不大。


  • 内存结构

image.png


垃圾收集器


如果说手机算法是内存回收的方法理论,那么垃圾收集器就是内存回收的具体实现。


虽然我们对各收集器进行比较,但并非为了挑选出一个最好的收集器,因为直到现在为止还没有最好的垃圾收集器出现, 更加没有万能的垃圾收集器,我们能做的就是根据具体应用场景选择适合自己的收集器,试想一下:如果有一个完美无暇的垃圾收集器适用于所有场景,那么我们 Java 虚拟机就不会去实现那么多的垃圾收集器了。


查询当前使用的 JVM 信息查询命令 java -XX:+PrintCommandLineFlags -version


➜  ~ java -XX:+PrintCommandLineFlags -version
-XX:InitialHeapSize=134217728 -XX:MaxHeapSize=2147483648 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC 
java version "1.8.0_281"
Java(TM) SE Runtime Environment (build 1.8.0_281-b09)
Java HotSpot(TM) 64-Bit Server VM (build 25.281-b09, mixed mode)


Serial 收集器


  • 单线程收集器,收集时会暂停所有工作线程(Stop The World, 简称 STW),使用复制收集算法,虚拟机运行在 Client 模式的默认新生代收集器


  • 最早的收集器,单线程进行GC
  • New 和 Old Generation 都可以使用
  • 在新生代, 采用复制算法;  在老年代,采用Mark-Compact算法
  • 因为使用单线程GC,没有多线程切换的额外开销,简单实用。
  • Hotspot Client 模式缺省的收集器
  • Safepoint 安全点


  • JVM 参数:-XX:+UseSerialGC -XX:+UseSerialOldGC


image.png

PerNew 收集器


  • ParNew 收集器就是 Serial 的多线程版本,除了使用多个收集线程外,其余行为包括算法、STW、对象分配规则、回收策略等都与Serial 收集器一模一样


  • 对应的这种收集器是虚拟机运行在 Server 模式的默认新生代收集器,在单 CPU 的环境下,ParNew 收集器的效果并不会比Serial收集器有更好的效果


  • Serial 收集器的在新生代的多线程版本
  • 使用复制算法(因为针对新生代)
  • 只有在多CPU的环境下,效率才会比Serial收集器高
  • 可以通过-XX:ParallelGCThreads来控制GC线程数的多少。需要结合具CPU的个数
  • Server模式下新生代的缺省收集器。


  • JVM 参数:-XX:UseParNewGC


image.png


Parallel Scavenge 收集器(1.8 默认)


  • Parallel Scavenge 收集器也是一个多线程收集器,也是使用复制算法,但它的对象分配规则与收集策略都与ParNew收集器有所不同,它是以吞吐量最大化(即GC时间占总运行时间最小)为目标的收集器实现,它允许较长的STW换取总吞吐量最大化。


  • JDK 1.8 默认垃圾收集器


  • JVM 参数:`-XX:UseParallelGC(年轻代) -XX:UseParallelOldGC(老年代)


Serial Old 收集器


  • Serial Old收集器是单线程收集器,使用标记-整理算法,是老年代的收集器


image.png


Parallel Old 收集器


  • 老年代版本吞吐量优先的收集器,使用多线程和标记-整理算法,JVM 1.6提供,在此之前,新生代使用了PS收集器的话,老年代除Serial Old外别无选择, 因为PS无法与CMS收集器配合工作。


  • Parallel Scavenge 在老年代的实现
  • 在JVM 1.6 才出现Parallel Old
  • 采用多线程,Mark-Compact 算法
  • 更注重吞吐量
  • Parallel Scavenge + Parallel Old = 高吞吐量,但是GC停顿可能不理想


相关文章
|
11月前
|
监控 算法 Java
JVM—垃圾收集算法和HotSpot算法实现细节
JVM的垃圾收集算法和HotSpot的实现细节复杂但至关重要,通过理解和掌握这些算法,可以为Java应用程序选择合适的垃圾收集器,并进行有效的性能调优。选择适当的垃圾收集策略,结合合理的内存配置和日志分析,能够显著提升应用的运行效率和稳定性。
257 15
|
缓存 算法 Java
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
这篇文章详细介绍了Java虚拟机(JVM)中的垃圾回收机制,包括垃圾的定义、垃圾回收算法、堆内存的逻辑分区、对象的内存分配和回收过程,以及不同垃圾回收器的工作原理和参数设置。
990 4
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
|
算法 Java
JVM进阶调优系列(4)年轻代和老年代采用什么GC算法回收?
本文详细介绍了JVM中的GC算法,包括年轻代的复制算法和老年代的标记-整理算法。复制算法适用于年轻代,因其高效且能避免内存碎片;标记-整理算法则用于老年代,虽然效率较低,但能有效解决内存碎片问题。文章还解释了这两种算法的具体过程及其优缺点,并简要提及了其他GC算法。
 JVM进阶调优系列(4)年轻代和老年代采用什么GC算法回收?
|
存储 监控 算法
JVM调优深度剖析:内存模型、垃圾收集、工具与实战
【10月更文挑战第9天】在Java开发领域,Java虚拟机(JVM)的性能调优是构建高性能、高并发系统不可或缺的一部分。作为一名资深架构师,深入理解JVM的内存模型、垃圾收集机制、调优工具及其实现原理,对于提升系统的整体性能和稳定性至关重要。本文将深入探讨这些内容,并提供针对单机几十万并发系统的JVM调优策略和Java代码示例。
415 2
|
存储 算法 Java
【JVM】垃圾释放方式:标记-清除、复制算法、标记-整理、分代回收
【JVM】垃圾释放方式:标记-清除、复制算法、标记-整理、分代回收
442 2
|
存储 算法 Java
JVM自动内存管理之垃圾收集算法
文章概述了JVM内存管理和垃圾收集的基本概念,提供一个关于JVM内存管理和垃圾收集的基础理解框架。
JVM自动内存管理之垃圾收集算法
|
缓存 算法 Java
GC垃圾收集算法
这篇文章详细讨论了垃圾收集(GC)的几种算法,包括引用计数、可达性分析、标记-清除、标记-复制和标记-整理算法,并介绍了这些算法的优缺点和适用场景。
219 0
GC垃圾收集算法
|
算法 Java
JVM有哪些垃圾回收算法?
(1)标记清除算法: 标记不需要回收的对象,然后清除没有标记的对象,会造成许多内存碎片。 (2)复制算法: 将内存分为两块,只使用一块,进行垃圾回收时,先将存活的对象复制到另一块区域,然后清空之前的区域。用在新生代 (3)标记整理算法: 与标记清除算法类似,但是在标记之后,将存活对象向一端移动,然后清除边界外的垃圾对象。用在老年代
175 0
|
存储 算法 Java
JVM组成结构详解:类加载、运行时数据区、执行引擎与垃圾收集器的协同工作
【8月更文挑战第25天】Java虚拟机(JVM)是Java平台的核心,它使Java程序能在任何支持JVM的平台上运行。JVM包含复杂的结构,如类加载子系统、运行时数据区、执行引擎、本地库接口和垃圾收集器。例如,当运行含有第三方库的程序时,类加载子系统会加载必要的.class文件;运行时数据区管理程序数据,如对象实例存储在堆中;执行引擎执行字节码;本地库接口允许Java调用本地应用程序;垃圾收集器则负责清理不再使用的对象,防止内存泄漏。这些组件协同工作,确保了Java程序的高效运行。
270 3
|
存储 监控 算法
(六)JVM成神路之GC基础篇:对象存活判定算法、GC算法、STW、GC种类详解
经过前面五个章节的分析后,对于JVM的大部分子系统都已阐述完毕,在本文中则开始对JVM的GC子系统进行全面阐述,GC机制也是JVM的重中之重,调优、监控、面试都逃不开的JVM话题。
1171 9

热门文章

最新文章