看完此文终于可以在简历上写上熟悉JVM了

一、概述

1.1 虚拟机介绍
  • java虚拟机是一个可以执行java字节码文件的虚拟机进程。java源文件被编译器编译成能被java虚拟机执行的字节码文件(.class)

java源文件—编译器—字节码文件—JVM—机器码

  • 跨平台的是java程序(包括字节码文件),而不是JVM。JVM是用C/C++开发的,是编译后的机器码,不能跨平台,不同平台需要安装不同版本的JVM
1.2 JVM组成部分

​ JVM由四块区域组成,分别是:

  • 类加载器:在JVM启动时或者类运行时将需要的class加载到JVM中
  • 运行时数据区:将内存划分为若干个区以模拟实际机器上的存储、记录和调度模块。
  • 执行引擎:负责执行class文件中包含的字节码指令,相当于实际机器上的CPU
  • 本地库接口:调用C或C++实现本地方法的代码返回结果

​ jvm首先把字节码通过一定方式的类加载器把文件加载到内存的运行时数据区中,然后由特定的命令解释器执行引擎, 将字节码翻译成底层系统指令再交由CPU去执行,而这个过程中需要调用其他语言的接口本地库接口来实现整个程序的功能

二、JVM运行时数据区

​ JVM 内存布局规定了 Java 在运行过程中内存申请、分配、管理的策略,保证了 JVM 的高效稳定运行。不同的 JVM 对于内存的划分方式和管理机制存在着部分差异。

如下图所示:

image

  • 线程私有的:生命周期和线程相同

    • 程序计数器
    • 虚拟机栈
    • 本地方法栈
  • 线程共享的:随虚拟机的生命

    • 方法区
  • 直接内存:不受JVM GC管理

2.1 虚拟机栈
  • 作用:主管Java程序的运行,保存方法的局部变量、部分结果,并参与方法调用和返回。

  • 特点:

    • 栈是一种快速有效的分配存储方式,访问速度仅次于程序计数器
    • 每个方法执行伴随着入栈,方法执行结束出栈。
    • 栈不存在垃圾回收
  • 可能出现异常

    • 线程请求的栈深度大于虚拟机允许的栈深度,将抛出StackOverflowError
    • 虚拟机栈空间可以动态扩展,当动态扩展是无法申请到足够的空间时,抛出OutOfMemory异常
  • 每个线程都有自己的栈,栈中的数据是以栈帧的格式存在的

2.1.1 栈帧
  • 存储内容

    • 局部变量表
    • 操作数栈:表达式栈,主要用于保存计算过程的中间结果,同时作为计算过程中变量临时的存储空间
    • 动态链接:指向运行时常量池的方法引用
    • 方法返回地址:方法正常退出或异常退出的地址
    • 一些附加信息
2.2 本地方法栈

​ 与虚拟机栈功能类似,这块区域也不需要进行GC

  • 区别:

    • 虚拟机栈为虚拟机执行Java方法时访问
    • 本地方法栈为虚拟机执行本地方法时提供服务
2.3 程序计数器
  • 是一块很小的内存空间,可以作为当前线程的行号指示器,运行速度也是
  • 主要作用是记录线程运行时的状态,方便线程被唤醒时能从上一次被挂起时的状态继续执行
  • 程序计数器是唯一一个在java虚拟机规范中没有规定任何OOM情况的区域,所以这块区域不需要进行GC
2.4 方法区
  • 方法区是JVM规范中定义的一个概念,用于存储类信息、常量池、静态变量、JIT编译后的代码等数据。
  • 永久代是Hotspot虚拟机特有的概念,JDK8时被元空间取代。元空间和永久代都可以理解为方法区的落地实现。
  • 永久代是堆的一部分,和新生代、老年代是连续的,而元空间存在于本地内存(堆外内存),即不受JVM限制了,较难发送OOM
  • 存储内容不同,元空间存储类的元信息,静态变量和常量池等并入堆中。相当于永久代的数据被分到了堆和元空间中。
  • 永久代的内存回收的主要目标是针对常量池的回收和类型的卸载。
  • 运行时常量池:用于存放编译期生成的各种字面量和符号引用,这部分才能将在类加载后存放到方法区的运行时常量池中。

