Java程序到底是如何执行的

44次阅读

共计 1664 个字符,预计需要花费 5 分钟才能阅读完成。

项目推荐: Spring Cloud、Spring Security OAuth2 的 RBAC 权限管理系统 欢迎关注

最近裸辞,好好歇歇。整理面试题,欢迎关注。

Java 程序执行过程

无论是使用开发工具IDEA Eclipse 还是使用容器 Tomcat jetty, Java 程序的执行流程如下:

  1. Java 代码编译成字节码, 从文件后缀来看 .java 编译成.class 类型的文件;
  2. class 文件放置到 Java 虚拟机,这个虚拟机通常指的是 Oracle 官方自带的 Hotspot JVM;
  3. Java 虚拟机使用类加载器 Class Loader 装载 class 文件;
  4. 加载完成之后,会进行字节码校验,字节码校验通过之后 JVM 解释器会把字节码翻译成机器码交由操作系统执行。

类加载器

简单来说,加载指的是把 class 字节码文件从各个来源通过类加载器装载入内存中。

这里有两个来源:

  • 字节码来源。一般的加载来源包括从本地路径下编译生成的.class 文件,从 jar 包中的.class 文件
  • 动态代理实时编译类加载器。一般包括启动类加载器,扩展类加载器,应用类加载器,以及用户的自定义类加载器。

注:为什么会有自定义类加载器?

一方面是由于 java 代码很容易被反编译,如果需要对自己的代码加密的话,可以对编译后的代码进行加密,然后再通过实现自己的自定义类加载器进行解密,最后再加载。
另一方面也有可能从非标准的来源加载代码,比如从网络来源,那就需要自己实现一个类加载器,从指定源进行加载。

(上图)这种层次关系称为类加载器的 双亲委派模型 。双亲委派模型的工作流程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此, 所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。

验证

主要是为了保证加载进来的字节流符合 JVM 规范。对于字节码的验证,保证程序语义的合理性,比如要保证类型转换的合理性。对于符号引用的验证,比如校验符号引用中通过全限定名是否能够找到对应的类?校验符号引用中的访问性(private,public 等)是否可被当前类访问?

准备

主要是为类变量分配内存,赋初始值。特别需要注意,初值,不是代码中具体写的初始化的值,而是 Java 虚拟机根据不同变量类型的默认初始值。比如 8 种基本类型的初值,默认为 0;引用类型的初值则为 null;常量的初值即为代码中设置的值,final static tmp = 123,那么该阶段 tmp 的初值就是 123

解析

将常量池内的符号引用替换为直接引用的过程。在解析阶段,虚拟机会把所有的类名,方法名,字段名这些符号引用替换为具体的内存地址或偏移量,也就是直接引用。

初始化

这个阶段主要是对类变量初始化,执行类构造器。

JIT 触发标准

在 HotSpot 虚拟机中,热点探测 是 JIT 的触发标准。

基于采样的热点判定

主要是虚拟机会周期性的检查各个线程的栈顶,若某个或某些方法经常出现在栈顶,那这个方法就是“热点方法”。这种判定方式的优点是实现简单;缺点是很难精确一个方法的热度,容易受到线程阻塞或外界因素的影响。
热点探测是基于计数器的热点探测,采用这种方法的虚拟机会为每个方法建立计数器统计方法的执行次数,如果执行次数超过一定的阈值就认为它是“热点方法”。

基于计数器的热点判断

虚拟机运行时候, 当计数器超过阈值溢出了,就会触发 JIT 编译,JIT 编译器就会将这段代码编译成机器语言并缓存,在该循环时间段内,会直接将执行代码替换,执行缓存的机器语言。

  • 方法调用计数器

方法调用计数器用于统计方法被调用的次数,默认阈值在 client 模式下是 1500 次,在 server 模式在是 10000 次,可通过 -XX: CompileThreshold 来设定;

  • 回边计数器

回边计数器用于统计的是方法中循环体代码执行的次数。默认阈值在 client 默认为 13995,server 默认为 10700,可通过 -XX: OnStackReplacePercentage=N来设置;

正文完
 0