JVM的垃圾回收机制了解

1.JVM的垃圾回收机制什么是垃圾(Garbage)?

垃圾是指在运行程序中没有任何指针指向的对象,这个对象就是需要回收的垃圾。

如果不及时对内存中的垃圾进行清理,那么这些垃圾对象所占的内存空间会一直保留到应用程序结束,被保留的空间无法被其他对象使用。甚至可能导致内存溢出(OOM)

内存溢出:通常出现在某一块内存空间耗尽的时候,Java中对OutOfMemoryError的解释是没有空闲内存,并且垃圾收集器也无法提供更多内存。常见的有堆溢出、直接内存溢出、永久内存溢出。

内存泄漏:严格来说,只有对象不会再被程序用到了,但是GC又不能回收他们的情况。例如:一些提供close方法的资源未关闭导致内存泄漏,数据库连接,网络连接和io连接都必须手动close,否则是不能被回收的。

2.为什么需要垃圾回收(Garbage Collector,GC)?

  1. 对于高级语言来说,一个基本认知就是如果不进行垃圾回收,内存迟早都会被消耗完,因为不断地分配内存空间而不进行回收,就好像不断地生产生活垃圾而从不打扫一样。
  2. 除了释放没用的对象,垃圾回收也可以清除内存里的记录碎片。碎片整理将所占用的堆内存移到堆的一端,以便JVM将整理出来的内存分配给新的对象。
  3. 随着应用程序所应付的业务越来越大、复杂,用户越来越多,没用GC就不能保证应用程序的正常运行。而经常造成STW的GC又跟不上实际的需求,所以才会不断地尝试对GC进行优化。

STW:Stop-The-World,是在垃圾回收算法执行过程中,将JVM内存冻结、应用程序停顿的一种状态。

在STW状态下,Java的所有线程都是停止执行的,GC线程除外,一旦STW发生,除了GC所需的线程外,其他线程都将停止工作,中断了的线程直到GC任务结束才继续它们的任务。STW是不可避免的,垃圾回收算法执行一定会出现STW,我们要做的只是减少停顿的时间。GC各种算法优化的重点,就是减少STW(暂停),同时这也是JVM优化的重点。

什么时候进入STW状态?

可达性分析算法中枚举根节点(GC Roots)会导致所有java执行线程停顿,进入STW状态。

GC Roots:特指的是GC的对象,GC会收集那些不是GC Roots且没有被GC Roots引用的对象。

为什么一定要STW停顿的原因?

  1. 分析工作必须在一个能确保一致性的快照中进行。
  2. 一致性指整个分析期间整个执行系统看起来像被冻结在某个时间点上。
  3. 如果出现分析过程中对象引用关系还在不断变化,则分析结果的准确性无法保证。
  4. 被STW中断的应用程序线程会在完成GC之后恢复,频繁的中断会让用户感觉卡顿。
  5. 所以我们要减少STW的发生,也就相当于要想办法降低GC垃圾回收的频率
  6. STW状态和采用哪款GC收集器无关,所有的GC收集器都有这个状态,因为要保证一致性。
  7. 但是好的GC收集器可以减少停顿的时间。
  8. 减少STW(暂停)降低GC垃圾回收的频率是调优的重点。

如果系统感觉卡顿很明显,大概率就是频繁执行GC垃圾回收,频繁进入STW状态产生停顿的缘故

3.Java中的垃圾回收

  1. Java中的自动内存管理,无需开发人员手动参与内存的分配与回收,这样降低内存泄漏和内存溢出的风险。
  2. Java中自动内存管理机制,将程序员从繁重的内存管理中释放出来,可以更加专心地专注于业务开发。
  3. Java堆是垃圾收集器管理的内存区域。也称为GC堆。

Java堆(Java Heap):对于java应用程序来说,Java堆是虚拟机所管理的内存中最大的一块。它是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,java世界里几乎所有的对象实例都在这里分配内存。

4.垃圾回收的相关算法

垃圾判断算法:

在堆里存放着几乎所有java对象实例,在GC(垃圾回收器)执行垃圾回收之前,首先需要区分出内存中哪些是存活对象(有用对象),哪些是死亡对象(垃圾对象)。只有被标记为已经死亡的对象,GC才会在执行垃圾回收时,释放掉其所占用的内存空间。因此这个过程我们可以称为垃圾标记阶段。那么在JVM中究竟是如何标记一个死亡对象呢?简单来说,当一个对象已经不再被任何的存活对象继续引用时,就可以宣告为已经死亡。

判断对象存活一般有两种方式:引用计数算法可达性分析算法

标记阶段:引用计数算法

