JVM:垃圾回收

概述

上一篇文章我们已经了解了 Java 的这几块内存区域。对于垃圾回收来说,针对或者关注的是 Java 堆这块区域。因为对于程序计数器、栈、本地方法栈来说,他们随线程而生,随线程而灭,所以这个区域的内存分配和回收可以看作具备确定性。对于方法区来说,分配完类相关信息后内存大小也基本确定了,加上在 JAVA8 中引入的元空间,所以这个部分也不用关注。

目的

对于堆中存储的那些不用的或者死掉的对象进行清理。

如何判断对象已死?

  • 引用计数器

    每当有一个地方引用它时,计数器的值就加一,如果引用失效时,计数器值减一。简单高效,但是没办法解决循环引用的问题。

  • 可达性分析算法

    这个算法的基本思路是通过一系列名为 GC ROOTS 的对象作为起始点,从这些节点开始向下搜索。当一个对象到 GC ROOTS 没有任何引用链时,则证明此对象不可用。可以作为 GC ROOTS 的对象包括下面几种?

    1. 方法里面引用的对象。
    2. 方法区中类静态属性引用的对象。
    3. 方法区中常量引用的对象。
    4. 本地方法中引用的对象。

    更为简单的理解方式为:

    • 当前各线程执行方法中的局部变量(包括形参)引用的对象
    • 已被加载的类的 static 域引用的对象
    • 方法区中常量引用的对象
    • JNI 引用

如何回收

当前的商业虚拟机的垃圾收集都采用分代垃圾回收的算法,这种算法并没有什么新的思想。只是根据对象的存活周期将不同的内存划分为几块。一般是把 Java 堆分为新生代老年代,根据新生代和老年代存活时间的不同采取不同的算法,使虚拟机的 GC 效率提高了很多。新生代采用复制算法,老年代采用标记-清除或者标记-整理算法。

回收算法

  • 标记-清除

    算法分为标记清除两个阶段,首先要标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。

    缺点:效率问题,标记和清除过程的效率都不高,另外会有不连续的内存碎片。

  • 复制

    为了解决效率问题,复制算法出现了,将内存按容量划分为大小相等的两块,每次只使用其中的一块。清除后将活着的对象复制到另外一块上面。简单高效。现在的商业虚拟机都采用这种收集算法来回收新生代。因为新生代中的对象98%都是朝生夕死的,所以并不需要按1:1划分内存,而是按8:1:1分为 Eden,survivor,survivor。每次只使用 Eden 和其中一块 Survivor。当回收时,将 Eden 和 Survivor 中还存活的对象一次性拷贝到另外一块 Survivor 上。

    8:1:1 是在复制算法的基础上改良而来的。

    当 Survivor 空间不够用时,需要依赖老年代进行分配担保。

    比较适合需要清除的对象比较多的情况。

    (图片来源于网络)

  • 标记-整理

    标记-整理算法和标记-清除算法的标记过程一样,后序有一个对内存进行整理的动作。和标记-整理算法一样,比较适合要清除对象不多的情况。复制算法在对象存活率较高时就要执行较多的复制操作,效率会变的很低。而且如果不想浪费 50% 的空间,就需要有额外的空间进行分配担保,以应对对象 100% 存活的极端情况,所以老年代一般不选复制算法,而选择标记-清除或者标记-整理算法。

新生代为什么按 8:1:1 分

因为新建出来的对象 98% 都是朝生夕死的,真正能在一轮 GC 之后留下的非常少,所以按照复制算法最初的 5:5 分是非常浪费空间的。所以将新生代分为 8:1:1 的 Eden survivor survivor。对象优先在 Eden 中分配,大多数情况下,对象在新生代 Eden 中分配,当 Eden 没有足够的空间进行分配时,虚拟机将发起一次 Minor GC

在 GC 开始的时候,对象只会存在于 Eden 区和名为 From 的 Survivor 区,名为 To 的 Survivor 区是空的。紧接着进行 GC,Eden 区中所有存活的对象都会被复制到 To,而在 From 区中,仍存活的对象会根据他们的年龄值来决定去向。年龄达到一定值(年龄阈值,可以通过 -XX:MaxTenuringThreshold 来设置)的对象会被移动到年老代中,没有达到阈值的对象会被复制到 To 区域。

经过这次 GC 后,Eden 区和 From 区已经被清空。这个时候,From 和 To 会交换他们的角色,也就是新的 To 就是上次 GC 前的 From ,新的 From 就是上次 GC 前的 To 。

