首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >001. JAVA程序运行原理分析

001. JAVA程序运行原理分析

作者头像
山海散人
发布2021-03-03 10:54:13
发布2021-03-03 10:54:13
4930
举报
文章被收录于专栏:山海散人技术山海散人技术

1. 先来看看JVM运行时数据区的结构

  • 线程独占: 每个线程都有它独立的空间,随线程生命周期而创建和销毁。
  • 线程共享: 所有线程能访问这块内存数据,随虚拟机GC 而创建和销毁。
方法区
  • JVM 用来存储加载的类信息、常量、静态变量、编译后的代码等数据。
  • 虚拟机规范中,这是一个逻辑区域。
  • 具体实现根据不同虚拟机来实现。
  • 如 oracle 的 HotSpot 在 java7 中方法区放在永久代,java8 放在元数据空间,并且通过 GC 机制对这个区域进行管理。
堆内存
  • 堆内存可以分为:
    • 老年代
    • 新生代
      • Eden
      • From Survivor
      • To Survivor
  • JVM 启动时创建,存放对象的实例。
  • 垃圾回收期主要就是管理堆内存。如果满了,就会出现 OutOfMemoryError
虚拟机栈
  • 每个线程在这个空间有一个私有的空间。
  • 线程栈由多个栈帧(Stack Frame)组成。
  • 一个线程会执行一个或多个方法,一个方法对应一个栈帧。
  • 栈帧内容包含: 局部变量表、操作数栈、动态链接、方法返回地址、附加信息等。
  • 栈内存默认最大是 1M,超出则抛出 StackOverflowError
本地方法栈
  • 和虚拟机栈功能类似,虚拟机栈是为虚拟机执行 JAVA 方法而准备的,本地方法栈是为虚拟机使用 Native 本地方法而准备的。
  • 虚拟机规范没有规定具体的实现,由不同的虚拟机厂商去实现。
  • HotSpot 虚拟机中虚拟机栈和本地方法栈的实现是一样的。同样,超出大小以后也会抛出 StackOverflowError
程序计数器(Program Counter Register)
  • 记录当前线程执行字节码的位置,存储的是字节码指令地址,如果执行 Native 方法,则计数器值为空。
  • 每个线程都在这个空间有一个私有的空间,占用内存空间很少。
  • CPU 同一时间,只会执行一条线程中的指令。JVM 多线程会轮流切换并分配 CPU 执行时间的方式。为了线程切换后,需要通过程序计数器,来恢复正确的执行位置。

2. 接下来看看我们经常提到的字节码文件吧

1. 先搞一个测试代码
代码语言:javascript
复制
public class Demo1 {
    public static void main(String[] args) {
        int x = 500;
        int y = 100;
        int a = x / y;
        int b = 50;
        System.out.println(a + b);
    }
}
2. 编译并生成class文件
代码语言:javascript
复制
# 编译
javac Demo1.java
# 查看文件内容
javap -v Demo1.class > Demo.txt
3. 接下来看看Demo.txt文件都有些什么吧

针对 class 文件的官方描述

代码语言:javascript
复制
Classfile /Users/shadowolf/Demo1.class
  Last modified 2019-11-7; size 414 bytes
  MD5 checksum ae6fa820973681b35609c75631cb255b
  Compiled from "Demo1.java"
public class Demo1
  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            // Demo1
   #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               Demo1.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               Demo1
  #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 Demo1();
    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: "Demo1.java"
Classfile
代码语言:javascript
复制
Classfile /Users/shadowolf/Demo1.class
  Last modified 2019-11-7; size 414 bytes
  MD5 checksum ae6fa820973681b35609c75631cb255b
  Compiled from "Demo1.java"
  • 主要记录了一些文件的信息,包括文件本地地址、文件大小、最后更新时间、MD5校验、编译来源等。
public class Demo1
代码语言:javascript
复制
public class Demo1
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
  • 这一块主要描述编译的一些信息。
  • major version: 主版本号,minor version: 次版本号,以下是版本的对应关系。 JDK版本 major.minor version 1.1 45 1.2 46 1.3 47 1.4 48 1.5 49 1.6 50 1.7 51 1.8 52
    • 剩下的自己往下计算便可。
  • flags: 访问标志。如下是访问标志列表及解释 标志名称 标志值 含义 ACC_PUBLIC 0x0001 是否为 public 类型 ACC_FINAL 0x0010 是否被声明为 final,只有类可设置 ACC_SUPER 0x0020 是否允许使用 invokespecial 字节码指令,JDK12 之后编译出来的类的这个标示为 true ACC_INTERFACE 0x0200 标志这个是一个接口 ACC_ABSTRACT 0x0400 是否为 abstract 类型,对于接口或抽象类来说,此标志值为 true,其他值为 false ACC_SYNTHETIC 0x1000 标志这个类并非️用户产生的 ACC_ANNOTATION 0x2000 标识这是一个注解 ACC_ENUM 0x4000 标识这是一个枚举
