JVM概述

目录

JVM概述

为什么学习jvm?

虚拟机

jvm作用

jvm组成部分

类加载器

作用

类加载过程

类什么时候会被加载(初始化)

类卸载

类加载器

类加载器规则

双亲委派机制

打破双亲委派机制

运行时数据区

1.程序计数器

2.本地方法栈

3.Java虚拟机栈

4.堆

基本作用特征

堆空间的分区

为什么要分区

堆空间的配置比例

5.方法区

方法区,堆,栈交互关系

方法区大小设置

方法区的内部结构

方法区的垃圾回收

字符串常量池

为什么将字符串常量池移动到堆中?

本地方法接口

执行引擎

对象的创建(重要!重要!重要!)

对象引用

垃圾回收

概述

分代收集思想 Minor GC、Major GC、Full GC

大对象直接进入老年代

什么样的对象是垃圾

为什么需要GC?

内存溢出和内存泄漏

自动内存管理

垃圾回收相关算法

标记阶段

引用计数算法(在现代的jvm中并没有被使用)

可达性分析算法/根搜索算法

GC Roots 可以是哪些元素?

对象的 finalization 机制

垃圾回收阶段的算法

标记-清除算法

标记-复制算法

标记-压缩算法(标记-整理)

垃圾回收器

垃圾回收器分类

Serial收集器

ParNew收集器

Serial Old收集器

Parallel Scavenge收集器

Parallel Old收集器

CMS收集器

G1收集器

ZGC收集器


JVM概述

为什么学习jvm?

Java Virtual Machine Java虚拟机

面试的需要,基础部分不能区分基础好与坏

对Java程序运行的过程更了解

为后期写出优质代码做好准备

虚拟机

在windows中,虚拟一个运行环境

分为系统虚拟机VMware,程序虚拟机JVM

jvm作用

负责将字节码加载到内存中(运行时数据区)

负责存储数据

把字节码翻译为机器码,执行

垃圾回收

jvm组成部分

  1. 类加载器(负责加载字节码文件)

  2. 运行时数据区(存储运行时数据, 堆, java虚拟机栈(运行java自己方法), 方法区, 程序计数器, 本地方法栈)

  3. 执行引擎(更底层, 把字节码翻译为机器码)

  4. 本地方法接口

  5. 垃圾回收

类加载器

作用

负责从硬盘/网络中加载字节码信息,加载到内存中(运行时数据区的方法区中)

每个Java类都有一个引用指它的ClassLoader

数组类不是通过ClassLoader创建的(数组类没有对应的二进制字节流),是由JVM直接生成

类加载过程

加载

使用IO读取字节码文件

转换并存储,为每个类创建一个Class类的对象

存储在方法区中

链接(验证、准备、解析)

验证:对字节码文件格式进行验证,文件是否被污染

对基本的语法格式进行验证

验证阶段阶段主要由四个检验阶段组成:

准备:为静态的变量进行内存分配

public static int value = 123;value在准备阶段后的初始值是0,而不是123

静态常量在编译期间就初始化

特殊情况:比如给 value 变量加上了 final 关键字public static final int value=111 ,那么准备阶段 value 的值就被赋值为 111。

解析:将符号引用转为直接引用,将字节码中的表现形式转为内存中表现(内存地址),也就是得到类或者字段、方法在内存中的指针或者偏移量

举个例子:在程序执行中,系统需要明确知道这个方法所在位置,Java虚拟机为每个类都准备了一张方法表来存放该类的所有方法。当需要该类调用一个方法的时候,只要知道这个方法在表中的偏移量就可以使用该方法。通过解析符号引用可以直接转变为目标方法在方法表的位置。

初始化

类的初始化,为类中定义的静态变量赋值

public static int value = 123;value在初始化阶段后值是123

类什么时候会被加载(初始化)

  1. 在类中运行main方法

  2. 创建对象

  3. 使用类中的静态变量,静态方法

  4. 反射 Class.forName("类的地址")

  5. 子类被加载

    以下两种情况类不会被初始化:

static final int b = 20; 编译期间赋值的静态常量
System.out.println(User.b);
User[] users = new User[10]; 作为数组类型存在

类卸载

卸载类即该类的Class被GC

卸载类需要满足3个要求:

  • 该类的所有实例对象已被GC,也就是堆中不存在该类的实例对象

  • 该类没有在其他任何地方被引用

  • 该类的类加载器的实例已经被GC