另外还有两条对象分配策略是:

  1. 大对象直接进入老年代,大对象指的是那些需要连续内存空间的 Java 对象,最典型的大对象就是那种很长的字符串以及数组。直接进入老年代避免了大对象在 Eden 区和 Survivor 区之间发生大量的内存拷贝。
  2. 长期存活的对象将进入老年代,虚拟机给每个对象定义了一个对象年龄计数器,如果对象在 Eden 出生并经过一次 Minor GC 后仍然存活,并且能被 Survivor 容纳就会被移动到 Survivor 中,并且年龄增加 1。当年龄达到某个阙值(默认为 15)时,就会晋升到老年代。

对于新生代,需要选择速度比较快的垃圾回收算法,因为新生代的垃圾回收是频繁的。所以选择复制算法。

对于老年代,需要考虑的是空间,因为老年代占用了大部分堆内存,而且针对该部分的垃圾回收算法,需要考虑到这个区域的垃圾密度比较低。所以选择标记清除和标记整理算法。

看完了上面的知识点那么我们对什么时候进行 Minor GC,什么时候进行 Full GC 也就明白了。

Eden 满了进行 Minor GC,升到老年代的对象大于老年代剩余空间进行 Full GC

在讲 Minor GC 和 Full GC

大家在通过这张图来了解一下堆内存的划分,堆内存分为 Eden,Survivor 和 Old 空间嘛,如下图所示:

在年轻代进行的内存回收称为 Minor GC,对老年代进行的内存回收称为 Major GC,而 Full GC 是对整个堆进行的。Major GC的速度一般会比Minor GC慢10倍以上。

下面列出几种进行 Full GC 的条件。

  1. System.gc()

    这个方法的调用是建议 JVM 进行 Full GC。

  2. 老年代空间不足

    老年代只有新生代对象转入或者新建大对象的时候才会出现不足的情况,如果执行 Full GC 后空间仍然不足,那么则会抛出 OOM error 了。

  3. 永久代空间不足

    方法区中存放的是一些 class 的信息,常量,静态变量等数据,当系统中要加载的类,反射的类或调用的方法较多时,永久代可能会被填满。在为配置为采用 CMS GC 的情况下也会执行 Full GC。

  4. CMS GC promotion failed 和 concurrent mode failure

    对于采用 CMS 进行老年代 GC 的程序而言,尤其要注意 GC 日志中是否有 promotion failed 和 concurrent mode failure 两种状态,这两种状态可能会触发 Full GC。

  5. HandlePromotionFailure

    在发生 Minor GC 之前,虚拟机会先检查老年代的最大可用连续空间是否大于新生代所有对象总空间,如果条件成立,那么 Minor GC 时可以确保安全的,否则不成立。虚拟机会查看 HandlePromotionFailure 设置是否允许担保。如果允许,会检察老年代的连续可用空间是否大于历次晋升的平均大小,如果大雨,尝试着进行一次 Minor GC,尽管有风险,如果小于或者 HandlePromotionFailure 设置为不允许冒险,则要进行一次 Full GC。

垃圾收集器

如果说收集算法是内存回收的方法论,垃圾收集器就是内存回收的具体实现。下面介绍基于 HotSpot 虚拟机中的垃圾收集器。对于垃圾收集器,大家有个概念就可以了,没有必要去深究垃圾收集器的底层原理,当然如果有余力,了解底层原理当然是最好的。

  • Serial 收集器

    最早的垃圾收集器,回收新生代,单线程。这里的单线程不仅仅说明它只会使用一个 CPU 或者一条收集线程去完成垃圾收集工作,重要的是,在进行垃圾收集时,必须暂停其他所有工作线程(Stop The World)。

  • ParNew 收集器

    新生代垃圾回收,ParNew 收集器其实就是 Serial 收集器的多线程版本,在收集算法,Stop The World 和对象分配规则,回收策略上都与 Serial 相同。ParNew 在单核甚至双核 CPU 上的表现不如 Serial,更多的 CPU 才能体现他的优点。

  • Parallel Scanvnge 收集器

    新生代垃圾回收,采用复制算法,关注吞吐量,不关注停顿时间。停顿时间越短就越适合需要于用户交互的程序,良好的响应速度能提升用户的体验。高吞吐量则可以最高效率地利用 CPU 时间,尽快完成运算任务,适合在后台运算而不需要太多交互的任务。

  • Serial Old 收集器

    Serial 的老年代版本,单线程,使用标记-整理算法。

  • Parallel Old 收集器

    Parallel New 的老年代版本,使用标记-整理算法。

  • CMS 收集器

    CMS 是一种以获取最短回收停顿时间为目标的收集器,注重响应速度。基于标记-清除算法实现的。不同于其他收集器的全程 Stop The World,CMS 会有两次短暂的 Stop The World,垃圾收集和工作线程并发执行。整个过程分为 4 个步骤:

    1. 初始标记(Stop The World),标记 GC Roots 能关联到的对象。
    2. 并发标记
    3. 重新标记(Stop The World)
    4. 并发清除
  • G1 收集器

    基于标记-整理实现。可以实现在基本不牺牲吞吐量的前提下完成低停顿的内存回收,新生代和老年代都可以回收。

