java程序运行原理分析

class文件内容

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

JVM运行时数据区

java程序最终执行是由JVM执行引擎和本地库来做的一些适配,所以在linux和windos上的执行引擎和本地库都会有所不同,目的是为了一次编写,随处运行.如果有想对于JVM有很深入的研究可以看一下C代码,比如openjdk

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

线程共享:所有线程能访问这块内存数据,随虚拟机或者GC而创建和销毁

方法区

jvm用来存储加载的类信息、常量、静态变量、编译后的代码等数据,虚拟机规范中这是一个逻辑区域。具体实现根据不同虚拟机实现。如oracle的HotSpotjava7中方法区放在永久代,java8放在元数据空间并且通过GC机制对这个区域进行管理。比如IBM的虚拟机实现就不同了

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

虚拟机栈

每个线程都在这个空间有一个私有的空间程序栈由多个栈帧(Stack Frame)组成。一个线程会执行一个或多个方法,一个方法对应一个栈帧

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

栈内存默认最大是1M,超出则抛出StackOverflowError

本地方法栈

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

程序计数器

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

每个线程都在这个空间有一个私有的空间,占用内存很少

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

实操分析

java文件示例

public class Demo {
    
    public static void main(String[] args) {
        int a = 1000;
        int b = 100;
        int c = a / b;
        int d = 13;
        System.out.println(c + d);
    }
}
javac Demo.java //将java文件编译成class文件
javap -v Demo.class > Demo.txt //将class文件输出到txt文件

分析指令码文件

