一、类文件
Java虚拟机不和包括java在内的任何语言绑定,它只于“Class文件”这种特定的二进制文件格式所关联,Class文件包含Java虚拟机指令集和符号表以及若干其他辅助信息。
任何一个Class文件都对应着唯一一个类或接口的定义信息。
Class文件是一组8位字节为基础的二进制流,各个数据项严格按照顺序紧凑排列在Class文件之中,中间没有分隔符。当遇到需要占用8字节以上的数据项,会按照高位在前的方式分割成若干个8位字节进行存储。
1、类文件
①、每个Class文件的头四个字节称为魔数(Magic Number),唯一确定这个文件是否能被虚拟机接受的Class文件。0xCAFEBABE(咖啡宝贝),紧接着魔数的四个字节存储的时候Class文件的版本号,56字节存放次版本号,78字节存放主版本号。高版本JDK能向下兼容。
②、紧接着常量池入口,是Class文件中的资源仓库,常量池主要存放两大类常量:字面量(Literal)和符号引用(Symbolic References)。字面量如文本字符串、声明的final常量值等,而符号引用则侧重于编译原理方面的概念,包括类和接口的全限定名、字段的名称和描述符、方法的名称和描述符,这种字段和方法需要经过运行期转换才能得到真正的内存入口地址。
③、常量池结束后是访问标志(access_flags),识别访问信息,如判断是类还是接口;是否定义为public;是否定义abstract类型;是否声明为final。
④、类索引、父类索引、接口索引集合
⑤、字段表集合用于描述接口或类中声明的变量,信息包括:作用域、实例变量还是类变量、可变性(final)、可并发性、可被序列化(transient)、字段类型数据、字段名称。
⑥、方法表存放信息依次包括:访问标志、名称索引、描述符索引、属性表集合。属性表集合中code属性即存放Java代码经过编译器编译成字节码指令。
二、类加载
虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可被虚拟机直接使用的Java类型,即虚拟机的类加载机制。
类的生命周期:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initalization)、使用(Using)、卸载(Unloading),验证、准备、解析统称为连接。
2、触发类初始化的五种情况:
①遇到new、getstatic、putstatic、invokestatic这四条字节指令时
②使用java.lang.reflect包对方法对类进行反射调用时
③初始化一个类,其父类还未初始化时
④虚拟机启动时,执行main方法,该主类会初始化
⑤java.lang.reflect.MethodHandle实例最后解析结果是REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并对应的类没有初始化
以上五种情况称为对一个类进行主动引用,除此之外,均为被动引用。如子类引用父类的静态字段,不会导致子类初始化;初始化数组,数组对象也不会被加载。
虚拟机设计团队把类加载阶段中“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类型。实现这个动作的模块称“类加载器”。类加载器在类层次划分、OSGi、热部署、代码加密等领域大放异彩,对于任意一个类都需要加载它的类加载器和类本身一同确立在java虚拟机中的唯一性。
3、从虚拟机的角度而言,只存在两种不同的类加载器:启动类加载器(C++实现,虚拟机自身的一部分);其他类加载器,java实现,独立于虚拟机外部,继承java.lang.ClassLoader。
①启动类加载器(Bootstrap ClassLoader):加载lib目录,或jvm设置的路径文件
②扩展类加载器(Extension ClassLoader):加载lib/ext目录文件,开发者可直接使用
③应用程序类加载器(Application ClassLoader):一般称系统类加载器,负责加载用户类路径上所指定的类库,是默认程序中的加载器。
4、双亲委派模型:要求顶层是启动类加载器,其余类加载器应当有自己的父类加载器。类加载器的父子关系不会以继承的关系来实现,通常是使用组合的方式来复用父加载器。并不是强制性约束模型,而是推荐给开发者的一种类加载实现方式。
双亲委派模型工作过程:如果一个类加载器收到一个类加载请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每个层次的类加载器都是如此,因此所有的请求都会传送到顶层的启动类加载器去完成,只有当父类加载器反馈无法加载时,子类才会尝试自己去加载。
好处:Java类随着类加载器一起具备了优先级的层次关系。一个类,无论使用何种加载器,都会有同一个类加载,都是同一个类。保证了Java程序运作的稳定性。
5、双亲委派模型主要出现过三次较大规模的“被破坏”情况:
①双亲委派模型在JDK1.2之后才被引用,1.2之前都是覆盖loadClass()方法
②自身缺陷,一般情况下基础类只会被用户代码调用,但若是基础类调用用户的代码,启动类加载就无法加载。例如JNDI服务
JNDI命名与目录接口(Java Naming and Directory Interface),在j2ee容器中配置JNDI参数,例如jdbc的引用参数,给数据源设置名称,然后通过对数据源的名称访问后台数据库。
JNDI避免了程序与数据库之间的紧耦合,使应用更加易于配置、易于部署。
Java设计团队通过线程上下文类加载器(Thread Context ClassLoader),可通过Thread类setContextClassLoader()方法进行设置,JNDI服务使用这个线程上下文类加载器去加载所需要的SPI代码。
③对程序动态性的追求,代码热替换(HotSwap)、模块热部署(Hot Deployment)等。
OSGi(Open Service Gateway Initiative)实现模块之间的真正“解耦”、“分离”。通过动态加载(热部署、热启动)来启动项目,即该项目放在web容器之后,可以把功能拿掉而不影响其他模块。OSGi关键则是它自己定义了类加载机制的实现,每一个模块(Bundle)都有一个自己的类加载器,当需要更换一个Bundle时,就把Bundle连同类加载一起换掉以实现代码的热替换。