1、类加载时机
仅加载直接引用的类(如:子类使用父类的静态字段,只初始化父类);有且仅有以下情况:
1.new/getstatic/putstatic/invokestatic指令(Java场景:new对象、读取或设置静态字段、调用静态方法)
2.java.lang.reflect包方法对类进行反射调用
3.初始化一个类时,如果发现其父类还未初始化则先初始化父类
4.JVM启动时初始化用户指定的执行主类(包含main()方法的类)
5.使用JDK1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法对应的类没进行初始化,则触发初始化
接口初始化与类基本一致,唯一有区别的是第3点:当一个类在初始化时,要求其父类全部都已初始化过,而接口不要求其父接口全部都已初始化,只有在真正用到父接口时(如引用接口中定义的常量)才进行初始化
2、类加载过程
1.加载
通过类的全限定名找到二进制字节流:从zip包获取(jar/war)、从网络获取(Applet)、运行时计算生成(动态代理)、由其他文件生成(JSP)、从数据库读取等
将字节流所代表的静态存储结构转化为方法区的运行时数据结构
在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据访问入口
数组类:JVM直接创建,不通过类加载器;数组类的元素类型(去掉所有维度之后的类型)由类加载器创建
数组类创建原则:1.数组的组件类型(去掉一个维度之后的类型)是引用类型,递归采用加载过程进行组件类型加载,数组在组件类型加载器的类名称空间上被标识 2.组件类型不是引用类型,JVM把数组标记为与引导类加载器关联 3.数组类的可见性与组件类型的可见性一致,如果组件类型不是引用类型,那数组类的可见性默认public
2.连接(验证、准备、解析):连接中部分内容与加载交叉进行
验证:确保Class文件的字节流中包含的信息符合当前虚拟机要求,并且不会危害虚拟机自身的安全;1.文件格式验证(魔数开头、主次版本号、常量类型) 2.元数据验证(保证字节码描述符合Java语言规范,是否有父类、是否实现了父类的抽象方法) 3.字节码验证(通过数据流和控制流分析,确保程序语义合法、符合逻辑) 4.符号引用验证(通过字符串描述的权限定名是否能找到对应类、指定类中是否存在符合方法的字段描述符合简单名称所描述的方法和字段、访问性是否合法;符号验证保证解析的正常执行,如果无法通过符号引用验证会跑出IncompatibleClassChangeError子类,如IllegalAccessError、NoSuchFieldError、NoSuchMethodError);验证阶段是非常重要但非必须的,可以通过-Xverify:none关闭大部分类验证措施
准备:为类变量分配内存及初始值,类变量使用内存在方法区分配,注意只对类变量(static)分配,初始值一般为数据类型的零值
解析:JVM将常量池内符号引用替换为直接引用的过程;符号引用以一组符号来描述所引用的目标,和JVM内存布局无关,引用目标不一定已经加载到内存;直接引用可以是直接指向目标的指针、相对偏移量或能间接定位到目标的句柄,和JVM内存布局相关,有了直接引用则引用目标必定已在内存中存在;解析主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符等七类符号引用,分别对应于常量池的CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info、CONSTANT_InterfaceMethodref_info、CONSTANT_MethodType_info、CONSTANT_MethodHandle_info、CONSTANT_InvokeDynamic_info;
3.初始化
执行类中定义的Java代码的阶段(用户可以自定义加载器参与加载过程);初始化阶段是执行类构造器<cinit>方法的过程
1.<cinit>方法由编译器自动收集所有类变量的赋值动作和静态语句块内的语句合并而成,编译器按照源文件中代码顺序收集,静态块只能访问到定义在它之前的变量,之后的只能在静态块中赋值,而不用访问
2.JVM保证父类的<cinit>在子类之前执行,因此第一个执行的永远是Object的<cinit>
3.父类的<cinit>先于子类执行,因此父类的静态块先于子类的变量赋值操作
4.类没有静态块,也没有变量赋值,可以不生成<cinit>
5.接口中有<cinit>
6.JVM保证<cinit>在多线程环境被正确加锁、同步,只会有一个线程去执行类的<cinit>,其他线程阻塞等待
3、类加载器
每个类加载器都有一个独立的类名称空间,类加载器和类本身一同确立其在JVM中的唯一性。比较两个类是否相等,必须是在同一个类加载器下才有意义
1.启动类加载器(BootStrap):负责加载<JAVA_HOME>/lib下或者-Xbootclasspath指定路径下,且被虚拟机识别的类(根据名称,如rt.jar)
2.扩展类加载器(Ext):负责加载<JAVA_HOME>/lib/ext下或者java.ext.dirs系统变量指定路径下的类,开发者可以直接使用,ClassLoader子类
3.应用程序类加载器(App):负责加载Classpath下的类,也叫系统类加载器,ClassLoader子类
双亲委派模型:如果一个类加载器收到类加载请求,首先会把请求委派给父类加载器去完成,因此所有的加载请求最后都要传送到顶层的启动类加载器中,父类无法完成时子加载器才会加载。双亲委派模型要求除了启动类加载器,其他类加载器都有自己的父类加载器。这样的一个好处就是Java类随着它的类加载器一起具备了一种带有优先级的层次关系。双亲委派模型的实现在java.lang.ClassLoader.loadClass()中