public class com.Demo
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #5.#25         // java/lang/Object."<init>":()V
   #2 = Fieldref           #26.#27        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = Methodref          #28.#29        // java/io/PrintStream.println:(I)V
   #4 = Class              #30            // com/Demo
   #5 = Class              #31            // java/lang/Object
   #6 = Utf8               <init>
   #7 = Utf8               ()V
   #8 = Utf8               Code
   #9 = Utf8               LineNumberTable
  #10 = Utf8               LocalVariableTable
  #11 = Utf8               this
  #12 = Utf8               Lcom/Demo;
  #13 = Utf8               main
  #14 = Utf8               ([Ljava/lang/String;)V
  #15 = Utf8               args
  #16 = Utf8               [Ljava/lang/String;
  #17 = Utf8               a
  #18 = Utf8               I
  #19 = Utf8               b
  #20 = Utf8               c
  #21 = Utf8               d
  #22 = Utf8               MethodParameters
  #23 = Utf8               SourceFile
  #24 = Utf8               Demo.java
  #25 = NameAndType        #6:#7          // "<init>":()V
  #26 = Class              #32            // java/lang/System
  #27 = NameAndType        #33:#34        // out:Ljava/io/PrintStream;
  #28 = Class              #35            // java/io/PrintStream
  #29 = NameAndType        #36:#37        // println:(I)V
  #30 = Utf8               com/Demo
  #31 = Utf8               java/lang/Object
  #32 = Utf8               java/lang/System
  #33 = Utf8               out
  #34 = Utf8               Ljava/io/PrintStream;
  #35 = Utf8               java/io/PrintStream
  #36 = Utf8               println
  #37 = Utf8               (I)V
{
  public com.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 8: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/Demo;

  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        1000
         3: istore_1
         4: bipush        100
         6: istore_2
         7: iload_1
         8: iload_2
         9: idiv
        10: istore_3
        11: bipush        13
        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 11: 0
        line 12: 4
        line 13: 7
        line 14: 11
        line 15: 15
        line 16: 25
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      26     0  args   [Ljava/lang/String;
            4      22     1     a   I
            7      19     2     b   I
           11      15     3     c   I
           15      11     4     d   I
    MethodParameters:
      Name                           Flags
      args
}
SourceFile: "Demo.java"
版本访问标志说明
 minor version: 0 // 次版本号
 major version: 52 // 主版本号 Java版本 6,7,8,9 依次对应的版本号为 49,50,51,52 > 咋们这里为java8
 flags: ACC_PUBLIC, ACC_SUPER // 访问标志
标志名称标志值含义
ACC_PUBLIC0x0001是否为public类型
ACC_FINAL0x0010是否被声明为final,只有类可设置
ACC_SUPER0x0020是否允许使用invokespecial字节码指令,JDK1.2之后编译出来的类这个标志位true
ACC_INTERFACE0X0200标志这是一个接口
ACC_ABSTRACT0X0400是否为abstract类型,对于接口和抽象类来说此标记为true,其他的为false
ACC_SYNTHETIC0X1000这个类并非由用户产生的
ACC_ANNOTATION0X2000标识这是一个注解
ACC_ENUM0x4000标识这是一个枚举
静态池说明

此常量池和String这个常量池有一定的区别,这里的静态池存的是类里面包含的一些静态常量,比如说 类的名字,方法的名字,变量声明的名字等这些信息。通过编译这个类以后就能确认。

Constant pool:
   #1 = Methodref          #5.#25         // java/lang/Object."<init>":()V
   #2 = Fieldref           #26.#27        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = Methodref          #28.#29        // java/io/PrintStream.println:(I)V
   #4 = Class              #30            // com/Demo
   #5 = Class              #31            // java/lang/Object
   #6 = Utf8               <init>
   #7 = Utf8               ()V
   #8 = Utf8               Code
   #9 = Utf8               LineNumberTable
  #10 = Utf8               LocalVariableTable
  #11 = Utf8               this
  #12 = Utf8               Lcom/Demo;
  #13 = Utf8               main
  #14 = Utf8               ([Ljava/lang/String;)V
  #15 = Utf8               args
  #16 = Utf8               [Ljava/lang/String;
  #17 = Utf8               a
  #18 = Utf8               I
  #19 = Utf8               b
  #20 = Utf8               c
  #21 = Utf8               d
  #22 = Utf8               MethodParameters
  #23 = Utf8               SourceFile
  #24 = Utf8               Demo.java
  #25 = NameAndType        #6:#7          // "<init>":()V
  #26 = Class              #32            // java/lang/System
  #27 = NameAndType        #33:#34        // out:Ljava/io/PrintStream;
  #28 = Class              #35            // java/io/PrintStream
  #29 = NameAndType        #36:#37        // println:(I)V
  #30 = Utf8               com/Demo
  #31 = Utf8               java/lang/Object
  #32 = Utf8               java/lang/System
  #33 = Utf8               out
  #34 = Utf8               Ljava/io/PrintStream;
  #35 = Utf8               java/io/PrintStream
  #36 = Utf8               println
  #37 = Utf8               (I)V
常量名含义
CONSTANT_utf8_infoUFU8编码的字符串
CONSTANT_Integer_info整形字面量
CONSTANT_Float_info浮点型字面量
CONSTANT_Long_info长整型字面量
CONSTANT_Double_info双精度浮点型字面量
CONSTANT_Class_info类或接口的符号引用
CONSTANT_String_info字符串类型字面量
CONSTANT_Fielderf_info字段的符号引用
CONSTANT_Methodref_info类中方法的符号引用
CONSTANT_InterfaceMethodref_info接口中方法的符号引用
CONSTANT_NameAndType_info字段或方法的符号引用
CONSTANT_MothodType_info标志方法类型
CONSTANT_MothodHandle_info表示方法句柄
CONSTANT_InvokeDynamic_info表示一个动态方法调用点
默认的构造函数
 public com.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

由此可见,没有定义构造函数时,会有隐式的公开的无参构造函数

程序入口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 // 1方法对应栈帧中操作数栈的深度 2本地变量数量 3参数数量
         0: sipush        1000  //将1000的值压到操作数栈的栈顶
         3: istore_1		    //将操作数栈栈顶中的值1000出栈挪到本地变量表且赋值istore_1
         4: bipush        100	//将100的值压到操作数栈的栈顶
         6: istore_2			//将操作数栈栈顶中的值100出栈挪到本地变量表且赋值istore_2
         7: iload_1				//将本地变量表1000的值压入操作数栈顶
         8: iload_2				//将本地变量表100的值压入操作数栈顶 1000的值被压到了栈底
         9: idiv				//将操作数栈中的值出栈进行整除1000/100=10,结果留在栈顶
        10: istore_3			//将操作数栈栈顶中的结果值10出栈挪到本地变量表且赋值istore_3
        11: bipush        13    //将13值压到操作数栈的栈顶
        13: istore        4		//将操作数栈栈顶中的值13栈挪到本地变量表且赋值istore
        15: getstatic     #2    //静态获取PrintStream打印流对象
        18: iload_3				//将本地变量表10的值压入操作数栈栈顶
        19: iload         4		//将本地变量表13的值压入操作数栈栈顶 10的值被压到了栈底
        21: iadd				//将操作数栈栈中的值出栈进行相加10+13=23,结果留在栈顶
        22: invokevirtual #3    //调用PrintStream.println(V)将栈顶结果传入.进入新的栈帧。程序计数									器归零,新的本地变量表,新的操作数栈
        25: return			   	//程序执行结束

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

总结

将JVM运行核心逻辑进行了详细梳理

运行时数据区栈和栈帧的关系本地变量表和操作数栈的执行逻辑相关指令码的介绍

JVM运行原理中更底层实现,针对不同的操作系统或者处理器,会有不同的实现。这也是java能够实现 ‘一次编写,随处运行’的原因。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值