JVM内存模型
javap -c xx.class看汇编c
概念图
栈帧:一个方法对应一块栈帧区域,基本就是数据结构中的栈,特性先进后出,后进先出,符合代码执行特性。
操作数栈:临时的内存空间
验证代码:
package com.gx.demo.jvm; public class MyMath { public int calculate(int a, int b){ int result = 0; result = (a + b)*5; return result; } public static void main(String[] args) { MyMath myMath = new MyMath(); int res = myMath.calculate(3, 7); System.out.println(res); } }
javap -c MyMath.class
{ public com.gx.demo.jvm.MyMath(); 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 3: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcom/gx/demo/jvm/MyMath; public int calculate(int, int); descriptor: (II)I flags: ACC_PUBLIC Code://一个方法对应一个栈帧,即有自己的局部变量表 stack=2, locals=4, args_size=3//a->局部变量1 b->局部变量2 0: iconst_0//将int类型常量0压入栈 常量0 1: istore_3// 将int类型值存入局部变量3 参数result 2: iload_1//从局部变量1中装载int类型值 参数a 3: iload_2//从局部变量2中装载int类型值 参数b 4: iadd//执行int类型的加法 5: iconst_5//将int类型常量5压入栈 得到a+b=5 6: imul//乘法 7: istore_3// 将int类型值存入局部变量3 即5*10=50 8: iload_3//从局部变量2中装载int类型值 50 9: ireturn//返回 LineNumberTable: line 6: 0 line 7: 2 line 8: 8 LocalVariableTable://局部变量 Start Length Slot Name Signature 0 10 0 this Lcom/gx/demo/jvm/MyMath; 0 10 1 a I 0 10 2 b I 2 8 3 result I public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=3, locals=3, args_size=1 0: new #2 // class com/gx/demo/jvm/MyMath 3: dup//复制栈顶部一个字长内容 4: invokespecial #3 // Method "<init>":()V 7: astore_1//将引用类型或returnAddress类型值存入局部变量1 参数new的myMath,存的堆地址 8: aload_1//从局部变量1中装载引用类型值 9: iconst_3//将int类型常量3压入栈 10: bipush 7 //将一个8位带符号整数压入栈 7 也占一次操作(11) //iconst_5 = 8 (0x8)指令码 //bipush = 16 (0x10)指令码 12: invokevirtual #4 //调度对象的方法 // Method calculate:(II)I 15: istore_2//将int类型值存入局部变量2 参数res 16: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream; 19: iload_2//从局部变量2中装载int类型值 calculate返回的结果50 20: invokevirtual #6 // Method java/io/PrintStream.println:(I)V 23: return LineNumberTable: line 12: 0 line 13: 8 line 14: 16 line 15: 23 LocalVariableTable: Start Length Slot Name Signature 0 24 0 args [Ljava/lang/String; 8 16 1 myMath Lcom/gx/demo/jvm/MyMath; 16 8 2 res I }
iconst_* 即-1-5
bipush 即-128~127
sipush 即-32768~32767
ldc 即-2147483648~2147483647
具体可见官网:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.iconst_i
icons-》push int constant
bipush-》push byte , etc.
指令名称 |
指令码(对应class码文件) |
数值 |
iconst_m1 |
2 (0x2) |
-1 |
iconst_0 |
3 (0x3) |
0 |
iconst_1 |
4 (0x4) |
1 |
iconst_2 |
5 (0x5) |
2 |
iconst_3 |
6 (0x6) |
3 |
iconst_4 |
7 (0x7) |
4 |
iconst_5 |
8 (0x8) |
5 |
简单说下calculate方法 运作情况:
--》0: iconst_0,0代表程序计数器的位置,每次操作完都会更新
2、由字节码执行引擎执行操作数为1的步骤,且更新操作数(由字节码执行引擎修改)
--》1: istore_3 程序计数器=1
3a+b操作
说明:3、7、5等常量都放在操作数栈,临时内存
局部变量和堆:new MyMath()
栈
Java虚拟机栈是线程私有的,就是平常说的堆栈中的“栈内存”。此处指的是JVM中的运行时内存区域的概念划分,而不是JMM(java 内存模型),每个方法在执行的同时都会创建一个栈帧(Stack Frame),方法结束,栈内存也就自动释放了。
其中包括四个部分,分别是:
1局部变量表(用来存储局部变量表的,包含两部分:一是方法中的参数,二是方法中创建的局部变量。局部变量必须被初始化才能使用。包括this,FILO)
2操作数栈(方法用到的常量等,FILO)
3动态链接(方法中对象的引用,存的堆地址等)
4方法出口(方法return到哪个地方)等信息
JVM 调用本地方法(Native)接口时使用,也是线程私有的(历史原因,之前很多接口由C/C++实现的,字节码解析后调用相应的dll文件),这时候使用的空间就是本地方法栈。
程序计数器
在JVM中的运行时内存区域的概念划分中,目前自己了解到主要以下作用,其通过字节码执行引擎工作执行class文件,由字节码执行引擎修改当前这个程序计数器的值来选取下一条需要执行的字节码指令(上面图中有体现)。
(以下为参考)
分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
JVM的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,为了各条线程之间的切换后计数器能恢复到正确的执行位置,所以每条线程都会有一个独立的程序计数器。
当线程正在执行一个Java方法,程序计数器记录的是正在执行的JVM字节码指令的地址;如果正在执行的是一个Natvie(本地方法),那么这个计数器的值则为空(Underfined)。
程序计数器占用的内存空间很少,也是唯一一个在JVM规范中没有规定任何OutOfMemoryError(内存不足错误)的区域
堆内存是所有线程共享的,基本上可以分为一个年轻代和一个老年代。
1.年轻代分为两部分:伊甸区(Eden space)和 幸存者区(Survivor space)。所有的类都是在伊甸区被new出来
的。幸存者区又分为 From 区 和 To 区。当Eden区的空间用完时,程序要继续new对象,JVM的垃圾回收器将对
Eden区进行垃圾回收(Minor GC,通过gc root(从栈(局部变量)、方法区中去找变量的引用,一直往前找,找到根时如果没有别人对象引用,则会标志为垃圾对象,否则标志为非垃圾对象)),将Eden区中的不再被其他对象应用的对象销毁,然后将剩余的对象移到From Survivor区。若From区也满了,再对该区进行垃圾回收,然后将剩余的移动到To Survivor区。
2.老年代:新生代经过多次GC仍然存活的对象移动到老年代。若老年代满了,则会发生Major GC(Full GC)进行老年区的清理。若老年区经理了清理后依然无法进行对象的保存,则会抛出OOM异常以及STW事件。
Stop一the一World,简称STW,指的是Gc事件发生过程中,会产生应用程序的停顿。停顿产生时整个应用程序线程都会被暂停,没有任何响应,有点像卡死的感觉,这个停顿称为STW。
3.元空间:在JDK1.8以后,元空间替代了永久代,它是对JVM规范中方法区的实现,区别在于元数据区不在虚拟机当中,而是用的本地内存。而永久代在虚拟机中,永久代逻辑结构上也属于堆,但是物理上不属于。
方法区也是所有线程所共享的。它用于存储已经被JVM加载的类信息(类型信息:差不多是C的对象元信息)、常量(存堆中的地址)、静态变量、即时编译器(JIT)编译后的代码等数据信息。且设有保护程序,当一个线程正在访问的时候,另一个线程不能同时加载一个类,需要延迟等待。
同时,方法区中的大小是可以改变的,运行时也可以扩展,对象也可进行垃圾回收,不过条件比较苛刻,需要没有任何引用才会进行回收。
JDK8之前是永久代,之后叫元空间。