类加载器

具体的负责加载类的一些代码

  1. BootstrapClassLoader(启动类加载器):用c/c++语言开发的,jvm底层的开发语言,负责加载java核心类库,与java语言无关

  2. ExtensionClassLoader(扩展类加载器)

    java语言编写的,由sun.misc.Launcher$ExtClassLoader实现,继承ClassLoader类,从JDK系统安装目录的jre/lib/ext子目录(扩展目录)下加载类库

  3. AppClassLoader(应用程序类加载器)

    java语言编写的,面向用户的加载器,派生于ClassLoader类,加载程序中自己开发的类

  4. 自定义类加载器

public class ClassLoaderDemo {
​
    public static void main(String[] args) {
​
        new String();// 逐级向上委托 最终引导类加载器找到了系统中真正的String
​
        ClassLoader classLoader = String.class.getClassLoader();
        System.out.println(classLoader);// null 是由引导类加载器加载的
​
        ClassLoader classLoader1 = ClassLoaderDemo.class.getClassLoader();
        System.out.println(classLoader1);//sun.misc.Launcher$AppClassLoader@18b4aac2
​
        ClassLoader parent = classLoader1.getParent();
        System.out.println(parent);//sun.misc.Launcher$ExtClassLoader@14ae5a5
​
        ClassLoader parent1 = parent.getParent();
        System.out.println(parent1);//null 引导类加载器
​
    }
}

类加载器规则

JVM启动的时候并不会一次性加载所有的类,而是动态加载,根据需要去加载,这样对内存也更加友好。

对于已经加载的类会放ClassLoader中。在类加载的时候,系统会去先判断当前类是否被加载过,加载过的直接返回,否则才会尝试去加载。也就是说,对于一个类加载器而言,相同名称的二进制类只会被加载一次

双亲委派机制

加载一个类时,先委托给父类加载器加载,如果父类加载器没有找到,继续向上委托,直到引导类加载器.父级找到就返回,父级如果最终没找到,就委派给子级加载器,

最终没有找到,报ClassNotFoundException

为了先确保加载系统类

优点:安全,避免用户自己编写的类替换java核心类,例如java.lang.String

避免类重复加载,父级已经加载该类,就没必要子级再加载

双亲委派机制,是Java提供的类加载的规范,但不是强制不能改变的

我们可以通过自定义的类加载器,改变加载方式

打破双亲委派机制

实现自定义的类加载,继承ClassLoader,如果不想打破双亲委派机制,那就重写findClass()方法即可,无法被父类加载的类都会通过此方法加载

如果想打破双亲委派机制,需要重写loadClass()方法,重写后我们就可以改变传统的双亲委派,在子类委派给父类加载前,自己先加载此类。在尝试从其他地方加载

典型的tomcat中,加载部署在tomcat中的项目时,就使用的是自己的类加载器,优先加载Web目录下的类,然后加载其他目录下的类,自定义了WebAppClassLoader来打破这种机制

运行时数据区

1.程序计数器

是一块很小的内存空间,用来记录每个线程运行的指令位置,字节码解释器通过改变程序计数器来一次读取指令,从而实现代码的流程控制

是线程私有的,每个线程都拥有一个程序计数器,生命周期与线程一致

是运行时数据区中,唯一一个不会出现内存溢出的空间

运行速度最快

2.本地方法栈

用来运行本地方法的区域,为虚拟机使用到的 Native 方法服务

是线程私有的

空间大小可以调整

可能会出现栈溢出以及内存溢出

3.Java虚拟机栈

基本运行特征:

栈是运行单位,管理方法的调用运行

是用来运行java方法的区域

可能会出现栈溢出(StackOverFlowError)以及内存溢出(OutOfMemoryError)

  • StackOverFlowError:若栈的内存大小不允许动态扩展,那么当线程请求栈的深度超过Java虚拟机栈的深度,就会抛出该异常,例如死循环

  • OutOfMemoryError:如果栈的内存大小允许动态扩展,但是虚拟机在动态扩展的时候无法申请到足够的内存空间,就会抛出该异常

是线程私有的

生命周期和线程相同,创建而创建,死亡而死亡

运行原理

