JVM内存模型
2023-10-20 11:33:18 10 举报
JVM内存模型分析
作者其他创作
大纲/内容
方法区(MetaData Space 元空间)/Non-Heap
加载Loading
-Xint
こんにちは、世界
rc = 1
标记-复制算法(Semispace Copying)
堆用来存放对象,几乎所有(逃逸分析技术,栈上分配、标量替换)对象都在堆上分配,将Java堆细分的目的只是为了更好地回收内存,或者更快地分配内存,堆中没有内存分配对象,并且无法扩展时,会抛出OutOfMemoryError异常
新生代
obj1
方法表
obj1 = null;
引用计数算法
字节码生成
类接口的访问标识
类加载器引用
为类的静态变量分配内存,并将其初始化为默认值
TLAB
返回地址(Return Address)
ref
OopMap作用:快速找到GC ROOTS,当所有线程停下来的时候,并不需要一个不漏的检查完所有执行上下文和全局引用位置,虚拟机应该是有办法直接知道哪些地方存放着对象引用。在HotSpot的实现中,是使用一组称为OopMap的数据结构来达到目的的。OopMap存储两种对象引用:1、对象内的引用。2、栈、寄存器中引用
类型信息
Parallel Old GC
字段计数器和字段表集合
标记完回收前
偏向线程ID
对象Object
8种基本类型
ExtensionClass Loader
......
S0/From
Object
本地接口
Object obj1 = new Object;
-Xcomp
标记-清除算法(Mark-Sweep) 是最早出现也是最基础的垃圾收集算法,分为“标记”和“清除”两个阶段,从根集合(GC Roots)进行扫描,对存活的对象进行标记,标记完毕后,再扫描整个空间中未被标记的死亡对象进行回收标记回收。主要缺点: 第一执行效率不稳定,如果Java堆中包含大量对象,而且其中大部分是需要被回收的,这时必须进行大量标记和清除的动作,导致标记和清除两个过程的执行效率都随对象数量增长而降低。 第二内存空间碎片化问题,标记、清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致当以后在程序运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
javac 编译
null
可回收对象
Survival区与Eden区相同都在Java堆的年轻代。Survival区有两块,一块称为from区,另一块为to区,这两个区是相对的,在发生一次Minor GC后,from区就会和to区互换。在发生Minor GC时,Eden区和Survivalfrom区会把一些仍然存活的对象复制进Survival to区,并清除内存。Survival to区会把一些存活得足够旧的对象移至年老代。
TLAB(线程私有)
逆优化
CA FE BA BE 00 00 00 34 00 14 0A 00 03 00 11...
G1 GC
obj2
...
动态链接(Dynamic Linking)
类型指针
标注检查
LocalVariableTable
reference类型
rc = 2
连接
基于计数器的热点探测
存活对象
数组边界检查消除
常量池 加载 进方法区运行时常量池
Mac机器指令
加载、验证、准备、初始化和卸载这5个阶段的顺序是确定的,类的加载过程必须按照这种顺序进行,而解析阶段则不一定,它在某些情况下可能在初始化阶段后在开始,因为java支持运行时绑定。
Object obj2 = obj1;
栈上分配
空闲
编译后 .Class文件的Code属性的max_stacks数据记录操作数栈的最大深度
解析Resolution
同步消除
解释器
$JAVA_HOME/jre/lib/rt.jar
font color=\"#ff3333\
方法内联
对象真正存储的有效信息,即我们在程序代码里面所定义的各种类型的字段内容,无论是从父类继承下来的,还是在子类中定义的字段都必须记录起来
public class Main { void test() { int a = 1; int b = 2; int c = a + b; System.out.println(c); }}
确保被加载的类的正确性
字节码指令
User-DefinedClass Loader
版本minorversionmajorversion
基于采样的热点探测
$CLASSPATH
程序计数器,当前线程所执行的字节码的行号指示器,字节码解释器工作时,通过改变计数器的值,执行下一条指令,此内存是JVM规范中唯一一个没有OutOfMemoryError的区域。
方法区和永久代的关系: 在JDK1.8之前,习惯把方法区称为“永久代”HotSpot 虚拟机在1.7之前使用永久代来管理(实现)方法区,使得jvm的垃圾回收器能像管理堆内存一样来管理永久代这部分内存,而不用专门为方法区编写内存管理代码。但这种设计使得虚拟机更容易出现内存溢出问题,而不像J9和JRockit只要没有触碰到进程可用内存的上限(32位4GB),就没有问题。 JDK1.7把原本放在永久代的字符串常量池、静态变量等移出(主要剩余类型信息) JDK1.8完全废弃了永久代的概念,改用与JRockit、J9一样在本地内存中实现的元空间方法区无法满足新的内存分配需求时,将抛出OutOfMemoryError异常。
公共子表达式消除
如果对象是数组,有一块用于记录数组长度的数据
源文件
数组
对象头
Operand StackLIFO
在语法树的基础上再做一些处理,如给类添加默认的构造函数,检查变量在使用前是否已经初始化,将一些常量进行合并处理,检查操作变量类型是否匹配,检查所有的操作语句是否可达,检查checked exception是否正确处理。
aload_n
循环引用问题
词法分析
准备Preparation
把类中的符号引用转化为直接引用
Class文件的结构
线程私有
链接过程负责对二进制字节码的格式进行:校验、解析类中调用的接口、类。校验是防止不合法的.class文件,然后 对类中的所有属性、调用方法进行解析,以确保其需要调用的属性、方法存在,以及具备应的权限(例如public、private域权限等),会造成NoSuchMethodError、NoSuchFieldError等错误信息。
本地方法栈(线程私有)
局部变量表
分代收集算法 Generational Collection(分代收集)是目前大部分JVM的垃圾收集器采用的算法。根据对象存活的生命周期将内存划分为若干个不同的区域
即时编译器(JIT)编译后的代码缓存
数组长度
Object5
Linux机器指令
栈针N(method...)
年老代里存放的都是存活时间较久的,大小较大的对象,因此年老代使用标记整理算法。当年老代容量满的时候,会触发一次Major GC(full GC),回收年老代和年轻代中不再被使用的对象资源
GC Root Set
可达性分析/根搜索算法
returnAddress
Eden区位于Java堆的年轻代,是新对象分配内存的地方,由于堆是所有线程共享的,因此在堆上分配内存需要加锁。而Sun JDK为提升效率,会为每个新建的线程在Eden上分配一块独立的空间由该线程独享,这块空间称为TLAB(Thread Local Allocation Buffer)。在TLAB上分配内存不需要加锁,因此JVM在给线程中的对象分配内存时会尽量在TLAB上分配。如果对象过大或TLAB用完,则仍然在堆上进行分配。如果Eden区内存也用完了,则会进行一次Minor GC(young GC)。
Object1
GC分代年龄
不是必然存在,也没有特别的含义,它仅仅起着占位符的作用。HotSpot虚拟机要求对象起始地址必须是8字节的整数倍
属性表集合
HelloWorld.java
执行引擎
Windows机器指令
b: 2
STW
a: 1
程序计数器/PC寄存器(线程私有)
ParNew GC
字段信息
Java虚拟机规范,允许虚拟机实现增,在栈帧之中增加一些规范里没有描述的信息,例如与调试、性能收集相关的信息。
HelloWorld.class
加载
逃逸分析
Class信息
Safe Point
astore_n
标记-整理算法(Mark-Compact)
标记-整理算法(Mark-Compact) 标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向内存空间一端移动,然后直接清理掉边界以外的内存主要缺点: 进过标记后如果有大量存活对象,移动存活对象,并更新所有引用这些对象的地方将会是一种极为负重的操作,而且这种对象移动操作必须全程暂停用户应用程序才能进行 (Stop The World)。
SystemClass Loader
Serial Old GC
魔数CA FE BA BE
栈针2(method2)
运行时常量池是方法区的一部分,运行期间将可以手动将新的常量放入池中,如:String的intern()方法;受到方法区内存的限制,当常量池无法再申请到内存时会抛出OutOfMemoryError异常。
OopMap
栈针3(method3)
类的继承关系包括类、父类、索引、接口索引集合
将前面生成的信息转换为字节码写入磁盘,并进行少量代码的添加和转换
溢出
热点代码
HashCode
Eclipse JDT中的增量式编译器(ECJ)
局部变量槽Slot,64位long、dubbo占两个Slot,其余占一个
ConstantValue
编译后 .Class文件的Code属性的max_locals数据记录局部变量表的最大容量
使用Using
将Java源文件的字符流转变成对应的Token标记流
标量替换
类型常量
ObjectA
即时编译
15/大对象
Code
编译器
控制流分析
老年代
将Token流组件转换成更加结构化的语法树
常量池信息
Exceptions
Parallel Scavenge GC
本地方法库
BootstrapClass Loader
锁状态标志
语法分析
实例数据
所有收集器在根节点枚举这一步骤时都是必须暂停用户线程“Stop The World”在分析过程中,根节点集合的对象引用关系必须保持不变,即使是号称停顿时间可控,或者(几乎)不会发生停顿的CMS、G1、ZGC等收集器,枚举根节点时也是必须要停顿。
运行时常量池
解释执行状态下,将访问最频繁的数据(程序计数器、栈顶)缓存在物理CPU的寄存器中,以此降低对内存的读/写次数,提升执行引擎的执行效率。
Object4
ObjectB
验证Verification
局部变量表(Local Variable Table)
回收前
안녕 한 세계
javac Main.javajavap -v -p Main.class
S1/To
GC Roots
标记-复制算法(Semispace Copying):将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。主要缺点: 第一Copying算法的效率跟存活对象的数目多少有很大的关系,如果存活对象很多,那么Copying算法的效率将会大大降低。 第二将可用内存缩小为了原来的一半,空间浪费。
.....
填充符号表
类加载子系统
方法信息
方法计数器和方法表集合
堆 Heap
Object2
回收后
加载class
InnerClasses
Eden
栈顶缓存技术
即时编译器(JIT)
1、将类.class文件中的二进制数据读入到内存中。2、将加载的静态数据结构转化为方法区的运行时数据结构。3、在堆区创建一个Class对象
HelloWorld
你好世界
ObjectC
新生代/Young区 1/3
GC Roots的对象:1、在虚拟机栈(栈帧中的本地变量表)中引用的对象,如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等。2、在方法区中类静态属性引用的对象,如Java类的引用类型静态变量。3、在方法区中常量引用的对象,如字符串常量池(String Table)里的引用。4、在本地方法栈中JNI(即通常所说的Native方法)引用的对象。5、Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象(比如NullPointExcepiton、OutOfMemoryError)等,还有系统类加载器。6、所有被同步锁(synchronized关键字)持有的对象。7、反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等
String字符串常量池
对齐填充
语言处理javacecj
CMS GC
Object3
Serial GC
常量池主要存放,字面量(Literal)和符号引用(Symbolic References)字面量:如文本字符串、被声明为final的常量值等。符号引用:类和接口的全限定名、字段的名称和描述符、方法的名称和描述符、方法句柄和方法类型、动态调用点和动态常量
字节码文件
CPU
Object6
Direct Memory(直接内存)
老年代2/3(old区)
$JAVA_HOME/jre/lib/ext/*.jar
自定义类加载器
数据分析
额外的信息,如调试信息等
语义分析
如果线程请求的栈深度大于虚拟机所允许深度,将抛出StackOverflowError异常;如果Java虚拟机栈容量可以动态扩展 ,当栈扩展时无法申请到足够的内存会抛出OutOfMemoryError异常。
编译器优化技术
标记-清除算法(Mark-Sweep)
提前编译器AOT,直接把程序编译成与目标机器指令集相关的二进制代码
方法区 CodeCache
初始化Initialization
Linux 机器指令
安全点就是程序能够停顿的位置。即程序不是在任何时候停顿下来进行GC,只有到了安全点才去更新OopMap和停顿,等待GC完成在继续执行。
卸载Unloading
方法的行号信息
对象
正常调用完成异常调用完成
操作数栈(Operand Stack)
Native Method Stacks 为虚拟机使用到的本地(Native)方法服务,本地方法栈也会在栈深度溢出或者栈扩展失败时分别抛出StackOverflowError和OutOfMemoryError异常。
引用计数器
运行期间把常量池中的符号引用转换成直接饮用的过程,叫做动态链接;每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接。
LineNumberTable
Class实例引用
运行时数据区
虚拟机栈(线程私有)
Mark Word
0 条评论
回复 删除
下一页