JVM 垃圾回收

GC 过程

Java 的自动内存管理主要是针对对象内存的回收和对象内存的分配。同时,Java 自动内存管理最核心的功能是 堆 内存中对象的分配与回收

Java 堆是垃圾收集器管理的主要区域,因此也被称作GC 堆(Garbage Collected Heap).从垃圾回收的角度,由于现在收集器基本都采用分代垃圾收集算法,所以 Java 堆还可以细分为:新生代和老年代:再细致一点有:Eden 空间、From Survivor、To Survivor 空间等。进一步划分的目的是更好地回收内存,或者更快地分配内存。
在这里插入图片描述
大部分情况,对象都会首先在 Eden 区域分配,在一次新生代垃圾回收后,如果对象还存活,则会进入 s0 或者 s1,并且对象的年龄还会加 1(Eden 区->Survivor 区后对象的初始年龄变为 1)。对象晋升到老年代的年龄阈值,可以通过参数 -XX:MaxTenuringThreshold 来设置默认值,这个值会在虚拟机运行过程中进行调整,可以通过-XX:+PrintTenuringDistribution来打印出当次GC后的Threshold。
Hotspot 遍历所有对象时,按照年龄从小到大对其所占用的大小进行累积,当累积的某个年龄大小超过了 survivor 区的一半时,取这个年龄和 MaxTenuringThreshold 中更小的一个值,作为新的晋升年龄阈值, 晋升到老年代中

动态年龄计算的代码如下:

uint ageTable::compute_tenuring_threshold(size_t survivor_capacity) {
    //survivor_capacity是survivor空间的大小
    size_t desired_survivor_size = (size_t)((((double)survivor_capacity)*TargetSurvivorRatio)/100);
    size_t total = 0;
    uint age = 1;
    while (age < table_size) {
        //sizes数组是每个年龄段对象大小
        total += sizes[age];
        if (total > desired_survivor_size) {
            break;
        }
        age++;
    }
    uint result = age < MaxTenuringThreshold ? age : MaxTenuringThreshold;
    ...
}

经过这次 GC 后,Eden 区和"From"区已经被清空。这个时候,“From"和"To"会交换他们的角色,也就是新的"To"就是上次 GC 前的“From”,新的"From"就是上次 GC 前的"To”。不管怎样,都会保证名为 To 的 Survivor 区域是空的。Minor GC 会一直重复这样的过程,在这个过程中,有可能当次Minor GC后,Survivor 的"From"区域空间不够用,有一些还达不到进入老年代条件的实例放不下,则放不下的部分会提前进入老年代。

大对象直接进入老年代,大对象就是需要大量连续内存空间的对象(比如:字符串、数组)

长期存活的对象将进入老年代,既然虚拟机采用了分代收集的思想来管理内存,那么内存回收时就必须能识别哪些对象应放在新生代,哪些对象应放在老年代中。为了做到这一点,虚拟机给每个对象一个对象年龄(Age)计数器

针对 HotSpot VM 的实现,它里面的 GC 其实准确分类只有两大种:

  • 部分收集 (Partial GC):

    • 新生代收集(Minor GC / Young GC):只对新生代进行垃圾收集;
    • 老年代收集(Major GC / Old GC):只对老年代进行垃圾收集。需要注意的是 Major GC 在有的语境中也用于指代整堆收集;
    • 混合收集(Mixed GC):对整个新生代和部分老年代进行垃圾收集
  • 整堆收集 (Full GC):收集整个 Java 堆和方法区。

空间分配担保
空间分配担保是为了确保在 Minor GC 之前老年代本身还有容纳新生代所有对象的剩余空间

JDK 6 Update 24 之前,在发生 Minor GC 之前,虚拟机必须先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,那这一次 Minor GC 可以确保是安全的。如果不成立,则虚拟机会先查看 -XX:HandlePromotionFailure 参数的设置值是否允许担保失败(Handle Promotion Failure);如果允许,那会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试进行一次 Minor GC,尽管这次 Minor GC 是有风险的;如果小于,或者 -XX: HandlePromotionFailure 设置不允许冒险,那这时就要改为进行一次 Full GC

