JVM总结3

JVM垃圾回收

垃圾回收的方式

如果一个或多个对象没有其他引用指向它,则此时这个对象就是垃圾,并可能会被垃圾回收器回收。回收的方式主要有两种:

(1)引用计数法

一个对象被引用一次,则在其对象引用头上增加一次引用次数,当引用次数为0时,该对象可被回收。该方法主要有以下优点:

1)运行时引用计数器为0时,直接可以回收,无需等到内存不够的时候;

2)垃圾回收过程中,应用不需要被挂起,当申请内存时出现内存不够的情况,直接报OOM错误;

3)更新引用计数器时,只会影响该对象,不会影响别的对象。

但也存在一些缺点:

1)每次引用更新时,都会更新引用计数器,会造成一定的时间开销;

2)在内存够用时,仍需要更新引用计数器,这样会浪费CPU资源;

3)可能会导致循环引用问题,造成内存泄露,具体例子如下:

上图中代码a、b对象在创建时分别引用自己所创建的内存,然后执行到a.instance=b和b.instance=a时a会引用b、b会引用a,此时a、b对象的引用计数器均为2,引用结构图如下:

当a、b置为null时,a和b的引用计数器都会减1,此时由于结构图如下:

虽然a、b均为null,但由于引用计数器不为0,因此a、b不会被回收。

(2)可达性分析

从初始存活集合GC Roots出发,探索所有能够被该集合引用到的对象,并将其加入到该集合中,这个过程我们也称之为标记(mark)。

对于未被探索到的对象,首先检查该对象是否执行了finalize方法,若未执行,则先执行该方法,并在执行后GC再次判断该对象是否可达,若不可达则进行回收,反之则不回收。

可以作为GC Roots的对象:

1)虚拟机栈即栈帧中的本地变量表中引用的对象,如下图中demo对象

2)方法区中类静态属性引用的对象,如下图中a对象

3)方法区中常量引用的对象,如下图中的a对象

4)本地方法栈中JNI,即Native方法引用的对象。

JVM垃圾算法

标记清除算法

该算法首先使用可达性分析算法找到需要回收的对象并进行标记,完成标记后对这些对象进行回收,一般没有从GC Roots节点引用的对象都会被删除,如图所示:

但该算法效率较低,在标记和清除时都需要遍历所有对象,且在GC时需要暂停应用程序,这对交互性要求较高的应用来说体验感很差。同时用于被回收的对象可能存在于内存各个角落,因此清理出来的内存不一定是连贯的,可能导致内存碎片的出现。

复制算法

将内存分成大小相同的两部分,在垃圾回收时,先进行标记并将存活的对象移动到另一块内存中,并清理这一块内存,完成清理后这两块内存交换角色。该算法适用于垃圾较多的场景,因为这样存活的对象较少,移动的次数就比较少。

该算法的优点是垃圾较多时,效率较高,同时不会产生碎片,但缺点是同一时刻只能用其中一块内存,内存使用率较低。

标记整理算法

该算法是标记清理算法的升级版,首先和标记清理算法一样,标记出存活对象,然后将存活的对象移动到内存的一端,并清理掉边界以外的垃圾,从而解决了碎片的问题。该算法的优点是解决了内存碎片的问题,但在标记的基础上,需要对存活的对象进行移动,因此效率有一定的影响。

分代收集算法

在Java8中,堆被分为新生代和老年代两部分,其大小比为1:2,而新生代又被分为eden、from、to三部分,大小比为8:1:1。分代收集算法的机制如下:

(1)在初始时,对象被分配到eden区;

(2)eden区内存不够时,标记eden和from中存活的对象,并采用复制算法将存活的对象复制到to区域;

(3)回收eden、from两区域中现阶段未存活的对象;

(4)回收完成后继续在eden区分配对象,当其空间不足时,标记eden、to区域中存活的对象,并将这些对象复制到from区

(5)当from、to区域的对象经过多次垃圾回收(一般15次)后未被回收,则会晋升老年代,from、to区域不足或对象较大时可能提前晋升。

对于不同的分代,回收的算法不一样,Minor GC发生在新生代的垃圾回收,需要STW,即Stop The World,暂停所有应用进程,等待垃圾回收完成;Major GC发生在老年代的垃圾回收;Mixed GC是新生代和老年代部分垃圾回收,G1垃圾回收器持有;Full GC是新生代和老年代全部垃圾回收,暂停时间长,应尽量避免。

垃圾回收器分类

串行垃圾回收器

分为Serial和Serial Old算法,其中Serial用于新生代垃圾的回收,采用复制算法;Serial Old用于老年代垃圾回收,采用标记整理算法。垃圾回收时只有一个线程工作,Java应用程序中其他线程都要STW,直至回收完成。

并行垃圾回收器

