美文网首页『互联网架构』
JAVA程序运行原理分析(一)

JAVA程序运行原理分析(一)

作者: IT人故事会 | 来源:发表于2020-07-02 09:08 被阅读0次

    作为JAVA的开发人员,需要知道JAVA是如何运行的,这个需要好好思考下。

    (一)class文件内容

    class文件包含JAVA程序执行的字节码,也就是说程序的执行是通过class里面的内容进行执行的。class文件内的信息严格按照一定的格式(虚拟机规范中的格式),紧凑排列在class文件中的二进制流,中间无任何分隔符。

    • ① 分析class文件内的内容

    文件开头有一个0xcafebabe 16进制的特殊的标志,cafebabe就是java的class的标识。

    整个class文件很多很多的内容,用肉眼肯定是无法分辨的,

    • ② class包含的内容

    这个文件是有复杂格式,专门有JVM读里面的内容,方便阅读源码。

    1.版本

    源代码是由java的哪个版本的编译的。

    2.访问标志

    这个类是public 还是private 。

    3.常量池

    常量哪些。

    4.当前类

    当前类的名称,类的信息。

    5.超级类

    被继承的类,类信息。

    6.接口

    实现的接口是什么?

    7.字段

    8.方法

    9.属性

    (二)JVM运行时数据区

    java 源代码编译后生成 class字节码,然后被加载到JVM运行时数据区里面

    • ① 方法区

    存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虚拟机规范中的一个逻辑区域(没有硬性的规定)。具体实现是根据不同的虚拟机来实现的。

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

    • ② 堆

    对象,垃圾回收,都是在堆中。
    堆内存还可以细分为:老年代,新生代(Eden,From Survivor,To Survivor)
    JVM启动时就创建了,存放对象的实例,垃圾回收期主要就是管理堆内存,内存满了,就会出现OutOfMemroyEorror,后续在内存模型中,详细讲解。

    • ③ 虚拟机栈

    Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。一个方法对应一个栈帧。栈内存默认最大是1M,超出则抛出StackOverflowError。计算,保存一些信息都是在栈里面。

    • ④ 本地方法栈

    和java虚拟机栈类似,不同的是其为Native方法服务。它跟java虚拟机栈的区别就是执行的方法不同。

    • ⑤ 程序计数器

    当前线程所执行的字节码的行号指示器,每个线程都有一个独立的程序计数器。每个线程都在这个控件有一个私有的空间,占用内存空间很少。CPU同一时间,只会执行一个线程中的指令。JVM多线程会轮流切换并分配CPU执行时间的方式。为了线程切换后,需要通过程序计数器,来回复正确的执行位置。

    学习java,学习jvm,了解JVM运行时数据区,这些就够了,下面【执行引擎】,【本地库接口】,【本地方法库】都是根据不同的操作系统,不同的平台,做了JVM的适配,例如:linux,windows的执行引擎,本地库接口都有一些的不同,JVM的目的就是一处编写导出的运行,作为开发人员掌握在执行引擎之上。

    • ⑥ 线程独占

    每个线程都会有它独立的空间,随着生命周期而创建和消亡。【虚拟机栈,本地方法栈,程序计数器。】。

    • ⑦ 线程共享

    所有线程能访问这块内存数据,随着虚拟机或者GC而创建和消亡。【方法区,堆】。
    对象就是放入了堆中,也就是线程共享的。

    (三)查看class文件内容

    • ① 找个例子
    public class Demo1{
        public static void main(String[] args){
            int x = 600;
            int y = 100;
            int a = x / y;
            int b = 60;
            System.out.println(a + b);
        }
    }
    
    • ②执行下面的命令

    使用Demo1.java进行测试,编译成class,完整的javap命令的解析结果

    javac Demo1.java
    
    // javap查看内容,说出Demo1.class所有的信息, 【>】意思是输入到一个Demo1.txt文件
    javap -v Demo1.class > Demo1.txt
    
    java -version
    
    • ③ 版本号、访问控制

    flags: ACC PUBLIC,ACC SUPER

    • ④ 常量池

    这个常量池指的类里面包含的静态常量,编译这个类需要用到的常量,类的名称类信息里面也是个常量,类本身需要用到的常量。

    • ⑤ 构造方法

    之前类并没有定义对应的构造方法,但是通过javap之后内部存在一个无参的构造方法。由此可见,没有定义构造函数时,会有隐式的无参构造函数。

    • ⑥ 程序入口main方法

    描述了方法的:访问控制,本地变量的数目,参数的数量,方法对应栈帧中操作的数栈的深度,JVM执行引擎去执行这些代码编译过后的指令码,javap翻译出来的操作符,class文件内存储的是指令码,前面的数字,是偏移量,jvm根据这个去区分不同的指令。工具叫【JVM指令码表】进行查阅查看具体指令的含义。

    (三)程序完整运行分析

    • ① 编译加载到方法区

    编译加载到方法区,最后加载Demo1,其实一个JVM运行不止是一个Demo1,涉及到很多很多的类,会将所有的类信息存放到方法区里面,运行的一些常量会放在常量池里面,1.7和之前称为永久代,1.8开始称为元数据空间。

    • ② 类加载进去,创建对象运行

    类已经加载进去了,需要创建一个对象来进行运行,运行代码JVM创建线程来执行这些代码,一定是创建线程,需要配合【虚拟机栈】和【程序计数器】分配响应的空间,这里不涉及到本地代码因为咱们都是在JVM里面,Thread有一个独占的空间,其他区域有其他线程占领,【程序计数器】对应了字节码指令的地址。

    • ③ main方法

    线程独占空间,【程序计数器】标注当前这个线程执行到得位置记录下来有对应的序号,虚拟机栈里面开辟了一个空间,一个栈有多个栈帧组成,方法对应的一些操作,线程就是取一个或者多个,其实线程就是对应了一个或者多个栈帧,main方法的入口,也就是程序的入口,main方法栈帧包含本地变量表,操作数栈,Demo1 里面一共有5个变量,老铁可能问不是4个吗,哪里来了5个,因为main方法里面的String[] args也是一个啊。

    解析方法,看不懂对照【JVM指令码表】

             0: sipush        600     #将600这个数值压入到操作数栈,栈从下往上
             3: istore_1                #将操作栈顶保存到本地变量表1,移除操作栈
             4: bipush        100     #将 600这个数值保存后,将100放入操作数栈
             6: istore_2                #操作数栈栈顶100 保存到本地变量表2上。
             7: iload_1                 # 读取本地变量1,压入操作数栈1 
             8: iload_2                 # 读取本地变量2,压入操作数栈,它变成位置1了,前一个操作数栈位置变成2了
             9: idiv                       # 将栈顶两int类型数相除,结果入栈600/100 = 6,原来操作数栈里面的100,600都移除。
            10: istore_3              # 操作数栈栈顶6,保存到本地变量表3上。
            11: bipush        60     #将 60这个数值保存后,将60放入操作数栈。
            13: istore        4         #  将60放入操作数栈,放入本地变量表4的位置上。
            15: getstatic     #2      # 取货类或者接口字段的值并将其推入操作数栈,#2对应常量中,#2放入栈顶。
            18: iload_3               #将本地变量3去取出压入操作数栈
            19: iload         4        # 将本地变量4取出来压入操作数栈
            21: iadd                   # 将栈顶两个int类型数相加,结果入栈。
            22: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
           # 调用静态方法,新的一个方法,那个main方法不跑了,jvm会根据这个方法的描述,创建新栈帧,方法和操作从操作数栈中弹出来,压入虚拟机栈,然后虚拟机会开始执行虚拟机栈最上边的栈帧,执行完毕后,再继续执行main方法对应的栈帧。 
            25: return   # void函数返回,main方法执行结果。
    

    其实java的操作就是对于本地变量表,操作数栈,线程表里面的信息,操作,实现程序想要的效果,一定会要对照【JVM指令码表】来看一定点分析几个,java的套路你就了解了。

    PS:本次将JVM运行的核心逻辑进行了详细的解析,JVM运行原理中更底层实现,针对不同的操作系统或者处理器,会有不同的实现,说了运行时数据区,讲到了栈,指令码的执行过程。这也是JAVA能够实现【一定编写,处处运行】的原因。下次说下Java线程。

    相关文章

      网友评论

        本文标题:JAVA程序运行原理分析(一)

        本文链接:https://www.haomeiwen.com/subject/dnjumctx.html