Java类加载及对象创建过程详解
类加载过程 类加载的五个过程:加载、验证、准备、解析、初始化。 加载 在加载阶段,虚拟机主要完成三件事:
验证 验证阶段作用是保证Class文件的字节流包含的信息符合JVM规范,不会给JVM造成危害。如果验证失败,就会抛出一个java.lang.VerifyError异常或其子类异常。验证过程分为四个阶段:
准备 准备阶段为变量分配内存并设置类变量的初始化。在这个阶段分配的仅为类的变量(static修饰的变量),而不包括类的实例变量。对已非final的变量,JVM会将其设置成“零值”,而不是其赋值语句的值:pirvate static int size = 12;。那么在这个阶段,size的值为0,而不是12。但final修饰的类变量将会赋值成真实的值。 解析 解析过程是将常量池内的符号引用替换成直接引用。主要包括四种类型引用的解析。类或接口的解析、字段解析、方法解析、接口方法解析。 初始化 在准备阶段,类变量已经经过一次初始化了,在这个阶段,则是通过程序制定的计划去初始化类的变量和其他资源。这些资源有static{}块,构造函数,父类的初始化等。 至于使用和卸载阶段阶段,这里不再过多说明,使用过程就是根据程序定义的行为执行,卸载由GC完成。 双亲委派模型 类加载器按照层次,从顶层到底层,分为以下三种:
上图只是类加载的顺序,和类继承无关。ExtClassLoader,AppClassLoder继承URLClassLoader,而URLClassLoader继承ClassLoader。BoopStrap ClassLoder是由C/C++编写的,它本身是虚拟机的一部分,并不是一个java类。 AppClassLoader的父加载器为ExtClassLoader,ExtClassLoader的父加载器为null,BoopStrap ClassLoader为顶级加载器 工作过程 如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传递到顶层的启动类加载器中,只有当父类加载器反馈自己无法完成这个请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。 相对应的实现逻辑:先检查类是否被加载过,若没有就调用父加载器的loadClass方法,若父加载器为空则默认使用启动类加载器作为父加载器。如果父加载器加载失败,抛出异常,再调用自己的findClass方法进行加载。 具体示例: 假如我们自定义Test class文件,jvm要加载Test.class的时候:
双亲委派的好处 Java类随着它的类加载器一起具备了一种带有优先级的层次关系。例如类Object,它放在rt.jar中,无论哪一个类加载器要加载这个类,最终都是委派给启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。 判断两个类是否相同是通过classloader.class这种方式进行的,所以哪怕是同一个class文件如果被两个classloader加载,那么他们也是不同的类。 实现自己的加载器,只需要继承ClassLoader,并覆盖findClass方法。 对象创建过程 对象的流程 1. 类加载检查 JVM遇到一条new指令时,首先检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。 如果没有,那必须先执行相应的类的加载过程。 2. 对象分配内存 对象所需内存的大小在类加载完成后便完全确定(对象内存布局),为对象分配空间的任务等同于把一块确定大小的内存从Java堆中划分出来。 根据Java堆中是否规整有两种内存的分配方式:(Java堆是否规整由所采用的垃圾收集器是否带有压缩整理功能决定)。 指针碰撞(Bump the pointer) (编辑:ASP站长网) |