前面我们了解了类加载器,类加载器是把class文件读取到jvm虚拟机内存中去。
那么我们的java虚拟机内存结构是怎样的呢?
我们来看这张图片。
- 堆(JMM主存)
用于存放new出的对象、数组
有垃圾回收机制
多线程共享,所以存在线程安全问题 - 虚拟机栈(JMM 本地空间/工作内存/主内存的副本数据)
存放局部变量、栈帧(记录方法信息、当方法执行结束之后或者抛出异常的情况,栈帧空间销毁)、栈操作
这里面主要的是栈帧空间(局部变量、操作数栈、动态链接、方法地址(方法出口))
虚拟机栈也叫线程栈、每个线程都有自己独立的栈,互不影响
多线程不共享 - 本地方法栈
java代码调用C语言代码,也称作jni
底层使用C写的方法,比如:CAS原子类、 线程Thread - 方法区(1.8之前叫元空间)
存放类的信息、常量、静态变量、运行时常量
多线程共享 - 程序计数器(PC寄存器)
只有在多线程中有作用,在多线程的情况下,记录当前线程执行的行号,便于上下文切换 - 类加载器
读取class文件读取到jvm虚拟机内存
在这里重点提一下,栈帧。
了解栈帧之前,我们需要理解一下栈,栈是一个先进后出的数据结构,是限定仅在表尾进行插入或删除操作的线性表。
那什么是栈帧(Stack Frame)呢?
每一次函数的调用,都会在调用栈(call stack)上维护一个独立的栈帧(stack frame).每个独立的栈帧一般包括:
函数的返回地址和参数
临时变量: 包括函数的非静态局部变量以及编译器自动生成的其他临时变量
函数调用的上下文
栈是从高地址向低地址延伸,一个函数的栈帧用ebp 和 esp 这两个寄存器来划定范围.ebp 指向当前的栈帧的底部,esp 始终指向栈帧的顶部;
ebp 寄存器又被称为帧指针(Frame Pointer);
esp 寄存器又被称为栈指针(Stack Pointer);
具体的体现在哪?
示例代码:
/**
* @author 龙小虬
* @date 2021/4/12 15:43
*/
public class Main {
public void test01(){
test02();
}
public void test02(){
System.out.println("02");
}
public static void main(String[] args) {
Main main = new Main();
main.test01();
}
}
这就是我们的栈帧,它首先存储了main,在存储test01,在存储test02,一旦test02结束,那么就会执行test01,栈帧只有在方法全部执行完,或者抛出了全局异常,才会销毁。
还有一个程序计数器
程序计数器
主要作用在多线程的情况中,有很明显的案例。在单核系统中,开了两个线程,线程1,线程2。线程1运行了十秒,运行到了第n行,休眠,线程2运行,这时候程序计数器就将线程一所运行到的行数,记录下来,在下次线程唤醒的时候继续执行。
注意:
这里记录的行数,并不是我们高级语言的代码的行数,而是汇编文件的行数。
那我们怎么才能看到汇编代码呢?其实java命令有自带的编译命令:javap -c -v class文件地址
。
使用SpiService03.class文件进行反汇编。
编译前:
package com.lxq.impl;
import com.lxq.SpiService;
public class SpiService03 implements SpiService {
public SpiService03() {
}
public String output() {
return "热部署进行中";
}
}
命令:javap -c -v C:\xxx\SpiService03.class
Last modified 2021-4-12; size 428 bytes
MD5 checksum 49e757dc0f2f2e1a2ba161b862dfbb63
Compiled from "SpiService03.java"
public class com.lxq.impl.SpiService03 implements com.lxq.SpiService
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #4.#17 // java/lang/Object."<init>":()V
#2 = String #18 // 热部署进行中
#3 = Class #19 // com/lxq/impl/SpiService03
#4 = Class #20 // java/lang/Object
#5 = Class #21 // com/lxq/SpiService
#6 = Utf8 <init>
#7 = Utf8 ()V
#8 = Utf8 Code
#9 = Utf8 LineNumberTable
#10 = Utf8 LocalVariableTable
#11 = Utf8 this
#12 = Utf8 Lcom/lxq/impl/SpiService03;
#13 = Utf8 output
#14 = Utf8 ()Ljava/lang/String;
#15 = Utf8 SourceFile
#16 = Utf8 SpiService03.java
#17 = NameAndType #6:#7 // "<init>":()V
#18 = Utf8 热部署进行中
#19 = Utf8 com/lxq/impl/SpiService03
#20 = Utf8 java/lang/Object
#21 = Utf8 com/lxq/SpiService
{
public com.lxq.impl.SpiService03();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 9: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/lxq/impl/SpiService03;
public java.lang.String output();
descriptor: ()Ljava/lang/String;
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: ldc #2 // String 热部署进行中
2: areturn
LineNumberTable:
line 12: 0
LocalVariableTable:
Start Length Slot Name Signature
0 3 0 this Lcom/lxq/impl/SpiService03;
}
如果想了解怎么看汇编语言,可以找博主拿取汇编指令文件。
最后我们就来总结一下线程jvm空间: