JVM垃圾回收GC

垃圾回收优缺点:

优点:

1. 自动内存管理:Java的垃圾回收机制自动处理内存分配和释放,减轻了开发人员对内存管理的负担。程序员不需要手动跟踪对象的生命周期和释放内存,减少了内存泄漏和野指针等内存错误的风险。

2. 避免了内存泄漏:由于垃圾回收器负责释放不再使用的对象,Java程序中的内存泄漏问题相对较少。即使开发人员忘记释放对象的引用,垃圾回收器也可以检测到并回收这些对象。

3. 提高了开发效率:垃圾回收机制减轻了开发人员的负担,使得开发更加高效。开发人员可以将更多精力集中在业务逻辑和功能实现上,而无需过多关注内存管理

缺点:

1. 不确定的垃圾回收时间:由于垃圾回收是由垃圾收集器自动触发的,程序员无法精确控制回收的时间和频率。这可能导致在某些情况下出现短暂的暂停,即所谓的"停顿时间",可能会影响某些对实时性要求较高的应用程序。

2. 性能开销:垃圾回收机制需要消耗一定的系统资源和处理时间来执行垃圾回收操作。尽管现代的垃圾收集器在性能方面进行了优化,但仍然存在一定的开销,尤其是在大型、复杂的应用程序中。

3. 可能产生不确定的延迟:垃圾回收过程中的停顿时间可能会导致应用程序的响应时间变长或产生不确定的延迟。这对于对实时性要求非常高的应用程序(如金融交易系统)可能是一个挑战。

4.「垃圾回收」是会导致「stop the world」
https://www.zhihu.com/question/21535724/answer/2998145696?utm_id=0

 
1.如何判断一个对象是否可以回收/死亡对象判断方法
(1)引用计数算法:
  给对象添加一个引用计数器,当对象增加一个引用时计数器加1,引用失效时计数器减 1。引用计数为0的对象可被回收。
  两个对象出现循环引用的情况下,此时引用计数器永远不为 0,导致无法对它们进行回收。正因为循环引用的存在,因此 Java 虚拟机不使用引用计数算法。
(2)可达性分析算法:
  通过 GC Roots 作为起始点进行搜索,能够到达到的对象都是存活的,不可达的对象可被回收。

Java虚拟机中使用可达性分析算法判断对象是否可被回收:

根对象
GC Roots一般包括:
  虚拟机栈中引用的对象
  本地方法栈中引用的对象
  方法区中类静态属性引用的对象
  方法区中的常量引用的对象
 
2.对象有哪些引用类型
Java具有四种强度不同的引用类型:
  强引用:被强引用关联的对象不会被回收。 (使用new一个新对象的方式来创建强引用)
  软引用:被软引用关联的对象只有在内存不够的情况下才会被回收。 (使用SoftReference类创建软引用)
Object obj = new Object();
SoftReference<Object> sf = new SoftReference<Object>(obj);
obj = null; // 使对象只被软引用关联
  弱引用:被弱引用关联的对象一定会被回收。(下一次GC必死)  (使用WeakReference类实现弱引用)
  虚引用:又称幽灵引用或幻影引用。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用取得一个对象。  (使用PhantomReference实现虚引用)
为一个对象设置虚引用关联的唯一目的就是能在这个对象被回收时收到一个系统通知
 
3.垃圾回收算法
(1)标记-清除:将存活的对象进行标记,然后清理掉未被标记的对象。
  不足:标记和清除过程效率都不高。
    会产生大量不连续的内存碎片,导致无法给大对象分配内存。
(2)标记-整理:让所有存活的对象都向一段移动,然后直接清理掉端边界以外的内存。
(3)复制:将内存划分为大小相等的两块,每次只使用其中一块,当这一块内存用完了就将还存活的对象复制到另一块上面,然后再把使用过的内存空间进行一次清理。
  不足:只使用了内存的一半。