java8中,永久代已经被移除,变为元数据区(元空间)。

元空间和永久代区之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。

2.5 堆——运行时的数据区
  • 创建的对象和数组都保存在java堆内存中,GC也主要对这两类数据进行回收

  • java虚拟机规范对这块的描述是:所有对象实例及数组都要在堆上分配内存。

  • 堆细分(方便GC优化)

    • 新生代:用来存放新生的对象,一般占据三分之一的空间,由于频繁的创建对象,所以会频繁触发MinorGC进行垃圾回收
      • Eden区
        • java新对象的出生地(如果占用内存很大,就直接分配到老年代)。当其内存不足时会触发MinorGC对新生代进行一次垃圾回收
      • ServivorFrom
        • 上一次GC的幸存者,作为这一次GC的被扫描者
      • ServivorTo
        • 保留了一次MinorGC过程中的幸存者

MinorGc的过程(复制-清空-互换)

  • 采用复制算法

  • 过程

    • eden、servicorFrom复制到ServicorTo。年龄+1
    • 清空eden、servicorFrom
    • Servicor和ServicorFrom中的对象互换
    • 老年代:
      • 主要存放引用程序中生命周期长的内存对象
      • 由于比较稳定,所以不会频繁GC。
      • 采用标记清除算法。

堆是分配对象存储的唯一选择吗?

  • 随着 JIT 编译期的发展和逃逸分析技术的逐渐成熟,栈上分配、标量替换优化技术将会导致一些微妙的变化,所有的对象都分配到堆上也渐渐变得不那么“绝对”了

三、类加载过程

  • 从类被加载到虚拟机内存中开始,到卸除内存为止
  • 生命过程
    image

其中加载、验证、准备、初始化、卸载这五个阶段的过程是固定的,在类加载过程中必须按照这种顺序按部就班的进行,而解析阶段则不一定,可以在初始化以后进行,是为了支持java语言的运行时绑定

3.1 加载

三件事情

  • 通过一个类的全限定名获取定义此类的二进制字节流
  • 将这个字节流所代表的静态存储结构转换为方法区的运行时数据结构
  • 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据结构的访问入口

​ 加载阶段完成后,虚拟机外部的二进制字节流就按照虚拟机所需的格式存储在方法区之中,而且在java堆中也创建一个java.lang.Class类的对象,这样便可以通过该对象访问方法区中的这些数据

3.2 验证

​ 这一阶段主要是为了确保Class文件的字节流中包含的信息符合虚拟机的要求,并且不会危害虚拟机自身的安全。

​ 四个校验动作

  • 文件格式验证:验证字节流是否符合Class文件格式的规范
  • 元数据验证:对字节码描述的信息进行语义分析,以保证其描述的信息符合Java语言规范的要求
  • 字节码验证:通过数据流和控制流分析。确定程序语义是合法的、符合逻辑的
  • 符号引用验证:确保解析动作能正确执行
3.3 准备

​ 是正式为类变量分配内存并设置初始值的阶段,这些变量所使用的内存都将在方法区分配

​ 进行内存分配的仅包括类变量(static),而不包括实例变量,实例变量会在对象实例化时随着对象一块分配在java堆中

​ 初始值通常情况下是数据类型默认的零值

3.4 解析

​ 是将虚拟机常量池内的符号引用替换为直接引用的过程。解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定附

​ 符号引用:与虚拟机实现的布局无关,引用的目标并不一定要已经加载到内存中。虚拟机能接收的符号引用必须是一致的,因为符号引用的字面量形式明确定义在java虚拟机规范的Class文件格式中。

