《深入理解Java虚拟机》读书笔记五

103 阅读5分钟

虚拟机类加载机制

类加载的时机

类被加载到虚拟机内存开始到结束过程,七个阶段:

加载→验证→准备→解析→初始化→使用→卸载

除了解析和使用,其他五个顺序按部就班执行

加载:虚拟机没有强行约束

加载和验证交叉进行,但仍然保持着固定的先后顺序

加载阶段,虚拟机需要三件事:

通过一个类的全限定名来获取定义此类的二进制流(可以是: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模块热部署