虚拟机类加载机制
类加载的时机
类被加载到虚拟机内存开始到结束过程,七个阶段:
加载→验证→准备→解析→初始化→使用→卸载
除了解析和使用,其他五个顺序按部就班执行
加载:虚拟机没有强行约束
加载和验证交叉进行,但仍然保持着固定的先后顺序
加载阶段,虚拟机需要三件事:
通过一个类的全限定名来获取定义此类的二进制流(可以是:jar包、applet运行容器、动态代理、JSP、数据库字节码服务器)
将字节流所代表的静态存储结构转化为方法区的运行时数据结构(方法区的数据格式可以自行定义)
在java堆中生成一个代表这个类的java.lang.class对象,作为方法区这些对象的访问入口
验证:
文件格式验证:是否符合class文件格式的规范,并且能被当前版本的虚拟机处理,主要目的是保证输入的字节流正确的解析并存储到方法区内
元数据验证:对字节码描述信息进行语义分析,保证描述信息符合java语言要求,主要目的是对类的元数据信息进行语义校验
字节码验证:进行数据流和控制流的分析,对类的方法体进行分析,著名的问题“halting problem”
符号引用验证:发生在虚拟机将符号引用转化为直接引用,将在第三阶段解析发生,主要目的确保解析动作能正常执行
准备:
正式为类变量分配内存并设置类变量初始值,都在方法去分配
这是其分配的进包括类变量(被static修饰的变量),不包括实例变量(实例变量会在对象实例化的时候一期分配到堆中)
这里的初始值指的是数据类型的零值
解析:
将符号引用转变为直接引用的过程
符号引用:可以是任何形式的字面量,引用目标不一定已经加载在内存,与内存布局无关
直接引用:直接指向目标的指针、相对偏移量、一个能直接定位到目标的句柄,与内存布局相关,其目标一定在内存中
并未规定执行时机,虚拟机根据需要判断
类和接口的解析:
字段解析:
类方法解析:
初始化:
<clinit>()方法是由编译器自动收集,收集顺序由语句在源文件中出现的顺序决定,静态语句块只能访问之前的变量,之后的只能赋值不能访问
<clinit>()与类构造函数不同,不需要显示的调用父类构造器,虚拟机会保证子类执行前父类的<clinit()>已经执行完毕
<clinit>()方法对于类或接口不是必须的,如果一个类没有静态语句块,也没有对变量赋值操作,那么编译器可以不为这个类生成<clinit>()方法
接口中不能使用静态语句块,但仍然有变量初始化的赋值操作,只有父接口中定义的变量被使用时,父接口才会被初始化
虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确的加锁和同步
虚拟机严格规定以下四种方式主动引用,其他的都是被动引用不会触发初始化
遇到new、getstatic、putstatic、invokestatic四条字节码指令
情景:new关键字实例化对象,设置读取累的静态字段(编译期放入常量池的除外),调用一个类的静态方法
使用java.lang.reflect包的方法对类进行反射调用
一个类的父类(接口的情况不一样,不需要父接口初始化)
用户指定的执行主类(包含main()方法的那个类)
使用:
卸载:
类加载器
虚拟机设计团队把类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到外部去实现
此技术被应用到:类层次划分、OSGi、热部署、代码加密等领域
类与类加载器
比较两个类是否“相等”,只有这两个类是由同一个类加载器加载的前提下才有意义,否则必定不相等
“相等”:equals() isAssignableForm() isInstance()的返回结果github.com/singgel/jav…
双亲委派模型
除了顶层的启动类加载器外,其余类加载器都应当有自己的父类加载器,只有自己的父类反馈无法完成类加载时,子加载器才会尝试自己去加载
站在java虚拟机角度:
一种启动类加载器(Bootstrap ClassLoader)由C++实现是虚拟机的一部分
另一种其他的类加载器,这类加载器由Java语言实现,独立于虚拟机之外,全都继承java.lang.ClassLoader
开发人员的角度:
启动类加载器:负责将<JAVA_HOME>/lib目录中的,或者-Xbootclasspath参数指定路径中的,并且是虚拟机识别的,无法被Java程序直接使用
扩展类加载器:由sun.misc.Launcher$ExtClassLoader实现,<JAVA_HOME>/lib/ext目录中的,或者被java.ext.dirs系统变量指定的路径
应用程序类加载器:由sun.misc.Launcher$AppClassLoader实现,也称为系统类加载器。负责加载用户类路径上所指定的类库
破坏双亲委派模型
第一次破坏:JDK1.2之前,因为之前,没有双亲委派模型
第二次破坏:基础类回调用户代码,例如JNDI(线程上下文类类加载器)、JDBC、JCE、JAXB和JBI等
第三次破坏:代码热替换、模块热加载等,典型的OSGi模块热部署