​ 直接引用:可以是指向目标的指针,相对偏移量或者是一个能间接定位到目标的句柄,如果有了直接引用那么引用目标必定已经在内存中存在

3.5 初始化

​ 类初始化时类加载的最后一步,处理加载阶段,用户可以通过自定义的类加载器参数,其他阶段都完全由虚拟机主导和控制。到了初始化阶段才真正执行Java代码

​ 类初始化的主要工作时为了静态变量赋程序设定的初值

static int a=100; 在准备阶段a被赋默认值0,在初始化阶段就会被赋值为100

​ java虚拟机规范中严格规定了有且只有五种情况必须对类进行初始化:

  • 使用new创建类的实例,或者使用getstatic、putstatic读取或设置一个静态字段的值(放入常量池中的常量除外),或者调用一个静态方法的时候,对应类必须进行初始化。
  • 通过java.lang.reflect包的方法对类进行反射调用的时候,要是类没有进行过初始化,则要首先进行初始化
  • 当初始化一个类的时候,如果发现其父类没有进行过初始化,则首先触发父类初始化
  • 当虚拟机启动时,用户需要指定一个主类(包含main()方法的类),虚拟机会首先初始化这个类
  • 使用jdk1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、RE_invokeStatic的方法句柄,并且这个方法句柄对应的类没有进行初始化,则需要先触发其初始化。

四、类加载器

​ 虚拟机设计团队把加载动作放到JVM外部实现,以便让应用程序决定如何获取所需的类。

4.1 启动类加载器

​ 负责加载JAVA_HOME\lib目录中的,或通过-XbootclassPath参数指点路径中的,且被虚拟机认可(按文件名识别,如rt、jar)的类

4.2 扩展类加载器

​ 负责加载JAVA_HOME\lib\ext目录中的,或者通过java.ext.dirs系统变量指定路径中的类库

4.3 应用程序类加载器

​ 负责加载用户路径(classpath)上的类库。

4.4 双亲委派

​ JVM通过双亲委派模型来进行类的加载,我们也可以通过继承java.lang.ClassLoader实现自定义的类加载器。

image

  • 当一个类收到了类加载请求,他首先不会尝试自己去加载这个类,而是把这个请求委派给父类去完成,每一层的类加载器都是如此。

  • 只有当父类加载器反馈自己无法完成这个请求的时候(在它的加载路径下没有找到所需加载的class)。子类才会去尝试自己去加载

  • 好处:

    • 保证了使用不同的类加载器最终得到的都是同样一个Object对象。

image

五、对象的创建过程

image

​ java中对象的创建就是在堆上分配内存空间的过程,此处说的对象创建仅限于new关键字创建的普通java对象,不包括数组对象的创建。

​ 当虚拟机遇到一条含有new的指令时,会进行一系列对象创建的操作

5.1 类加载检查
  • 检查常量池中是否有即将要创建的这个对象所属的类的符号引用;若常量池中没有这个类的符号引用,说明这个类还没有被定义。抛出ClassNotFoundException
  • 进而检查这个符号引用所代表的类是否已经被JVM加载,若该类还没有被加载,就找该类的class文件,并加载进方法区;若该类已经被JVm加载,则准备为对象分配内存
5.2 分配内存
  • 根据方法区中该类的信息确定该类所需的内存大小;一个对象所需的内存大小是在这个对象所属类被定义完就能确定的。且一个类所生产的所有对象的内存大小是一样的。JVM在一个类被加载进方法区的时候就知道该类生产的每一个对象所需的内存大小
  • 从堆中划分一块大小的内存给新的对象

分配堆中内存有两种方式

  • 指针碰撞

    • 如果JVM的垃圾收集器采用复制算法或标记-整理算法,那么堆中空闲内存是完整的区域,并且空闲内存和已使用内存之间由一个指针标记
  • 空闲列表

    • 如果JVM的垃圾回收机制采用标记-清除算法,则需要一张空闲列表来记录空闲区域