JDK 6 Update 24之后的规则变为只要老年代的连续空间大于新生代对象总大小或者历次晋升的平均大小,就会进行 Minor GC,否则将进行 Full GC。

在这里插入图片描述

上图中在JDK1.8+的版本中,JVM内存管理结构有了一定的优化调整。主要是方法区(持久代)取消变成了直接使用元数据区(直接内存)的方式

在👆的图中,我们也大致对整个垃圾回收系统进行了标注,这里主要涉及回收策略回收算法、垃圾回收器这几个部分。形象一点表述,就是JVM需要知道那些内存可以被回收,要有一套识别机制,在知道那些内存可以回收以后具体采用什么样的回收方式,这就需要设计一些回收算法,而具体的垃圾回收器就是根据不同内存区域的使用特点,采用相应地回收策略和算法的具体实现了。

在👆图中,我们也标注了不同垃圾回收器所适用的特定内存区域,对于JVM垃圾回收这块的优化,就是我们需要在了解这些垃圾回收算法、垃圾回收器特点后能够根据自己应用的场景选择合适的垃圾收集器,以及各区域垃圾收集器的搭配关系。下面我们就从这几个方面给大家介绍,JVM的垃圾回收相关的知识点。

回收策略

我们知道,JVM进行内存回收的主要目的是为了回收不再使用的内存,因为在进行JAVA程序编写时,我们只有new的操作,而不需要手动释放不再使用的空间,如果这些空闲内存不能及时被回收,很快我们的JVM内存空间就会泄露(新申请内存空间的操作失败,导致程序报错),所以回收不再使用的内存的目的则是为了及时释放空间,腾笼换鸟,以防止内存泄漏

我们知道在JVM中内存分配的基本粒度主要是对象、基本类型。而基本类型的使用主要是包括在对象中的局部变量,所以回收对象所占用的内存是JAVA垃圾回收的主要目标。

那么如何判断对象是处于可回收状态的呢?在主流的JVM中是采用“可达性分析算法”来进行判断的。

  1. 可达性分析算法

    这个算法的基本思路就是通过一系列的称为“GC Roots”的对象作为起始点,并从这些节点开始往下进行搜索,搜索走过的路径我们称之为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,我们就称之为对象引用不可达,则证明这个对象是不可用的,就可以暂时判定这个对象为可回收对象。示意图如下:
    在这里插入图片描述

    在图中虽然Obj F与Obj J之间互相有关联但是它们到GC Roots是不可达的,所以将会被判定为可回收对象。既然如此,什么样的对象可以作为GC Roots对象呢?

    在JAVA中可以被作为GC Roots的对象主要是:虚拟机栈-栈帧中的本地变量表所引用的对象、方法区(<JDK1.8)中类静态属性所引用的对象/常量属性所引用的对象、本地方法栈中引用的对象。

    注意:即使在可达性分析算法中不可达的对象,也并非是“非死不可”,这时候它们暂时处于“缓刑”阶段,要真正宣告一个对象死亡,至少要经历两次标记过程。

    第一次标记:如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链,那它将会被第一次标记;

    第二次标记:第一次标记后接着会进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。在finalize()方法中没有重新与引用链建立关联关系的,将被进行第二次标记。

    第二次标记成功的对象将真的会被回收,如果对象在finalize()方法中重新与引用链建立了关联关系,那么将会逃离本次回收,继续存活。

  2. 引用计数

    引用计数是垃圾收集器中的早期策略,给对象中添加一个引用计数器,每当有一个地方引用它,计数器就加 1;当引用失效,计数器就减 1;任何时候计数器为 0 的对象就是不可能再被使用的

    这个方法实现简单,效率高,但是目前主流的虚拟机中并没有选择这个算法来管理内存,其最主要的原因是它很难解决对象之间相互循环引用的问题。 所谓对象之间的相互引用问题,除了对象 objA 和 objB 相互引用着对方之外,这两个对象之间再无任何引用。但是他们因为互相引用对方,导致它们的引用计数器都不为 0,于是引用计数算法无法通知 GC 回收器回收他们。

java中的引用类型

