JVM类加载,jvm虚拟机启动时加载类的顺序
00-1010 1类2加载时机概述、类3加载流程、3.1加载、3.2验证、3.3准备、3.4解析、3.5初始化类4加载器、4.1父母委托模型、4.2打破父母委托模型
00-1010 Java虚拟机将描述类的数据从类文件中加载到内存中,并对数据进行验证、转换、分析和初始化,最终形成虚拟机可以直接使用的Java类型。这个过程叫做虚拟机的类加载机制。在Java语言中,类型的加载、连接和初始化都是在程序运行过程中完成的。
00-1010一个类型的整个生命周期,从加载到虚拟机内存到卸载,会经历加载、验证、准备、解析、初始化、使用、卸载七个阶段,其中验证、准备、解析这三个部分统称为连接。发生的顺序如下:
003010严格规定类必须立即“初始化”的情况只有六种(加载、验证、准备自然需要在此之前开始):
当遇到new、getstatic、putstatic或invokestatic这四个字节码指令时,如果类型没有初始化,就需要先触发它的初始化阶段。可以生成这四条指令的典型Java代码场景有:用new关键字实例化一个对象时,读取或设置一个类型的静态字段时,调用一个类型的静态方法时,使用java.lang.reflect包的方法对该类型进行反射调用时,如果还没有初始化,需要先触发其初始化。初始化一个类时,如果发现其父类还没有初始化,需要先触发其父类的初始化。当虚拟机启动时,用户需要指定一个要执行的主类。虚拟机首先初始化这个主类。当jdk1.7新加入动态语言支持时。当jdk8新添加的默认方法在接口中定义时。这六种情况的行为称为对类型的主动引用。另外,所有引用类型的方式都不会触发初始化,这就是所谓的被动引用。这里有一些被动引语的例子。
例1:通过子类引用父类的静态字段不会导致子类被初始化。
public class not initialization { public static void main(String[]args){ system . out . println(subclass . value);//只输出“超类init”,不输出“子类init”超类[]超类=newsuperclass[10];//不会输出“超类init”} } class super class { static { system . out . println(超类init );} public static int value=123}class子类扩展超类{ static { system . out . println( SubClass init!);}}}例2:常量在编译阶段会存储在调用类的常量池中,本质上并不直接引用定义该常量的类,所以不会触发已定义常量的初始化。
public class not initialization { public static void main(String[]args){ system . out . println(const class。hello world);//不会输出“ConstClass init!”因为常量直接存储在常量池中。} } class const class { static { system . out . println( const class init!);} public static final String hello world= hello world ;} class Test { static { I=0;} static int I=1;}
目录
00-1010在加载阶段,虚拟机需要完成以下三件事:
获取通过完全限定名定义类的二进制字节流。将此字节流表示的静态存储结构转换为方法区域的运行时数据结构。在内存中生成一个表示该类的java.lang.Class对象,作为该类在方法区域中的各种数据的访问入口。
00-1010验证是连接阶段的第一步。这个阶段的目的是确保类文件的字节流中包含的信息满足《Java虚拟机规范》的约束要求,并且这些信息不会伤害虚拟机本身。
ss="maodian">
3.3 准备
准备阶段是正式为类中定义的变量(即静态变量,被static修饰的变量)分配内存并设置类变量初始值的阶段。
3.4 解析
解析阶段是将常量池内的符号引用替换为直接引用的过程。
3.5 初始化
初始化是类加载过程的最后一个阶段,直到初始化阶段,Java虚拟机才真正开始执行类中编写的Java程序代码,将主导权移交给应用程序。初始化阶段就是执行类构造器的()方法的过程。
()方法与类的构造函数不同,它不需要显示地调用父类构造器,Java虚拟机会保证在子类的()方法执行前,父类的()方法已经执行完毕。因此在Java虚拟机中第一个被执行的()方法肯定是Object。由于父类的()方法先执行,也就意味着父类中定义的静态语句块要优先于子类的变量赋值操作,如下代码:字段值将会是2而不是1。
public static void main(String[] args) { System.out.println(Sub.B);}static class Parent { public static int A = 1; static { A = 2; }}static class Sub extends Parent { public static int B = A;}
4 类加载器
类加载器用于实现类的加载动作,JVM中内置了三个重要的ClassLoader,除了BootstrapClassLoader其他类加载器均由Java实现且全部继承自java.lang.ClassLoader:
BootstrapClassLoader(启动类加载器):最顶层的加载类,负责加载%JAVA_HOME%/lib
目录下的jar包和类或者被-X:bootclasspath
c参数指定的路径下的所有类。Extension Class Loader(扩展类加载器):主要负责加载<JAVA_HOME>libext目录中的所有类库。AppClassLoader(应用程序类加载器):面向我们用户的加载器,负责加载当前应用的classpath中的所有jar包和类。
4.1 双亲委派模型
如上图:双亲委派模型的工作流程是如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求时,子加载器才会尝试自己去完成加载。
好处
Java中的类随着它的类加载器一起具备了一种带有优先级的层次关系。
实现
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; } }
4.2 破坏双亲委派模型
双亲委派模型主要出现过3次较大规模的被破坏的情况。具体想了解的详看《深入理解Java虚拟机》。
到此这篇关于 jvm虚拟机类加载机制详解的文章就介绍到这了,更多相关 jvm虚拟机类加载内容请搜索盛行IT以前的文章或继续浏览下面的相关文章希望大家以后多多支持盛行IT!
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。