什么时候加载类
1. 5种主动引用-触发类的初始化
- 遇到new(用new实例对象),getStatic(读取一个静态字段),putstatic(设置一个静态字段),invokeStatic(调用一个类的静态方法)这四条指令字节码命令时
- 使用Java.lang.reflect包的方法对类进行反射调用时,如果此时类没有进行init,会先init
- 当初始化一个类时,如果其父类没有进行初始化,先初始化父类
- jvm启动时,用户需要指定一个执行的主类(包含main的类)虚拟机会先执行这个类
- 当使用JDK1.7的动态语言支持的时候,当java.lang.invoke.MethodHandler实例后的结果是REF-getStatic/REF_putstatic/REF_invokeStatic的句柄,并且这些句柄对应的类没初始化的话应该首先初始
2. 被动引用-不会触发类的初始化
- 通过子类引用父类的静态字段,不会导致子类初始化(子类是不继承父类的static变量和方法的。因为这是属于类本身的。但是子类是可以访问的)
- 通过数组定义来引用类,不会触发此类的初始化
- 常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用定义常量的类,所以不会触发定义常来的类的初始化
加载类的过程
1. 加载
加载是类加载的一个过程,需要经历三个步骤
- 通过类的全限定名(绝对路径,包名+类名)来获取定义此类的二进制字节流
- 将这个字节流所代表的静态存储结果转化为方法区的运行时数据结构
- 在内存中生成一个代表这个类的java.lang.Class(用于存储类的相关信息的类)对象,作为方法去这个类的各种数据的访问入口
2. 验证
验证是连接阶段的第一步,这一步的目的是为了确保Class文件的字节流包含的信息符合当前虚拟机的要求,不会危害到虚拟机自身的安全
- 文件格式验证
- 是否以魔数开头,0XCAFEBABE
- 主、次版本号是否在当前虚拟机处理范围之内(UnsupportedClassVersionError应该就是这里报的)
- 常量吃的长两种是否有不被支持的常量类型(检查常量tag类型)
- 指向常量的各种索引值中是否有指向不存在的常量或不符合类型的常量
- ...
- 元数据验证
- 是否有父类(除了java.lang.Object之外,所有类都应该有父类)
- 是否集成了不允许被继承的类(被final修饰的类)
- 如果不是抽象类,是否实现了其父类或者接口之中要求实现的所有方法
- 类中的字段、方法是否与父类产生矛盾(例如覆盖了父类的final字段,重载方法不和规则等)
- 字节码验证
- 保证任意时刻操作数栈的数据类型和指令代码序列都能配合工作,例如不会出现放一个int的数据,却按照long类型加载出来
- 保证跳转指令不会跳转到方法以外的字节码指令上
- 保证方法体中的类型转换是有效的
- 符号引用验证
- 发号引用中通过字符串描述的全限定名是否能找到对应的类
- 在指定类中是否存在符合方法的字段描述符以及简单名称所描述的方法和字段
- 符号引用中的类、字段、方法的访问性是否可以被当前类访问
3. 准备
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,类变量(被static修饰的变量)都将在方法区中分配,这里说的初始值是数据类型的零值,真正的赋值动作会在初始化阶段执行
4. 解析
- 虚拟机将常量池内的符号引用替换为直接引用的过程(比如从类的全限定名转化为具体的内存地址)
5. 初始化
- 父类的()方法先执行(就是静态变量赋值以及静态代码块中的代码)
- 如果类中没有静态语句和静态代码块,那可以不生成()方法
- 初始化完成之后,才真正开始执行类中定义的Java程序代码(或者说是字节码)
用什么加载类
1. 类加载器
类加载器通过一个类的全限定名来获取描述此类的二进制字节流,对于任意一个类,需要加载这个类的加载器和类本身以通确立其在jvm中的唯一性
2. 双亲委派模型
java中只存在两种不同的类加载器,一种是启动类加载器,由c++实现,是虚拟机的一部分。一种是其他的类加载器,由Java语言实现,全部集成自抽象类java.lang.ClassLoader,独立于虚拟机。
双亲委派模型要求,除了顶层的启动类加载器外,其他的类加载器都应该有自己的父类加载器(不是继承,而是组合)
如果一个类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器完成。每个类加载器都是如此,因此,所有的加载请求都应该传动到顶层的启动类加载器中,只有当父加载器在自己的搜索范围内找不到指定的类时(即ClassNotFoundException),子加载器才会尝试自己去加载。
- 启动加载类(Bootstrapp ClassLoader):负责将存放在<JAVA_HOME>\lib目录或-Xbootclasspath参数指定的路径中的类库加载到内存中
- 扩展类加载器(Extension ClassLoader):负责加载<JAVA_HOME>\lib\ext目录或java.ext.dirs系统变量指定的路径中的所有类库
- 应用程序类加载器(Application ClassLoader):负责加载用户类路径(classpath)上的指定类库,我们可以直接使用这个类加载器。一般情况,如果我们没有自定义类加载器默认就是用这个加载器
3. 破坏双亲委派模型
- 自定义类加载器,重写loadClass方法(双亲委派的具体逻辑就实现在这个方法之中)
- 使用线程上下文类加载器
- 比如JNDI,JDBC等
- OSGi:灵活的类加载器架构