java内存模型和Java程序运行原理初探

java内存模型和Java程序运行原理初探

Scroll Down

JMM到底是个啥子

欲学多线程,先学JMM。

JMM(Java Memory Model),即Java内存模型,是一个逻辑概念,并不是说物理内存里就是这样的结构,其实内存中的物理地址本身对于操作系统也是一个逻辑地址。 未命名文件.png

  • 线程独占:每个线程都有它独立的空间,随线程的生命周期而创建和销毁
  • 线程共享:所有线程都能访问此内存数据,随虚拟机或者GC创建和销毁

方法区

JVM用来存储加载的类信息、常量、静态变量、编译后的代码等数据

堆内存

Java启动时创建此内存空间,用于存放类的实例,即对象,满了就会OOM(OutMemoryError),垃圾回收主要管理的就是堆内存。

虚拟机栈

每个线程都有自己的一个虚拟机栈,线程栈由多个栈帧组成,线程中执行的每个方法对应着各自的一个栈帧。 栈帧内容包括局部变量表、操作数栈、动态链接、方法返回地址、附加信息等,栈内存默认最大1M,超出则StackOverFlow

本地方法栈

用于执行一些native方法,调用操作系统的一些功能等,比如文件读写的底层涉及到文件系统,肯定要经过操作系统的,还比如jvm初始化时候执行的一些C++代码。虚拟机规范并没有规定具体实现,所以不同的虚拟机实现方式不同。

程序计数器(PC)

Program Counter Register,记录当前线程执行的字节码的位置,存储的时字节码的指令地址,执行native方法时PC里的值为空,相当于CPU切换执行线程时用于记录的小本本。


初探.class神秘的裙底风光

public class test{
    public static void main(String[] args){
        int x = 500;
        int y = 100;
        int a = x / y;
        int b = 50;
        System.out.println(a + b);
    }
}

这段代码编译之后会变成什么鬼样子呢,来康康。

第一步先将java编译成class,因为.class是给jvm看的,人类并不能看懂,所以第二步将人类能够看懂的信息保存至test.txt

javac test.java
javap -v test.class > test.txt

打开test.txt,wow,awesome,这是记事本独享的momenttieba_emotion_87 这里直接贴出来吧

Classfile /C:/Users/rice/Desktop/test.class
  Last modified 2019-10-15; size 412 bytes
  MD5 checksum 10535501b08a5c0e05319708975b247d
  Compiled from "test.java"
public class test
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #5.#14         // java/lang/Object."<init>":()V
   #2 = Fieldref           #15.#16        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = Methodref          #17.#18        // java/io/PrintStream.println:(I)V
   #4 = Class              #19            // test
   #5 = Class              #20            // java/lang/Object
   #6 = Utf8               <init>
   #7 = Utf8               ()V
   #8 = Utf8               Code
   #9 = Utf8               LineNumberTable
  #10 = Utf8               main
  #11 = Utf8               ([Ljava/lang/String;)V
  #12 = Utf8               SourceFile
  #13 = Utf8               test.java
  #14 = NameAndType        #6:#7          // "<init>":()V
  #15 = Class              #21            // java/lang/System
  #16 = NameAndType        #22:#23        // out:Ljava/io/PrintStream;
  #17 = Class              #24            // java/io/PrintStream
  #18 = NameAndType        #25:#26        // println:(I)V
  #19 = Utf8               test
  #20 = Utf8               java/lang/Object
  #21 = Utf8               java/lang/System
  #22 = Utf8               out
  #23 = Utf8               Ljava/io/PrintStream;
  #24 = Utf8               java/io/PrintStream
  #25 = Utf8               println
  #26 = Utf8               (I)V
{
  public test();
    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 1: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=5, args_size=1
         0: sipush        500
         3: istore_1
         4: bipush        100
         6: istore_2
         7: iload_1
         8: iload_2
         9: idiv
        10: istore_3
        11: bipush        50
        13: istore        4
        15: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
        18: iload_3
        19: iload         4
        21: iadd
        22: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
        25: return
      LineNumberTable:
        line 3: 0
        line 4: 4
        line 5: 7
        line 6: 11
        line 7: 15
        line 8: 25
}
SourceFile: "test.java"

这里有好多字节码指令看不懂,没关系,以后慢慢学。

这整段代码可以分为三个部分来看,先来讲讲第一部分,也即是最前面的几行, 分别是版本号和访问标志,版本号的规则JDK5、6、7、8分别对应49、50、51、52。

public class test
  minor version: 0 //次版本号
  major version: 52 //主版本号
  flags: ACC_PUBLIC, ACC_SUPER //访问标志

常用的访问标志如下表

标志名称标志值含义
ACC_PUBLIC0X0001是否为public类型
ACC_FINAL0x0010是否被声明为final,只有类可设置
ACC_SUPER0x0020是否允许使用invokespecial字节码指令
ACC_INTERFACE0x0200标志这是一个接口
ACC_ABSTRACT0x0400是否为abstract类型
ACC_ANNOTATION0x2000标志这是一个注解
ACC_ENUM0x4000标志这是一个枚举
ACC_SYNTHETIC0x1000标志这个类并非由用户产生的

接下来偷懒直接截个图,左边是class文件,右边是一些静态常量的枚举。

class_常量池.jpg

 public test();
    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 1: 0

上面几行,就是传说的,一直不用我们自己写的,jvm帮我们加的,默认的无参构造函数,呼呼~

然后还有一部分就是最重要的入口函数,main方法了


public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=5, args_size=1
         0: sipush        500
         3: istore_1
         4: bipush        100
         6: istore_2
         7: iload_1
         8: iload_2
         9: idiv
        10: istore_3
        11: bipush        50
        13: istore        4
        15: getstatic     #2 // Field java/lang/System.out:Ljava/io/PrintStream;
        18: iload_3
        19: iload         4
        21: iadd
        22: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
        25: return
      LineNumberTable:
        line 3: 0
        line 4: 4
        line 5: 7
        line 6: 11
        line 7: 15
        line 8: 25

指令顺序执行,整个过程无非就是不停的将数值入栈、出栈、运算、结果再入栈,最后的剩下的,就是结果,这些助记符的意思这里就不挨个解释了,等下附上指令码表,不过别忘了这个class文件的text是我们通过javap得到的,jvm实际执行的,可不是这些简明易懂的助记符,而是实实在在的16进制指令。

执行过程中,计数器帮我们记住指令执行到的位置,当遇到调用新的方法时,JVM会保留犯罪现场(将当前栈帧压入虚拟机栈),去执行新的任务(创建新的栈帧),也就是在另一个方法里如此循环,直到执行完所有方法,返回最后的结果。


总结

好多都是平时知道一些但总也说不明白,这次总结了一下清晰多了,平时的代码都是IDE帮着编译,这次自己动手感觉竟然有点不错?(我看你是工作不饱和coolapk_emotion_55_doge

附件

JVM指令码表

引用

Java微专业