先进后出的结构,每一次方法调用的时候都会有一个对应的栈帧被压入栈中,方法调用结束后,都会有一个栈帧被弹出

最顶部的称为当前栈帧

栈帧结构

一个栈帧包含:

局部变量表(存储在方法中声明的变量以及编译期可知的各种数据类型)

操作数栈(实际计算运行,计算过程中产生的临时变量也会放在这里)

动态链接(为了将符号引用转为调用方法的直接引用这个过程)

方法返回地址

4.堆

基本作用特征

是存储空间,用来存储对象,几乎所有的实例对象和数组都在这里分配内存,是内存空间最大的一块区域

在jvm启动时就被创建,大小可以调整(jvm调优)

本区域是存在垃圾回收的,是线程共享的,会出现OutOfMemoryError异常

堆空间的分区

  • 年轻代(新生区/新生代)

    • 伊甸园区(对象刚刚创建存储在此区域)

    • 幸存者1区

    • 幸存者2区

  • 老年代(老年区)

为什么要分区

  • 可以根据对象存活的时间放在不同的区域,可以区别对待

  • 频繁回收年轻代,较少回收老年代

创建对象,在堆内存中分布

1.新创建的对象,都存储在伊甸园区

2.当垃圾回收时,将伊甸园中垃圾对象直接销毁,将存活的对象移动到幸存者1区

3.之后创建的新对象还是存储在伊甸园区,再次垃圾回收到来时,将伊甸园中的存活对象移动到幸存者2区,同样将幸存者1区的存活对象移动到幸存者2区,每次保证一个幸存者区为空的,相互转换

4.每次垃圾回收时,都会记录此对象经历的垃圾回收次数,当一个对象经历过15次回收,仍然存活,就会被移动到老年代

垃圾回收次数,在对象头中有一个4bit的空间记录 最大值只能是15

5.老年区回收次数较少,当内存空间不够用时,才会去回收老年代

堆空间的配置比例

默认的新生代与老年代的比例:1:2 可以通过 -XX:NewRatio=2 进行设置

如果项目中生命周期长的对象较多,就可以把老年代设置更大

在新生代中,伊甸园区和两个幸存者比例:8:1:1

可以通过-XX:SurvivorRatio=8进行设置

对象垃圾回收的年龄-XX:MaxTenuringTreshold,默认是15

5.方法区

作用: 主要用来存储加载的类信息, 以及即时编译期编译后的信息, 以及运行时常量池

特点: 在jvm启动时创建,大小也是可以调整,是线程共享,也会出现内存溢出

方法区,堆,栈交互关系

方法区存储类信息(元信息)

堆中存储创建的对象

栈中存储对象引用

方法区大小设置

-XX:MataspaceSize 设置方法区的大小

windows jdk默认的大小是21MB

也可以设置-XX:MaxMataspaceSize为-1,没有限制 就可以使用计算机内存

可以将初始值设置较大一点,减少了Full GC发生

方法区的内部结构

类信息

即时编译期编译后的信息,

运行时常量池(指的是类中各个元素的编号)

方法区的垃圾回收

在FULL GC时方法区发生垃圾回收

主要是回收类信息,类信息回收条件比较苛刻,满足以下3点即可:

1.在堆中,该类及其子类的对象都不存在了

2.该类的类加载器不存在了

3.该类的Class对象不存在了

也可以认为了类一旦被加载,就不会被卸载

特点总结

程序计数器,java栈,本地栈是线程私有的

程序计数器不会出现内存溢出

java栈,本地栈可能会出现内存溢出

java栈,本地栈大小是可以调整的

堆,方法区是线程共享的,是会出现垃圾回收的

字符串常量池

是JVM为了减少内存消耗专门为字符串开辟的一个区域,主要目的是为了避免字符串重建

// 在堆中创建字符串对象”ab“
// 将字符串对象”ab“的引用保存在字符串常量池中
String aa = "ab";
// 直接返回字符串常量池中字符串对象”ab“的引用
String bb = "ab";
System.out.println(aa==bb);// true

在jdk7之后,将字符串常量池的位置从方法区转移到堆空间中

为什么将字符串常量池移动到堆中?

因为方法区的GC回收频率太低,只在FULL GC的时候进行GC,Java程序中通常有大量的字符串创建死亡,将字符串放到堆中,能够高效及时的回收

本地方法接口

什么是本地方法