无论是通过引用计数算法判断对象的引用数量,还是通过可达性分析算法判断对象的引用链是否可达,判定对象是否存活都与“引用”有关。在Java语言中,将引用又分为强引用、软引用、弱引用、虚引用4种,这四种引用强度依次逐渐减弱。

  1. 强引用

    在程序代码中普遍存在的,类似 Object obj = new Object() 这类引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。无论引用计数算法还是可达性分析算法都是基于强引用而言的

  2. 软引用

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

  3. 弱引用

    也是用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。

  4. 虚引用

    也叫幽灵引用或幻影引用,是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。它的作用是能在这个对象被收集器回收时收到一个系统通知。

如何判断一个常量是废弃常量
JDK1.7 之前运行时常量池逻辑包含字符串常量池存放在方法区, 此时 hotspot 虚拟机对方法区的实现为永久代
JDK1.7 字符串常量池被从方法区拿到了堆中, 这里没有提到运行时常量池,也就是说字符串常量池被单独拿到堆,运行时常量池剩下的东西还在方法区, 也就是 hotspot 中的永久代
JDK1.8 hotspot 移除了永久代用元空间(Metaspace)取而代之, 这时候字符串常量池还在堆, 运行时常量池还在方法区, 只不过方法区的实现从永久代变成了元空间(Metaspace)
假如在字符串常量池中存在字符串 “abc”,如果当前没有任何 String 对象引用该字符串常量的话,就说明常量 “abc” 就是废弃常量,如果这时发生内存回收的话而且有必要的话,“abc” 就会被系统清理出常量池了。

如何判断一个类是无用的类
该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例
加载该类的 ClassLoader 已经被回收
该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法

回收算法