(4)分代收集:
  现在的商业虚拟机采用分代收集算法,它根据对象存活周期将内存划分为几块,不同块采用适当的收集算法。
  新生代使用: 复制算法(并不是将新生代划分为大小相等的两块,而是分为一块较大的 Eden 空间和两块较小的 Survivor 空间,每次使用 Eden 空间和其中一块 Survivor。在回收时,将 Eden Survivor 中还存活着的对象一次性复制到另一块 Survivor 空间上,最后清理 Eden 和使用过的那一块 Survivor
HotSpot 虚拟机的 Eden Survivor 的大小比例默认为 8:1,保证了内存的利用率达到 90%。如果每次回收有多于 10% 的对象存活,那么一块 Survivor 空间就不够用了,此时需要依赖于老年代进行分配担保,也就是借用老年代的空间存储放不下的对象)
  老年代使用: 标记 - 清除 或者 标记 - 整理 算法
 

4.什么是Minor GC,Major GC,Full GC   (minor少数的)
  针对HotSpot VM的实现,它里面的GC按照回收区域又分为两大类:部分收集(Partial GC),整堆收集(Full GC
·  部分收集:不是完整收集整个 Java 堆的垃圾收集。其中又分为:
  新生代收集(Minor GC/Young GC):只是新生代的垃圾收集
  老年代收集(Major GC/Old GC):只是老年代的垃圾收集 CMS
    很多时候 Major GC会和Full GC混用,需要分辨是老年代回收还是整堆回收
  混合收集(Mixed GC):收集整个新生代以及部分老年代的垃圾收集 G1
·  整堆收集Full GC):收集整个 Java 堆和方法区的垃圾
为了进行高效的垃圾回收,虚拟机把堆内存逻辑上划分成三块区域(分代的唯一理由就是优化 GC 性能):
  新生(年轻代):新对象和没达到一定年龄的对象都在新生代
  老年代(养老区):被长时间使用的对象,老年代的内存空间应该要比年轻代更大
  元空间JDK1.8 之前叫永久代):像一些方法中的操作临时对象等,JDK1.8 之前是占用 JVM 内存,JDK1.8 之后直接使用物理内存。
 
5.JVM内存分配策略
(1)对象优先在Eden分配
  Eden空间不够时,发起Minor GC
(2)大对象直接进入老年代
  大对象是指需要连续内存空间的对象
  -XX:PretenureSizeThreshold,大于此值的对象直接在老年代分配,避免在Eden区和 Survivor区之间的大量内存复制。