引用计数算法:对每个对象保存一个整型的引用计数器属性,用于标记对象被引用的情况

优点:实现简单,垃圾对象便于辨识;判定效率高,回收没有延迟性。

缺点:1.它需要单独的字段存储计数器,这样的做法增加了存储空间的开销;

  1. 每次赋值都需要更新计数器,伴随着加法和减法操作,这增加了时间开销;
  2. 引用计数器有一个很严重的问题,即无法处理循环引用的情况。这是一条致命缺陷,导致在java的垃圾回收器中没有使用这类算法。

标记阶段:可达性分析算法

特点:

相较于引用计数算法而言,可达性分析算法不仅同样具备实现简单和执行高效等特点,更重要的是该算法可以有效地解决在引用计数算法中循环引用的问题,防止内存泄漏的发生。

相较于引用计数算法,这里的可达性分析就是Java、C#选择的。这种类型的垃圾回收通常也叫作追踪性垃圾收集

基本思路:
a.可达性分析是以根对象集合(GC Roots)为起始点,按从上至下的方式搜索被根对象集合所连接的目标对象是否可达。

b.使用可达性分析算法后,内存中存活的对象都会被根对象集合直接或间接连接着,搜索过的路径称为引用链。

c.如果对象没有和任何引用链相连,则是不可达的,就以为对象已经死亡,可以标记为垃圾对象。

d.在可达性分析算法中,只有能被根对象集合直接或间接连接的对象才是存活对象。

哪些对象可被称为GC Roots对象呢?或者说Java中,GC Roots包含哪几类对象呢?

虚拟机栈中引用的对象,比如:java线程中,当前所有正在被调用的方法的引用类型参数、局部变量等;

本地方法栈中引用的对象;

方法区中类静态属性引用的对象;

方法区中常量引用的对象,比如:字符串常量池里的引用;

所有被同步锁synchronized持有的对象;

Java虚拟机内部的引用,基本数据类型对应的class对象,一些常驻的异常对象,系统加载器。

注意:如果要使用可达性分析算法来判断内存是否可回收,那么分析工作必须在一个能保证一致性的快照中进行,这点不满足则分析结果的准确性就无法保证,这点也是导致GC进行时必须STW的一个重要原因。即使是号称几乎不会停顿的CMS垃圾回收器中,枚举根节点时也是必须要停顿的。

垃圾清除算法:

当成功区分出内存中存活对象和死亡对象后,GC接下来的任务就是执行垃圾回收、释放掉垃圾对象所占用的内存,以便有足够的可用空间为新对象分配内存。

目前在JVM中比较常见的三种垃圾回收算法是:

标记-清除算法(Mark-Sweep)、复制算法(Copying)、标记-压缩算法(Mark-Compact)

清除阶段:标记-清除算法

标记-清除算法是最基础的收集算法,它分为“标记”和“清除”两个阶段:

   标记:从GC Roots开始遍历,标记所有被引用的对象。一般是在对象头中记录是否是可达对象

   清除:对堆内存从头到尾遍历,如果发现某个对象的对象头中没有标记为可达对象,则将其回收

优点:不需要进行对象的移动,并且仅对不存货的对象进行处理,在存活对象比较多的情况下极为高效。

缺点:标记和清除过程的效率都不算高;这种需要使用一个空闲列表(空闲列表记录哪些内存是没有被占用状态,空闲的)来记录所有的空闲区域以及大小,对空闲列表的管理会增加分配对象时的工作量;标记清楚后会产生大量不连续的内存碎片。

清除阶段:标记-整理算法

标记:和标记清除算法一样,从GC Roots开始标记所有被引用的对象

整理:将所有的存活对象压缩到内存的一端,按顺序排放。之后清理外边界的空间(清理垃圾)

标记-整理算法的最终效果等同于标记-清除算法执行后,再进行一次内存碎片整理,因此也称为标记-清除-压缩算法

优点:消除了标记-清除算法中,内存区域分散的缺点(内存碎片)。

缺点:移动对象的同时,如果对象被其他对象引用,还需要调整引用的地址,移动过程中,需要全程暂停用户应用程序。即STW.

清除阶段:复制算法

核心思想:将内存空间分为两块,每次只使用其中一块,在垃圾回收时将正在使用的内存中的存活对象复制到未被使用的内存块中,之后清除正在使用的内存块中的对象,交换两个内存的角色,最后完成垃圾回收。

优点:没有标记和清除的过程,实现简单,运行高效;复制过去以后保证空间的连续性,不会出现碎片问题。

缺点:需要两倍的内存空间,比较浪费,如果存活对象较多,那么复制操作就比较多,效率相对会降低。

  • 18
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值