-
运行时数据区域:Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域
在jvm中,jvm内存主要分为堆,程序计数器,方法区,虚拟机栈和本地方法栈等。同时按照与线程的关系也可以这么划分:线程私有区域与线程共享区域。
线程私有区域:一个线程拥有单独的一份内存区域。
线程共享区域:被所有线程共享,且只有一份。除了以上,还有一个直接内存,虽然直接内存不是运行时数据区的一部分,但是会被频繁的使用。例如:操作 系统上有 8G 内存,被 JVM 虚拟化了 3G,那么还剩余 5G, JVM 是借助一些工具使用这 5G 内存的,这个内存部分称之为直接内存
-
Java方法的运行与虚拟机栈
虚拟机栈是线程运行Java方法所需的数据,指令,返回地址存储的地方。在我们实际的代码中,一个线程是可以运行多个方法的。
public class MethodAndStack {
public static void main(String[] args) {
A();
}
public static void A(){
B();
}
public static void B(){
C();
}
public static void C(){
}
}
这段代码很简单,就是起一个 main 方法,在 main 方法运行中调用 A 方法,A 方法中调用 B 方法,B 方法中运行 C 方法。 我们把代码跑起来,线程 1 来运行这段代码, 线程 1 跑起来,就会有一个对应 的虚拟机栈,同时在执行每个方法的时候都会打包成一个栈帧。 比如 main 开始运行,打包一个栈帧送入到虚拟机栈。
C 方法运行完了,C 方法出栈,接着 B 方法运行完了,B 方法出栈、接着 A 方法运行完了,A 方法出栈,最后 main 方法运行完了,main 方法这个栈帧就 出栈了。 这个就是 Java 方法运行对虚拟机栈的一个影响。虚拟机栈就是用来存储线程运行方法中的数据的。而每一个方法对应一个栈帧。
-
虚拟机栈:是一种先进后出的数据结构,在jvm运行过程中存储当前线程运行方法所需要的数据,指令,返回地址
虚拟机栈是基于线程的:哪怕你只有一个 main() 方法,也是以线程的方式运行的。在线程的生命周期中,参与计算的数据会频繁地入栈和出栈,栈的生 命周期是和线程一样的。
虚拟机栈的大小缺省为 1M,可用参数 –Xss 调整大小,例如-Xss256k。
参考官方文档:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html每个Java方法被调用时都会创建一个栈帧,并入栈,一旦方法完成相应的调用,则出栈。
栈帧大体都包含四个区域(局部变量表,操作数栈,动态连接,返回地址)1.局部变量表:用于存放我们的局部变量的(方法中的变量) 。首先它是一个 32 位的长度,主要存放我们的 Java 的八大基础数据类型,一般 32 位就可以存放下,如果是 64 位的就使用高低位占用两个也可以存放下,如果是局部的一些对象,比如我们的 Object 对象,我们只需要存放它的一个引用地址即可。
2.操作数栈:存放 java 方法执行的操作数的,它就是一个栈,先进后出的栈结构,操作数栈,就是用来操作的,操作的的元素可以是任意的 java 数据类型,所 以我们知道一个方法刚刚开始的时候,这个方法的操作数栈就是空的。
3.动态连接: Java 语言特性多态
4.返回地址:正常返回(调用程序计数器中的地址作为返回)、异常的话(通过异常处理器表<非栈帧中的>来确定) 同时,虚拟机栈这个内存也不是无限大,它有大小限制,默认情况下是 1M。 如果我们不断的往虚拟机栈中入栈帧,但是就是不出栈的话,那么这个虚拟机栈就会爆掉。
-
程序计数器
当前线程执行的字节码的行号指示器;各线程之间独立存储,互不影响。程序计数器是一块很小的内存空间,主要用来记录各个线程执行的字节码的地址,例如,分支、循环、跳转、异常、线程恢复等都依赖于计数器。 由于 Java 是多线程语言,当执行的线程数量超过 CPU 核数时,线程之间会根据时间片轮询争夺 CPU 资源。
如果一个线程的时间片用完了,或者是其它原因导致这个线程的 CPU 资源被提前抢夺,那么这个退出的线程就需要单独的一个程序计数器,来记录下一条运行的指令。 因为 JVM 是虚拟机,内部有完整的指令与执行的一套流程,所以在运行 Java 方法的时候需要使用程序计数器(记录字节码执行的地址或行号) ,如果 是遇到本地方法(native 方法),这个方法不是 JVM 来具体执行,所以程序计数器不需要记录了,这个是因为在操作系统层面也有一个程序计数器,这个 会记录本地代码的执行的地址,所以在执行 native 方法时,JVM 中程序计数器的值为空(Undefined)。 另外程序计数器也是 JVM 中唯一不会 OOM(OutOfMemory)的内存区域。
-
栈帧执行对内存区域的影响
public class Person {
public int work()throws Exception{
int x =1;
int y =2;
int z =(x+y)*10;
return z;
}
public static void main(String[] args) throws Exception{
Person person = new Person();//person 栈中--、 new Person 对象是在堆
person.work();
person.hashCode();
}
}
首先对class进行反汇编
public class ex1.Person {
public ex1.Person();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public int work() throws java.lang.Exception;
Code:
0: iconst_1
1: istore_1
2: iconst_2
3: istore_2
4: iload_1
5: iload_2
6: iadd
7: bipush 10
9: imul
10: istore_3
11: iload_3
12: ireturn
public static void main(java.lang.String[]) throws java.lang.Exception;
Code:
0: new #2 // class ex1/Person
3: dup
4: invokespecial #3 // Method "<init>":()V
7: astore_1
8: aload_1
9: invokevirtual #4 // Method work:()I
12: pop
13: return
}
在执行work方法时,
iconst_1指令将常量1加载到操作数栈
istore_1指令将数值1从操作数栈存储到局部变量表
iconst_2,istore_2和以上相同
iload_1,iload_2将x,y两个局部变量加载到操作数栈
执行iadd加法指令
bipush指令将10加载到操作数栈
imul指令经过乘法运算后,结果返回到操作数栈中,然后通过istore指令将结果从操作数栈加载到局部变量表,iload再将最后的结果加载到操作数栈进行ireturn指令返回到上级方法。
以上就是一个虚拟机栈的具体过程,指令参考地址:https://cloud.tencent.com/developer/article/1333540
今天就啃到这,明天继续~