在JVM中主要的垃圾收集算法有:标记-清除、标记-清除-压缩(简称**“标记-整理”)、标记-复制-清除(简称“复制”、分代收集算法**。这几种收集算法互相配合,针对不同的内存区域采取对应的收集算法实现(这里具体是由相应的垃圾收集器实现)。

  1. 标记-清除

    标记-清除算法是最为基础的一种收集算法,算法分为:“标记”和“清除”两个阶段。首先标记出所有需要回收的对象(标记的过程就是上面介绍过的根节点可达算法),在标记完后统一回收所有被标记对象占用的内存空间。
    在这里插入图片描述
    在这里插入图片描述
    这种收集算法的优点是简单直接,不会影响JVM进程的正常运行。而其缺点也是非常明显,首先,这样的回收方式会产生大量不连续的内存碎片,不利于后续连续内存的分配;其次,这种方式的效率也不高。

  2. 标记-复制-清除(复制)

    这种算法的思路是将可用的内存空间按容量划分为大小相等的两块,每次只使用其中一块。当这一块使用完了,就将还存活着的对象复制到另外一块上面(移动堆顶指针,按顺序分配内存),然后再把已使用过的内存空间一次清理掉。

    在这里插入图片描述
    在这里插入图片描述
    这种收集方式比较好的解决了效率和内存碎片的问题,但是会浪费掉一半的内存空间。目前此种算法主要用于新生代回收

    因为新生代的中98%的对象都是很快就需要被回收的对象,这一点大家在编程时可以体会到,所以并不需要1:1的比例来划分内存空间,在新生代中JVM是按照“8:1:1”的比例来将整个新生代内存划分为一块较大的Eden区和两块较小的Survivor区(S0、S1)。

    每次使用Eden区和其中一个Survivor区,当发生回收时将Eden区和Survivor区中还存活的对象一次性复制到另一块Survivor区上,最后清理掉Eden区和刚才使用过的Survivor区。理想情况下,每次新生代中的可用空间是整个新生代容量的90%(80%+10%),只会有10%的内存会被浪费。实际情况中,如果另外一个10%的Survivor区无法装下所有还存活的对象时,就会将这些对象直接放入老年代空间中(这块在后面的分代回收算法会说到,这里先了解下)。
    在GC开始的时候,对象只会存在于Eden区和名为“From”的Survivor区,Survivor区“To”是空的。
    紧接着进行GC,Eden区中所有存活的对象都会被复制到“To”,而在“From”区中,仍存活的对象会根据他们的年龄值来决定去向。
    年龄达到一定值(年龄阈值,可以通过-XX:MaxTenuringThreshold来设置)的对象会被移动到年老代中,没有达到阈值的对象会被复制到“To”区域。这个阈值是根据垃圾收集器来区分的,parallel Scavenge和parallel old默认是15,cms和G1默认是6;
    经过这次GC后,Eden区和From区已经被清空。这个时候,“From”和“To”会交换他们的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。不管怎样,都会保证名为To的Survivor区域是空的。Minor GC会一直重复这样的过程,直到“To”区被填满,“To”区被填满之后,会将所有对象移动到年老代中。

  3. 标记-整理

    如果在对象存活率较高的情况下,仍然采用复制算法的话,因为要进行较多的复制操作,效率就会变得很低,而且如果不想浪费50%的内存空间的话,就还需要额外的空间进行分配担保以应对存活对象超额的情况。显然老年代不能采用2 中的复制算法。

    根据老年代的特点,标记-清除-压缩(简称标记-整理)算法应运而生,这种算法的标记过程仍然与“标记-清除”算法一样,只是后续的步骤不再是直接清除可以回收的对象,而是将所有存活的对象都向一端移动后,再直接清理掉端边界以外的内存。
    在这里插入图片描述
    在这里插入图片描述

  4. 分代回收

    实际上在讲解复制算法时已经涉及到了分代回收的内容,这种算法根据对象存活周期的不同将内存划分为几块,Java中主要是新生代、年老代。这样就可以根据各个年代的特点,采用合适的收集算法了在文顶的图中已经标示,新生代采用了复制算法,而老年代采用了整理算法

垃圾收集器
年轻代使用的垃圾收集器
  1. Serial收集器
    Serial(串行)收集器是最基本、历史最悠久的垃圾收集器了。大家看名字就知道这个收集器是一个单线程收集器了。它的 “单线程” 的意义不仅仅意味着它只会使用一条垃圾收集线程去完成垃圾收集工作,更重要的是它在进行垃圾收集工作的时候必须暂停其他所有的工作线程( “Stop The World” ),直到它收集结束
    在这里插入图片描述
    虚拟机的设计者们当然知道 Stop The World 带来的不良用户体验,所以在后续的垃圾收集器设计中停顿时间在不断缩短(仍然还有停顿,寻找最优秀的垃圾收集器的过程仍然在继续)。

    但是 Serial 收集器有没有优于其他垃圾收集器的地方呢?当然有,它简单而高效(与其他收集器的单线程相比)。Serial 收集器由于没有线程交互的开销,自然可以获得很高的单线程收集效率。Serial 收集器对于运行在 Client 模式下的虚拟机来说是个不错的选择。

  2. ParNew收集器
    使用多条线程进行垃圾收集,其余行为和Serial收集器一样,可以和CMS收集器配合工作。可以通过设置-XX:+UseParNewGC进行使用,一般使用在server模式下。使用的是复制算法回收。
    在这里插入图片描述
    它是许多运行在 Server 模式下的虚拟机的首要选择,除了 Serial 收集器外,只有它能与 CMS 收集器(真正意义上的并发收集器,后面会介绍到)配合工作

  3. Parallel Scavenge
    前面两个收集器在于关注用户线程停顿时间,而这个收集器关注点在于达到一个可控的吞吐量【吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)】,适合在后台运算不需要太多交互的任务。可以通过设置-XX:+UseParallelGC进行使用,是在server模式下的默认收集器。使用的是复制算法回收。