CMS 收集器

目前 CMS 时最常用的收集器(JDK8 的应用一般都切换到了 G1 收集器了)。这个收集器和其他收集器的区别是不会全程 Stop-The-World,可以做到垃圾回收线程和应用程序线程同时运行。

对于许多程序来说,吞吐量不如响应时间来的重要。通常年轻代的垃圾收集不会停顿多长时间,但是老年代的垃圾回收,虽然不频繁,但是可能会导致长时间的停顿,尤其是当堆内存比较大的时候。为了解决这个问题,HotSpot 虚拟机提供了 CMS 收集器,也叫做低延时收集器。

新生代使用 CMS 收集器

和其他新生代并行收集器一样,并行清除-> stop-the-world->复制。

老年代使用 CMS 收集器

在老年代的垃圾收集过程中,大部分收集任务和应用是并发执行的。

CMS 会有一段小停顿 stop-the-world,叫做初始标记阶段,用于确定 GC Roots。然后是并发标记阶段,标记 GC Roots 可达的存活对象,由于这个阶段应用也在运行,所以并发标记结束后,并不能标记所有的存活对象,所以需要在此停顿,再次标记阶段,遍历在并发标记阶段应用程序修改的对象,这次停顿会比较长,会使用多线程并行执行来增加效率

再次标记结束后,接下来进入并发清理阶段

CMS 是唯一不进行压缩的收集器,就是它使用标记清除算法。

原文地址:https://www.cnblogs.com/paulwang92115/p/12252851.html

时间: 2024-08-30 03:29:56

JVM:垃圾回收的相关文章

JVM垃圾回收算法 总结及汇总

先看一眼JVM虚拟机运行时的内存模型: 1.方法区 Perm(永久代.非堆) 2.虚拟机栈 3.本地方法栈 (Native方法) 4.堆 5.程序计数器 1 首先的问题是:jvm如何知道那些对象需要回收 ? 目前两种标识算法.三种回收算法.两种清除算法.三种收集器 引用计数法 每个对象上都有一个引用计数,对象每被引用一次,引用计数器就+1,对象引用被释放,引用计数器-1,直到对象的引用计数为0,对象就标识可以回收 这个可以用数据算法中的图形表示,对象A-对象B-对象C 都有引用,所以不会被回收,

JVM垃圾回收算法(最全)

JVM垃圾回收算法(最全) 下面是JVM虚拟机运行时的内存模型: 1.方法区 Perm(永久代.非堆) 2.虚拟机栈 3.本地方法栈 (Native方法) 4.堆 5.程序计数器 1 首先的问题是:jvm如何知道那些对象需要回收 ? 目前两种标识算法.三种回收算法.两种清除算法.三种收集器 引用计数法 每个对象上都有一个引用计数,对象每被引用一次,引用计数器就+1,对象引用被释放,引用计数器-1,直到对象的引用计数为0,对象就标识可以回收 这个可以用数据算法中的图形表示,对象A-对象B-对象C

JVM垃圾回收机制

JVM采用分代的垃圾回收策略:不同的对象的生命周期是不一样的.因此,不同生命周期的对象可以采取不同的收集方式,以便提高回收效率. 从垃圾回收角度看内存分配 从JVM垃圾回收的角度来看,Java内存分为三个区:新生代(Young Generation).老年代(Old Generation)和持久代(Permanent Generation),如下图. 新生代: 所有新生成的对象首先都是放在年轻代的.年轻代分三个区.一个Eden区,两个Survivor区(一般而言).大部分对象在Eden区中生成.

JDK分析工具&JVM垃圾回收(转)

