JVM图谱
2021-01-22 11:24:17 3 举报
JVM详细图谱,涉及到JVM内存模型,JMM内存模型,垃圾回收器
作者其他创作
大纲/内容
S2
混合模式
Constant_Pool_Count
Mark Sweep 或 Mark Compact
参见官方文档:https://docs.oracle.com/javase/specs/jvms/se15/jvms15.pdf
年龄达到的存活对象进入Old区
用户线程4
SafePoint
ZGC
N
Y
1
主线程
java类库
共享变量
CPU2
用户线程3
验证文件是否符合JVM规定
G1
机器码
YGC
JVM:JVM有自己完善的硬件架构,如处理器、堆栈(Stack)、寄存器等,还具有相应的指令系统(字节码就是一种指令格式)。JVM屏蔽了与具体操作系统平台相关的信息,使得Java程序只需要生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。JVM是Java平台无关的基础。JVM负责运行字节码:JVM把每一条要执行的字节码交给解释器,翻译成对应的机器码,然后由解释器执行。JVM解释执行字节码文件就是JVM操作Java解释器进行解释执行字节码文件的过程。(还有JIT的作用)注意:通常情况下,一个平台上的二进制可执行文件不能在其他平台上工作,因为此可执行文件包含了对目标处理器的机器语言。而Class文件这种特殊的二进制文件,是可以运行在任何支持Java虚拟机的硬件平台和操作系统上的!Java编译执行和解释执行的意义:HotSpot VM是Sun JDK和OpenJDK中所带的虚拟机,也是目前使用范围最广的Java虚拟机。但是如今的HotSpot VM中不仅内置有解释器,还内置有先进的JIT(Just In Time Compiler)编译器,在Java虚拟机运行时,解释器和即时编译器能够相互协作,各自取长补短。有一点需要注意,无论是采用解释器进行解释执行,还是采用即时编译器进行编译执行,最终字节码都需要被转换为对应平台的本地机器指令。 问:既然HotSpot VM中已经内置JIT编译器了,那么为什么还需要再使用解释器来“拖累”程序的执行性能呢?对于服务端应用来说,启动时间并非是关注重点,但对于那些看中启动时间的应用场景而言,或许就需要采用解释器与即时编译器并存的架构来换取一个平衡点。 由于即时编译器将本地机器指令的编译推迟到了运行时,自此Java程序的运行性能已经达到了可以和C/C++程序一较高下的地步。这主要是因为JIT编译器可以针对那些频繁被调用的“热点代码”做出深度优化,而静态编译器的代码优化则无法完全推断出运行时热点,因此通过JIT编译器编译的本地机器指令比直接生成的本地机器指令拥有更高的执行效率也就理所当然了。
自身被标记,成员变量未被标记
Serial
Initializing
Class
C
00
findClass()
Verification
编译执行过程
自顶向下实际查找和加载child
逻辑分代,物理不分代
CMS用到,关注引用的增加,把黑色重新标记为灰色,下次重新扫描属性
javac
调用缓存
CMS
java(JVM)
共享变量副本
A
D
申请内存
用户线程1
JIT编译器Just In-Time Compiler
首先考虑栈上分配
加载自定义ClassLoader
Incremental Update
ClassNotFound Exception
JVM 内存模型
Serial / Serial Old年轻代,Serial 串行回收老年代,Serial Old 串行回收
加载jre/lib/rt.jar charset.jar等核心类, C++ 实现
End
用户线程
Bootstrap ClassLoader
CPU0
未被标记的对象
JMM三个特性 1. 原子性 2. 可见性 3. 有序性
Custom ClassLoader
flag 副本
JVM图谱
Minor GC / YGC :年轻代空间耗尽时触发Major GC / FGC :在老年代无法继续分配空间时触发,新生代老年代同时进行回收
Parent
栈上分配:- 线程私有小对象- 无逃逸- 支持标量替换- 无需调整线程本地分配(TLAB - Thread Local Allocation Buffer):- 每个线程占用Eden区1%- 多线程的时候不用竞争Eden区就可以申请空间,提高效率- 小对象- 无需调整老年代:- 分配大对象
Resolution
Survivor
垃圾回收线程多个线程并行处理
Extendsion ClassLoader
OLD 区
年龄是否达到
解释
Linking
CPU3
调用
用户线程2
JVM
flag
JVM是按需动态加载采用双亲委派机制
类装载器子系统 Class Loader
OS硬件
GC线程
GC
栈上分配
Minor Version
JMM模型(Java Memory Model):共享内存模型,JMM决定一个线程对共享变量的写入时对另一个线程可见。从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。 缓存行:缓存行越大,局部性空间效率越高,但读取时间慢缓存行越小,局部性空间效率越低,但读取时间快取一个折中值,目前多用:64字节(Intel)关于缓存一致性(锁总线):MESI Cache 缓存一致性协议(Intel): 在同一个缓存行保证数据一致性锁总线:在不同的缓存行保证数据一致性需要锁总线乱序问题:CPU为了提高指令执行效率,会在一条指令执行过程中(比如去内存读数据(慢100倍)),去同时执行另一条指令,前提是,两条指令没有依赖关系禁止乱序:- 硬件内存屏障 X86 CPU内存屏障sfence : store | 在sfence指令前的写操作当必须在sfence指令后的写操作前完成。 lfence : load | 在lfence指令前的读操作当必须在lfence指令后的读操作前完成。 mfence : modify/mix | 在mfence指令前的读写操作当必须在mfence指令后的读写操作前完成。原子指令:如x86上的”lock …” 指令是一个Full Barrier,执行时会锁住内存子系统来确保执行顺序,甚至跨多个CPU。Software Locks通常使用了内存屏障或原子指令来实现变量可见性和保持程序顺序- JVM级别如何规范(JSR133)JVM只是一个规范,具体实现还是需要看CPU或虚拟机如何实现。LoadLoad屏障:对于这样的语句Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。StoreStore屏障:对于这样的语句Store1; StoreStore; Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。LoadStore屏障:对于这样的语句Load1; LoadStore; Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。StoreLoad屏障: 对于这样的语句Store1; StoreLoad; Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。
其他线程
JDK = JRE + Development Kit
Application ClassLoader
执行构造方法语句
Constant_Pool(index: 1 ~ constant_pool_count - 1)
并发标记
栈帧
Garbage没有任何引用指向的一个对象或者多个对象(循环引用)如何定位垃圾1. 引用计数(ReferenceCount)记录对象是否被其他对象引用,当对象没有被其他对象引用就说明这个对象已经可以作为垃圾进行回收了。问题是无法解决循环引用。2. 可达性分析算法(RootSearching)通过一系列“GC Roots”对象作为起点进行搜索,如果在“GC Roots”和一个对象之间没有可达路径,则称该对象是不可达的。不可达对象不一定会成为可回收对象。进入DEAD状态的线程还可以恢复,GC不会回收它的内存。垃圾回收算法1. 标记清除算法(Mark-Sweep)标记清除算法分为两个阶段,标记阶段和清除阶段。标记阶段任务是标记出所有需要回收的对象,清除阶段就是清除被标记对象的空间。优点:实现简单缺点:位置不连续,容易产生碎片,效率偏低(两遍扫描)2. 复制清除算法(Copying)将可用内存划分为大小相等的两块,每次只使用其中的一块。当进行垃圾回收的时候了,把其中存活对象全部复制到另外一块中,然后把已使用的内存空间一次清空掉。优点:没有碎片缺点:浪费空间3. 标记整理算法(Mark-Compact)先标记存活对象,然后把存活对象向一边移动,然后清理掉端边界以外的内存。优点:没有碎片,缺点:效率偏低(两遍扫描,指针需要调整)GC算法基础概念Card Table 由于做YGC时,需要扫描整个old区,效率非常低,所以JVM设计了CardTable, 如果一个old区CardTable中有对象指向young区,就将它设为Dirty,下次扫描时,只需要扫描Dirty Card,在结构上,Card Table用BitMap来实现(位图)何时触发YGC:Eden区不足 / 多线程并行执行FGC:Old空间不足 / System.gc()
S1
N 调用子加载器findClass()加载
返回地址
共享内存
Shenandoah
自底向上检查该类是否已经加载parent
FE
RSet
-Xmn
CPU1
虚拟机栈JVM Stack
CMS 垃圾收集器
findLoadedClass()
class
成员变量赋初始值
jdk1.8默认
存活对象
当主线程将flag值修改后,其他线程马上能看到修改后的值,底层使用CPU的缓存一致性协议MESI
.......
自定义ClassLoader:1. extends ClassLoader (继承java.lang.ClassLoader)2. overwrite findClass() -> defineClass(byte[] -> Class clazz) (重写 findClass() 方法,调用父类 defineClass() 将字节流转换为class对象)自定义ClassLoader的parent如何指定:1. 在自定义ClassLoader的构造方法中调用super(parent),也就是调用ClassLoader构造方法手动指定parent,这个parent可以是自定义的也可以是 getSystemClassLoader() 即ApplicationClassLoader如何打破双亲委派:1. extends ClassLoader (继承java.lang.ClassLoader)2. overwrite loadClass() (重写 loadClass() 方法,不需要重写 findClass() 方法)何时需要打破双亲委派:1. 热启动,热部署(osgi tomcat 都有自己的模块指定classloader(可以加载同一类库的不同版本))2. ThreadContextClassLoader可以实现基础类调用实现类代码,通过thread.setContextClassLoader指定
Eden区
读取到自己工作空间
GC算法1. 三色标记白色:未被标记的对象灰色:自身被标记,成员变量未被标记黑色:自身和成员变量均已标记完成漏标问题:Remark过程中,黑色指向了白色,如果不对黑色重新扫描,则会漏标,会把白色D对象当做没有新引用指向从而被回收掉。并发标记过程中,Mutator删除了所有从灰色到白色的引用,会产生漏标,此时白色对象应该被回收。只有两者同时出现时,漏标问题产生。备注:未被标记的对象会被垃圾回收器清理算法核心:incremental updateCMS用到,关注引用的增加,把黑色重新标记为灰色,下次重新扫描属性snapshot at the beginning(SATB) G1用到,关注引用的删除,当灰色->白色消失时,要把这个引用push到GC的堆栈,保证D还能被 GC扫描到(由于有RSet存在,不需要扫描整个堆去查找指向白色的引用,效率比较高,SATB配合RSet使用,浑然天成)2. 颜色指针Colored Pointers ZGC使用
字节码文件
局部变量表
线程私有
TLAB分配
Local Memory本地内存A
Humongous
ParNew
每一块内存都是一个Region区
FGC
Parallel Old
new Object()过程
Serial / Serial Old 垃圾收集器
垃圾回收线程
GC 清除
Copying
Regin
Old
Eden
Magic Number
CA
运行时常量池Run-Time Constant Pool
Old区
GC 分代模型
标记-整理算法
对象分配过程
xx.java
编译
Java编译器
-Xmixed 默认为混合模式,开始解释执行,启动速度较快,对热点代码实行检测和编译-Xint 使用纯解释模式,启动很快,执行稍慢-Xcomp 使用纯编译模式,启动很慢,执行很快
方法区Method Area
...
E
parent.loadClass()
程序计数器Program Counter Register
调用构造方法<init>
Java源程序
Loading
classloader
类加载过程
class文件
Snapshot At The Beginning
Major Version
B
线程B
成员变量赋默认值
YOUNG 区
22
06
垃圾回收器分类
三色标记算法
执行引擎
ParNew / CMS年轻代,对Parallel Scavenge进行了增强,配合CMS的并行回收老年代,CMS并发回收(无法忍受SWT)语义的区别:PS/PO是用户线程停住,多个GC线程并行回收的概念CMS是用户线程不用停,跟GC线程并发运行的概念初始标记:标记根对象并发标记:边产生垃圾边清理(最浪费时间)重新标记:标记并发标记过程中产生的新垃圾或者取消又被引用的垃圾并发清理:并发清理垃圾,并发清理过程中产生的垃圾叫做浮动垃圾,等下次GC清理
内存分区,逻辑上分代(Eden区,Survivor区,Old区,Humongous区-大对象区)MixedGC:跟CMS并行回收类似,默认值45%-堆内存空间超过45%启动MixedGC,这个值可以手工修改,唯一的区别是最后一个阶段叫做筛选回收,筛选垃圾占用最多的Region区,将存活的对象复制到另外一个Region区域,复制同时进行了压缩,碎片没有CMS多。CSet:Collection Set 一组可被回收的分区的集合。在CSet中存活的数据会在GC过程中被移动到另一个可用分区,CSet中的分区可以来自Eden区,Survivor区,Old区。CSet会占用不到整个堆空间的1%的大小。RSet:Remembered Set 记录了其他Region中的对象到本Region的引用。RSet的价值在于使得垃圾回收器不需要扫描整个堆找到谁引用了当前分区中的对象,只需要扫描RSet即可。新生代:老年代 = 5% ~ 60% 最好不要手工指定,因为这是G1预测停顿时间的基准
堆Heap
Local Memory本地内存B
31
线程共享
本地方法接口Native Interface
重新标记
引用3
Serial Old
Class文件 16进制
线程A
标记-复制算法
Java编译器:将Java源文件(.java文件)编译成字节码文件(.class文件,是特殊的二进制文件,二进制字节码文件),这种字节码就是JVM的“机器语言”。javac.exe可以简单看成是Java编译器。字节码文件:已经编译过的.class文件,与平台无关,与特定机器码无关,是需要解释器转译后才能成为机器码的中间代码。Java解释器:是一种电脑程序,能够把高级编程语言一行一行直接翻译运行。解释器不会一次把整个程序翻译出来,只像一位“中间人”,每次运行程序时都要先转成另一种语言再作运行,因此解释器的程序运行速度比较缓慢。它每翻译一行程序叙述就立刻运行,然后再翻译下一行,再运行,如此不停地进行下去。它会先将源码翻译成另一种语言,以供多次运行而无需再经编译。其制成品无需依赖编译器而运行,程序运行速度比较快。JIT编译器:JIT编译器是JRE的一部分。原本的Java程序都是要经过解释执行的,其执行速度肯定比可执行的二进制字节码程序慢。为了提高执行速度,引入了JIT。在运行时,JIT会把翻译过来的机器码保存起来,以备下次使用。而如果JIT对每条字节码都进行编译,则会负担过重,所以,JIT只会对经常执行的字节码进行编译,如循环,高频度使用的方法等。它会以整个方法为单位,一次性将整个方法的字节码编译为本地机器码,然后直接运行编译后的机器码。https://blog.csdn.net/qq_42322103/article/details/88038677
自身和成员变量均已标记完成
即时编译
并发清理
Java解释器Bytecode Intepreter
Preparation
POP弹栈
volatile作用1. 保证线程可见性 - MESI 缓存一致性协议 2. 禁止指令重排序 - CPU指令有可能会进行重排序,volatile能保证禁止指令重排序 例:DCL单例
动态链接
G1用到,关注引用的删除,当灰色->白色消失时,要把这个引用push到GC的堆栈,保证D还能被GC扫描到(由于有RSet存在,不需要扫描整个堆去查找指向白色的引用,效率比较高,SATB配合RSet使用,浑然天成)
对象是否很大
执行引擎Execution Engine
JDK JRE JVM 关系
引用2
Volatile
Parallel Scavenge / Parallel Old 垃圾收集器
Parallel Scavenge
初始标记
JMM 内存模型
本地方法栈Native Method Stack
运行时数据区(Runtime Data Area)
加载classpath指定内容
-Xms -Xmx
1Byte
Start
加载扩展jar包,jre/lib/ext/*.jar,或由-Djava.ext.dirs指定
JRE = JVM + Core Lib
三色标记算法会产生漏标问题:1. Remark过程中,黑色指向了白色,如果不对黑色重新扫描,则会漏标,会把白色D对象当做没有新引用指向从而被回收掉。2. 并发标记过程中,Mutator删除了所有从灰色到白色的引用,会产生漏标,此时白色对象应该被回收。只有两者同时出现时,漏标问题产生。
主要因为安全问题,保证核心类库不被自定义加载器所加载。备注:父加载器并不是常说的继承关系,而是组合关系。
Return
xx.class
G1 垃圾收集器
JMM控制
0a
本地方法库
8
字节码解析器
引用1
对象创建过程
基于双亲委派机制将class load到内存
逻辑分代,物理分代
操作数栈
将类、方法、属性等符号引用解析为直接引用
静态成员变量赋初始值
Main Memory 主内存
GC线程多个线程并行处理
静态成员变量赋默认值
Parallel Scavenge / Parallel Old 年轻代,Parallel Scavenge 并行回收老年代,Parallel Old 并行回收
JIT即时编译器
new 对象
0 条评论
下一页