PS:多线程并发时会出现正在给对象A分配内存,还没来得及修改指针,对象B又用这个指针分配内存

  • 采用同步的方法:使用CAS来保证操作的原子性
  • 每个线程分配内存都在自己的空间内进行,即是每个线程都在堆中预先分配一小块内存,称为本地线程分配缓冲,
5.3 初始化零值

​ 对象的内存分配完成后,还需要将对象的内存空间都初始化为零值,这样能保证对象即时没有赋初值也可以直接使用

5.4 设置对象头

设置对象头中的信息

​ 所属类、类的元数据信息、对象的hashcode、GC分代年龄等信息

5.5 执行init方法

调用对象的构造函数进行初始化

顺序:先初始化父类的静态代码—>初始化子类的静态代码–>初始化父类的非静态代码—>初始化父类构造函数—>初始化子类非静态代码—>初始化子类构造函数

六、对象的内存布局

6.1 对象头
  • 第一部分用于存储对象自身运行时数据,如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳、对象分代年龄。
  • 第二部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是那个类的实例
  • 如果是一个java数组,那么在对象头中还必须有一块用于记录数组长度的数据
6.2 实例数据
  • 是对象真正存储的有效信息,也是在程序代码中所定义的各种类型的字段内容
  • 分配策略:相同宽度的字段总是放在一起
  • 这部分的存储顺序会受到虚拟机分配此类参数和字段在java源码中定义顺序的影响
6.3 对其填充
6.4 预估对象大小

七、对象访问

对象的访问方式由虚拟机决定,java虚拟机提供两种主流方式

  • 句柄访问对象
  • 直接指针访问对象
7.1 句柄访问

​ java堆划出一块内存作为句柄池,引用中存储对象的句柄地址,句柄中包含对象实例数据、类型数据的地址信息

  • 优点:引用中存储的是稳定的句柄地址,在对象被移动时,只需要改变句柄中实例数据的指针,不需要改动引用ref本身

image

7.2 直接指针

​ 与句柄访问不同的是,ref中直接存储的就是对象的实例数据,但是类型数据跟句柄访问方式一样。

  • 优点:速度快,相对于句柄访问少了一次指针定位的开销时间

image

八、对象存活判断

  • 引用计数
  • 可达性分析
8.1 引用计数

​ 每个对象都有一个引用计数属性,新增一个引用时计数加一,引用释放时计数减一,计数为0时可以回收。此方法简单,无法解决对象互相循环引用的问题

8.2 可达性分析

​ 从GC Roots开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可达的

但是不可达对象不等价于可回收对象,不可达对象变为可回收对象至少要经过两次标记过程

GC Roots 对象:

​ 虚拟机栈(栈帧中的本地变量表)中引用的对象

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

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

​ 本地方法栈中JNI(Native方法)中引用的对象

​ 如何判断无用的类

该类所有实例都被回收(Java堆中没有该类的对象)

加载该类的ClassLoader已经被回收

该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何对方利用反射访问该类

8.3 finalize
  • finalize()方法,是在释放该对象内存前由GC调用
  • 通常建议在这个方法中释放该对象持有的资源,例如持有的堆外内存、远程服务长连接。一般情形下不建议重写该方法。对于一个对象,该方法有且仅会被调用一次
8.4 对象引用类型

可以参考我写过的: java四大引用.

九、垃圾回收算法

9.1 标记-清除
  • 过程:

    • 标记阶段:通过根节点,标记所有从根节点开始的可达对象。因此,未被标记的对象就是垃圾对象
    • 清除阶段:清除所有未被标记的对象
  • 缺点:

    • 效率:标记和清除两个过程效率都不高
    • 空间:标记清除后产生大量不连续的内存空间,导致空间碎片太多
9.2 标记-整理