转自:http://blog.163.com/[email protected]/blog/static/10510751320144201519454/ 官方手册:http://docs.oracle.com/javase/7/docs/     ---->http://docs.oracle.com/javase/7/docs/technotes/tools/solaris/java.html   java命令的各种选项的说明 参考书籍: <深入理解Java虚拟机:JVM高级特性与最佳实践

浅析JVM垃圾回收机制

首先我们需要知道Java的内存分配与回收全部由JVM垃圾回收机制自动完成.每种JVM实现可能采用不同的方法实现垃圾回收机制.在收购SUN之前,Oracle使用的是JRockit JVM,收购之后使用HotSpot JVM.目前Oracle拥有两种JVM实现并且一段时间后两个JVM实现会合二为一.HotSpot JVM是目前Oracle SE平台标准核心组件的一部分.市面上探讨垃圾回收机制,默认都是基于HotSpot JVM的.Ok,我们切入正题,先来看下JVM的内存区域模型, 这张图非常直观的展

JVM内存管理和JVM垃圾回收机制

JVM内存管理和JVM垃圾回收机制(1) 这里向大家描述一下JVM学习笔记之JVM内存管理和JVM垃圾回收的概念,JVM内存结构由堆.栈.本地方法栈.方法区等部分组成,另外JVM分别对新生代和旧生代采用不同的垃圾回收机制. AD: 你对JVM内存组成结构和JVM垃圾回收机制是否熟悉,这里和大家简单分享一下,希望对你的学习有所帮助,首先来看一下JVM内存结构,它是由堆.栈.本地方法栈.方法区等部分组成,结构图如下所示. JVM学习笔记 JVM内存管理和JVM垃圾回收 JVM内存组成结构 JVM内存

JVM垃圾回收机制与算法

JVM内存由几个部分组成:堆.方法区.栈.程序计数器.本地方法栈 JVM垃圾回收仅针对公共内存区域,即:堆和方法区进行,因为只有这两个区域在运行时才能知道需要创建些对象,其内存分配和回收都是动态的. 一.垃圾回收策略 1.1分代管理 将堆和方法区按照对象不同年龄进行分代: (Young Generation and Old Generation)堆中会频繁创建对象,基于一种分代的思想,按照对象存活时间将堆划分为新生代和旧生代两部分,并不是一次垃圾回收新生代存活的对象就放入旧生代, 而是要经过几次

JVM垃圾回收算法总结

整理了一下JVM垃圾回收的分代回收算法,旨在能够以后能够快速熟悉这些算法,而不用去查找大量资料(可以认为是偷懒),也是为了分纤箱一下自己的一些理解,有不足或错误之处,希望大家指正,共同进步!1.分代回收算法分代回收算法是标记-复制算法和标记-整理算法(标记-清楚)的集合,朱亚平是对新生代和老年代分别进行处理:在介绍分代回收算法之前首先介绍一下标记-复制算法.标记-清除和标记-整理算法:1)标记-复制算法:将可用内存分为两部分,只用其中的一部分,当这部分内存用完时,会将存活的对象复制到另一部分内存

JVM垃圾回收 GC

一.判断对象是否存活 1.引用计数算法 给对象添加一个引用计数器,每当一个地方引用了该对象,计数器加1,:当引用失效,计数器减1.当计数器为0表示该对象已死,可回收.但是无法解决两个对象互相引用的情况 2.可达性分析算法 通过一系列称为的GC Roots对象为起点,从这些节点往下搜索,搜索走过的路径为引用链,当一个对象到GC Roots没有任何引用链相连是,即对象到GC Roots不可达,则证明对象已死,可回收 可作为GC Roots的对象包括:虚拟机栈中引用的对象,本地方法栈中Native方法

必知必会JVM垃圾回收——对象搜索算法与回收算法

垃圾回收(GC)是JVM的一大杀器,它使程序员可以更高效地专注于程序的开发设计,而不用过多地考虑对象的创建销毁等操作.但是这并不是说程序员不需要了解GC.GC只是Java编程中一项自动化工具,任何一个工具都有它适用的范围,当超出它的范围的时候,可能它将不是那么自动,而是需要人工去了解与适应地适用. 拥有一定工作年限的程序员,在工作期间肯定会经常碰到像内存溢出.内存泄露.高并发的场景.这时候在应对这些问题或场景时,如果对GC不了解,很可能会成为个人的发展瓶颈. 接下来的两文将详细学习下JVM中垃圾