前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JAVA程序运行原理分析

JAVA程序运行原理分析

作者头像
IT架构圈
发布2019-11-11 17:27:53
1.1K0
发布2019-11-11 17:27:53
举报
文章被收录于专栏:IT架构圈IT架构圈

作为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文件内容
  • ① 找个例子
代码语言:javascript
复制
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命令的解析结果

代码语言:javascript
复制
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指令码表】

代码语言:javascript
复制
         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线程。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-11-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 编程坑太多 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • (一)class文件内容
  • (二)JVM运行时数据区
  • (三)查看class文件内容
  • (三)程序完整运行分析
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档