类似于标记-清除,只是标记完对象后,让所有存活的对象都向一端移动,然后清理掉边界以外的内存

  • 优点:

    • 解决了内存碎片问题
    • 没有内存碎片后,对象创建内存分配也更快速了(可以使用TLAB进行分配)
  • 缺点:效率问题

TLAB:本地线程分配缓冲

9.3 复制算法

​ 将可用内存划分为大小相等的两块,每次只使用其中一块,当一块内存用完后,就将存活的对象复制到另一块上,然后再把使用过的内存空间一次清理掉。只需要移动堆顶指针,按顺序分配内存

  • 优点:效率高没有内存碎片

  • 缺点:

    • 浪费一半空间
    • 复制收集算法在对象存活率较高时由于较多的复制操作,导致效率变低
9.4 分代算法

​ 根据对象的存活周期的不同将内存划分为几块,一般是把java堆分为新生代和老年代。然后根据各个年代的特点采用适当的收集算法

  • 新生代:大批死去,少数存活使用复制算法
  • 老年代:存活率较高,没有额外空间对它进行分配担保,就必须使用标记清理或者标记整理
9.5 分区收集算法

​ 将整个堆空间划分为连续的不同小区间,每个小区间独立使用,独立回收。

​ 可以控制一次回收多少个小区间,根据目标停顿时间,每次合理地回收若干小区间,从而减少一次GC所产生的停顿

十、安全点

10.1 安全点

​ 一些特定的位置:当线程运行到这些位置时,线程的一些状态可以被确定,比如记录OopMap的状态,从而确定GC Root的信息,使JVM可以安全的进行一些操作

特定位置:

  • 循环的末尾(防止大循环的时候一直不进入安全点,而其他线程在等待)
  • 方法返回前
  • 调用方法的Call
  • 抛出异常的位置
10.2 安全区域

​ 安全点完美解决了如何进入GC的问题,当程序长时间不执行的时候就需要安全区域

​ 安全区域是指一段代码中,引用关系不会发生变化,在这个区域任何地方GC都是安全的,安全区域可以看做是安全点的一个扩展。线程执行到安全区域的代码时,首先标识自己进入了安全区域,这样GC时就不用管进入安全区域的线层了,线层要离开安全区域时就检查JVM是否完成了GC Roots枚举,如果完成就继续执行,如果没有完成就等待直到收到可以安全离开的信号。

十一、JVM垃圾回收机制

收集器串行、并行or并发新生代/老年代算法目标适用场景
Serial串行新生代复制算法响应速度优先单CPU环境下的Client模式
Serial Old串行老年代标记-整理响应速度优先单CPU环境下的Client模式、CMS的后备预案
ParNew并行新生代复制算法响应速度优先多CPU环境时在Server模式下与CMS配合
Parallel Scavenge并行新生代复制算法吞吐量优先在后台运算而不需要太多交互的任务
Parallel Old并行老年代标记-整理吞吐量优先在后台运算而不需要太多交互的任务
CMS并发老年代标记-清除响应速度优先集中在互联网站或B/S系统 服务端上的Java应用
G1并发both标记-整理+复制算法响应速度优先面向服务端应用,将来替换CMS
ZGC并发both标记-整理+复制算法响应速度优先面向服务端应用,将来替换CMS
11.1 Serial(新生代)
  • 单线程复制算法、简单高效
  • 工作时会Stop The World,暂停所有用户线程,造成卡顿,适合运行在Client模式下的虚拟机
11.2 ParNew(新生代)
  • Serial+多线程
  • 除了Serial只有它可以和CMS搭配使用的收集器
  • 默认开启和CPU数目相同的线程数。可以通过参数(-XX:ParallelGCThreads)来限制垃圾收集器的线程数
11.3 Parallel Scavenge(新生代)

​ 多线程复制算法、高效。重点关注是程序达到一个可控制的吞吐量,高吞吐量可以最高效率地利用CPU时间,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务。

