jvm编译类加载和执行

JVM是Java Virtual Machine(Java虚拟机),Java虚拟机是实现跨平台这一特点的关键。

Java代码编译和执行的整个过程包含了以下三个重要的机制:

  1. Java源码编译机制
  2. 类加载机制
  3. 类执行机制

Java源码编译机制:

  1. 分析和输入到符号表
  2. 注解处理
  3. 语义分析和生成class文件

class文件包括结构信息。包括class文件格式版本号及各部分的数量与大小的信息元数据。对应于Java源码中声明与常量的信息。包含类/继承的超类/实现的接口的声明信息、域与方法声明信息和常量池
方法信息。对应Java源码中语句和表达式对应的信息。包含字节码、异常处理器表、求值栈与局部变量区大小、求值栈的类型记录、调试符号信息


类加载机制

JVM的类加载是通过ClassLoader及其子类来完成的
1.Bootstrap ClassLoader(c++编写,加载java核心库 java.,开发者无法直接获取到启动类加载器的引用)
负责加载JAVA_HOME中jre/lib/rt.jar里所有的class
2.Extension ClassLoader
负责加载java平台中扩展功能的一些jar包,包括$JAVA_HOME中jre/lib/
.jar或-Djava.ext.dirs指定目录下的jar包
3.App ClassLoader(系统类加载器)
负责记载classpath中指定的jar包及目录中class
4.Custom ClassLoader(用户自定义类加载器)
属于应用程序根据自身需要自定义的ClassLoader,如tomcat、jboss都会根据j2ee规范自行实现ClassLoader

加载过程中会先检查类是否被已加载,检查顺序是自底向上,从Custom ClassLoader到BootStrap ClassLoader逐层检查,只要某个classloader已加载就视为已加载此类,保证此类只所有ClassLoader加载一次。而加载的顺序是自顶向下,也就是由上层来逐层尝试加载此类。

双亲委托机制:

自底向上检查类是否已加载;其二,自顶向下尝试加载类。

另外需要注意:貌似从关系图上看好像是系统类加载器继承至扩展类加载器,而扩展类加载器又继承根类加载器,其实不是这样的,而是一种包含关系:系统类加载器包含扩展类加载器,而扩展类加载器包含了根类加载器

作用:

  • 防止重复加载同一个.class
  • 保证核心.class不能被篡改

类执行机制

JVM是基于栈的体系结构来执行class字节码的。线程创建后,都会产生程序计数器(PC)和栈(Stack),程序计数器存放下一条要执行的指令在方法内的偏移量,栈中存放一个个栈帧,每个栈帧对应着每个方法的每次调用,而栈帧又是有局部变量区和操作数栈两部分组成,局部变量区用于存放方法中的局部变量和参数,操作数栈中用于存放方法执行过程中产生的中间结果

类加载生命周期
加载连接初始化使用卸载
hotspot虚拟机是当真正用到一个类的时候才对它进行加载。

其中连接分为:
校验(符合JVM的要求),准备(静态变量,常量赋值),解析(符号引用-> 直接引用)
初始化:执行没有static修饰的赋值语句和执行语句,为静态变量赋程序设定的初值

加载:

找到需要加载的类并把类的信息加载到jvm的方法区中,然后在堆区中实例化一个java.lang.Class对象

  1. 通过一个类的全限定名(包名与类名)来获取定义此类的二进制字节流(Class文件)。
  2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
  3. 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
连接
  • 验证:验证被加载后的类是否有正确的结构,类数据是否会符合虚拟机的要求,确保不会危害虚拟机安全。
  • 准备:为类的静态变量(static filed)在方法区分配内存,并赋默认初值(0值或null值)。静态常量(static final filed)会在准备阶段赋程序设定的初值,如static final int a = 666; 静态常量a就会在准备阶段被直接赋值为666,对于静态变量,这个操作是在初始化阶段进行的。
  • 解析:将类的二进制数据中的符号引用换为直接引用。在解析阶段,jvm会将所有的类或接口名、字段名、方法名转换为具体的内存地址。
初始化

初始化阶段才真正执行Java代码。
类的初始化的主要工作是为静态变量赋程序设定的初值。
没有static修饰的赋值语句和执行语句在实例化对象的时候才会运行。


执行过程

执行过程简介

当编译和连接一个C++程序时,所获得的可执行二进制文件只能在指定的硬件平台和操作系统上运行,因为这个二进制文件包含了对目标处理器的机器语言。而Java编译器把Java源文件的指令翻译成字节码,这种字节码就是Java虚拟机的“机器语言”。
与普通程序不同的是,Java程序(class文件)并不是本地的可执行程序。当运行Java程序时,首先运行JVM(Java虚拟机),然后再把Java class加载到JVM里头运行,负责加载Java class的这部分就叫做Class Loader。

JVM中的ClassLoader

JVM本身包含了一个ClassLoader称为BootstrapClassLoader,和JVM一样,BootstrapClassLoader是用本地代码实现的,它负责加载核心JavaClass(即所有java.*开头的类)。另外JVM还会提供两个ClassLoader,它们都是用Java语言编写的,由BootstrapClassLoader加载;其中ExtensionClassLoader负责加载扩展的Javaclass(例如所有javax.*开头的类和存放在JRE的ext目录下的类)ApplicationClassLoader负责加载应用程序自身的类。
当运行一个程序的时候,JVM启动,运行bootstrapclassloader,该ClassLoader加载java核心API(ExtClassLoader和AppClassLoader也在此时被加载),然后调用ExtClassLoader加载扩展API,最后AppClassLoader加载CLASSPATH目录下定义的Class,这就是一个程序最基本的加载流程。

执行ClassLoader例子

什么时候JVM会使用ClassLoader加载一个类呢?当你使用java去执行一个类,JVM使用ApplicationClassLoader加载这个类;然后如果类A引用了类B,不管是直接引用还是用Class.forName()引用,JVM就会找到加载类A的ClassLoader,并用这个ClassLoader来加载类B。JVM按照运行时的有效执行语句,来决定是否需要装载新类,从而装载尽可能少的类,这一点和编译类是不相同的。