native关键字修饰的方法称为一个本地方法,没有方法体

hashCode();

为什么用本地方法

java语言需要与外部的环境进行交互(例如需要访问内存,硬盘,其他的硬件设备),直接访问操作系统的接口即可

java的jvm本身开发也是在底层使用了C语言

执行引擎

作用:将加载到内存中的字节码(不是直接运行的机器码),解释/编译为不同平台的机器码,

.java-->编译-->.class 在开发期间,由jdk提供的编译期(javac)进行源码编译(前端编译)

.class(字节码)-->解释/编译-->机器码(后端编译,在运行时,由执行引擎完成的)

解释器:将字节码逐行解释执行,效率低

编译器(JIT just in time 即时编译器):将字节码编译,缓存起来,执行更高效,不会立即使用编译器

将一些频繁执行的热点代码进行编译,并缓存到方法区中,以后执行效率提高了

程序启动后,先使用解释器立即执行,省去了编译时间

程序运行一段时间后,对热点编译缓存,提高后续执行效率

采用的解释器和编译器结合的方案

对象的创建(重要!重要!重要!)

  1. 类加载检查:虚拟机遇到一个new指令,会先去检查在常量池中是否能找到其符号引用,并且检查是否被类加载过,如果没有,需要先进行类加载

  2. 分配内存:类加载检查通过后,就开始给对象分配内存。对象所需内存大小在类加载过后便可确定

  3. 初始化零值:内存分配完后,虚拟机需要将分配到内存空间都初始化零值(不包括对象头),这一步是为了实例字段在Java代码中不赋初值就可以使用,程序可以直接访问这些字段的零值

  4. 设置对象头:初始化零值后,虚拟机对对象开始设置对象头,包含分代年龄,哈希码,类的元数据信息等

  5. 执行init方法:上面的操作一个新的对象诞生,但是Java的角度还需要执行init方法,将对象按照程序员的意愿进行初始化

对象引用

强引用 即对象有引用指向的,例如Object obj=new Object()

这种情况下new出来的对象 哪怕报OOM,都不会被垃圾回收

区别于软、弱、虚引用

软、弱、虚引用都是用来标记对象的一状态

当一些对象称为垃圾后,还需要有不同的状态,可以继承SoftReference、WeakReference、PhantomReference

或者把自己的对象添加到软、弱、虚对象中

软引用(SoftReference) 如果内存充足的情况下,可以保留软引用的对象

如果内存不够,在进行一次垃圾回收后,还是不够,那么就会清除软引用对象

弱引用(WeakReference) 弱引用管理的对象 无论内存空间是否充足,只要垃圾回收发现,就会回收它

虚引用(PhantomReference) 相当于没有任何引用 只是为了系统检测,能在这个对象被收集器回收时收到一个系统通知。也就是跟踪对象被垃圾回收的活动的。

垃圾回收

概述

java是支持自动垃圾回收,有些语言不支持需要手动

自动垃圾回收不是java语言首创的

垃圾回收关系的问题:

哪些区域需要回收 堆 方法区

什么时候回收

如何回收

java自动垃圾回收经过长时间的发展,已经非常强大

分代收集思想 Minor GC、Major GC、Full GC

我们知道,对象会优先在Eden区分配,如果伊甸园区空间满了,会先进行Minor GC/yong GC,对年轻代进行垃圾回收称为 Minor GC/yong GC 是频繁进行回收

但是在Minor GC前,会进行空间分配担保,确保老年代有足够的空间容纳新生代的对象,有的话进行Minor GC,不够的话会进行FULL GC

如果Minor GC后,伊甸园区还是不够,会将对象放在幸存者区,若不够,就会直接放在老年代中,后面如果还有新生对象,还是放在伊甸园区中

老年代中如果空间不够,则会进行FULL GC,对老年代进行垃圾回收称为 Major GC/ old GC 回收的次数较少

总结:针对虚拟机垃圾收集主要分为两种:

  • 部分收集

    • 新生代收集(Minor GC / Young GC):只针对新生代的垃圾收集

    • 老年代收集(Major GC / Old GC):只对老年代收集,

    • 混合收集(Mixed GC):对整个新生代和部分老年代收集

  • 整堆收集 (Full GC):收集整个 Java 堆和方法区(尽量避免)

大对象直接进入老年代