参数:

  • MaxGCPauseMills:控制最大垃圾收集停顿时间
  • GCTimeRatio:直接设置吞吐量大小的
11.4 Serial Old(老年代)
  • 单线程标记整理算法。

  • 主要运行在Client默认的java虚拟机默认的年老代垃圾收集器。

  • 用途

    • JDK1.5以前与新生代的Parallel Scavenge收集器搭配使用
    • 作为年老代中使用CMS收集器的后备垃圾收集方案。
11.5 Parallel Old(老年代)
  • 多线程标记整理算法
  • 在JDK 1.6中开始提供。 在注重吞吐量的场合,配合Parallel Scavenge收集器使用。
11.6 CMS(老年代)
  • 多线程标记清除算法

  • 目标:获取最短垃圾回收停顿时间,使用多线程的标记-清除算法

  • 一种以获取最短回收停顿时间为目标的收集器。适合需要与用户交互的程序,良好的响应速度能提升用户体验

  • 过程:

    • 初始标记:只是标记一下GC Roots能直接关联到的对象,速度很快。会暂停所有的工作线程
    • 并发标记:进行GC Roots Tracing(可达性分析)的过程
    • 重写标记:会Stop The -World。为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般比初始标记阶段稍长些,但远比并发标记的时间短。
    • 并发清除:回收内存
  • 耗时最长的并发标记和并发清除过程收集器线程都可以与用户线程一起工作,所以是并发执行的

  • 缺点:

    • 并发阶段,虽然不会导致用户线程暂停,但是会占用一部分资源(CPU线程),导致应用变慢,吞吐量降低。默认启动收集线程数是(CPU数量+3)/4。即当CPU在4个以上时,并发回收时垃圾收集线程不少于25%的CPU资源,并且随着CPU数量的增加而下降。但是当CPU不足4个(譬如2个)时,CMS对用户程序的影响就可能变得很大。
    • 无法清除浮动垃圾。并发清除阶段,用户线程还在运行,还会产生新垃圾。这些垃圾不会在此次GC中被标记,只能等到下次GC被回收
    • 标记-清除算法会产生大量不连续内存,导致分配大内存时内存不够,提前触发Full GC
11.7 G1
  • 在JDK 1.7提供的先进垃圾收集器
  • 即使用于新生代,也适用于老年代
  • 空间整合:使用标记-整理算法,不产生碎片空间
  • 可以非常精准的控制停顿时间,在不牺牲吞吐量前提下,实现低停顿垃圾回收。

G1收集器避免全区域垃圾收集,把堆内存划分为大小固定的几个独立区域,并且跟踪这些区域的垃圾收集进度,同时在后台维护一个优先级列表,每次根据所运行的收集时间,优先回收垃圾最多的区域。区域划分和优先级区域回收机制,确保G1收集器可以在有限时间获得最高的垃圾收集效率。

  • 默认把堆平均分为2048个region,最小1M最大32M,必须是2的幂次方,可以通过-xx:G1HeapRegionSize参数指定

region

  • E:eden区,新生代
  • S:survivor区,新生代
  • O:old区,老年代
  • H:humongous区,用来放大对象,当对象新建大小超过region大小一半时,直接在新的一个或多个连续region中分配
  • young GC:新生代eden区没有足够可用空间时触发。存活对象移到survivor区,或晋升old区

  • mixed GC:当old区对象很多时,老年代对象空间占堆总空间的比值达到阈值会触发,它除了回收年轻代,也回收部分老年代

    • 回收步骤
      • 初始标记:只是标记一下GC Roots能直接关联到的对象,并且修改TAMS的值,让下一阶段用户程序并发运行时,能在正确可用的Region中创建新对象。这阶段需要停顿线程(STW),但耗时很短,共用YGC的停顿,所以一般伴随着YGC发生。
      • 并发标记:进行可达性分析,找出存活对象,耗时长,但可与用户线程并发执行。
      • 最终标记:修正并发标记阶段用户线程运行导致的变动记录。会STW,但可以并行执行,时间不会很长。
      • 筛选回收:根据每个region的回收价值和回收成本排序,根据用户配置的GC停顿时间开始回收。
  • 当对象分配过快,mixed GC来不及回收,G1会退化,触发Full GC,它使用单线程的Serial收集器来回收,整个过程STW,要尽量避免这种情况。

  • 当内存很少的时候(存活对象占用大量空间),没有足够空间来复制对象,会导致回收失败。这时会保留被移动过的对象和没移动的对象,只调整引用。失败发生后,收集器认为存活对象被移动了,有足够空间让应用程序使用,于是用户线程继续工作,等待下一次触发GC。如果内存不够,就会触发Full GC。

