1. JDK体系结构
JDK: 包含JAVA运行语言,JRE运行环境,JVM虚拟机。
2. Java语言的跨平台特性
不同平台JDK不同即JVM不同,所以同一JAVA代码在不同平台能生成平台对应的机器码运行,这就是跨平台。
3. JVM整体结构及内存模型
JVM虚拟机组成部分
虚拟机核心是运行时数据区。
1. 类装载子系统(C++):加载Class文件,丢到运行时数据区(JVM内存区)。
2. 字节码执行引擎(C++),执行方法区的加载类信息的代码,修改线程运行方法内容指令时,程序计数器的存储指令的地址。
3. 虚拟机每个线程,都会分配一块工作内存。一个工作内存包含,虚拟机栈,程序计数器,本地方法栈。
4. 虚拟机栈和本地方法栈也成为JAVA的栈区。 栈使用都是本地内存,也就是内存条剩余物理内存。
线程栈(虚拟机栈)
1. 每个线程分配唯一的栈,私有的 。
2. 线程运行每个方法都会分配唯一的栈帧,存放方法运行过程中的数据结构,栈帧是存放在线程栈中。
栈里头栈帧是后入先出,一个方法运行完栈帧就出栈,释放栈帧的数据。
栈帧存放:局部变量表,动态链接,操作数栈,方法出口等。
3. 栈和栈帧的大小可以设置,有默认大小。
查看设置栈大小
查看虚拟机栈的默认大小
java -XX:+PrintFlagsFinal -version | findstr ThreadStackSize
这边默认虚拟机栈VMThreadStackSize为0B。
通过JVM启动参数来设置栈的大小。例如,使用-Xss参数可以指定每个线程的栈大小,单位可以是K、M等。例如,-Xss2M表示将线程的栈大小设置为2MB。
查看设置栈帧大小
java -XX:+PrintFlagsFinal -version | findstr MaxJavaStackTraceDepth
这边默认栈帧大小1024B,栈帧大小是由编译器或解释器自动计算和设置的,一般不需要手动设置。
4. 栈帧里头数据满了或者栈里头栈帧满了,会报发生栈溢出错误(StackOverflowError)。
栈帧的组成部分
局部变量表
局部变量表,存储的就是某个方法运行期间,会使用或创建的变量(local variable),以数组方式存储。一个存储单位称之为slot(槽)。一个方法的局部变量表长度(slot的个数,也就是数组的长度)是在编译器就已经决定了的,我们能在class文件里找到这个值。
局部变量表大概如下所示:
0位置:每个方法的局部变量表,第一个位置必须是this,调用该方法的对象。
方法参数列表:方法参数列表排在this后面。
方法内部创建的变量:方法内部创建变量排在方法参数列表后面。譬如我们在方法里声明了一个int值,那么在局部变量表后面就会新增一个slot,存储这个int变量。但是需要注意的是,如果你只是new Object,却并没有定义变量,那么是不会增加slot的。
需要注意的是,int、boolean、char、Object这种都只占一个slot,如果遇到long或者double类型的,则占用两个slot来存储。
操作数栈
操作数栈,是一种栈结构。运行方法指令时,临时存放操作数据地方。类似于局部变量表,其操作数栈最大深度也是在编译期间就已经决定了,也能在class文件中找到该值。
JAVAP -V 命令解析一个Class文件来理解操作数栈
源代码内容:
JAVAP -V 解析代码编译后Class文件的助计指令:
分析:
结合JVM指令表,分析其中compute方法。
0. iconst_1 将int类型常量1入操作数栈。
1. istore_1 将操作栈顶int类型值存入局部变量1。局部变量1相当于局部变量表数组下标1的元素。这边是局部变量a,将栈顶的常量1出栈存入a,这时a=1,局部变量都已在局部变量表中分配了一个槽。
2. iconst_2 将int类型常量2入栈。
3. istore_2 将操作栈顶int类型值出栈存入局部变量2,这边就是赋值给b。
4. iload_1 从局部变量1中装载int类型值,将a的值复制入操作栈。
5. iload_2 从局部变量2中装载int类型值,将b的值复制入操作栈。
6. iadd 从操作数栈顶弹出两个int类型的数值,将它们相加再入栈,这边就是a+b。结合cpu来说,该指令转成cpu指令,cpu从寄存器或内存中