1、类加载机制:虚拟机将class文件加载到内存,并对数据进行校验、转换解析、初始化,变成可被虚拟机直接执行的Java类型。这就是虚拟机的类加载机制。 类的生命周期:加载–>链接[验证–>准备–>解析]–>初始化–>使用–>卸载
其中,初始化有明确规定:
1)遇到new、getstatic、putstatic、invokestatic字节码指令时,如果类没有进行过初始化,则需触发其初始化;
2)使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需触发其初始化;
3)当初始化一个类时其父类没有进行过初始化,则需要触发其父类的初始化;
4)虚拟机启动时,用户需要指定主类,虚拟机会先初始化主类; 5)、当使用JDK1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic,REF_putStatic,REF_invokeStatic的方法句柄时,这个方法对应的类没有初始化化;
类加载过程:
1、加载:
*通过类的全限定名来获取定义类的二进制字节流;
*将字节流所代表的静态存储结构转化为方法区的运行时数据结构;
*在内存中生成一个代表该类的java.lang.Class对象,作为方法区这个累的各种数据的访问入口;
2、链接:
1)、验证:验证是连接阶段的第一步,这一步的目的是为了确保Class文件字节流中包含的信息符合当前虚拟机的要求,并且不会威胁虚拟机自身的安全。其实如果纯粹是从Java源码编译得到的Class文件,自身是可以确保安全的,但是因为Class文件可以由任何途径产生(甚至可以由十六进制编辑器直接编写来产生Class文件),所以虚拟机很有必要对输入的字节流进行验证以维护自身的安全。
2)、准备:准备阶段是正式为类实例变量分配内存并且设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。这里的类变量指的是被static修饰的变量,不包括实例变量,实例变量将会在对象实例化的时候随着对象一起分配在java堆中;另外这里的初始值不是代码中指的初始值而是变量数据类型对应的零值(int为0,String为null等)。
3)解析:解析阶段简单的来说就是虚拟机将常量池内的符号引用替换为直接引用的过程。具体的过程牵涉到Class文件格式中符号引用的规定,在这里就不展开了。
3、初始化:初始化是类加载过程的最后一步,前面的类加载过程中,除了在加载阶段用户应用程序通过自定义类加载器参与之外,其余动作完全由虚拟机主导。到了初始化过程,才会真正开始执行类中定义的Java程序代码。这一步主要就是执行静态变量的初始化,包括静态变量的赋值和静态初始化块的执行。
三种系统类加载器:
(1) Bootstrap ClassLoader(启动类加载器又称为根加载器/引导类加载器)主要加载 JAVA_HOME/jre/lib 里的jar 包,该目录下的所有 jar 包都是运行 JVM 时所必需的 jar 包。注意:类加载器其实自身也是一个 Java 类,因此,图论科技 南静老师整理自身类加载器需要被其他类加载器进行加载后方可使用,显然必须有一个类加载器的顶级父类(也就是BootstrapClassLoader,该类加载器是由 C 语言代码进行开发的)是其他类加载器的父类。关键点在于,如果一个类的类加载器是 Bootstrap ClassLoader,那么该类的 getClassLoader()方法返回 null。
(2) ExtClassLoader 主要加载 Java 核心扩展类,即 JAVA_HOME/jre/ext 目录下的 jar 文件。
(3) AppClassLoader 主要加载的是开发者在应用程序中编写的类,即 CLASSPATH 路径下所有的 jar 文件。
双亲委派模型:双亲委派模型要求除顶层启动类加载器外其余类加载器都应该有自己的父类加载器;类加载器之间通过复用关系来复用父加载器的代码。
工作过程:
(1)当前类加载器从自己已经加载的类中查询是否此类已经加载,如果已经加载则返回原来已经加载的类。
(2)如果没有找到,就去委托父类加载器去加载。父类加载器也会采用同样的策略,查看自己已经加载过的类中是否包含这个类,有就返回,没有就委托其父类去加载,直到委托到启动类加载器为止。因为如果父类加载器为空了,就代表使用启动类加载器作为父加载器去加载该类。(也就是看到的 String 类加载器为 null)
(3)如果启动类加载器加载失败,就会使用扩展类加载器来尝试加载,继续失败则会使用 AppClassLoader 来加载,继续失败就会抛出一个异常 ClassNotFoundException。
使用双亲委派模型的好处:
(1)安全性,避免用户自己编写的类动态替换 Java 的一些核心类。如果不采用双亲委派模型的加载方式进行类的加载工作,那我们就可以随时使用自定义的类来动态替代 Java 核心 API 中定义的类。例如:如果黑客将“病毒代码”植入到自定义的 String 类当中,随后类加载器将自定义的 String 类加载到 JVM 上,那么此时就会对 JVM 产生意想不到“病毒攻击”。而双亲委派的这种加载方式就可以避免这种情况,因为 String 类已经在启动时就被引导类加载器进行了加载。
(2)避免类的重复加载,因为 JVM 判定两个类是否是同一个类,不仅仅根据类名是否相同进行判定,还需要判断加载该类的类加载器是否是同一个类加载器,相同的 class 文件被不同的类加载器加载得到的结果就是两个不同的类。
08-17
08-17