java基础-java运行原理

目录

1、Class文件内容:

2、JVM运行时数据区:

 方法区:

堆内存:

虚拟机栈:

本地方法栈:

程序计数器:(Peogram Counter Register)

3、查看Class文件内容:

4、Class文件解析内容:

①版本号/访问控制:

 ②常量池 Constant pool

③构造方法:

 ④程序入口Main方法:

5、这个Demo1到底是怎么执行的?

程序完整运行分析:

执行步骤:

6、本章结语:


1、Class文件内容:

class文件包含JAVA程序执行的字节码,数据按照格式紧凑排列在class文件中的二进制流,中间没有任何分割符;文件开头有一个0xcafebabe(16进制)特殊的标志

上图展示16进制的class文件内容 

这个文件有着复杂的格式,专门给JVM读取里面的内容,人阅读需要借助工具查看。

内容包含:版本、访问标志、常量池、当前类、超级类、接口、字段、方法、属性。

2、JVM运行时数据区:

 线程独占:每个线程都会有它的独立空间,随线程生命周期而创建和销毁。

线程共享:所有线程能访问这块区域,随虚拟机或者GC而创建和销毁。

 方法区:

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

虚拟机规范中这是一个逻辑区划。具体实现根据不同虚拟机来实现。

如:oracle的HotSpot在java7中,方法区放在永久代,java8放在元数据空间,并且通过GC机制对这个空间进行管理。

堆内存:

还可以细分为:老年代、新生代(Eden、From Survivor、To Survivor).

JVM启动时创建,存放对象的实例。垃圾回收器主要就是管理堆内存。

内存如果满了,就会出现OutOfMemroyError。后面在内存模型中详细说明。

虚拟机栈:

为虚拟机执行java方法而准备的

每个线程都在这个空间有个私有的空间。

线程栈由多个栈帧(Stack Frame)组成。

一个线程会执行一个或多个方法,一个方法对应一个栈帧。

栈帧内容:局部变量表、操作数栈、动态链接、方法返回地址、附加信息等。

栈内存默认最大为1M,超过则抛出StackOverFlowError 

本地方法栈:

为虚拟机使用Native本地方法而准备的。

虚拟机规范没有规定具体的实现,由不同的虚拟机厂商去实现。

HotSpot虚拟机中两者的实现方式是一样的。同样超出超出大小以后也会抛出StackOverFlowError 

程序计数器:(Peogram Counter Register)

记录当前线程执行字节码的位置,存储字节码指令地址,如果执行Native方法,则计时器值为空。

每个线程都在这个空间有个私有空间,占用内存空间较少。

CPU同一时间只会执行一条线程中的指令。JVM多线程会轮流切换并分配CPU执行时间的方式。线程切换后,需要通过程序计数器,来恢复正确的执行位置。

3、查看Class文件内容:

java源代码:

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);
    }
}

在当前java文件目录下 控制台执行如下命令:

编写.java源代码,javac命令编译成.class文件,使用javap命令解析结果,查看txt文件解析内容。Class解析内容如下:

4、Class文件解析内容:

①版本号/访问控制:

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

版本号规则:JDK5、6、7.、8分别对应49、50、51、52

jvm编译源代码的时候对应的版本号

访问标志:

 ②常量池 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

 可以对照表,一 一对应起来。

③构造方法:

Deme1这个例子中,我们并没有写构造函数。

内容如下,由此可见,没有自定义构造函数,会有隐式的无参构造函数。

  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

 ④程序入口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

描述了方法的:

访问控制

flags: ACC_PUBLIC, ACC_STATIC

本地变量数量、参数数量、方法对应栈帧中操作数栈的深度:

 stack=3, locals=5, args_size=1

JVM执行引擎去执行这些源码编译过后的指令码。javap翻译出来是操作符,class文件存储的是指令码。前面的数字是偏移量(字节),jvm根据这个区分不同的指令。可以对照JVM指令码表。

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

有兴趣的小伙伴可以对照JVM指令码表对照解读。

5、这个Demo1到底是怎么执行的?

程序完整运行分析:

①加载类信息存储在方法区中。方法区可以存不同类的信息

②JVM创建线程来执行代码。因为要创建线程,所以需要在虚拟机栈程序计数器上分配相应的空间给这个线程。Demo1案例纯java代码,不涉及本地方法,所以不需要在本地方法栈分配空间。这个就是前面讲的线程独占空间。 

 Demo1有五个本地变量,变量0是方法参数args

 程序计数器,给当前线程开辟一个空间,用来记录当前线程执行代码执行到哪个位置了,就是执行到哪个序号了。在多线程切换的时候,cpu执行哪个线程,程序计数器就还原到哪个线程的执行位置。

 虚拟机栈:给当前线程开辟一个栈空间一个线程对应一个栈,一个栈由多个栈帧组成,一个栈帧对应一个方法对应的操作。线程就是不断的执行一个或多个方法,所以一个线程对应一个或多个栈帧。

其他栈帧信息,有兴趣的小伙伴自行查阅。

程序运行规则:代码转换为指令码,jvm根据指令码执行。

执行步骤:

初始化: 5个本地变量(代码继续得出),变量0是方法参数args方法参数对应本地变量表0的位置

0:sipush500

程序计数器:0,将500压入操作数栈

3:istore_1

将操作数栈 栈顶内容,也就是500取出,保存到本地变量表1的位置

  

4:bipush 100

将100压入操作数栈

6:istore_2

将操作数栈 栈顶内容,也就是100取出,保存到本地变量表2的位置

对照Demo1的源代码,开始执行出发运算啦

7:iload_1

读取本地变量1,压入操作数栈。

 

8:iload_2

读取本地变量2,压入操作数栈。

 

9:idiv

将栈顶两int类型数出栈,做相除运算,结果入栈,500/100=5

10:iload_3

将操作数栈 栈顶内容,也就是5取出,保存到本地变量表3的位置

11:bipush 50

将50压入操作数栈

13:istore_4

将操作数栈 栈顶内容,也就是50取出,保存到本地变量表4中

 15:getstatic#2

获取类或接口字段的值并将其推入操作数栈。

#2对应常量池中的Fieldref #15.#16

 该类的常量池:

18:iload_3 

读取本地变量3,压入操作数栈。

19:iload_4

读取本地变量4,压入操作数栈。

 21:iadd

将操作数栈 栈顶两个int类型50和5取出,执行相加操作。结果55再压入操作数栈。

22:invokevirtual #3

开始新的方法调用,原来的main方法的栈帧不跑了,main方法的程序计数器也不跑了。开始 执行新的方法栈帧。

执行完新的方法栈栈,再回到main方法的栈帧执行。(JVM控制的过程)

25:return

void方法返回,main方法执行结束

总结:这个执行过程实际上就是java程序运行过程,就是在对我们本地变量表、操作数栈等等这些线程里面的信息进行操作,来现实最终程序运行的效果。

6、本章结语:

本章将JVM运行的核心逻辑进行详细解析。 

运行时数据区

栈的一些内容

栈帧的一些内容

指令码的执行过程

JVM运行底层原理:线程怎么去调度的?怎么根据操作数栈、程序计数器完成功能的。

不同操作系统,有不同的JVM实现,所以JAVA可以实现“一处编写,处处运行”的原因

掌握程序运行的逻辑梳理差不多了。更深层次可以看看openJDK

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值