概述
JVM运行时数据区.pngJVM在执行Java程序会把内存大致划分成图中5个部分
1. 程序计数器
- 程序计数器(类似cpu中PC寄存器)存储下一条字节码指令地址,可以看作是当前线程所执行的字节码的行号指示器。解释执行时cpu通过程序计数器中下一条指令地址,不断取出下一条指令进行执行。
- 每个线程独立运行并且操作系统多任务调度时会发生线程切换,因此每个线程都有一个独立的程序计数器。
- 该区域内存占用大小固定,不会发生OOM
2. 虚拟机栈
虚拟机栈.png- 线程被创建时会同步为该线程创建一个栈帧,其生命周期与该线程相同
- 栈中用于存储局部变量表、操作数栈、动态连接、方法出口等信息
2.1 局部变量表
局部变量表是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。在 Java 程序编译为 Class 文件时,就在方法的 Code 属性的 max_locals 数据项中确定了该方法所需要分配的局部变量表的最大容量
JVM使用基于栈的指令集,相对于只用寄存器的指令集,指令比较简单;操作数存储在方法局部变量表中,通过load相关指令把操作数从局部变量表压栈到栈帧执行,再通过store相关指令把操作数从栈中写会局部变量表,其实局部变量表就相当于基于寄存器指令集中的寄存器用于存储局部变量,但是相对于寄存器使用局部变量表速度稍慢
--示例代码:
public void tests(String a){
{
int b = 4;
}
long c = 1L;
int d = 5;
}
先执行 javac -g Demo.java先编译,然后执行javap -c -l -p -v Demo 输出反汇编信息:
--反汇编信息:
public void tests(java.lang.String);
descriptor: (Ljava/lang/String;)V
flags: ACC_PUBLIC
Code:
stack=2, locals=5, args_size=2
0: iconst_4
1: istore_2
2: lconst_1
3: lstore_2
4: iconst_5
5: istore 4
7: return
LineNumberTable:
line 7: 0
line 9: 2
line 10: 4
line 11: 7
LocalVariableTable:
Start Length Slot Name Signature
0 8 0 this Lzzzliu/JVM/Demo;
0 8 1 a Ljava/lang/String;
4 4 2 c J
7 1 4 d I
局部变量表
以上面代码为例分析下局部变量表:
- 局部变量表的容量是以变量槽为最小单位。一个变量槽可以存放一个32位以内的数据类型,有boolean/byte/char/short/int/float/reference/returnAddress 8种,long/double需要分配两个连续的变量槽;
- 局部变量是有作用域的,如果当前字节码程序计数器的值已经超过了某个变量的作用域,则这个变量对应的变量槽是可以交给其他变量重用的;
- 示例反汇编代码:
- Code: 保存方法的汇编指令、局部变量表、行号指令映射表
- stack=2, locals=3, args_size=2
stack : 操作数栈的深度
args_size: 方法参数,这里多了一个this为jvm默认添加
locals: 占用的槽的大小,4个局部变量,this/a为reference类型各占1个变量槽;d为int类型占1个变量槽;c为long类型占2个变量槽,一共5个 - LineNumberTable:java代码中行号跟汇编指令的映射关系,debug或打印调用栈时会用
- LocalVariableTable:局部变量表
Start:变量在汇编指令中作用域的起始索引,例如this/a作用域从索引0的指令开始,c从4开始
Length:变量在汇编指令中作用域的长度(偏移量),例如this/a作用域从索引0的指令开始偏移量为8,即到方法最后一条指令结束作用于整个方法;c作用域从索引为4的指令开始到索引为8指令结束,即到方法结束
Slot:变量所占变量槽个数
Name:变量名(对应常量池索引)
Signature:变量签名(对应常量池索引)
2.2 操作数栈
操作数栈是一个后进先出栈,栈中每一个元素都可以是任意的java数据类型。栈中元素的数据类型必须与字节码指令的序列严格匹配,例如iadd指令执行时,最接近栈顶的两个元素的数据类型必须为int;
2.3 动态连接
每个栈帧中都包含一个指向运行时常量池中该栈帧所属方法的引用,通过该引用可以找到Class的常量池;
2.4 方法返回地址
方法正常退出时,主调方法的程序计数器的值就可以作为返回地址,栈帧中可能会保存这个值;异常退出时,返回地址要通过异常处理器来确定,栈帧中一般不会保存这部分信息;
2.5 附加信息
与调试、性能收集等相关的信息;
3. 堆
堆时被所有线程共享的一块内存区域,几乎所有的对象实例都在这里分配内存。
- 从回收内存角度看,大部分垃圾收集器都基于分代理论设计。在概念上有新生代 / 老年代 / Eden区 / Survivor区 / Region等;
- 从内存分配角度看,所有线程共享的java堆可以划分出多个线程私有的分配缓冲区(TLAB),线程先尝试在私有TLAB进行内存分配,以提升对象分配的效率;
- 字符串常量池也在堆中存储
4. 方法区
方法区是各个线程共享的内存区域,用于存储已经被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等Class(*.class)相关信息。
- 运行时常量池是方法区的一部分,Class文件常量池表中字面量和符号引用在类加载后都存放在运行时常量池中,同时也会存储由符号引用翻译出来的直接引用
- 运行时常量池具有动态性,并非预置入Class文件中常量池的内容才会进入常量池,例如String::intern就会在运行时放入运行时常量池(真正的字符串在堆中字符串常量池中)
5. 本地方法栈
本地方法栈在虚拟机调用本地方法(Native方法)时使用
--------over---------
- 参考《深入理解Java虚拟机》
网友评论