赞
踩
CLass文件需要加载到虚拟机中才能运行和使用,那么虚拟机是如何加载这些Class文件呢?
系统加载Class类型的文件主要三步:加载、连接、初始化。连接过程有可分为验证、准备、解析。
类加载过程的第一步,主要完成一下三个事情:
确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中分配。
将常量池的符号引用替换为直接引用的过程,也就是得到类或者字段、方法在内存中的指针或者偏移量。
符号引用:一组符号来描述目标,可以是任何字面量。
直接引用:就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。
其中解析过程在某些情况下可以在初始化阶段之后再开始,这是为了支持Java的动态绑定。
初始化阶段才真正开始执行类中定义的Java程序代码。初始化阶段是虚拟机执行类构造器方法的过程。在准备阶段,类变量已经赋过一次系统要求的初始值,而在初始化阶段,根据程序员通过程序指定的主管计划区初始化类变量和其他资源。
类会初始化的情况:
不会导致类初始化的情况:
JVM中内置了三个重要的ClassLoader,除了BotstrapClassLoader其他类加载器均由Java实现且全部继承自java.lang.ClassLoader:
类加载器的优先级(从高到低):启动类加载器->扩展类加载器->应用程序类加载器->自定义类加载器。
图中展示了类加载器之间的层次关系,称为 双亲委派模型(Parents DeleGation Model)。该模型要求除了顶层的启动类加载器外,其他的类加载器都要有自己的父类加载器。这里的父子关系一般通过组合关系(Composition)来复用父加载器的代码,而不是继承关系(Inheritance)。
双亲委派模型的工作过程:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是会把这个请求委派给父类加载器区完成,每一个层次的类加载器都是如此,所以最终会传送到顶层的启动类加载器中,只有当父加载器反馈自己无法加载这个加载请求时(它的搜索范围中没有找到所需的类),子加载器才会尝试自己去加载。
双亲委派模型的实现代码非常简单,逻辑非常清晰,都集中在 java.lang.ClassLoader
的 loadClass()
中,相关代码如下所示。
private final ClassLoader parent; protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // 首先,检查请求的类是否已经被加载过 Class<?> c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { if (parent != null) {//父加载器不为空,调用父加载器loadClass()方法处理 c = parent.loadClass(name, false); } else {//父加载器为空,使用启动类加载器 BootstrapClassLoader 加载 c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { //抛出异常说明父类加载器无法完成加载请求 } if (c == null) { long t1 = System.nanoTime(); //自己尝试加载 c = findClass(name); // this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveClass(c); } return c; } }
双亲委派模型保证了Java程序的稳定运行,可以避免类的重复加载(JVM区分不同类的方式不仅仅根据类名,相同的类文件被不同的类加载器加载产生的是两个不同的类),也保证了Java的核心API不被篡改。如果没有使用双亲委派模型,而是每个类加载器加载自己的话就会出现一些问题,比如我们编写一个称为java.lang.Object类的话,那么程序运行的时候,系统就会出现多个不同的Object类。
自己定义加载器的话,需要继承ClassLoader。如果我们不想打破双亲委派模型,就重写CLassLoader类中的findClass()方法即可,无法被父类加载器加载的类最终会通过这个方法被加载。但是,如果想打破双亲委派模型,则需要重写loadClass()方法。
参考文章:
深入理解Java虚拟机第三版-周志明
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。