Java代码执行流程
类生命周期
类的生命周期包括:加载、链接、初始化、使用和卸载,其中加载、链接、初始化,属于类加载的过程,我们下面仔细讲解。使用是指我们new对象进行使用,卸载指对象被垃圾回收掉了。
加载
加载指的是把class字节码文件从各个来源通过类加器(classLoader)装入内存中:
通过一个类的全限定名(包名+类名)来获取定义此类的二进制字节流。将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入字节码来源:一般的加载源包括从本地路径下编译生成的class文件,从jar包中的clas文件,从远程网络,以及动态代理时编译的class文件。
链接
验证(Verify)
确保class文件中的字节流包含的信息,符合当前虚拟机的要求,保证这个被加载的class类的正确性,不会危害到虚拟机的安全。
文件格式的验证,文件中是否有不规范的或者附加的其他信息。例如常量中是否有不被支持的常量元数据的验证,保证其描述的信息符合Java语言规范的要求,例如是否有父类,是否承了不被允许的finall类等字节码的验证,保证程序语义的合理性,比如要保证类型转换的合理性符号引用的验证,比如校检符号引用中通过全限定名是否能够找到对应的类,校验符号引用中的访问性(priate,pubc等)是否可被当前类访问等准备(prepare)
为类变量(注息,不是实例变量)分配内存,并且予初值。初值不是代码中具体写的初始化的值,而是Java拟机根不同变量类型的赋认初始值。例如int型初值为0,reference为null等。
解析(resolve)
将常量池内的符号引用换为直接引用的过程。举个例子来说,现在调用方法hello(),这个方法的地址是,那么hello就是符号引用,就是直接引用。在解析阶段,虚拟机会把所有的类名,方法名,字段名这些符号引用皆换为具体的内存地址或偏移量,也就是直接引用。初始化
这个阶段主要是对类变量初始化,是执行类构造器的过程,《Java虚拟机规范》严格规定了有且只有六种情况必须立即对类进行“初始化”
遇到new、getstatic、putstatic或invokestatic这四条字节码指令时,如果类型没有进行过初始化,则需要先触发其初始化阶段。能够生成这四条指令的典型Java代码场景有:使用new关键字实例化对象的时候。读取或设置一个类型的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外)的时候。调用一个类型的静态方法的时候使用java.lang.reflect包的方法对类型进行反射调用的时候,如果类型没有进行过初始化,则需要先触发其初始化。当初始化类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。当使用JDK7新加入的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果为REF_getStatic、REF_putStatic、REF_invokeStatic、REF_newInvokeSpecial四种类型的方法句柄,并且这个方法句柄对应的类没有进行过初始化,则需要先触发其初始化。当一个接口中定义了JDK8新加入的默认方法(被default关键字修饰的接口方法)时,如果有这个接口的实现类发生了初始化,那该接口要在其之前被初始化。类加载机制
双亲委派模型
arentDelegation,双亲委托机制(这个翻译和socket一样,莫名其妙),指多个类加载器之间存在父子关系的时候,某个class类具体由哪个加载器进行加载的问题。
具体过程如下:
当一个类加载的过程中,它首先不会去加载,而是委托给自己的父类去加载,父类又委托给自己的父类。因此所有的类加载都会委托给顶层的父类,即BootstrapClassloader进行加载。如果父类自己无法完成这个加载请求,子加载器才会尝试自己去加载。双亲委派机制的作用:
避免类重复加载导致冲突,保证Java核心库的安全。
例如类java.lang.Object,它存放在rt.jar之中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都能够保证是同一个类。反之,如果没有使用双亲委派模型,都由各个类加载器自行去加载的话,如果用户自己也编写了一个名为java.lang.Object的类,并放在程序的ClassPath中,那系统中就会出现多个不同的Object类,Java类型体系中最基础的行为也就无从保证,应用程序将会变得一片混乱。
BootstrapClassLoader
BootstrapClassLoader无父类加载器,嵌套在JVM内部,java程序无法直接操作这个类,使用C/C++语言实现。用于加载Java核心类库,如JAVA_HOME/lib目录下的类库,出于安全考虑,启动类只加载包名为:java、javax、sun开头的类。ExtensionClassLoader
扩展类加载器(ExtentionClassloader)父类加载器为BootstrapClassLoader,由Java语言编写。扩展类加载器(ExtentionClassloader)负责加载JVM扩展类,比如从系统属性java.ext.dirs目录中加载类库,或者从JDK安装目录JAVA_HOME/jre/lib/ext目录下加载类库。我们就可以将我们自己的包放在以上目录下,就会自动加载进来了。ApplicationClassLoader
应用程序加载器(ApplicationClassloader)也叫系统类加载器,负责加载环境变量classpath或者系统属性java.class.path指定路径下的类库。它同时也是程序中默认的类加载器,我们Java程序中的类,都是由它加载完成的。CustomClassLoader
我们可以自定义类加载器,满足特殊的类加载需求,如解决类冲突,实现热加载,实现jar包的加密保护。主要由两种实现方式:
继承java.lang.ClassLoader,重写findClass()方法继承URLClassLoader类,重写loadClass方法反向委派
Java中有一个SPI机制,全称是ServiceProviderInterface,是Java提供的一套用来被第三方实现或者扩展的API,它可以用来启用框架扩展和替换组件。
常见的SPI有JDBC、JNDI等,这些SPI的接口属于Java核心库,一般存在rt.jar包中,如下面的java.sql.Driver,这些类的类加载器是BootstrapClassLoader。
但是