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停顿可能不理想


相关文章
|
5天前
|
存储 算法 Java
深入浅出JVM(十八)之并发垃圾收集器G1
深入浅出JVM(十八)之并发垃圾收集器G1
|
5天前
|
存储 算法 Java
深入浅出JVM(十七)之并发垃圾收集器CMS
深入浅出JVM(十七)之并发垃圾收集器CMS
|
5天前
|
算法 Java
深入浅出JVM(十五)之垃圾收集器(上篇)
深入浅出JVM(十五)之垃圾收集器(上篇)
|
5天前
|
安全 算法 Java
深入浅出JVM(十三)之垃圾回收算法细节
深入浅出JVM(十三)之垃圾回收算法细节
|
5天前
|
存储 算法 Java
深入浅出JVM(十二)之垃圾回收算法
深入浅出JVM(十二)之垃圾回收算法
|
5天前
|
算法 Java PHP
JVM 的垃圾回收机制以及垃圾回收算法的详解
JVM 的垃圾回收机制以及垃圾回收算法的详解
9 0
|
7天前
|
Arthas 监控 算法
JVM工作原理与实战(二十五):堆的垃圾回收-垃圾回收算法
JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了垃圾回收算法评价标准、标记清除算法、复制算法、标记整理算法、分代垃圾回收算法等内容。
19 0
JVM工作原理与实战(二十五):堆的垃圾回收-垃圾回收算法
|
20天前
|
算法 Java
JVM 垃圾回收算法(重要)
JVM 垃圾回收算法(重要)
|
1月前
|
存储 缓存 算法
深度解析JVM世界:垃圾判断和垃圾回收算法
深度解析JVM世界:垃圾判断和垃圾回收算法
|
1月前
|
算法 Java UED
【五一创作】值得一看的JVM垃圾收集器
【五一创作】值得一看的JVM垃圾收集器