JVM学习(三)JVM垃圾回收
一、引用的分类
在了解JVM垃圾回收机制之前,了解一下对象的引用类型是非常必要的。
-
强引用:
GC时不会被回收 -
软引用:
描述有用但不是必须的对象,在发生内存溢出异常之前被回收 -
弱引用:
描述有用但不是必须的对象,在下一次GC时被回收 -
虚引用(幽灵引用/幻影引用):
无法通过虚引用获得对象,用PhantomReference实现虚引用,在任何时候都可能被回收。
二、什么是JVM垃圾回收
定义:由于Java语言没有提供释放已分配内存的显示操作方法,垃圾回收(GC)是可以自动监测对象是否超过作用域从而达到自动回收内存的一种方式。
好处:
Java程序员不用担心内存管理,因为垃圾收集器会自动进行管理。
显式调用:
System.gc() 或Runtime.getRuntime().gc() ,但JVM可以屏蔽掉显式的垃圾回收调用。
- PS:显式调用并不一定会立即执行,也不推荐显式调用,因为JVM有自己的垃圾回收策略。
- PS:程序员不能实时的调用垃圾回收器对某个对象或所有对象进行垃圾回收。(垃圾回收的不可预知性)
三、JVM垃圾回收机制
![](https://img.haomeiwen.com/i26777047/e9d89c81430113d6.png)
JVM的垃圾回收机制是基于Java语言的自动内存管理机制。其核心思想是让程序员摆脱手工内存管理的麻烦,专注于业务逻辑的处理,而将内存管理的复杂工作交由JVM进行实现。
在JVM中,垃圾回收器负责回收无用的对象。垃圾回收器一般分为两类:新生代垃圾回收器
和老年代垃圾回收器
。
新生代垃圾回收器
:新生代垃圾回收器针对年轻代的对象进行垃圾回收,并采用分代收集算法,通常采用的垃圾回收器有 serial、parNew、Parallel Scavenge 等,其中 Serial 和 Parallel Scavenge 适用于单核处理器机器,而 ParNew 则是 Serial 的多线程版本,适用于多核处理器机器。老年代垃圾回收器
:老年代垃圾回收器针对年老代的对象进行垃圾回收,并采用标记清除、标记-整理等算法进行垃圾回收,常见的垃圾回收器有 serial old、Parallel Old、CMS、G1 等,其中 serial old 应用场景适合于处理器性能较弱的机器,而 Parallel Old 和 CMS 则适用于处理器性能更加强劲的机器。总的来说,JVM的垃圾回收机制是非常复杂的,具体的垃圾回收器的选择以及调优需要根据应用场景和实际需求来选择合适的垃圾回收器。
-
伊甸园(Eden):
这是对象最初诞生的区域,并且对大多数对象来说,这里是它们唯一存在过的区域。 -
幸存者乐园(Survivor):
从伊甸园幸存下来的对象会被挪到这里。 -
终身颐养园(Tenured):
这是足够老的幸存对象的归宿。年轻代收集(Minor-GC)过程是不会触及这个地方的。当年轻代收集不能把对象放进终身颐养园时,就会触发一次完全收集(Major-GC),这里可能还会牵扯到压缩,以便为大对象腾出足够的空间。
与垃圾回收相关的JVM参数:
-Xms / -Xmx
— 堆的初始大小 / 堆的最大大小
-Xmn
— 堆中年轻代的大小
-XX:-DisableExplicitGC
— 让System.gc()不产生任何作用【生产环境一般开启】
-XX:+PrintGCDetails
— 打印GC的细节
-XX:+PrintGCDateStamps
— 打印GC操作的时间戳
-XX:NewSize / XX:MaxNewSize
— 设置新生代大小/新生代最大大小
-XX:NewRatio
— 可以设置老生代和新生代的比例
-XX:PrintTenuringDistribution
— 设置每次新生代GC后输出幸存者乐园中对象年龄的分布
-XX:InitialTenuringThreshold / -XX:MaxTenuringThreshold
— 设置老年代阀值的初始值和最大值
-XX:TargetSurvivorRatio
— 设置幸存区的目标使用率 `
四、GC Roots
1、判断一个对象是否应该被回收
判断对象是否存活一般有两种方式:
-
引用计数:
每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以回收。此方法简单,无法解决对象相互循环引用的问题。 -
可达性分析(Reachability Analysis):
从GC Roots开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的,不可达对象。
![](https://img.haomeiwen.com/i26777047/a9db59bfad84637c.png)
2、可以作为GC Root对象
![](https://img.haomeiwen.com/i26777047/df2ffefac93b0638.png)
在Java语言中,GC Roots是指程序中被直接引用的对象集合
。被直接引用的对象包括以下几种:
-
1、
虚拟机栈中引用的对象:
虚拟机栈是为每个线程所创建的,保存每个方法执行的状态,包括局部变量和参数等,如果一个对象被虚拟机栈中的变量所引用,则表明该对象可达,不会被垃圾回收器回收。 -
2、
本地方法栈中引用的对象:
本地方法栈是为Java虚拟机使用的,它保存了Native方法的相关信息,也需要为Native方法中涉及的对象建立栈帧。 -
3、
方法区中静态变量引用的对象:
静态变量属于ClassLoader的内存区域,因此静态变量所引用的对象也不会被垃圾回收器回收。 -
4、
方法区中常量引用的对象:
编译器会把所有的字符串、数字等常量都放到一个常量池中,该常量池在运行期被装载到内存中,因此常量引用的对象也不会被垃圾回收器回收。 -
5、
JNI引用的对象:
JNI是Java Native Interface的缩写,它提供了一种机制可以让Java应用程序调用Native方法,JNI需要用到Native方法在执行时涉及到的Java对象。 -
6、
被Synchronized锁定的对象
:被Synchronized锁定的对象在锁定过程中是不能被回收的,因为还有其他线程可能正在使用该对象。 -
7、
临时对象,跨代引用。
五、回收方法区
方法区回收价值很低,主要回收废弃的常量和无用的类。
如何判断无用的类:
该类所有实例都被回收(Java堆中没有该类的对象)
加载该类的ClassLoader已经被回收
该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方利用反射访问该类
六、垃圾回收算法
GC最基础的算法有三种:
复制算法、标记 -清除算法、标记-整理算法,
我们常用的垃圾回收器一般都采用分代收集算法
。
-
复制算法,“复制”(Copying)的收集算法,
它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。
复制算法.png
![](https://img.haomeiwen.com/i26777047/3b4d24870806b817.png)
缺点:复制算法可以避免内存碎片的问题,但需要复制存活对象,可能在空间使用率和复制代价方面不如标记-整理算法。
-
标记 -清除算法(Mark-Sweep),
算法分为“标记”和“清除”两个阶段:在标记阶段,简单来说,垃圾收集器会从根对象出发,遍历整个堆内存中的对象,并为其打上标记,表示这些对象是存活的,而未打标记的对象则为垃圾。
![](https://img.haomeiwen.com/i26777047/737f5928da9a923e.png)
缺点:标记-清除算法的缺点在于可能会出现内存碎片问题。因为垃圾收集器解决了垃圾对象占据内存的问题,而没有提供一种机制去优化内部内存的分配。简单来说,由于清理不连续的内存块,可能会导致分配较大对象时出现内存不足的情况。
-
标记-整理算法
,包括标记、整理、清除三个主要步骤。在标记阶段,对于所有存活的对象进行标记;在整理阶段,将存活的对象压缩,生成一个新的连续空间;在清除阶段,回收未被标记的空间。
标记-整理算法.png
注意:标记-压缩算法
属于标记-整理算法
缺点:标记-整理算法避免了内存碎片的问题,但需要移动存活对象,可能在时间效率方面不如标记-清除算法。
-
分代收集算法,
“分代收集”(Generational Collection)算法,把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。
1、分代收集算法属于什么算法
分代收集算法属于标记-整理算法和复制算法的混合使用。它将堆分为几代,通常分为年轻代和老年代两代,通过将不同代的对象分别存储,来针对对象的生命周期设计不同的GC策略。其中,
年轻代使用复制算法,因为年轻代的对象生命周期短,相互引用关系较少,适合采用快速复制的方式进行GC
。老年代采用标记-整理算法,因为老年代的对象生命周期长,对象间相互引用关系复杂,进行标记后整理可以避免内存碎片的产生
。分代收集算法通过针对不同代的对象选择不同的GC策略,可以有效地提高GC的效率和减少GC次数,减少系统的停顿时间。
七、垃圾回收器
1、垃圾回收器介绍:
-
Serial收集器
,串行收集器是最古老,最稳定以及效率高的收集器,可能会产生较长的停顿,只使用一个线程去回收。 -
ParNew收集器,
ParNew收集器其实就是Serial收集器的多线程版本。 -
Parallel收集器,
Parallel Scavenge收集器类似ParNew收集器,Parallel收集器更关注系统的吞吐量。 -
Parallel Old 收集器
,Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法 -
CMS收集器,
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。 -
G1收集器,
G1 (Garbage-First)是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器,以极高概率满足GC停顿时间要求的同时,还具备高吞吐量性能特征。
![](https://img.haomeiwen.com/i26777047/4fcd1f1b22f60c1a.png)
2、垃圾回收过程
![](https://img.haomeiwen.com/i26777047/7d516dccfad2ac93.png)
3、CMS垃圾回收器
![](https://img.haomeiwen.com/i26777047/8a1e0941315d413c.png)
![](https://img.haomeiwen.com/i26777047/7b6eec963c18cb0b.png)
4、CMS垃圾回收器- java中的预处理
是指在重新标记之前执行,减少暂停所有用户线程的时间;
5、CMS垃圾回收器 - 并发可中断处理
打断:3种,其中可以中断
- 1、CMSMaxAbortablePrecleanLoops:控制 次数
- 2、CMSMaxAbortablePrecleanTime:控制 时间5s
- 3、CMSScheduleRemarkEdenPenetration: Eden区内存使达到50%
6、android中jdk1.7采用什么垃圾回收器,简单介绍一下
Android 中的 JDK 1.7 在 Dalvik 虚拟机上运行,采用了两种不同的垃圾回收器,分别是:
-
Serial (串行垃圾回收器):
该垃圾回收器采用单线程模式执行垃圾回收,会暂停所有的应用线程并等待垃圾回收完成后再继续执行应用线程。串行垃圾回收器适用于小型内存的设备,因为它占用的系统资源少,而且容易控制。但是,它的回收效率较低,无法满足大型内存设备的要求。 -
CMS (并发垃圾回收器):
该垃圾回收器不会暂停应用线程,而是在后台运行,通过标记-清除算法,定期回收未使用的内存。使用并发垃圾回收器时,应用程序可以继续执行,而不必等待垃圾回收完成。这种垃圾回收器适用于大型内存的设备,因为它可以快速回收未使用的内存,而且对系统资源的占用也相对较少。
需要注意的是,Android 中的 JDK 1.7 采用的是混合垃圾回收器,即 Serial
和CMS
的结合体。具体来说,当垃圾回收器在进行垃圾回收时,如果内存使用率比较低,则使用并发垃圾回收器;如果内存使用率比较高,则使用串行垃圾回收器。这样就能够在不同的场景下选择最合适的垃圾回收器,从而达到最优的效果。
7、Concurrent GC 是CMS收集器吗
是的,Concurrent GC 通常指的是 CMS(Concurrent Mark Sweep)垃圾回收器,在JDK 8之前,这是Java默认的并发垃圾回收器。
8、android中jdk1.8采用什么垃圾回收器,简单介绍一下
在 Android 中,JDK 1.8 默认采用的是 G1 垃圾回收器(Garbage First)
。G1 是一种面向服务端应用场景的垃圾回收器,在 JDK 9 之后成为了 Java 虚拟机的默认垃圾回收器
。
G1 是一种分代垃圾回收器,它将 Java 堆分成许多个小块(Region),并非分成三代,这样可以更灵活地管理内存。在 G1 回收过程中,将 heap 拆成了不同的 region,每个 Region 拥有单独的记忆(用快速操作表格处理)。在垃圾回收期间,G1 垃圾回收器优先回收垃圾占用较多的区域(其名称"G1"就源于此),优化了垃圾回收的效率,减少了应用的停顿时间,因此适用于大内存的、多处理器的服务器应用场景。
与 CMS 垃圾回收器相比,G1 垃圾回收器有更高的系统吞吐量和更短的 GC 暂停时间,因此得到了越来越多的关注和使用。在使用 G1 垃圾回收器时,我们需要根据实际情况调整垃圾回收器参数以达到最佳的性能表现。
9、android中jdk11采用什么垃圾回收器,简单介绍一下
在 Android 中,JDK 11 默认采用的是 ZGC(Z Garbage Collector)垃圾回收器
。
ZGC 是一种非常先进的垃圾回收器,其设计目的是为了能够在几乎任何规模的堆内存下都能够提供低延迟,即便是以非常大的堆内存和长时间的垃圾回收为代价。
与其他常见的垃圾回收器相比,ZGC 的最大特点是其非常低的暂停时间。ZGC 可以在几个毫秒内完成垃圾回收,最大暂停时间不会超过 10ms,这大大减少了应用停顿时间,因此适用于需要高可用性和低延迟的应用场景
。此外,ZGC 回收器效率非常高,因此对 CPU、内存资源占用较少,有利于提升系统吞吐量。
ZGC 给大型、多处理器(多处理器)系统提供了良好的性能支持。ZGC 在垃圾回收时采用类似记录日志的方式进行,通过将所有修改记录在缓冲区中,然后将缓冲区中的内容复制回堆上来,来避免垃圾回收期间大规模的复制和重定位。这种机制降低了复制和重定位对应用的影响,增加了并发性和可扩展性。然而,ZGC 也有一些局限性,例如需要较大的内存空间支持,只支持 64 位平台等
。对于使用 JDK 11 并且在大型、多处理器系统下需要低延迟的应用场景,建议使用 ZGC,同时需要根据实际情况调整垃圾回收器参数以达到最佳的性能表现。
10、ParNew和Parallel scavenge的区别
ParNew和Parallel Scavenge都是JVM的垃圾收集器,两者都属于新生代垃圾收集器,对于年轻代的垃圾回收具有更好的性能表现。然而,两者在垃圾回收的算法和实现上有所不同。
-
ParNew是一种多线程的年轻代垃圾收集器,主要与CMS垃圾回收器搭配使用
。ParNew收集器主要使用标记-复制算法,将年轻代分为1个Eden区和2个Survivor区,由ParNew来回扫描这些区域,将没有用的对象回收,存活的对象通过复制到Survivor区来实现内存的再利用。ParNew在监测到垃圾增加时会暂停应用程序来执行垃圾回收操作。 -
Parallel Scavenge也是一种多线程的年轻代垃圾收集器,是JDK 6引入的,其目标是为了提高吞吐量和减少GC暂停的时间
。它同样使用标记-复制算法,并将年轻代分为1个Eden区和2个Survivor区。与ParNew不同的是,Parallel Scavenge注重吞吐量,其算法会先尝试尽可能多地回收垃圾,以达到最大化的吞吐量。当然,这也意味着它可能会产生较长的暂停时间。
总之,ParNew注重减少严格意义上的GC暂停时间,Parallel Scavenge注重吞吐量的最大化。从整体来看,ParNew在大部分情况下表现更为稳定,而Parallel Scavenge在需要高吞吐量时表现更好
。在实际应用中,应根据具体需要选择合适的垃圾回收器。
![](https://img.haomeiwen.com/i26777047/ae507dd66b93cdff.png)