Java程序运行原理笔记

一、Java字节码文件

Java程序经过编译之后会生成class文件,它是供虚拟机解释执行的二进制字节码文件,文件以0xcafebabe(16进制,特殊标志)开头,中间无任何分隔符,如下图(16进制)。

 

class文件格式复杂,不便于直接阅读,可以使用javap命令来辅助阅读。

javap是jdk自带的反解析工具。它的作用就是根据class字节码文件,反解析出当前类对应的指令区、本地变量表、异常表和代码行偏移量映射表、常量池等等信息。

二、JVM运行时数据区

JVM是字节码文件的运行环境,它由方法区、堆内存、虚拟机栈、本地方法栈和程序计数器构成。

其中方法区和堆内存由线程共享,虚拟机栈、本地方法栈和程序计数器为线程独占。

 

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

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

 

方法区:JVM用来存储加载的类信息、常量、静态变量、编译后的代码等数据。在虚拟机规范中并未定义该区的实现方式,具体实现由各个虚拟机自己决定。如Oracle的Hotspot在Java7中将方法区放在永久代,Java8中将方法区放到了元数据空间,并且通过GC机制对这个区域进行管理。

 

堆内存:可以细分为老年代(Old)、新生代(Eden),它是JVM启动时创建存放对象实例的地方。垃圾回收器主要就是管理堆内存,如果满了就会出现OutOfMemoryError。

 

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

 

本地方法栈:和虚拟机栈功能类似,虚拟机栈是为虚拟机执行Java方法而准备的,本地方法栈是为

虚拟机使用Native本地方法而准备的。虚拟机规范没有规定具体的实现,由不同的虚拟机厂商去实现。HotSpot虚拟机中本地方法栈和虚拟机栈的实现是一样的。超过栈内存大小也会抛出StackOverflowError。

 

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

 

三、JVM程序运行演示

 

1. Demo源码

package test;

public class Demo {

	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. 编译Demo

javac Demo.java

3. 使用Javap命令查看编译后的字节码文件

javap -v Demo.class > Demo.txt

4. Demo.txt内容解析

版本号/访问控制

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

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

常量池

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/Demo
   #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               Demo.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/Demo
  #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.Demo();
    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

Demo程序中,并未编写构造函数,此为隐式的无参构造函数。

 

程序入口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 6: 0
        line 7: 4
        line 8: 7
        line 9: 11
        line 10: 15
        line 11: 25

flags描述了该方法的访问控制,stack代表该方法对应栈帧中操作数栈的深度,locals为本地变量数量,包括入参、x、y、a、b,一共5个。args_size为入参的数量。

紧接着的是main方法内容编译后的指令,每条指令前的数字代表偏移量(字节),JVM根据这个偏移量区分不同的指令。需要注意的是,class文件中存储的是指令码,而当前展示的是经javap翻译后的操作符。

 

5. JVM运行过程

(1)加载信息到方法区

(2)JVM创建线程执行代码

(3)JVM执行main方法指令

执行序号0的指令,将500压入操作数栈

执行序号3的指令,弹出操作数栈栈顶的500,保存到本地变量表

 执行序号4的指令,将100压入操作数栈

执行序号6的指令,弹出操作数栈栈顶100,保存到本地变量表

执行序号7的指令 

执行序号8的指令 

 

执行序号9的指令,将栈顶两个数据相除,结果入栈

执行序号10的指令 

 执行序号11的指令

执行序号13的指令

执行序号15的指令

执行序号18的指令

执行序号19的指令

执行序号21的指令

执行序号22的指令

 执行序号25的指令

main方法执行结束。

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值