分为Parallel New和Parallel Old,前者用于新生代垃圾的回收,采用复制算法;后者用于老年代垃圾回收,采用标记整理算法。垃圾回收时多个线程工作,Java应用程序中其他线程都要STW,直至回收完成。该垃圾回收器是Java8默认的垃圾回收器。

CMS垃圾回收器

又被成为并发垃圾回收器,全称为Concurrent Mark Sweep,是一款并发的、使用标记清除算法的垃圾回收器,用于对老年代垃圾的回收。采用该垃圾回收器对垃圾进行回收时,主要分为以下4个步骤:

(1)初始标记:从CG Roots出发标记可以直达的对象,这一阶段需要STW;

(2)并发标记:从上一阶段标记的对象出发,遍历所有的对象,标记所有可达的对象,这一阶段用户线程与垃圾线程并发执行;

(3)重新标记:在上一阶段,由于用户线程继续执行程序,因此对象的可达性可能发生变化,因此需要重新进行标记,该阶段需要短暂的STW;

(4)并发清除:清除未被标记的对象,回收它们占用的空间。

该算法具有并发收集、低停顿的优点,但也存在一些缺点:

(1)该算法使用标记清除算法,会产生碎片;

(2)该算法的并发能力依赖于CPU性能,并发回收时垃圾收集线程可能会抢占用户线程的资源,导致用户程序性能下降;

(3)并发清除阶段,用户线程依然在运行,会产生所谓的理"浮动垃圾",本次垃圾收集无法处理浮动垃圾,必须到下一次垃圾收集才能处理。如果浮动垃圾太多,会触发新的垃圾回收,导致性能降低。

G1垃圾回收器

G1将堆分为多个大小相等的Region,即区域,每个区域都可扮演eden、Survivor、老年代的角色,同时包含了存放大对象的Humongous区域,当对象超过Region大小的一半时则会被放到Humongous区域,每个Region大小可通过-XX:G1HeapRegionSize设定,大小为1-32MB,且为2的整数幂,G1把大多数Humongous视作老年代。

eden、Survivor、老年代间的对象移动和分代收集发一致,只是对于Survivor和eden都需要回收时,使用复制算法将这两个区域存活的对象复制到新的Survivor区,较老的对象晋升到老年代。

当老年代的对象占比超过阈值(一般为45%)时,触发并发标记,找到堆中的垃圾对象,该阶段垃圾回收线程与应用线程并发执行,不会导致应用进程停止。

在并发标记完成后,进行混合收集,G1会计算出哪些区域回收价值高,优先回收这些区域(一般是包含垃圾最多的区域),这种回收方式包括了部分新生代区域和老年代区域。选择回收成本低而收益高的区域进行回收,可以提高回收效率和减少停顿时间。

由于G1使用了复制算法,因此解决了CMS中的碎片问题。

各类引用的区别

强引用

指代码间普遍存在的赋值引用关系,只要引用关系还存在,垃圾回收器就不会回收这些被引用的对象。

软引用

是用来描述一些还有用,但非必须的对象。只被软引用关联着的对象,在系统将要发生内存溢出异常前,会把这些对象列进回收范围之中进行第二次回收,如果这次回收还没有足够的内存, 才会抛出内存溢出异常,一般使用SoftReference实现软引用。

弱引用

弱引用的对象只能生存到下一次垃圾回收启动时,无论内存是否充足,当垃圾回收器启动后,弱引用的对象都会被回收,一般使用WeekReference实现弱引用。

虚引用

必须配合引用队列使用,被引用对象回收时,会将虚引用入队,由Reference Handler线程调用虚引用相关方法释放直接内存,使用PhantomReference类来实现虚引用。

相关推荐
xintaiideas7 小时前
将堆内存的最小值(Xms)与最大值(Xmx)设置为相同的配置,可以防止JVM在运行过程中根据需要动态调整堆内存大小
jvm
依晴无旧8 小时前
JVM原理(十八):JVM虚拟机的编译器优化技术
jvm
碎像9 小时前
java并发编程 JUC-基础篇 快速入门
java·jvm·intellij-idea
资深流水灯工程师10 小时前
C++:构造函数是什么东西
开发语言·jvm·c++
万zp10 小时前
jvm 05JVM - 对象的创建 ,oop模型,字符串常量池
jvm
令人着迷10 小时前
JVM专题之走进类加载
jvm
-无-为-10 小时前
科普文:一文搞懂jvm实战(一)Runtime实时监控jvm
jvm·jvm监控·runtime
zouhuu13 小时前
面试题006-Java-JVM(下)
java·jvm·java面试题·java虚拟机面试题
程序猿小D16 小时前
第一百三十八节 Java数据类型教程 - Java boolean数据类型和float数据类型
java·开发语言·jvm·java-ee·eclipse·面向对象·数据类型
量化交易学徒1 天前
【DevOps】Java内存分配与JVM参数详解
java·开发语言·jvm·参数调优