(3)长期存活的对象进入老年代
  为对象定义年龄计数器,每经过Minor GC依然存活并移动到Survivor中,年龄+1
  -XX:MaxTenuringThreshold 用来定义年龄的阈值(通常是15
(4)动态对象年龄判定
  虚拟机并不是永远地要求对象的年龄必须达到 MaxTenuringThreshold 才能晋升老年代,如果Survivor中相同年龄所有对象大小的总和大于Survivor空间的一半,则年龄大于或等于该年龄的对象可以直接进入老年代,无需等到 MaxTenuringThreshold 中要求的年龄。
(5)空间分配担保
  在发生 Minor GC 之前,虚拟机先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果条件成立的话,那么 Minor GC可以确认是安全的。
  如果不成立的话虚拟机会查看 HandlePromotionFailure 设置值是否允许担保失败,如果允许那么就会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次 Minor GC;如果小于,或者 HandlePromotionFailure 设置不允许冒险,那么就要进行一次 Full GC
 
Eden满,触发Minor GC
6.什么情况下会触发Full GC
调用System.gc()只是建议虚拟机执行 Full GC,但是虚拟机不一定真正去执行。不建议使用这种方式,而是让虚拟机管理内存。
老年代空间不足:老年代空间不足的常见场景为前文所讲的大对象直接进入老年代、长期存活的对象进入老年代等。
  为了避免以上原因引起的 Full GC
    (1)不要创建过大的对象以及数组。
    (2)可以通过 -Xmn 虚拟机参数调大新生代的大小,让对象尽量在新生代被回收掉。
    (3)可以通过-XX:MaxTenuringThreshold 调大对象进入老年代的年龄。
空间分配担保失败:使用复制算法的 Minor GC 需要老年代的内存空间作担保,如果担保失败会执行一次 Full GC
JDK 1.7及以前的永久代空间不足:当系统中要加载的类、反射的类和调用的方法较多时,永久代可能会被占满,在未配置为采用 CMS GC 的情况下也会执行 Full GC。如果经过 Full GC仍然回收不了,那么虚拟机会抛出java.lang.OutOfMemoryError
  为避免以上原因引起的 Full GC,可采用的方法为增大永久代空间或转为使用 CMS GC
Concurrent Mode Failure:执行 CMS GC 的过程中同时有对象要放入老年代,而此时老年代空间不足(可能是 GC 过程中浮动垃圾过多导致暂时性的空间不足),便会报 Concurrent Mode Failure 错误,并触发 Full GC
 
7.Hotspot虚拟机中有哪些垃圾回收器
  HotSpot是一款高性能的Java虚拟机,可以大大提高Java运行性能。Java原先是把源代码编译为字节码在虚拟机执行,这样整体执行效率不高。而HotSpot关注的是对部分热点(hot spot)代码的动态优化,将频繁执行的热点代码编译为本地原生代码,这样显著提高了性能。
  ·单线程与多线程: 单线程指垃圾收集器只用一个线程进行收集,而多线程使用多个线程;
  ·串行与并行: 串行指的是垃圾收集器与用户程序交替执行,这意味着在执行垃圾收集的时候需要停顿用户程序;并行指的是垃圾收集器和用户程序同时执行
 
串行回收器:SerialSerial old
并行回收器:ParNewParallel ScavengeParallel old
并发回收器:CMSG1
 
新生代收集器:SerialParNewParallel Scavenge
老年代收集器:Serial oldParallel oldCMS
整堆收集器:G1
在JDK 8及之前的版本中,默认的垃圾收集器是Parallel Scavenge加上Serial Old收集器组合。这个组合在新生代和老年代使用不同的垃圾收集器。
在JDK 9中,默认的垃圾收集器是G1(Garbage-First)收集器。G1收集器是一种面向服务端应用的垃圾收集器,它的目标是在有限的停顿时间内,尽量减少垃圾收集的时间。
需要注意的是,可以通过JVM参数来指定使用其他垃圾收集器,例如,可以使用-XX:+UseConcMarkSweepGC参数来启用CMS(Concurrent Mark Sweep)收集器。

 

(1) Serial收集器(串行)

单线程、串行  新生代采用标记-复制算法,老年代采用标记-整理算法。
  优点:简单高效,对于单个 CPU 环境来说,由于没有线程交互的开销,因此拥有最高的单线程收集效率。
  它是Client模式下的默认新生代收集器,因为在用户的桌面应用场景下,分配给虚拟机管理的内存一般来说不会很大。Serial 收集器收集几十兆甚至一两百兆的新生代停顿时间可以控制在一百多毫秒以内,只要不是太频繁,这点停顿是可以接受的。
 
(2) ParNew 收集器(同新式)
多线程(Serial多线程版本)  新生代采用标记-复制算法,老年代采用标记-整理算法。
  是Server模式下的虚拟机首选新生代收集器,除了性能原因外,主要是因为除了 Serial 收集器,只有它能与 CMS 收集器配合工作。
  默认开启的线程数量与CPU数量相同,可以用-XX:ParallelGCThreads参数来设置线程数。
 
(3) Parallel Scavenge收集器(并行/吞吐优先收集器)

多线程  新生代采用标记-复制算法,老年代采用标记-整理算法。
  其目标是达到一个可控制的吞吐量,它被称为吞吐量优先收集器。这里的吞吐量指 CPU用于运行用户代码的时间占总时间的比值。其它收集器关注点是尽可能缩短垃圾收集时用户线程的停顿时间
  高吞吐量可以高效率地利用CPU时间,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互(停顿)的任务。
  缩短停顿时间是以牺牲吞吐量和新生代空间来换取的: 新生代空间变小,垃圾回收变得频繁,导致吞吐量下降。
  可以通过一个开关参数打开GC自适应的调节策略(GC Ergonomics),就不需要手动指定新生代的大小(-Xmn)Eden Survivor 区的比例、晋升老年代对象年龄等细节参数了。虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量。
 
(4) Serial Old收集器

单线程(Serial 收集器的老年代版本
   Client 模式下的虚拟机使用。如果用在 Server 模式下,它有两大用途:
·  JDK 1.5以及之前版本(Parallel Old诞生以前)中与Parallel Scavenge收集器搭配使用。
·  作为CMS收集器的后备预案,在并发收集发生Concurrent Mode Failure时使用。
 
(5) Parallel Old 收集器

多线程(Parallel Scavenge 收集器的老年代版本
  在注重吞吐量以及 CPU 资源敏感的场合,都可以优先考虑 Parallel Scavenge Parallel Old 收集器。
 
(6) CMS收集器

多线程、并发  CMS(Concurrent Mark Sweep)Mark Sweep 指的是标记 - 清除算法。
  CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。它非常符合在注重用户体验的应用上使用。  
  CMS(Concurrent Mark Sweep)收集器是 HotSpot 虚拟机第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程(基本上)同时工作。
 
CMS收集器运行过程:
  初始标记(标记GC Roots能直接关联到的对象,速度很快,需要停顿
  并发标记(进行GC Roots tracing的过程,时间较长,不需要停顿
  重新标记(修正并发标记期间因用户程序继续运行而导致标记变动的那一部分对象的标记记录,需要停顿
  并发清除(不需要停顿
CMS优点:并发收集、低停顿
CMS缺点:吞吐量低(低停顿时间是以牺牲吞吐量为代价的,导致 CPU 利用率不够高)、无法处理浮动垃圾、收集结束后会有大量空间碎片
  浮动垃圾:由于CMS并发清理阶段用户线程还在运行着,伴随着运行自然就还会有新的垃圾不断产生,这一部分垃圾出现在标记过程后,CMS无法在当次收集中处理掉它们,只好留待下一次GC时再清理掉,这一部分垃圾就称为浮动垃圾Floating Garbage)。
 
(7) G1收集器

并发
  G1(Garbage-First),它是一款面向服务端应用的垃圾收集器,在CPU和大内存的场景下有很好的性能。HotSpot 开发团队赋予它的使命是未来可以替换掉 CMS 收集器。
  G1 可以直接对新生代和老年代一起回收。
  G1GC 的目的就是高效地实现软实时性,能够让用户设置期望暂停时间。在确保吞吐量比以往的GC更好的前提下,实现了软实时性。
  G1GC 能最大程度利用服务器上多处理器的优势,而且在处理巨大的堆时,也不会降低 GC 的性能。
G1 的执行过程是什么样的?
  ·初始标记
  ·并发标记(concurrent marking):和应用程序并发执行,针对区域内所有的存活对象进行标记。
  ·最终标记
  ·筛选回收:首先对各个Region中的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划。
G1 选择的回收算法:
  在新生代,G1采用的仍然是并行的复制算法,所以同样会发生Stop-The-World的暂停。
  在老年代,大部分情况下都是并发标记,而整理(Compact)则是和新生代 GC 时捎带进行,并且不是整体性的整理,而是增量进行的。
G1特点:

  并行与并发:G1 能充分利用 CPU、多核环境下的硬件优势,使用多个 CPU(CPU 或者 CPU 核心)来缩短 Stop-The-World 停顿时间。部分其他收集器原本需要停顿 Java 线程执行的 GC 动作,G1 收集器仍然可以通过并发的方式让 java 程序继续执行。

  分代收集:虽然 G1 可以不需要其他收集器配合就能独立管理整个 GC 堆,但是还是保留了分代的概念。

  空间整合: 整体来看是基于“标记 - 整理”算法实现的收集器,从局部(两个 Region 之间)上来看是基于“复制”算法实现的,这意味着运行期间不会产生内存空间碎片。

  可预测的停顿: 能让使用者明确指定在一个长度为 M 毫秒的时间片段内,消耗在 GC上的时间不得超过N毫秒。

 

(8) ZGCJDK11中推出的一款低延迟垃圾回收器(java15可正式使用),适用于大内存低延迟服务的内存管理和回收,SPECjbb 2015 基准测试,在 128G 的大堆下,最大停顿时间才 1.68 ms,停顿时间远胜于G1CMS

ZGC工作流程大体上可以分为三个阶段:
  标记阶段(标记存活对象)
  对象转移阶段(转移存活对象)
  对象重定位阶段(重定位对象指针)

目前ZGC不支持指针压缩和分代GC,其内存的占用会比G1的要大很多,因此ZGC更合适在大堆场景下去使用.
  在超大堆上适用。在百GBTB级堆应用上,ZGC的停顿时间仍然会保持在处理十几GB堆一样,而CMSG1可能执行时间就是分钟级别。
  在业务有硬性要求下,停顿时间必须低于100ms,那么此类场景下ZGC必然是首推的。

可以通过下面的参数启动 ZGC:

$ java -XX:+UseZGC className

 

ZGC(可伸缩低延迟垃圾收集器)

ZGC 即 Z Garbage Collector,是一个可伸缩的、低延迟的垃圾收集器。

ZGC 主要为了满足如下目标进行设计:

  • GC 停顿时间不超过 10ms
  • 即能处理几百 MB 的小堆,也能处理几个 TB 的大堆
  • 应用吞吐能力不会下降超过 15%(与 G1 回收算法相比)
  • 方便在此基础上引入新的 GC 特性和利用 colored 针以及 Load barriers 优化奠定基础
  • 当前只支持 Linux/x64 位平台

ZGC 目前 处在实验阶段,只支持 Linux/x64 平台。

与 CMS 中的 ParNew 和 G1 类似,ZGC 也采用标记-复制算法,不过 ZGC 对该算法做了重大改进。

在 ZGC 中出现 Stop The World 的情况会更少!

 PS C:\Users\Administrator> java -XX:+PrintGCDetails -version
java version "1.8.0_202"
Java(TM) SE Runtime Environment (build 1.8.0_202-b08)
Java HotSpot(TM) 64-Bit Server VM (build 25.202-b08, mixed mode)
Heap
 PSYoungGen      total 37888K, used 1966K [0x00000000d6100000, 0x00000000d8b00000, 0x0000000100000000)
  eden space 32768K, 6% used [0x00000000d6100000,0x00000000d62eba40,0x00000000d8100000)
  from space 5120K, 0% used [0x00000000d8600000,0x00000000d8600000,0x00000000d8b00000)
  to   space 5120K, 0% used [0x00000000d8100000,0x00000000d8100000,0x00000000d8600000)
 ParOldGen       total 86016K, used 0K [0x0000000082200000, 0x0000000087600000, 0x00000000d6100000)
  object space 86016K, 0% used [0x0000000082200000,0x0000000082200000,0x0000000087600000)
 Metaspace       used 2300K, capacity 4480K, committed 4480K, reserved 1056768K
  class space    used 255K, capacity 384K, committed 384K, reserved 1048576K

 

 

posted @ 2023-05-04 20:04  壹索007  阅读(23)  评论(0编辑  收藏  举报