类的执行过程

java源文件-编译器–>字节码文件-JVM–>机器码

1.编译过程

这是由.java源码文件转为 .class二进制字节码文件的过程。
我们编写好的源代码,就是*.java文件。使用“javac test.java”就可以编译test.java文件。

如果这个类所依赖的类没有被编译,编译器则会自动的先编译这个所依赖的类再引用。

编译过程主要有三步:

  • 词法分析和输入到符号表
  • 注解处理
  • 语义分析和生成字节码

详细过程:

源代码文件*.java -> 词法分析器 -> tokens流 -> 语法分析器 -> 语法树/抽象语法树 -> 语义分析器 -> 注解抽象语法树 -> 字节码生成器 -> JVM字节码文件*.class 

最后剩成的JVM字节码文件,使用命令“javap -c test”可以查看test.class的字节码信息,主要包含三项内容:

  • 结构信息:class文件相关信息。
  • 元数据:Java源码中的声明和常量信息。
  • 方法信息:Java源码语句和表达式对应的字节码。

2.加载过程

JVM

JVM 是可运行 Java 代码的假想计算机 ,包括一套字节码指令集、一组寄存器、一个栈、

一个垃圾回收,堆 和 一个存储方法域。JVM 是运行在操作系统之上的,它与硬件没有直接

的交互。

线程

JVM 允许一个应用并发执行多个线程。 Hotspot JVM 中的 Java 线程与原生操作系统线程有直接的映射关系。当线程本地存储、缓冲区分配、同步对象、栈、程序计数器等准备好以后,就会创建一个操作系统原生线程。 Java 线程结束,原生线程随之被回收。操作系统负责调度所有线程,并把它们分配到任何可 用的 CPU 上。当原生线程初始化完毕,就会调用 Java 线程的 run() 方法。当线程结束时,会释放原生线程和 Java 线程的所有资源。

Hotspot JVM 后台运行的系统线程主要有下面几个:

1.虚拟机线程 (VM thread) 

这个线程等待 JVM 到达安全点操作出现。这些操作必须要在独立的线程里执行,因为当 

堆修改无法进行时,线程都需要 JVM 位于安全点。这些操作的类型有:stop-the

world 垃圾回收、线程栈 dump、线程暂停、线程偏向锁(biased locking)解除。 

2.周期性任务线程 

这线程负责定时器事件(也就是中断),用来调度周期性操作的执行。 

3.GC 线程 

这些线程支持 JVM 中不同的垃圾回收活动。 

4.编译器线程 

这些线程在运行时将字节码动态编译成本地平台相关的机器码。 

5.信号分发线程 

这个线程接收发送到 JVM 的信号并调用适当的 JVM 方法处理

JVM 类加载机制分为五个部分:加载,验证,准备,解析,初始化

类加载机制

类加载器其实也是Java类。有四大类:

  • 根加载器Bootstrap Class Loader
  • 扩展加载器Extension Class Loader
  • 系统应用加载器APP Class Loader
  • 用户自定义加载器Customer Class Loader

启动类加载器(Bootstrap ClassLoader)

  1. 负责加载 JAVA_HOME\lib 目录中的,或通过-Xbootclasspath 参数指定路径中的,且被虚拟机认可(按文件名识别,如 rt.jar)的类。

扩展类加载器(Extension ClassLoader)

  1. 负责加载 JAVA_HOME\lib\ext 目录中的,或通过 java.ext.dirs 系统变量指定路径中的类库

应用程序类加载器(Application ClassLoader):

  1. 负责加载用户路径(classpath)上的类库。
JVM 通过双亲委派模型进行类的加载,当然我们也可以通过继承 java.lang.ClassLoader 
实现自定义的类加载器。

当一个类收到了类加载请求,他首先不会尝试自己去加载这个类,而是把这个请求委派给父类去完成,每一个层次类加载器都是如此,因此所有的加载请求都应该传送到启动类加载其中,只有当父类加载器反馈自己无法完成这个请求的时候(在它的加载路径下没有找到所需加载的Class),子类加载器才会尝试自己去加载

采用双亲委派的一个好处是比如加载位于 rt.jar 包中的类 java.lang.Object,不管是哪个加载器加载这个类,最终都是委托给顶层的启动类加载器进行加载,这样就保证了使用不同的类加载器最终得到的都是同样一个 Object 对象

检查过程

检查类是否已经被加载,从底层往上层依次检查各个加载器已经加载的类,顺序是系统应用类加载器、扩展加载器、根加载器,一旦发现被某个加载器加载过,则马上使用该类。如果一直找到最顶层的根加载器,发现类还没有被加载进JVM运行数据区的方法区,则接下来就要加载该类。

类加载过程

加载过程和检查过程顺序相反,从上层往下层的顺序进行加载。从加载器检查自己的加载路径,找要加载的类,一旦找到类就进行加载。

注:对每个加载器,最多只能加载一次系统绝对路径下的同一个类。对类而言,可以被不同加载器重复加载,只要你把类放到类加载器的加载路径下,就可以被那个加载器加载。

3.类执行机制

连接

连接(linking)包括三个部分:

  • 验证verifying:验证类符合Java规范和JVM规范,和编译阶段的语法语义分析不同。
  • 准备preparing:为类的静态变量分配内存,初始化为系统的初始值。(不初始化静态代码块)。对于final static修饰的变量,直接赋值为用户的定义值。
  • 解析resolving:将符号引用(字面量描述)转为直接引用(对象和实例的地址指针、实例变量和方法的偏移量)

类初始化

初始化类的静态变量和静态代码块为用户自定义的值。非静态类在实例化类,在Java堆中创建对象的时候,才会进行初始化。初始化的顺序,和Java源码的从上到下顺序一致。注意:什么时候触发初始化?在类被Java程序“第一次主动使用”的时候,才会触发初始化操作(如果还没有加载,则会顺势触发类的加载过程)。

类的主动使用(一定会发生类的初始化)类的被动是用(不会发生类的初始化)
main方法所在的类
new一个类的对象数组定义类的引用
调用类的静态成员和静态方法引用常量不会触发,已加入方法去的常量池中
修改类的静态成员值访问静态域,只有定义改域的类才会被初始化
初始化子类时,父类若没有初始化,则先初始化父类再到子类(接口不适用)通过子类调用父类的静态变量,子类不会初始化父类会初始化
使用java.lang.reflect的包对类进行反射调用

内存分配

JVM运行时数据区

内存块关系说明
java堆JVM实例的所有线程共享存放程序创建的实例。这里是垃圾回收的最重要的地方。
方法区JVM实例的所有线程共享Class被加载到的内存区域,运行常量池存放所有类和接口的常量
java栈单线程独自拥有有栈帧组成,栈帧又有局部变量表、操作数栈和常量池引用组成
PC寄存器单线程独自拥有存储下一条要执行的字节码指令的地址(在方法内的偏移量)
本地方法栈单线程独自拥有提供本地方法接口(JNI,java Native Interface),供程序调用本地方法库

类具体的执行过程

本步骤由执行引擎Execute Engine来完成。执行引擎把字节码转为机器码,然后操作系统才可以真正调用,在硬件环境上执行代码。执行引擎的通过Java字节码解释器(一行一行解释字节码)和JIT(Just In Time)即时编译器(对热代码整段编译)来完成机器码的翻译工作。

©️2020 CSDN 皮肤主题: 1024 设计师:上身试试 返回首页