JVM垃圾回收GC

发布时间 2023-05-04 20:07:48作者: 壹索007
1.如何判断一个对象是否可以回收
引用计数算法:
  给对象添加一个引用计数器,当对象增加一个引用时计数器加1,引用失效时计数器减 1。引用计数为0的对象可被回收。
  两个对象出现循环引用的情况下,此时引用计数器永远不为 0,导致无法对它们进行回收。正因为循环引用的存在,因此 Java 虚拟机不使用引用计数算法。
可达性分析算法:
  通过 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)有哪些基本垃圾回收算法
标记-清除:将存活的对象进行标记,然后清理掉未被标记的对象。
不足:标记和清除过程效率都不高。
会产生大量不连续的内存碎片,导致无法给大对象分配内存。
标记-整理:让所有存活的对象都向一段移动,然后直接清理掉端边界以外的内存。
复制:将内存划分为大小相等的两块,每次只使用其中一块,当这一块内存用完了就将还存活的对象复制到另一块上面,然后再把使用过的内存空间进行一次清理。
不足:只使用了内存的一半。

 

 
现在的商业虚拟机都采用复制收集算法来回收新生代,但是并不是将新生代划分为大小相等的两块,而是分为一块较大的 Eden 空间和两块较小的 Survivor 空间,每次使用 Eden 空间和其中一块 Survivor。在回收时,将 Eden Survivor 中还存活着的对象一次性复制到另一块 Survivor 空间上,最后清理 Eden 和使用过的那一块 Survivor
HotSpot 虚拟机的 Eden Survivor 的大小比例默认为 8:1,保证了内存的利用率达到 90%。如果每次回收有多于 10% 的对象存活,那么一块 Survivor 空间就不够用了,此时需要依赖于老年代进行分配担保,也就是借用老年代的空间存储放不下的对象。
分代收集:
现在的商业虚拟机采用分代收集算法,它根据对象存活周期将内存划分为几块,不同块采用适当的收集算法。
新生代使用: 复制算法
老年代使用: 标记 - 清除 或者 标记 - 整理 算法
 
(4)分代收集算法和分区收集算法的区别
分代收集算法:
当前主流 VM 垃圾收集都采用”分代收集”(Generational Collection)算法, 这种算法会根据 对象存活周期的不同将内存划分为几块, JVM 中的 新生代、老年代、永久代,这样就可以根据 各年代特点分别采用最适当的 GC 算法。
在新生代-复制算法:
每次垃圾收集都能发现大批对象已死, 只有少量存活. 因此选用复制算法, 只需要付出少量 存活对象的复制成本就可以完成收集
在老年代-标记整理算法:
因为对象存活率高、没有额外空间对它进行分配担保, 就必须采用“标记—清理”或“标 记—整理”算法来进行回收, 不必进行内存复制, 且直接腾出空闲内存.
ParNew:一块多线程的收集器,采用复制算法,主要工作在Young区,可以通过 -XX:ParallelGCThreads参数来控制收集的线程数,整个过程都是STW的(Stop-The-World简称STW 是在垃圾回收算法执行过程中,jvm内存冻结,停顿的一种状态 在STW状态下,所有的线程都是停止运行的 - >垃圾回收线程除外),常与CMS组合使用。
CMSConcurrent Mark Sweep(同时发生标记清除)一块收集器,以获得最短回收停顿时间为目标,采用“标记-清除”算法,分4大步进行垃圾收集,其中初始标记和重新标记会STW,多数应用于互联网站或者B/S系统的服务器端上(希望系统停顿时间最短,给用户带来较好的体验,CMS收集器就非常符合这类应用的需求),JDK9被标记弃用,JDK14被删除。
分区收集算法:
将整个堆空间划分为连续的不同小区间,每个小区间独立使用,独立回收。
这样做的好处是可以控制一次回收多少个小区间 , 根据目标停顿时间, 每次合理地回收若干个小区间(而不是 整个堆), 从而减少一次 GC 所产生的停顿。
G1一种服务器端的垃圾收集器,应用在多处理器和大容量内存环境中,在实现高吞吐量的同时,尽可能满足垃圾收集暂停时间的要求。
一个横跨新生代和老年代的垃圾收集器。实际上,它已经打乱了新生代和老年代的堆结构,直接将堆分成极其多个区域。每个区域都可以充当 Eden 区、Survivor 区或者老年代中的一个。 它采用的是标记 - 压缩算法,而且和 CMS 一样都能够在应用程序运行过程中并发地进行垃圾回收。G1 能够针对每个细分的区域来进行垃圾回收。在选择进行垃圾回收的区域时,它会优先回收死亡对象较多的区域。这也是 G1 名字的由来。
ZGCJDK11中推出的一款低延迟垃圾回收器,适用于大内存低延迟服务的内存管理和回收,SPECjbb 2015 基准测试,在 128G 的大堆下,最大停顿时间才 1.68 ms,停顿时间远胜于G1CMS
ZGC工作流程大体上可以分为三个阶段:
1.标记阶段(标记存活对象)
2.对象转移阶段(转移存活对象)
3.对象重定位阶段(重定位对象指针)
目前ZGC不支持指针压缩和分代GC,其内存的占用会比G1的要大很多,因此ZGC更合适在大堆场景下去使用.
1.在超大堆上适用。在百GBTB级堆应用上,ZGC的停顿时间仍然会保持在处理十几GB堆一样,而CMSG1可能执行时间就是分钟级别。
2.在业务有硬性要求下,停顿时间必须低于100ms,那么此类场景下ZGC必然是首推的。
http://events.jianshu.io/p/b6fbafab2925
 