大对象就是需要大量连续内存空间的对象,例如字符串、数组

虚拟机要求大对象直接进入老年代,旨在减少新生代垃圾回收频率和成本

什么样的对象是垃圾

在运行过程中,没有被任何引用指向的对象,被称为垃圾对象

为什么需要GC?

如果不及时清理这些垃圾对象,会导致内存溢出

在回收时,还可以将内存碎片进行整理.(数组必须是连续空间的)

内存溢出和内存泄漏

内存溢出:经过垃圾回收后,内存中仍然无法存储新创建的对象,内存不够用溢出了

内存泄漏:IO流 close jdbc close 没有关闭,生命周期很长的对象, 一些不用的对象,但是垃圾回收器不能判定为垃圾,这些对象就默默地占用着内存,称为内存泄漏,大量的此类对象存在,也是导致内存溢出的原因

自动内存管理

好处:解放程序员,对内存管理更合理,自动化

坏处:对程序员管理内存的能力降低了,解决问题的能力变弱了,不能调整垃圾回收的机制

垃圾回收相关算法

标记阶段

作用:判断对象是否是垃圾对象, 是否有引用指向对象.

相关的标记算法:引用计数算法和可达性分析算法

引用计数算法(在现代的jvm中并没有被使用)

有个计数器来记录对象的引用数量

String s1 = new String("aaa");
        String s2 = s1;//有两个引用变量指向aaa对象
        s2 = null;  -1
        s1 = null;  -1

优点:实现简单,效率高

缺点:

需要维护计数器,占用空间,频繁操作计数器需要时间开销

无法解决循环引用问题.多个对象之间相互引用,没有其他外部引用指向他们,计数器都不为0,不能回收,产生内存泄漏

public class ReferenceCountingGc {
    Object instance = null;
    public static void main(String[] args) {
        ReferenceCountingGc objA = new ReferenceCountingGc();
        ReferenceCountingGc objB = new ReferenceCountingGc();
        objA.instance = objB;
        objB.instance = objA;
        objA = null;
        objB = null;
    }
}
可达性分析算法/根搜索算法

实现思路:从一些为根对象(GCRoots)的对象出发去查找,与根对象直接或间接连接的对象就是存活对象,不与根对象引用链连接的对象就是垃圾对象,此方法可以解决循环引用问题

GC Roots 可以是哪些元素?
  • 在虚拟机栈(栈中的局部变量表)中引用的对象

  • 在本地方法栈中引用的对象

  • 在方法区中存储的静态成员指向的对象

  • 方法区中常量引用的对象

  • 所有被同步锁持有的对象

对象的 finalization 机制

当一个对象被标记为垃圾后,在真正被回收之前,会调用一次Object类中的finalize(). 是否还有逻辑需要处理

自己不要在程序中调用finalize(),留给垃圾回收器调用

有了finalization机制的存在,在虚拟机中把对象状态分为3种:

  1. 可触及的 不是垃圾,与根对象连接的

  2. 可复活的 判定为垃圾了,但是还没有调用finalize(),(在finalize()中对象可能会复活)

  3. 不可触及的 判定为垃圾了,finalize()也被执行过了,这种就是必须被回收的对象

垃圾回收阶段的算法

标记-清除算法

首先标记出所有不需要回收的对象,在标记完后统一回收掉所有没有被标记的对象。也是最基础的收集算法

优点:实现简单

缺点:标记和清除两个过程效率不高,清除后会产生大量不连续的内存碎片

标记-复制算法

将内存分为大小相等的两份空间, 把当前使用的空间中存活的对象, 复制到另一个空间中,将正在使用的空间中垃圾对象清除

优点:减少内存碎片

缺点:如果需要复制的对象数量多,效率就低

适用场景:存活对象少 新生代适合使用标记复制算法

标记-压缩算法(标记-整理)

标记过程还是标记不需要回收的对象,然后将所有存活的对象向一端移动,清理边界外的所有空间,可以认为是标记-清除算法执行完后,进行了一次内存碎片整理

优点:消除了内存区域分散特点,消除了内存减半高额代价,适合老年代这种收集不频繁的场景

缺点:效率低于复制算法

如果对象被其他对象引用,还需调整引用的地址

需要全程暂停用户应用程序,即STW

垃圾回收器

垃圾收集器是垃圾回收的实际实现者,垃圾回收算法是方法论