老年代使用的垃圾收集器
  1. Serial Old收集器

    单线程收集器,进行垃圾收集的时候必须暂停其他所有工作线程,直到收集结束。client模式下的虚拟机默认的老年代收集器。可以通过设置-XX:+UseSerialOldGC进行使用,使用的是标记-整理算法回收。

  2. Parallel Old收集器

    配合年轻代为Parallel Scavenger收集器使用的,行为都与Parallel Scavenger收集器差不多只是算法不一样。同样适合在后台运算不需要太多交互的任务。可以通过设置-XX:+UseParallelOldGC进行使用。使用的是标记-整理算法回收。

  3. CMS收集器
    CMS收集器是一种 “标记-清除”算法实现的,它的运作过程相比于前面几种垃圾收集器来说更加复杂一些。整个过程可分为四个步骤:

    • 初始标记:需要虚拟机进行stop-the-world,初始标记仅仅标记GC Roots能直接关联到的对象,速度很快
    • 并发标记:进行GC Roots 追踪的过程,用户线程和GC线程并发执行,因为用户线程可能会不断的更新引用域,所以GC线程无法保证可达性分析的实时性。所以这个算法里会跟踪记录这些发生引用更新的地方;
    • 重新标记:重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录(采用多线程并行执行来提升效率);需要"Stop The World",且停顿时间比初始标记稍长,但远比并发标记短;
    • 并发清除:开启用户线程,同时GC线程开始对为标记的区域做清扫,回收所有的垃圾对象;
      在这里插入图片描述

    由于CMS是基于“标记+清除”算法来回收老年代对象的,因此长时间运行后会产生大量的空间碎片问题,可能导致新生代对象晋升到老生代失败。
    由于碎片过多,将会给大对象的分配带来麻烦。因此会出现这样的情况,老年代还有很多剩余的空间,但是找不到连续的空间来分配当前对象,这样不得不提前触发一次Full GC。
    为了解决空间碎片问题,CMS收集器提供−XX:+UseCMSCompactAlFullCollection标志,使得CMS出现上面这种情况时不进行Full GC,而开启内存碎片的合并整理过程:

    • 但合并整理过程无法并发,停顿时间会变长;
    • 默认开启(但不会进行,需要结合CMSFullGCsBeforeCompaction使用);

G1收集器 Garbage-First
G1 回收的目标不再是整个新生代或者是老年代。G1 可以回收堆内存的任何空间,不再是根据年代来区分,而是那块空间垃圾多就去回收,通过 Mixed GC 的方式去进行回收。
先看下堆空间的划分:
在这里插入图片描述
G1 垃圾回收器把堆划分成大小相同的 Region,每个 Region 都会扮演一个角色,分别为 H、S、E、O。

  • E 代表伊甸区
  • S 代表 Survivor 区
  • H 代表的是 Humongous 区
  • O 代表 Old 区

G1 的工作流程图:
在这里插入图片描述

  • 初始标记:标记出来 GC Roots 能直接关联到的对象,修改 TAMS 的值以便于并发回收时新对象分配
  • 并发标记:根据刚刚关联的对像扫描整个对象引用图,和用户线程并发执行,记录 SATB(原始快照) 在并发时有引用的值
  • 最终标记:处理第二步遗留下来的少量 SATB(原始快照) 记录,会发生 STW
  • 筛选回收:维护之前提到的优先级列表,根据优先级列表、用户设置的最大暂停时间来回收 Region

特点:

  • 并行与并发:G1 能充分利用多 CPU、多核环境下的硬件优势,可以通过并发的方式让 Java 程序继续执行,进一步缩短 STW 的时间。
  • 分代收集:分代概念在 G1 中依然得以保留,它能够采用不同的方式去处理新创建的对象和已经存活了一段时间、熬过多次 GC 的旧对象来获得更好的收集效果。
  • 空间整合:G1 从整体上看是基于标记-整理算法实现的,从局部(两个 Region 之间)上看是基于复制算法实现的,G1 运行期间不会产生内存空间碎片。
  • 可预测停顿:G1 比 CMS 厉害在能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为 M 毫秒的时间片段内,消耗在垃圾收集上的时间不得超过 N 毫秒。
    参数设置:
    指定使用G1收集器:
    "-XX:+UseG1GC"
    
    当整个Java堆的占用率达到参数值时,开始并发标记阶段;默认为45"-XX:InitiatingHeapOccupancyPercent"
    
    为G1设置暂停时间目标,默认值为200毫秒:
    "-XX:MaxGCPauseMillis"
    
    设置每个Region大小,范围1MB到32MB;目标是在最小Java堆时可以拥有约2048Region:
    "-XX:G1HeapRegionSize"
    
    新生代最小值,默认值5%:
    "-XX:G1NewSizePercent"
    
    新生代最大值,默认值60%:
    "-XX:G1MaxNewSizePercent"
    
    设置STW期间,并行GC线程数:
    "-XX:ParallelGCThreads"
    
    设置并发标记阶段,并行执行的线程数:
    "-XX:ConcGCThreads"
    
    运作过程:
    1. 初始标记
      仅标记一下GC Roots能直接关联到的对象;
      且修改NTAMS(Next Top at Mark Start),让下一阶段并发运行时,用户程序能在正确可用的Region中创建新对象;
      需要"Stop The World",但速度很快;
    2. 并发标记
      从GC Roots开始进行可达性分析,找出存活对象,耗时长,可与用户线程并发执行
      并不能保证可以标记出所有的存活对象;(在分析过程中会产生新的存活对象)
    3. 重复标记
      修正并发标记阶段因用户线程继续运行而导致标记发生变化的那部分对象的标记记录。
      上一阶段对象的变化记录在线程的Remembered Set Log;
      这里把Remembered Set Log合并到Remembered Set中;
      需要"Stop The World",且停顿时间比初始标记稍长,但远比并发标记短;
      G1采用多线程并行执行来提升效率;且采用了比CMS更快的初始快照算法:Snapshot-At-The-Beginning (SATB)
    4. 筛选回收
      首先排序各个Region的回收价值和成本;
      然后根据用户期望的GC停顿时间来制定回收计划;
      最后按计划回收一些价值高的Region中垃圾对象;
      回收时采用"复制"算法,从一个或多个Region复制存活对象到堆上的另一个空的Region,并且在此过程中压缩和释放内存;
      可以并发进行,降低停顿时间,并增加吞吐量;

