文档章节

001. JAVA程序运行原理分析

紫穹
 紫穹
发布于 11/22 00:20
字数 2577
阅读 47
收藏 0

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

img

  • 线程独占: 每个线程都有它独立的空间,随线程生命周期而创建和销毁。
  • 线程共享: 所有线程能访问这块内存数据,随虚拟机GC 而创建和销毁。
  • JVM 用来存储加载的类信息、常量、静态变量、编译后的代码等数据。

方法区

  • 虚拟机规范中,这是一个逻辑区域。
  • 具体实现根据不同虚拟机来实现。
  • 如 oracle 的 HotSpot 在 java7 中方法区放在永久代,java8 放在元数据空间,并且通过 GC 机制对这个区域进行管理。

堆内存

img

  • 堆内存可以分为:
    • 老年代
    • 新生代
      • Eden
      • From Survivor
      • To Survivor
  • JVM 启动时创建,存放对象的实例。
  • 垃圾回收期主要就是管理堆内存。如果满了,就会出现 OutOfMemoryError

虚拟机栈

  • 每个线程在这个空间有一个私有的空间。
  • 线程栈由多个栈帧(Stack Frame)组成。
  • 一个线程会执行一个或多个方法,一个方法对应一个栈帧。
  • 栈帧内容包含: 局部变量表、操作数栈、动态链接、方法返回地址、附加信息等。
  • 栈内存默认最大是 1M,超出则抛出 StackOverflowError

本地方法栈

  • 和虚拟机栈功能类似,虚拟机栈是为虚拟机执行 JAVA 方法而准备的,本地方法栈是为虚拟机使用 Native 本地方法而准备的。
  • 虚拟机规范没有规定具体的实现,由不同的虚拟机厂商去实现。
  • HotSpot 虚拟机中虚拟机栈和本地方法栈的实现是一样的。同样,超出大小以后也会抛出 StackOverflowError

程序计数器(Program Counter Register)

  • 记录当前线程执行字节码的位置,存储的是字节码指令地址,如果执行 Native 方法,则计数器值为空。
  • 每个线程都在这个空间有一个私有的空间,占用内存空间很少。
  • CPU 同一时间,只会执行一条线程中的指令。JVM 多线程会轮流切换并分配 CPU 执行时间的方式。为了线程切换后,需要通过程序计数器,来恢复正确的执行位置。

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

1. 先搞一个测试代码

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文件

# 编译
javac Demo1.java
# 查看文件内容
javap -v Demo1.class > Demo.txt

3. 接下来看看Demo.txt文件都有些什么吧

针对 class 文件的官方描述(https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.1)

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

Classfile /Users/shadowolf/Demo1.class
  Last modified 2019-11-7; size 414 bytes
  MD5 checksum ae6fa820973681b35609c75631cb255b
  Compiled from "Demo1.java"
  • 主要记录了一些文件的信息,包括文件本地地址、文件大小、最后更新时间、MD5校验、编译来源等。

public class Demo1

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

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 表示一个动态方法调用点

构造方法

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 函数

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. 加载信息到方法区

img

2. JVM创建线程来执行

img

3. 执行main函数

  • 该部分上面已做分析,在此不再重复。

© 著作权归作者所有

紫穹
粉丝 33
博文 166
码字总数 92733
作品 0
海淀
程序员
私信 提问
LINUX类主机JAVA应用程序占用CPU、内存过高分析手段

转载声明:本文为DBA+社群原创文章,转载必须连同本订阅号二维码全文转载,并注明作者名字及来源:DBA+社群(dbaplus)。 做为一个IT运维人员,通常在运维过程中会遇到各种各样的问题,系统问...

丁启良
2015/12/04
0
0
【JAVA基础☞探针技术】Java探针-Java Agent技术

1、原理:基于javaAgent和Java字节码注入技术的java探针工具技术原理 2、原理分析 动态代理功能实现说明,我们利用javaAgent和ASM字节码技术开发java探针工具,实现原理如下: jdk1.5以后引入...

卯金刀GG
06/26
244
0
[Java学习探讨]为什么学Java虚拟机的Java程序员更值钱?

[Java学习探讨]为什么学Java虚拟机的Java程序员更值钱? 曾经的我经常害怕处理与JVM相关的异常,对JVM的配置参数也一无所知,那时候我天真地认为,JVM的出现本身就是想让程序员屏蔽实现细节,...

原创小博客
2018/07/19
346
0
JVM学习总结五——性能监控及故障处理工具

之前扯了四篇理论,这一篇终于可以动动手了。本篇我们将介绍JVM常用的一些工具,这些工具将是我们监控JVM状态、处理故障和调优分析的利器。 不过在开始之前,我还是要先车扯两句:工具终归只...

oO脾气不坏Oo
2014/03/16
289
0
加强Docker容器与Java 10集成

很多运行在Java虚拟机(JVM)中的应用,包括数据服务如Apache Spark和Kafka以及传统企业应用,都运行在容器中。最近,运行在容器里的JVM出现了由于内存和CPU资源限制和使用率导致性能损失问题...

java高级架构牛人
2018/06/04
25
0

没有更多内容

加载失败,请刷新页面

加载更多

Java 运行时获取方法参数名

本文整理 Java 运行时获取方法参数名的两种方法,Java 8 的最新的方法和 Java 8 之前的方法。 Java 8 的新特性 翻阅 Java 8 的新特性,可以看到有这么一条“JEP 118: Access to Parameter Na...

xiaomin0322
8分钟前
2
0
varchar和nvarchar有什么区别?

只是nvarchar支持多字节字符吗? 如果是这种情况,除了存储问题之外,使用varchars什么意义吗? #1楼 nVarchar将帮助您存储Unicode字符。 如果要存储本地化数据,这是可行的方法。 #2楼 nvar...

javail
11分钟前
2
0
如何通过curl调用使用HTTP请求发送标头?

我希望在Linux机器上向我的Apache服务器发送一个标头。 如何通过卷曲调用实现此目的? #1楼 得到: 使用JSON: curl -i -H "Accept: application/json" -H "Content-Type: application/json......

技术盛宴
27分钟前
4
0
SQL语句的执行过程

(一)架构组件 (二)执行流程

麦馍
32分钟前
4
0
如何快速核对Excel数据表?这样操作不同的数据一目了然

怎么快速的核对两个Excel数据表呢?组长给了同事两份报表让他在半小时内核对完毕,不同的地方要标记起来,这时候同事抱怨起来:“数据那么多半小时怎么够呢”?虽然嘴上在抱怨,但是他还是乖...

百因必有果
38分钟前
6
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部