对于类文件结构,笔者认为没必要太过多的深入,意义不大,了解即可;
java虚拟机提供的语言无关性,使用java编译器可以把java代码编译为存储字节码的Class文件,使用JRuby等其他语言的编译器意义可以把程序代码编译成class文件,虚拟机并不关心Class的文件来自于何种语言:
类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸载(Unloading)7个阶段。
在加载过程中,虚拟机需要完成一下3件事情:
1)通过一个类的权限定名来获取定义此类的二进制字节流;
2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构;
3)在内存中生成一个代表这个类的java.lang.class对象,作为方法区这个类的各种数据的访问入口;
验证的目的就是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求;
1.文件格式验证
验证字节流是否符合class文件格式的规范,并且能被当前版本的虚拟机处理:
1)是否是魔术oxCAFFEBABE开头;(每个class文件的头4个字节称为魔术(magic number),它的唯一作用是确定这个文件是否为一个能被虚拟机接受的class文件)
2)主、次版本号是否在当前虚拟机处理范围以内;
2.元数据验证
对字节码描述的信息进行语义分析,以保障其描述的信息符合java语言规范的要求。
1)这个类是否有父类。这个类的父类是否继承了不允许被继承的类(被final修饰的类);
2)如果这个类不是抽象类,是否实现了其父类或接口之中要实现的所有方法;
3)类中的字段、方法是否存在冲突;
3.字节码验证
通过数据流和控制流分析,确定程序语义是否合法,符合逻辑;
4.符号引用验证
符号引用验证可以看做是对类自身以外的信息进行匹配性校验(常量池中的各种符号引用);
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行匹配。
解析阶段是虚拟机即将常量池中的符号引用替换成直接引用的过程。(在类加载过程中,第一次就直接转化成直接引用的过程称为静态解析,另一部分在每次运行时转换的称为动态连接)
符号引用(symbolic references):符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可;
直接引用(direct references):直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。
笔者觉得符号引用就是非规范化的符号指向目标,直接引用时规范化的符号指向目标。
1)类或接口的解析
2)字段解析
3)接口方法解析
到了初始化阶段,才真正开始执行类中定义的java程序代码(或者说字节码)
类加载器最初是为了满足java applet 的需求而开发出来的。虽然目前java applet已经“死掉”,但类加载器却在类层次划分、OSGI、热部署、代码加密等领域大放异彩,成为了java技术的重要基石。
3.1类与类加载器
对于任意一个类,都需要由加载它的类加载器和这个类本身一同确定其在java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。依旧是说:比较一个类是不是”相等”,只有在这两个类是由同一个类加载器加载的前提下才有意义,否则,即使这两个类来源于同一个Class文件,被同一个虚拟机加载,只要加载他们的类加载器不同,那这两个类就必定不相同。
从java 虚拟机的角度来看,只存在两种不同的类加载器:
一种是启动类加载器(bootstrap classloader),这个类加载器使用C++语言实现,是虚拟机自身的一部分;
另一种就是所有其他的类加载器,这些类加载器都有java 语言实现,独立于虚拟机外部,并且全部继承抽象类java.lang.ClassLoader。
从java开发人员的角度来说,可分为三种不同的类加载器:
启动类加载器(bootstrap ClassLoader),和上面那个一样,这个类加载器负责将存放在
扩展类加载器(Extension ClassLoader):这个加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载
应用程序类加载器(Application ClassLoader):这个加载器由sum.misc.Launcher$App-ClassLoader实现。由于这个类加载器是ClassLoader的getSystemClassLoader()方法实现的返回值,所以一般也称为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类库,开发者可以直接使用这个类加载器,如果没有指定,一般就是程序默认的类加载器;
3.2双亲委派机制
工作过程:如果一个类加载器收到了一个类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都会送到顶层的启动类加载器中,只有当父加载器无法加载时,子加载器才会尝试直接去加载;
为什么要使用双亲委派机制?使用双亲委派机制来组件类加载器之间的关系,有一个显而易见的好处就是java类随着它的类加载器一起具备了一种带有优先级的层次关系。例如:java.lang.Object,它存放在rt.jar中,无论哪个类加载它,都会从底部加载器加载;这样的话,对于自定义一个一样的Object类,java程序也只会加载rt.jar上面的Object。这样的好处就是,java程序在执行的时候,不会出现加载混乱。