5)什么是Minor GC,Major GC,Full GC   minor少数的
针对HotSpot VM的实现,它里面的GC按照回收区域又分为两大类:部分收集(Partial GC),整堆收集(Full GC
·  部分收集:不是完整收集整个 Java 堆的垃圾收集。其中又分为:
新生代收集(Minor GC/Young GC):只是新生代的垃圾收集
老年代收集(Major GC/Old GC):只是老年代的垃圾收集
目前,只有 CMS GC 会有单独收集老年代的行为
很多时候 Major GC会和Full GC混用,需要分辨是老年代回收还是整堆回收
混合收集(Mixed GC):收集整个新生代以及部分老年代的垃圾收集
目前只有 G1 GC 会有这种行为
·  整堆收集(Full GC):收集整个 Java 堆和方法区的垃圾
 
6JVM内存分配策略
对象优先在Eden分配
Eden空间不够时,发起Minor GC
大对象直接进入老年代
大对象是指需要连续内存空间的对象
-XX:PretenureSizeThreshold,大于此值的对象直接在老年代分配,避免在Eden区和 Survivor区之间的大量内存复制。
长期存活的对象进入老年代
为对象定义年龄计数器,每经过Minor GC依然存活并移动到Survivor中,年龄+1
-XX:MaxTenuringThreshold 用来定义年龄的阈值(通常是15
动态对象年龄判定
虚拟机并不是永远地要求对象的年龄必须达到 MaxTenuringThreshold 才能晋升老年代,如果Survivor中相同年龄所有对象大小的总和大于Survivor空间的一半,则年龄大于或等于该年龄的对象可以直接进入老年代,无需等到 MaxTenuringThreshold 中要求的年龄。
空间分配担保
在发生 Minor GC 之前,虚拟机先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果条件成立的话,那么 Minor GC可以确认是安全的。
如果不成立的话虚拟机会查看 HandlePromotionFailure 设置值是否允许担保失败,如果允许那么就会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次 Minor GC;如果小于,或者 HandlePromotionFailure 设置不允许冒险,那么就要进行一次 Full GC
 
Eden满,触发Minor GC
7)什么情况下会触发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
 
(8)Hotspot虚拟机中有哪些垃圾回收器
HotSpot是一款高性能的Java虚拟机,可以大大提高Java运行性能。Java原先是把源代码编译为字节码在虚拟机执行,这样整体执行效率不高。而HotSpot关注的是对部分热点(hot spot)代码的动态优化,将频繁执行的热点代码编译为本地原生代码,这样显著提高了性能。
 
(连线表示垃圾收集器可以配合使用)
单线程与多线程: 单线程指垃圾收集器只用一个线程进行收集,而多线程使用多个线程;
串行与并行: 串行指的是垃圾收集器与用户程序交替执行,这意味着在执行垃圾收集的时候需要停顿用户程序;并行指的是垃圾收集器和用户程序同时执行
 
串行回收器:SerialSerial old
并行回收器:ParNewParallel ScavengeParallel old
并发回收器:CMSG1
 
新生代收集器:SerialParNewParallel Scavenge
老年代收集器:Serial oldParallel oldCMS
整堆收集器:G1
 
Serial收集器(串行)
 
单线程、串行
优点:简单高效,对于单个 CPU 环境来说,由于没有线程交互的开销,因此拥有最高的单线程收集效率。
它是Client模式下的默认新生代收集器,因为在用户的桌面应用场景下,分配给虚拟机管理的内存一般来说不会很大。Serial 收集器收集几十兆甚至一两百兆的新生代停顿时间可以控制在一百多毫秒以内,只要不是太频繁,这点停顿是可以接受的。
ParNew 收集器(同新式)
 
多线程、并行
它是Serial收集器的多线程版本。
Server模式下的虚拟机首选新生代收集器,除了性能原因外,主要是因为除了 Serial 收集器,只有它能与 CMS 收集器配合工作。
默认开启的线程数量与CPU数量相同,可以用-XX:ParallelGCThreads参数来设置线程数。
 
Parallel Scavenge收集器(并行/吞吐优先收集器)
多线程、并行
目标是达到一个可控制的吞吐量,它被称为吞吐量优先收集器。这里的吞吐量指 CPU用于运行用户代码的时间占总时间的比值。其它收集器关注点是尽可能缩短垃圾收集时用户线程的停顿时间
高吞吐量可以高效率地利用CPU时间,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互(停顿)的任务。
缩短停顿时间是以牺牲吞吐量和新生代空间来换取的: 新生代空间变小,垃圾回收变得频繁,导致吞吐量下降。
可以通过一个开关参数打开GC自适应的调节策略(GC Ergonomics),就不需要手动指定新生代的大小(-Xmn)Eden Survivor 区的比例、晋升老年代对象年龄等细节参数了。虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量。
Serial Old收集器
 
单线程、串行
Serial收集器的老年代版本也是给 Client 模式下的虚拟机使用。如果用在 Server 模式下,它有两大用途:
·  JDK 1.5以及之前版本(Parallel Old诞生以前)中与Parallel Scavenge收集器搭配使用。
·  作为CMS收集器的后备预案,在并发收集发生Concurrent Mode Failure时使用。
 
Parallel Old 收集器
 
多线程、并行
Parallel Scavenge 收集器的老年代版本。
在注重吞吐量以及 CPU 资源敏感的场合,都可以优先考虑 Parallel Scavenge Parallel Old 收集器。
 
 
 
CMS收集器
 
多线程、并发
CMS(Concurrent Mark Sweep)Mark Sweep 指的是标记 - 清除算法。
CMS收集器运行过程:
初始标记(标记GC Roots能直接关联到的对象,速度很快,需要停顿
并发标记(进行GC Roots tracing的过程,时间较长,不需要停顿
重新标记(修正并发标记期间因用户程序继续运行而导致标记变动的那一部分对象的标记记录,需要停顿
并发清除(不需要停顿
CMS优点:并发收集、低停顿
CMS缺点:吞吐量低(低停顿时间是以牺牲吞吐量为代价的,导致 CPU 利用率不够高)、无法处理浮动垃圾、收集结束后会有大量空间碎片
由于CMS并发清理阶段用户线程还在运行着,伴随着运行自然就还会有新的垃圾不断产生,这一部分垃圾出现在标记过程后,CMS无法在当次收集中处理掉它们,只好留待下一次GC时再清理掉,这一部分垃圾就称为浮动垃圾Floating Garbage)。
 
G1收集器
 
并发
G1(Garbage-First),它是一款面向服务端应用的垃圾收集器,在CPU和大内存的场景下有很好的性能。HotSpot 开发团队赋予它的使命是未来可以替换掉 CMS 收集器。
G1 可以直接对新生代和老年代一起回收。
G1GC 的目的就是高效地实现软实时性,能够让用户设置期望暂停时间。在确保吞吐量比以往的GC更好的前提下,实现了软实时性。
G1GC 能最大程度利用服务器上多处理器的优势,而且在处理巨大的堆时,也不会降低 GC 的性能。
G1GC 的执行过程是什么样的?
初始标记
并发标记(concurrent marking):和应用程序并发执行,针对区域内所有的存活对象进行标记。
最终标记
筛选回收:首先对各个Region中的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划。
G1 选择的回收算法:
在新生代,G1采用的仍然是并行的复制算法,所以同样会发生Stop-The-World的暂停。
在老年代,大部分情况下都是并发标记,而整理(Compact)则是和新生代 GC 时捎带进行,并且不是整体性的整理,而是增量进行的。
特点:
·  空间整合: 整体来看是基于“标记 - 整理”算法实现的收集器,从局部(两个 Region 之间)上来看是基于“复制”算法实现的,这意味着运行期间不会产生内存空间碎片。
·  可预测的停顿: 能让使用者明确指定在一个长度为 M 毫秒的时间片段内,消耗在 GC上的时间不得超过N毫秒。