编译机制
Java代码编译是由Java源码编译器来完成,流程图以下所示:java

最后生成的class文件由如下部分组成:程序员
- 结构信息。 class文件格式版本号及各部分的数量与大小的信息;
- 元数据信息。Java源码中声明与常量的信息。包含类/继承的超类/实现的接口的声明信息、域与方法声明信息和常量池;
- 方法信息。 Java源码中语句和表达式对应的信息。包含字节码、异常处理器表、求值栈与局部变量区大小、求值栈的类型记录、调试符号信息。
Java 编译器把全部的类变量初始化语句和类的静态初始化块收集到 <clinit> 方法内,该方法不须要程序员进行显式调用,只能被 JVM 调用,专门承担类初始化工做。虚拟机会保证在子类类构造器<clinit>()执行以前,父类的类构造<clinit>()执行完毕。除接口之外,初始化一个类以前必须保证其直接超类已被初始化,而且该初始化过程是由 JVM 保证线程安全的。tomcat
并不是全部的类都会拥有一个 <clinit>方法, 安全
- 该类既没有声明任何类变量,也没有静态初始化语句;
- 该类声明了类变量,但没有明确使用类变量初始化语句或静态初始化语句 初始化;
- 该类仅包含静态 final 变量的类变量初始化语句,而且初始化的值是编译时常量表达式。
eg.
public static final String a = new Date().toString(); // 有
public static final String a = "1" + "2"; // 无
在一个类的生命周期中,类构造器<clinit>()最多会被虚拟机调用一次,而实例构造器<init>()则会被虚拟机调用屡次,只要程序员还在建立对象。数据结构
类执行机制
JVM是基于栈的体系结构来执行class字节码的。线程建立后,都会产生程序计数器(PC)和栈(Stack),程序计数器存放下一条要执行的指令在方法内的偏移量,栈中存放一个个栈帧,每一个栈帧对应着每一个方法的每次调用,而栈帧又是有局部变量区和操做数栈两部分组成,局部变量区用于存放方法中的局部变量和参数,操做数栈中用于存放方法执行过程当中产生的中间结果。函数

装载过程
资料引用spa


- 加载: 寻找并导入指定类型的二进制文件信息,能够是class文件、jar 、动态代理生成等等;根据读取到的字节信息,在方法区建立运行时数据结构:方法列表(保存此类可能调用的全部实例方法的引用 )。
- 链接:
- 验证: 确保导入的类型正确;
- 准备: 在方法区,为static field分配内存,并设置其初始值。
- 解析: 把类文件的常量池部分的符号引用转化为运行时常量池的直接引用。
- 类初始化:如有静态初始化过程 调用<clinit>方法
类加载器

Classloader加载类Classs: 文件验证器检测编译版本、常量池、 访问标识、 字段、方法、指令 等。.net
- Bootstrap ClassLoader 启动类加载器
$JAVA_HOME中jre/lib/rt.jar里全部的class,由C++实现,不是ClassLoader子类。
- Extension ClassLoader 扩展类加载器
加载java平台中扩展功能的一些jar包,包括$JAVA_HOME中jre/lib/ext/*.jar或-Djava.ext.dirs指定目录下的jar包。
- App ClassLoader 系统类加载器
加载classpath中指定的jar包及目录中class。
- Custom ClassLoader 用户自定义类加载器(java.lang.ClassLoader的子类)
应用程序根据自身须要自定义的ClassLoader,如tomcat、jboss都会根据j2ee规范自行实现ClassLoader
双亲委派机制
加载过程当中会先检查类是否被已加载,检查顺序是自底向上,从Custom 到BootStrap 逐层检查,只要某个classloader已加载就视为已加载此类,保证此类只全部ClassLoader加载一次。线程
而加载的顺序是自顶向下,也就是由上层来逐层尝试加载此类。通俗的讲,就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,若是父类加载器能够完成类加载任务,就成功返回;只有父类加载器没法完成此加载任务时,才本身去加载。 代理
类的初始化
java 虚拟机规范为类的初始化时机作了严格定义:在首次主动使用时初始化。这个规则直接影响着类装载、链接和初始化类的机制。由于在类型被初始化以前它必须已经被链接,然而在链接以前又必须保证它已经被装载了。
首次主动使用的情形:
- 建立某个类的新实例时: new、反射、克隆或反序列化;
- 调用某个类的静态方法时;
- 使用某个类或接口的静态字段或对该字段赋值时(final字段除外);
- 调用Java的某些反射方法时;
- 初始化某个类的子类;
- 在虚拟机启动时某个含有main()方法的那个启动类。
对象实例化
<clinit>()不须要调用父类的<clinit>(),这是由虚拟机来确保完成的。
【static变量(初始化)、static初始化块 (内部前后按出现顺序)】 -> 【变量、初始化块(内部前后按出现顺序)】> 构造器
父类的类构造器<clinit>() -> 子类的类构造器<clinit>() -> 父类的成员变量和实例代码块 -> 父类的构造函数 -> 子类的成员变量和实例代码块 -> 子类的构造函数。
(注意: 变量须要有赋值语句才会初始化)
Java 编译器在编译每一个类时都会为该类至少生成一个实例"<init>" 初始化方法。包括了全部具备指定初始化值的实例变量初始化语句和java类的构造方法内的全部语句。此方法与源代码中的每一个构造方法相对应,若是类没有明确地声明任何构造方法,编译器则为该类生成一个默认的无参构造方法,这个默认的构造器仅仅调用父类的无参构造器,与此同时也会生成一个与默认构造方法对应的 "<init>" 方法。
- 一般来讲,<init>方法内包括的代码内容大概为:调用另外一个 <init>方法;对实例变量初始化;与其对应的构造方法内的代码。
- 若是构造方法是明确地从调用同一个类中的另外一个构造方法开始,那它对应的 <init>方法体内包括的内容为:一个对本类的 <init>方法的调用;对应构造方法内的全部字节码。
- 若是构造方法不是经过调用自身类的其它构造方法开始,该对象不是 Object 对象,那 <init>法内则包括的内容为:一个对父类 <init>方法的调用;对实例变量初始化方法的字节码;最后是对应构造子类的方法体字节码。若是这个类是 Object,那么它的 <init>方法则不包括对父类 <init>方法的调用。