垃圾回收器分类

Serial收集器

串行收集器,是一个单线程的收集器,垃圾收集只会使用一个线程去完成垃圾收集工作。所以其在进行垃圾收集的时候需要暂停其他所有的工作线程(Stop The World),直到它收集结束

新生代采用标记-复制算法,老年代采用标记-整理算法。

优点:相比于其他收集器的单线程相比更加简单高效,因为没有线程交互的开销,对于运行在 Client 模式下的虚拟机来说是个不错的选择。

ParNew收集器

ParNew 收集器是Serial收集器的多线程版本,除了使用多线程进行垃圾回收,其他行为和Serial收集器完全一样

优点:是许多运行在 Server 模式下的虚拟机的首要选择,除了Serial 收集器外,只有它能与 CMS 收集器配合工作

Serial Old收集器

Serial 收集器的老年代版本,也是单线程收集器,用途是作为 CMS 收集器的后备方案

Parallel Scavenge收集器

也是多线程收集器,关注的是吞吐量(高效利用CPU),优点是提供多个参数给用户找到最合适的停顿时间或最大吞吐量

吞吐量就是 CPU 中用于运行用户代码的时间与 CPU 总消耗时间的比值。

这是jdk1.8默认收集器

Parallel Old收集器

Parallel Scavenge 收集器的老年代版本。

CMS收集器

CMS(Concurrent Mark Sweep)收集器是获取最短回收停顿时间为目标,注重在用户体验上,也是第一款真正意义上的并发收集器,第一次实现让垃圾回收和用户线程同时工作,是一种 “标记-清除”算法实现的

  • 初始标记:暂停其他现线程,记录下与root相连的对象,速度很快

  • 并发标记:垃圾回收线程和用户线程同时执行

  • 重新标记:为了解决并发标记时用户线程导致的部分对象标记变化的情况

  • 并发清除:开启用户线程,并且开始垃圾回收

优点:并发收集、低停顿

缺点:无法处理浮动垃圾(在清理垃圾时 用户线程也在工作 无法确定某个对象是否是垃圾)

使用的是标记清除算法,会导致大量的空间碎片

三色标记:

由于CMS有并发执行过程,所以在标记对象时有不确定性.

所以在标记时,将对象分为3中颜色(3中状态)

黑色:例如GCRoots 确定是存活的对象

灰色:在黑色对象中关联的对象,其中还有未扫描完的,之后还需要再次进行扫描

白色:与黑色,灰色对象无关联的,垃圾收集算法不可达的对象

标记过程:

1.先确定GCRoots, 把GCRoots标记为黑色

2.与GCRoots关联的对象标记为灰色

3.再次遍历灰色,灰色变为黑色,灰色下面有关联的对象,关联的对象变为灰色

4.最终保留黑色,灰色,回收白色对象

可能会出现漏标,错标问题

漏标

假设 GC 已经在遍历对象 B 了,而此时用户线程执行了 A.B=null的操作, 切断了 A 到 B 的引用,本来应该回收BDE垃圾,但是B已经是灰色,所以只能回收DE

错标

假设 GC 线程已经遍历到 B 了,此时用户线程执行了以下操作:

B.D=null;//B 到 D 的引用被切断

A.xx=D;//A 到 D 的引用被建立

G1收集器

G1 (Garbage-First) 是一款面向服务器的垃圾收集器,高概率满足 GC 停顿时间要求的同时,还具备高吞吐量性能特征.

G1收集器的运作大致是:

  • 初始标记

  • 并发标记

  • 最终标记

  • 筛选回收

G1 收集器在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的区域(这也就是它的名字 Garbage-First 的由来) 。这种使用 Region 划分内存空间以及有优先级的区域回收方式,保证了 G1 收集器在有限时间内可以尽可能高的收集效率(把内存化整为零)。

ZGC收集器

  • 停顿时间不超过10ms,ZGC在标记、转移和重定位阶段几乎都是并发的;

  • 停顿时间不会随着堆的大小,或者活跃对象的大小而增加,只依赖于GC Roots集合大小;

个人理解:ZGC的STW阶段只有初次的三个阶段,也就是初始标记,再标记,初始清理,之后就可以并发清理了,而G1不管是初始还是后面的多次在STW的阶段还是要STW

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

重开之Java程序员

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值