11.8 ZGC
  • ZGC是一个并发、基于区域、增量式压缩的收集器,STW阶段只会在根对象扫描阶段发送,这样GC暂停时间不会随着堆和存活对象的数量而增加

  • 处理阶段

    • 标记
    • 重定位/压缩
    • 重新分配集的选择
    • 引用处理
    • 弱引用的清理
    • 字符串常量池和符号表的清理
    • 类卸载
  • 着色指针:

    • ZGC利用指针的64位中的几位表示Finalizable、Remapped、Marked1、Marked0(ZGC仅支持64位平台),以标记该指向内存的存储状态。 相当于在对象的指针上标注了对象的信息。注意,这里的指针相当于Java术语当中的引用。 在这个被指向的内存发生变化的时候(内存在Compact被移动时),颜色就会发生变化。
  • 读屏障

    • 由于着色指针的存在,在程序运行时访问对象的时候,可以轻易知道对象在内存的存储状态(通过指针访问对象),
      若请求读的内存在被着色了,那么则会触发读屏障。读屏障会更新指针再返回结果,此过程有一定的耗费,从而达到与用户线程并发的效果。
  • 与标记对象的传统算法相比,ZGC在指针上做标记,在访问指针时加入Load Barrier(读屏障),比如当对象正被GC移动,指针上的颜色就会不对,这个屏障就会先把指针更新为有效地址再返回,也就是,永远只有单个对象读取时有概率被减速,而不存在为了保持应用与GC一致而粗暴整体的Stop The World。

清理

  • 字符串常量池和符号表的清理

  • 类卸载

  • 着色指针:

    • ZGC利用指针的64位中的几位表示Finalizable、Remapped、Marked1、Marked0(ZGC仅支持64位平台),以标记该指向内存的存储状态。 相当于在对象的指针上标注了对象的信息。注意,这里的指针相当于Java术语当中的引用。 在这个被指向的内存发生变化的时候(内存在Compact被移动时),颜色就会发生变化。
  • 读屏障

    • 由于着色指针的存在,在程序运行时访问对象的时候,可以轻易知道对象在内存的存储状态(通过指针访问对象),
      若请求读的内存在被着色了,那么则会触发读屏障。读屏障会更新指针再返回结果,此过程有一定的耗费,从而达到与用户线程并发的效果。
  • 与标记对象的传统算法相比,ZGC在指针上做标记,在访问指针时加入Load Barrier(读屏障),比如当对象正被GC移动,指针上的颜色就会不对,这个屏障就会先把指针更新为有效地址再返回,也就是,永远只有单个对象读取时有概率被减速,而不存在为了保持应用与GC一致而粗暴整体的Stop The World。

最后

  • 如果觉得看完有收获,希望能给我点个赞,这将会是我更新的最大动力,感谢各位的支持
  • 欢迎各位关注我的公众号【java冢狐】,专注于java和计算机基础知识,保证让你看完有所收获,不信你打我
  • 如果看完有不同的意见或者建议,欢迎多多评论一起交流。感谢各位的支持以及厚爱。

image

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值