JVM系统图
2024-02-06 08:09:25 0 举报
JVM(Java Virtual Machine)系统图描述了Java虚拟机的体系结构。这个体系结构主要包括类加载器、运行时数据区、执行引擎和本地方法接口。类加载器负责将编译后的Java类加载到JVM中,运行时数据区用于存储类实例、方法区、栈和程序计数器等运行时数据。执行引擎负责执行Java字节码指令,包括解释执行和即时编译。本地方法接口提供了与本地库交互的能力。该图还展示了JVM如何与操作系统和硬件交互,以确保Java程序的高效执行。
作者其他创作
大纲/内容
特征
线程栈(B)(虚拟机栈)-Xss设置大小
对齐填充(Padding)保证对象是8个字节的整数倍
指向
1.为什么要采用分代模式?因为采用分代模式可以使得年轻代和老年的回收算法不同,从而提高回收的效率,比如年轻代采用的回收算法是复制,而老年代采用的回收算法是标记整理或标记清除算法2. 年轻代为什么分为Eden区和Survivor区(幸存区)?①.是因为新生代的对象首先先分配到Eden区,当Eden区内存
空,不需要记录信息
本地方法栈(C)
自旋
膨胀(重量级锁定)
程序计数器(A)
偏向时间戳
EPOCH
4. 分代收集算法:1. 一般是把java堆分为新生代和老年代2. 在新生代中,每次垃圾收集时会发生大批对象死去,只有少量存活,所以选择复制算法,只需要将少量存活的对象复制成本即可完成收集。3. 老年代中因为对象存活率高,没有额外空间对他进行分配担当,那就必须用\"标记-清除\"或者\"标记-整理\"算法来进行回收
2bit
升级
s01/10
3
轻量级锁CAS操作:1.锁对象头的Mark World是否指向当前线程的锁记录2.当前线程锁记录Mark World和锁对象头的Mark World是否一致
标记整理算法主要是将内存进行标记,标记后不是直接清除操作,而是进行整理操作,将存活的对象都向一端移动,让后将另一端内存进行清理操作。
int
1
内存分配
01
堆(D)
使用句柄来访问的最大好处是reference中存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而reference本身不需要修改。
3.虚拟机为新生成的对象在堆(D)中分配内存空间
对象创建过程(new)
对象实例数据
回收后状态
元空间
栈帧:main()
GC Roots
指向锁记录的指针
方法区/元空间
Java程序需要通过线程栈(B)中局部变量中reference数据来操作堆的具体对象。由于reference类型在Java虚拟机规范只规定了一个纸箱对象的引用,并没有定义这个引用应该通过何种方式去定位、访问对重的对象具体位置,所以对象访问方式取决于虚拟机实现而定。目前主流访问方式有以下两种:使用句柄和直接指针两种,Sun HotSpot虚拟机使用的是直接指针方式。1、使用句柄:Java堆中将会划分出一块内存来作为句柄池,reference中存储的是对象的句柄地址,而句柄中存放的才是实例数据与类型数据地址2、直接指针:reference数据存放的实例数据或类型数据的地址,也就是直接存放的对象的地址,而Java堆中的对象的布局需要考虑如何放置访问类型数据的相关信息。
设置对象
Lock Record
线程
1.首先先去方法区(E)定位到一个类的符号引用,并检测这个符号引用代表的类是否已经被加载,解析和初始化
到对象类型数据的指针
Java堆
对象分代年龄
instanceKlass
可回收
Survivor区
回收前状态
初始化
对象的内存布局(32位虚拟机)
只需要比较线程ID
线程栈变量
对象的访问定位方式
对象生存还是死亡?判断一个对象是否生存和死亡需要进行两次标记,如果对象在可达性分析算法中没有与GC Roots引用链进行引用,那它会进行第一次标记并进行筛选,筛选条件是对象是否必要执行finalize()方法,如果对象没有覆盖finalize()方法或finalize()方法被虚拟机调用过,则代表虚拟机没必要执行方法,如果有必要执行finalize()方法,虚拟机会将对象放到一个F-Queue队列中等待执行,虚拟机会创建一个优先级别低一些的Finalizer线程去执行finalize()方法,这里执行只是调用方法,但是不会等待方法的返回,因为如果队列中的等待返回会阻碍后面队列进行操作,稍后,GC会对F-Queue队列进行第二次标记,如果对象在finalize()中拯救了自己,等于这个对象又与GC Roots引用链进行引用,否则第二次标记时将它移除到“即将回收”的集合中等待回收动作。
到对象实例数据的指针
链接
类加载子系统
00
C
5.虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例、如何找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息(对象头)
用于标记GC
动态链接
执行GC操作
reference
Young Gen (-Xmn)
方法区(E)(元空间)常量+静态变量+类信息
栈
加载
存活对象
GC标记
new Person()
Displaced Mark World 中存放的是01状态的内容,拷贝的原先状态
11
使用
1.每次发生GC,分代年龄会增加1,当发生minor GC时,会将Eden区进行可达性分析算法,查找存活的对象将存活的对象放入到s0区域,已经失去引用的对象则进行直接销毁。2.当分代年龄达到15岁后,直接从survivor区移动到老年代3.STW:Stop the world,发生GC时,停止所有的用户线程,为什么会出现STW?为了能够更好地可达性分析算法去进行收集。4.Xms是指设定程序启动时占用内存大小。5.Xmx是指程序运行中的最大可占用的内存。6.Xmn是指年轻代运行期间的占用内存大小。7.-XX:MaxPermSize:元空间或方法区的占用内存大小
句柄池
线程A ID
锁状态
引用计数
方法区
1. 标记-清除算法
回收方法区1. 方法区主要回收的内容有两种,分别是无用的常量、无用的类2. 判断无用的常量比较简单,判断是否有引用还在引用无用的常量即可。3. 判断无用的类比较苛刻需要以下三个条件:①. 该类的所有的在堆中分配的实例对象都被回收,也就是Java堆中不存在该类的任何实例。②. 该类的ClassLoader被回收,这里是因为类加载器在加载类时会先去判断是否加载过该类,是去方法区中找元数据是否存在,所以这里如果清除掉元数据需要类加载器需要先进行回收。③. 该类的java.lang.class类信息没有没引用,无法在任何地方反射该类的方法。JVM参数:1. 是否对类进行回收:-Xnoclassgc参数进行控制2. 查看类加载和卸载信息:-verbose:class以及-XX:+TraceClassLoading、-XX:TraceClassUnLoading其中-verbose:class以及-XX:+TraceClassLoading可以再Product版的虚拟机使用,-XX:TraceClassUnLoading参数需要FastDebug版的虚拟机支持
垃圾回收算法
常量池
动态链接库
操作数栈
可达性分析算法
User Class Loader....
主要算法:1. 引入计数器给对象引用添加一个计数器,当有对象引用时,计数器加一,当对象不在引用时,计数器减一,当计数器为0时代表对象不再被引用。存在的问题:环形引用,A引用B,B引用C,C引用A这种的没办法进行回收,因为三个对象计数器都是1。2. 可达性分析算法(GC Roots算法)通过一系列称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索走过的路径称为“引用链”,当一个对象到 GC Roots 没有任何的引用链相连时(从 GC Roots 到这个对象不可达)时,证明此对象不可用。GC Roots的对象包括:1. 栈中的局部变量表中引用的对象。2. 方法区中静态属性引用的对象。3. 方法区中常量引用的对象。4. 本地方法栈中JNI引用的对象。
准备Prepare
存放局部变量
-
需要进行类加载
reference数据
老年代2/3
float
Person
赋值
Bootstrap Class Loader
synchronized锁膨胀过程
a=1
未锁定
指向重量级锁的指针
=
局部变量表
10
Owner
复制算法:它可以将内存分为大小相等的两块,每次只是用其中一块,当一块内存用完了,将存活的对象复制到另一块区域中,将使用的一块进行清理操作。
short
25bit
对象访问
A
偏向线程ID
Klass Pointer
0
实例数据(Instance Data)
元数据
对象已死吗?
对象头(Header)
6.执行init方法,把对象按照程序员的意愿进行初始化
锁标志
Extention Class Loader
未使用
修改对象头
实例池
执行方法区中的Main.class
指向锁对象
Eden8/10
当前栈帧:test()
Main.class
方法返回
修改程序索引位置
Main.class文件(Class文件)
成功
对象头包括两部分:1.标记字段(Mark World)用于存储对象自身运行时数据,右测表格2.类型指针(Klass Pointer),即指向它的类元数据的指针,虚拟机通过这个指针来确定对象是哪个类的实例。3.数组长度,只有数组对象才会有
Java栈本地变量表
3. 标记-整理算法
4bit
4.虚拟机需要将分配到内存空间都初始化为零值。
Application Class Loader
Mark World
B
double
依赖mutex(操作系统的互斥)
2、当有线程A访问时,首先判断标志,此时标志位为01,再判断是否偏向锁标志位,访问时标志位0,此时不偏向,将是否偏向锁变到1,设置为偏向锁,同时设置偏向线程ID信息
JVM Head(-Xmx -Xms)
对象哈希码
是否偏向锁
年轻代
2. 复制算法
b
s11/10
静态变量
校验Verify
CAS操作获得锁
压入
JVM堆内存分代模型
对象类型数据
线程B ID
划分内存的方法:1. “指针碰撞”(Bump the Pointer)如果Java堆中内存是绝对规整的,所有用过的内 存都放在一边,空闲的内存放在另一边,中间放着一个指针作为分界点的指示器,那所分配 内存就仅仅是把那个指针向空闲空间那边挪动一段与对象大小相等的距离。2. “空闲列表”(Free List)如果Java堆中的内存并不是规整的,已使用的内存和空 闲的内存相互交错,那就没有办法简单地进行指针碰撞了,虚拟机就必须维护一个列表,记 录上哪些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例, 并更新列表上的记录
复制算法:java年轻代将内存分为Eden和两块较小的Survivor空间,回收时,每次Eden区域和其中一个Survivor区域存活的对象复制到另外一个Survivor区域,如果Survivor区域内存不足则有老年代来进行分担。
卸载
Full GC(Mojar GC)
执行器引擎
线程A(线程栈(B))
minor GC
-XX:MaxPermSize、-XX:PermSize
(1)装载——查找并装载类型的二进制数据。(2)连接——指向验证、准备、以及解析(可选)。 ●验证:确保被导入类型的正确性。(java可以自定义安全策略等) ●准备:为类变量分配内存,并将其初始化为默认值。(这里的准备和下面的初始化的顺序问题体现在java初始化中值变化的各种陷阱) ●解析:把类型中的符号引用转换为直接引用。(3)初始化——把类变量初始化为正确初始值。(准备的时候设为默认值,此时才会正式给变量赋值)
JNI指针
4.当原持有偏向锁的线程(线程 A)获取轻量级锁后,JVM 唤醒线程 A,线程 A 执行同步代码块,执行完成后,开始轻量级锁的释放过程。对于其他线程而言,也会在栈帧中建立锁记录,存储锁对象目前的 Mark Word 的拷贝。JVM 利用 CAS 操作尝试将锁对象的 Mark Word 更正指向当前线程的 Lock Record,如果成功,表明竞争到锁,则执行同步代码块,如果失败,那么线程尝试使用自旋的方式来等待持有轻量级锁的线程释放锁。当然,它不会一直自旋下去,因为自旋的过程也会消耗 CPU,而是自旋一定的次数,如果自旋了一定次数后还是失败,则升级为重量级锁,阻塞所有未获取锁的线程,等待释放锁后唤醒。轻量级锁的释放,会使用 CAS 操作将 Displaced Mark Word 替换会对象头中,成功,则表示没有发生竞争,直接释放。如果失败,表明锁对象存在竞争关系,这时会轻量级锁会升级为重量级锁,然后释放锁,唤醒被挂起的线程,开始新一轮锁竞争,注意这个时候的锁是重量级锁。
返回地址
一个方法一个栈帧
使用直接指针访问方式的最大好处就是访问速度快,它省了一次指针访问的时间开销,由于对象的访问在Java中非常频繁,因此这类开销极少成多后也是一项非常客观的执行成本。
1bit
p
Mark World拷贝
对象头表示方式
2.如果没有,就需要先进行类加载过程
2
堆
1.绿色区域:代表线程私有2.蓝色区域:代表线程共享3.程序计数器:当前线程字节码执行的行号指示器4.线程栈:Java方法执行的内存模型,每个方法都会产生一个栈帧,用于存放局部变量表,操作数栈,动态链接,方法返回等,局部变量表所需要的内存空间在编译期完成分配,在进入一个方法时栈帧中的局部变量表大小是完全确认的,在方法运行的过程中局部变量表的大小是不会影响的。5.本地方法栈:虚拟机使用到Native方法服务。6.堆:被所有线程共享的一块内存区域,在虚拟机启动时创建。7.元空间:用于存储一杯虚拟机在的类信息、常量、静态变量、即时编译器编译后的代码等数据。
可偏向
标记清除算法主要是将内存进行标记然后进行清除操作.缺点:1. 效率问题:标记清理的效率不高2. 被清理地址空间不连续,空间碎片太多可能导致以后程序运行时有大对象分配时,没有足够的连续空间而不得提前触发另一次垃圾收集动作。
3、当线程B访问同步块时,发现当前为偏向锁,首先检测当前获得锁的线程ID是不是自己,如果是自己,则执行同步块代码,如果发现线程ID不是自己的ID,利用 CAS 尝试替换 Mark Word 中的 Thread ID,成功,表示该线程(线程 B)获取偏向锁,执行同步代码块,如果CAS失败,则代表当前环境存在锁竞争情况,则执行偏向锁的撤销工作撤销偏向锁的操作需要等到全局安全点才会执行,然后暂停持有偏向锁的线程,同时检查该线程的状态,如果该线程不处于活动状态或者已经退出同步代码块,则设置为无锁状态(线程 ID 为空,是否为偏向锁为 0 ,锁标志位为01)重新偏向,同时恢复该线程。若该线程活着,则会遍历该线程栈帧中的锁记录,检查锁记录的使用情况,如果仍然需要持有偏向锁,则撤销偏向锁,升级为轻量级锁。在升级为轻量级锁之前,持有偏向锁的线程(线程 A)是暂停的,JVM 首先会在原持有偏向锁的线程(线程 A)的栈中创建一个名为锁记录的空间(Lock Record),用于存放锁对象目前的 Mark Word 的拷贝,然后拷贝对象头中的 Mark Word 到原持有偏向锁的线程(线程 A)的锁记录中(官方称之为 Displaced Mark Word ),这时线程 A 获取轻量级锁,此时 Mark Word 的锁标志位为 00,指向锁记录的指针指向线程 A 的锁记录地址,如右图(失败流程):
轻量级锁定
Main
失败
1、一个锁对象刚创建时,没有线程范围它,它此时状态时无锁状态,锁状态时01
解析Resolve
23bit
JVM内存模型
本地方法栈(C)-Xoss设置大小
0 条评论
下一页