在这里插入图片描述

补充

什么时候触发GC

Minor GC(young GC):

  • 当年轻代中eden区分配满的时候触发[值得一提的是因为young GC后部分存活的对象会已到老年代(比如对象熬过15轮),所以过后old gen的占用量通常会变高]

Full GC:

  • 手动调用System.gc()方法 [增加了full GC频率,不建议使用而是让jvm自己管理内存,可以设置-XX:+ DisableExplicitGC来禁止RMI调用System.gc]
  • 发现perm gen(如果存在永久代的话)需分配空间但已经没有足够空间
  • 老年代空间不足,比如说新生代的大对象大数组晋升到老年代就可能导致老年代空间不足。
  • 统计得到的Minor GC晋升到旧生代的平均大小大于老年代的剩余空间。
    这个比较难理解,这是HotSpot为了避免由于新生代晋升到老年代导致老年代空间不足而触发的FUll GC。
    比如程序第一次触发Minor GC后,有5m的对象晋升到老年代,姑且现在平均算5m,那么下次Minor GC发生时,先判断现在老年代剩余空间大小是否超过5m,如果小于5m,则HotSpot则会触发full GC(这点挺智能的)

GC是怎么判断对象是被标记的
通过枚举根节点的方式,通过jvm提供的一种oopMap的数据结构,简单来说就是不要再通过去遍历内存里的东西,而是通过OOPMap的数据结构去记录该记录的信息,比如说它可以不用去遍历整个栈,而是扫描栈上面引用的信息并记录下来。

总结:通过OOPMap把栈上代表引用的位置全部记录下来,避免全栈扫描,加快枚举根节点的速度,除此之外还有一个极为重要的作用,可以帮HotSpot实现准确式GC【这边的准确关键就是类型,可以根据给定位置的某块数据知道它的准确类型,HotSpot是通过oopMap外部记录下这些信息,存成映射表一样的东西】

cms收集器是否会扫描年轻代
会,在初始标记的时候会扫描新生代, 虽然cms是老年代收集器,但是我们知道年轻代的对象是可以晋升为老年代的,为了空间分配担保,还是有必要去扫描年轻代

为什么需要 Survivor
如果没有 Survivor 区,Eden 区每进行一次 Minor GC,存活的对象就会被送到老年代,老年代很快就会被填满。而有很多对象虽然一次 Minor GC 没有消灭,但其实或许第二次,第三次就需要被清除。这时候移入老年区,很明显不是一个明智的决定。

所以,Survivor 的存在意义就是减少被送到老年代的对象,进而减少老年代 GC 的发生。Survivor 的预筛选保证,只有经历 15 次 Minor GC 还能在新生代中存活的对象,才会被送到老年代。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值