一、类加载的时机
类的生命周期,如下
何时需要进行类加载:
-
遇到new、getstatic、putstatic、invokestatic字节码指令。
-
使用java.lang.reflect包方法对类反射调用。
-
初始化一个类,其父类未初始化时。
-
虚拟机启动时,需要执行一个main方法,那么需要初始化该类。
-
jdk1.7动态语言支持时,当java.lang.invoke.MethodHandle实例最后的解析结果为REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,且该句柄所对应的类没有初始化,那么需要先触发其初始化。
二、类的加载过程
1)加载(Class Loading)
-
通过类全限定名获取定义此类的二进制字节流。
-
将字节流代表的静态存储结构转化为方法区的运行时数据结构。
-
在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
数组的加载:数组类本身不通过类加载器创建,是由JVM直接创建。
-
数组的组件类型(Component Type)是引用类型,那就加载改组件类型,数组将在加载该组件类型的类加载器的类名称空间上被标识。
-
数组的组件类型不是引用类型,JVM将会把数组标记为与引导类加载器关联。
-
数组类的可见性与它的组件类型的可见性一致,如果组件类型不是引用类型,那数组的类的可见性默认为public。
2)验证
-
文件格式验证:魔数、版本、常量池中的常量类型、编码格式...
-
元数据验证:是否有父类、父类是否允许继承、非抽象类是否实现了所有接口中的方法、字段方法是否与父类有冲突...
-
字节码校验:操作数栈的数据类型与指令代码序列不冲突、跳转指令不会跳转到方法体以外的字节码指令上、方法体中的类型转换是有效的...
-
符号引用验证:发生在符号引用转为直接引用时(解析),符号引用中的字符串描述的全限定名是否能找到对应的类、是否能找到符合描述的方法与字段、符号引用的类、字段、方法是否可被当前类访问(private、public等等)
3)准备
为类变量分配内存,并设置初始值。static 变量
4)解析
将常量池的符号引用替换为直接引用,包含:类或接口解析、字段解析、类方法解析、接口方法解析
5)初始化
执行<clinit>,初始化类变量,执行静态代码块。
三、类加载器
类与类加载器
对于任何一个类,都需要有加载它的类加载器与这个类本身确定其在JVM中的唯一性。
双亲委派模型
-
启动类加载器:加载JAVA_HOME/lib下的类,或者-Xbootclasspath指定路径的类。
-
扩展类加载器:加载JAVA_HOME/lib/ext下的类,或者被java.ext.dirs系统变量指定路径的类。
-
应用类加载器:加载用户路径(ClassPath)上所制定的类。
双亲委派:类加载器收到类加载请求时,先委派给父类加载器去加载,每一个类加载器都是如此,当父类加载器加载了,则加载完成,若父类无法加载,则再由子加载器尝试加载。
破坏双亲委派模型
使用线程上下文类加载器(Thread Context ClassLoader),可以通过java.lang.Thread的setContextClassLoader()进行设置,在JVM中会把当前线程的类加载器加载不到的类交给线程上下文类加载器来加载。
-
JDBC
-
OSGi