Java中的体系架构
在Java体系架构中JVM属于jre环境中的一部分。
体系架构.png
JVM执行过程
首先我们先将程序代码编译成class或者jar文件,然后通过ClassLoader将字节码写入JVM的运行时数据区,JVM一边翻译一边执行
jvm机制.png
jvm执行过程.png
JVM运行时数据区
- 线程共享:所有线程都共用一个
- 方法区
用来存放类的信息(类的字节码),常量,静态变量,
jdk1.7之前是永久代,存放在虚拟机中
jdk1.8是元空间
元空间存放在本地存储中,只会受本地内存的限制 - 堆
用来存放new出来的对象以及该对象中的普通成员变量
- 线程不共享:每个线程独有一份
- 程序计数器
用来记录当前代码执行的位置
因为线程在运行的过程中会被CPU进行调度去执行其他线程代码,在调度的过程中会对线程的数据进行保存或读取,那下次获取执行权的时候该线程如何知道自己执行到了哪里?就是通过程序计数器来实现的。 - 虚拟机栈:每一个方法都是一个栈帧,执行的时候最先执行的先入栈,最后执行的先出栈
//举个例子
public static void main(String[] args){
a();
}
public static void a(){
b();
}
public static void b(){
}
虚拟机栈中的栈帧.png
(1) 局部变量表:用来存放方法中的局部变量的
(2) 操作数栈 :对数据进行操作是会进行入栈和出栈
(3) 动态链接 :一般用于多态中寻找某个具体的对象。静态分派,动态分派
(4) 完成出口 :记录方法出栈的位置
-
本地方法栈 :native方法的栈,hotspot虚拟机中将虚拟机栈和本地方法栈合二为一了
JVM运行时数据区模型.png
JVM工作流程
//原始代码
public class StackTest {
public static final int CONST_NUM=7;
public static String TAG="StackTest";
public static void main(String[] args) {
StackTest stackTest = new StackTest();
stackTest.add(10,5);
}
public int add(int x,int y){
return x+y;
}
}
//反编译出来的代码
Compiled from "StackTest.java"
public class com.example.myapplication.stack.StackTest {
public static final int CONST_NUM;
public static java.lang.String TAG;
public com.example.myapplication.stack.StackTest();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: new #2 // class com/example/myapplication/stack/StackTest
3: dup
4: invokespecial #3 // Method "<init>":()V
7: astore_1
8: aload_1
9: bipush 10
11: iconst_5
12: invokevirtual #4 // Method add:(II)I
15: pop
16: return
public int add(int, int);
Code:
0: iload_1
1: iload_2
2: iadd
3: ireturn
static {};
Code:
0: ldc #5 // String StackTest
2: putstatic #6 // Field TAG:Ljava/lang/String;
5: return
}
虚拟机栈工作过程
虚拟机指令集:https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html
首先看一看add方法是如何执行的,前面我们了解了每一个方法都是一个栈帧,所以add方法会当作一个栈帧压入虚拟机栈中
1.第一步首先应该是将两个参数放入局部变量表中,但是由于虚拟机优化的时候他会在栈帧之间进行数据共享,所以add方法会共享main方法中的局部变量表。
第一步操作.png
2.接下来会执行iload指令,将局部变量表中的角标为1和2的变量加载到操作数栈中
第二步操作.png
3.执行iadd指令,在执行这个指令的时候会将操作数栈中的数据pop,相加之后再放入操作数栈中
第三步操作.png
4.执行ireturn指令将操作数栈中的数据返回
JVM整体过程分析
JVM.png
本地内存
本地内存.png
堆和栈的区别
栈中的数据是线程独享的,而堆中的数据在所有线程中都可以使用
栈中的数据在出了作用域之后就会被释放,但是所有对象都存在堆中,堆需要垃圾回收机制来回收。
栈的大小远小于堆的大小
内存溢出
栈内存溢出:一般情况下都是递归循环调用的时候
堆溢出:空间不够用了,有可能内存泄漏
方法区溢出
(1) 运行时常量池溢出
(2)方法区中保存的Class对象没有被及时回收掉或者Class信息占用的内存超过了我们配置。
本地直接内存溢出:如果本地机器内存不足