Constant pool
代码语言:javascript
复制
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            // Demo1
   #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               Demo1.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               Demo1
  #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
  • 常量池。
  • 关于常量池的详细理解,推荐查看博客(http://softlab.sdut.edu.cn/blog/subaochen/2018/12/java-class%E6%96%87%E4%BB%B6%E7%BB%93%E6%9E%84%EF%BC%9A%E5%B8%B8%E9%87%8F%E6%B1%A0/)
  • 列举一下常量表项 类型 描述 CONSTANT_utf8_info UTF-8 编码的字符串 CONSTANT_Integer_info 整型字面量 CONSTANT_Float_info 浮点型字面量 CONSTANT_Long_info 长整型字面量 CONSTANT_Double_info 双精度浮点型字面量 CONSTANT_Class_info 类或接口的符号引用 CONSTANT_String_info 字符串类型字面量 CONSTANT_Fieldref_info 字段的符号引用 CONSTANT_Methodref_info 类中方法的符号引用 CONSTANT_InterfaceMethodref_info 接口中方法的符号引用 CONSTANT_NameAndType_info 字段或方法的符号引用 CONSTANT_MethodType_info 标志方法类型 CONSTANT_MethodHandle_info 表示方法句柄 CONSTANT_InvokeDynamic_info 表示一个动态方法调用点
构造方法
代码语言:javascript
复制
public Demo1();
    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
  • Demo1 中,我们并没有写构造函数。
  • 由此可见,没有定义构造函数时,会有隐式的无参构造函数。
  • descriptor: ()V -> 对于这个东西的理解,是入参为空,返回值为 void
入口函数: main 函数
代码语言:javascript
复制
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
  • 我们来看看整个程序的执行顺序
    • 0: sipush 500: 将500压入操作数栈 序号 本地变量表 0 args 操作数栈 500
    • 3: istore_1: 将500保存到本地变量表1的位置 序号 本地变量表 0 args 1 500 操作数栈
    • 4: bipush 100: 将100压入操作数栈 序号 本地变量表 0 args 1 500 操作数栈 100
    • 6: istore_2: 将100保存到本地变量表2的位置 序号 本地变量表 0 args 1 500 2 100 操作数栈
    • 7: iload_18: iload_2: 将本地变量表1、2位置的数据压入操作数栈 序号 本地变量表 0 args 1 500 2 100 操作数栈 100 500
    • 9: idiv: 进行除法运算,并且将结果压入操作数栈 序号 本地变量表 0 args 1 500 2 100 操作数栈 5
    • 10: istore_3: 将5(500/100)保存到本地变量表3的位置 序号 本地变量表 0 args 1 500 2 100 3 5 操作数栈
    • 11: bipush 50: 将50压入操作数栈 序号 本地变量表 0 args 1 500 2 100 3 5 操作数栈 50
    • 13: istore 4: 将50保存到本地变量表4的位置 序号 本地变量表 0 args 1 500 2 100 3 5 4 50 操作数栈
    • 15: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;: 将常量池中#2对应的常量压入操作数栈 序号 本地变量表 0 args 1 500 2 100 3 5 4 50 操作数栈 #2
    • 18: iload_3: 将本地变量表中3位置的数据(5)压入操作数栈 序号 本地变量表 0 args 1 500 2 100 3 5 4 50 操作数栈 5 #2
    • 19: iload 4: 将本地变量表中4位置的数据(50)压入操作数栈 序号 本地变量表 0 args 1 500 2 100 3 5 4 50 操作数栈 50 5 #2
    • 21: iadd: 将栈的前两个元素执行加法操作,并将执行结果(50+5=55)压入操作数栈 序号 本地变量表 0 args 1 500 2 100 3 5 4 50 操作数栈 55 #2
    • 22: invokevirtual #3 // Method java/io/PrintStream.println:(I)V: jvm回根据这个方法的描述,创建新栈帧,方法的参数从操作数栈中弹出,压入虚拟机栈中,然后虚拟机栈会开始执行虚拟机栈最上面的栈帧。
    • 25: return: 执行完毕,返回来继续执行main方法,返回,main方法结束。
    • 至此,我们的整个main函数的执行过程便解释完了。

3. 看看整体函数的运行分析吧


1. 加载信息到方法区
2. JVM创建线程来执行
3. 执行main函数
  • 该部分上面已做分析,在此不再重复。
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019/11/22 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 先来看看JVM运行时数据区的结构
    • 方法区
    • 堆内存
    • 虚拟机栈
    • 本地方法栈
    • 程序计数器(Program Counter Register)
  • 2. 接下来看看我们经常提到的字节码文件吧
    • 1. 先搞一个测试代码
    • 2. 编译并生成class文件
    • 3. 接下来看看Demo.txt文件都有些什么吧
    • Classfile
    • public class Demo1
    • Constant pool
    • 构造方法
    • 入口函数: main 函数
  • 3. 看看整体函数的运行分析吧
    • 1. 加载信息到方法区
    • 2. JVM创建线程来执行
    • 3. 执行main函数
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档