Java类加载及对象创建过程详解( 二 )

  • 如果没有加载过,则询问上一层加载器(ExtClassLoader)是否已经加载过 。
  • 如果没有加载过,则继续询问上一层加载(BoopStrap ClassLoader)是否已经加载过 。
  • 如果BoopStrap ClassLoader依然没有加载过,则到自己指定类加载路径下("sun.boot.class.path")查看是否有Test.class字节码,有则返回,没有通知下一层加载器ExtClassLoader到自己指定的类加载路径下(java.ext.dirs)查看 。
  • 依次类推,最后到自定义类加载器指定的路径还没有找到Test.class字节码,则抛出异常ClassNotFoundException 。
  • 双亲委派的好处
    Java类随着它的类加载器一起具备了一种带有优先级的层次关系 。例如类Object,它放在rt.jar中,无论哪一个类加载器要加载这个类,最终都是委派给启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都是同一个类 。
    判断两个类是否相同是通过classloader.class这种方式进行的,所以哪怕是同一个class文件如果被两个classloader加载,那么他们也是不同的类 。
    实现自己的加载器,只需要继承ClassLoader,并覆盖findClass方法 。
    对象创建过程 
    Java类加载及对象创建过程详解

    文章插图
     
     
    对象的流程
    1. 类加载检查
    JVM遇到一条new指令时,首先检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过 。
    如果没有,那必须先执行相应的类的加载过程 。
    2. 对象分配内存
    【Java类加载及对象创建过程详解】对象所需内存的大小在类加载完成后便完全确定(对象内存布局),为对象分配空间的任务等同于把一块确定大小的内存从Java堆中划分出来 。
    根据Java堆中是否规整有两种内存的分配方式:(Java堆是否规整由所采用的垃圾收集器是否带有压缩整理功能决定) 。
    • 指针碰撞(Bump the pointer)
    • Java堆中的内存是规整的,所有用过的内存都放在一边,空闲的内存放在另一边,中间放着一个指针作为分界点的指示器,分配内存也就是把指针向空闲空间那边移动一段与内存大小相等的距离 。例如:Serial、ParNew等收集器 。
    • 空闲列表(Free List)
    • Java堆中的内存不是规整的,已使用的内存和空闲的内存相互交错,就没有办法简单的进行指针碰撞了 。虚拟机必须维护一张列表,记录哪些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录 。例如:CMS这种基于Mark-Sweep算法的收集器 。
    3. 并发处理
    对象创建在虚拟机中时非常频繁的行为,即使是仅仅修改一个指针指向的位置,在并发情况下也并不是线程安全的,可能出现正在给对象A分配内存,指针还没来得及修改,对象B又同时使用了原来的指针来分配内存的情况 。解决这个问题有两种方案:
    • 同步
    • 虚拟机采用CAS配上失败重试的方式保证更新操作的原子性
    • 本地线程分配缓冲(Thread Local Allocation Buffer, TLAB)
    • 把内存分配的动作按照线程划分为在不同的空间之中进行,即每个线程在Java堆中预先分配一小块内存(TLAB) 。哪个线程要分配内存,就在哪个线程的TLAB上分配 。只有TLAB用完并分配新的TLAB时,才需要同步锁定 。
    虚拟机是否使用TLAB,可以通过-XX:+/-UseTLAB参数来设定 。
    4. 内存空间初始化
    虚拟机将分配到的内存空间都初始化为零值(不包括对象头),如果使用了TLAB,这一工作过程也可以提前至TLAB分配时进行 。
    内存空间初始化保证了对象的实例字段在Java代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值 。
    注意:类的成员变量可以不显示地初始化(Java虚拟机都会先自动给它初始化为默认值) 。方法中的局部变量如果只负责接收一个表达式的值,可以不初始化,但是参与运算和直接输出等其它情况的局部变量需要初始化 。
    5. 对象设置
    虚拟机对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息 。这些信息存放在对象的对象头之中 。
    6. 执行init()
    在上面的工作都完成之后,从虚拟机的角度看,一个新的对象已经产生了 。但是从Java程序的角度看,对象的创建才刚刚开始init()方法还没有执行,所有的字段都还是零 。


    推荐阅读