专栏首页编程坑太多JAVA程序运行原理分析

JAVA程序运行原理分析

作为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线程。

本文分享自微信公众号 - 编程坑太多(idig88)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-11-11

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Java自动化测试框架-04 - TestNG之Test Method篇 - 道法自然,法力无边(详细教程)

    测试方法是可以带有参数的。每个测试方法都可以带有任意数量的参数,并且可以通过使用TestNG的@Parameters向方法传递正确的参数。

    北京-宏哥
  • 常问的15个顶级Java多线程面试题

    在任何Java面试当中多线程和并发方面的问题都是必不可少的一部分。如果你想获得更多职位,那么你应该准备很多关于多线程的问题。

    搜云库技术团队
  • Java日志Log4j或者Logback的NDC和MDC功能

    Java中使用的日志的实现框架有很多种,常用的log4j和logback以及java.util.logging,而log4j是apache实现的一个开源日志组件...

    JAVA日知录
  • 【Python 第75课】可迭代对象和迭代器

    for 循环是我们在 Python 里非常常用的一个语法,但你有没有思考过 for 循环是怎样实现的?

    Crossin先生
  • 每个阿里程序员都必须搞懂的Maven基础知识

    以前我们写代码时,jar包都默认放在一个叫 /lib 的目录下,然后把该目录设置为classpath可以读取到的目录,如下图所示:

    老钱
  • 帮你少写一大半参数校验代码的小技巧

    几乎每个web网站都会对用户提交的参数进行校验,前端要做,后端也要做。防止用户直接通过接口调用的方式来请求或保存数据,从而导致产生脏数据等其他严重的后果。

    Java识堂
  • 面试再问ThreadLocal,别说你不会

    以前面试的时候问到ThreadLocal总是一脸懵逼,只知道有这个哥们,不了解他是用来做什么的,更不清楚他的原理了。表面上看他是和多线程,线程同步有关的一个工具...

    业余草
  • 一个 Java 对象到底有多大?

    编写Java代码的时候,大多数情况下,我们很少关注一个Java对象究竟有多大(占据多少内存),更多的是关注业务与逻辑。但是殊不知,在我们不经意间,大量的内存被无...

    芋道源码
  • 慌了!面试官又拿JVM开怼!

    对于Java人来说,JVM无疑是进阶时必须迈过的坎。不管初入职场还是跳槽升职,JVM更是面试时的必考题。如果不懂JVM的话,薪酬会非常吃亏(近70%的面试者挂在...

    Java3y
  • 面试官,Java8 JVM内存结构变了,永久代到元空间

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 ...

    用户1161110

扫码关注云+社区

领取腾讯云代金券