类的加载过程:首先通过Javac命令将java源文件编译为字节码文件.class 。在通过java命令启动加载,将字节码文件加载到内存中运行。
类的加载分为:加载 => 连接 => 初始化三个阶段 ,其中连接又细分为 :验证 => 准备 => 解析这三个阶段。
加载:通过一个类的全限定名在硬盘上查找并且通过IO将字节码文件读入到JVM的方法区,同时在堆中创建Class对象。(全限定名就是一个绝对路径)
验证:校验字节码文件的正确性,比如验证它是不是开头cafe babe。
准备:为类的静态变量分配内存,并将静态变量初始化为默认值。此阶段仅仅只为静态类的变量分配内存,赋值操作会在下一阶段初始化中完成。其中final staic 修饰的变量在编译的时候就会分配内存,也不会分配实例变量的内存。
解析:把类中的符号引用转换为直接引用。符号引用就是没有指定具体的地址,用一个符号来代替具体的内存地址,直接引用就是能找到具体地址的引用。
初始化:对类的静态变量初始化为指定的值,执行静态代码块。
实例对象和Class对象。每个类的运行时的类型信息就是用Class对象表示的。它包含了与类有关的信息。其实我们的实例对象就通过Class对象来创建的。Java使用Class对象执行其RTTI(运行时类型识别,Run-Time Type Identification),多态是基于RTTI实现的。
类加载的底层流程
运行java命令后,在window系统下 java.exe 会调用底层的jvm.dll文件创建java虚拟机的实现。同时会创建一个引导类加载器(也叫启动类加载器) ,其他类型的加载器都需要经过引导类加载器调用Java代码进行加载。
getLauncher()创建启动类实例和其他加载器(拓展类加载器和应用类加载器),通过launcher.getClassLoader()获取应用类加载器,通过应用类加载器去加载运行的类HelloWorld。加载的过程就是上面所述的:加载、验证、准备、解析、 初始化五个阶段。
拓展类加载器和应用类加载器都是申明在Launcher中的static内部类。在创建启动器实例时,会同时创建拓展类和应用类实例,因为在启动器实例的构造器中写了创建那两种加载器的代码。
总结下:就是调用c++代码创建启动器,创建启动器时在构造器中会同时创建拓展类加载器和应用类加载器的实例。
各个类加载器的加载范围有一定的区别:
引导类加载器:负责加载jre/lib目录下的核心类库,比如rt.jar /charset.jar等
扩展类加载器:负责加载jre/lib/ext目录中的jar类包
应用程序类加载器:负责加载ClassPath路径下的字节码文件,主要是加载自己写的那些类
自定义加载器:负责加载自定义路径下的字节码文件
为什么要设计双亲委派机制?
1、避免重复加载,当父加载器已经加载了一个类时,字加载器就没有必要再去加载了
2、沙箱安全机制:当你自己写了一个String类,先让父类去加载因为父类是从jre的原码中进行加载,所以你写的String类就不会被加载运行,这样就能保证java运行的安全,保证核心类能够被加载
为什么类的加载顺序不直接从引导类加载器开始?而是要从应用类加载器一直向上委托?
加载的过程是这样的,先查看应用类加载器有没有加载,有直接用,没有向上委托给拓展类加载器进行加载,同样,有直接用,没有向上委托给引导类加载器进行加载。
当第一次加载时,直接中引导类开始加载是比较快的,但是如果进行多次加载,每次都从上层引导类向下来进行加载的话效率就会低很多。例如应用类加载器已经完成了加载,当要再次加载时直接查看应用类加载器是否已经加载,若已经加载了可以直接用,不用再向上委托了。
这就是双亲委派机制的好处。
双亲委派的具体过程如下:
应用类加载器也叫系统类加载器。
如何破坏双亲委派模式?
1、通过自定义一个类加载器,然后破坏双亲委托模型,最后在重写defineClass方法(在这个native方法里面也有检查限制),绕过Java语言的各种限制,是可以达到目标的,但其实这里面存在很大安全隐患的,对于java开头的包里面的基础数据类型是没有任何理由去破坏的,这种行为属于破坏双亲委托模型的最顶级行为
2、最左边的类也就是最底层的类,可以访问到顶层的类加载的类,但是反过来却不行,但在实际开发情况下,可能会遇到,顶级的加载器需要回调低级加载器加载的实现类。为了克服这个问题,双亲委派模型中又引入了ThreadContextClassLoader,可以通过Thread的setContextClassLoader和getContextClassLoader获取底层的加载器,从而通过底层加载器来加载该类来避免这个问题。
举个常见的例子: Java里面的SPI机制,或者java.sql的驱动实例化的例子,他们的核心接口都是由Java的引导类加载器加载的,但是他们的实现却是各个厂商提供的或者根据约定设置的,这种情况下引导类加载器是看不到底层加载器的(classpath)的类的,所以只能通过底层加载器本身来加载,这个时候相当于顶层加载器需要使用底层加载器加载的类,从而间接的破坏了双亲委托模型,相当于走了后门。
3、另外一种破坏双亲委托模型的例子是热加载模式,为了解决不停机或者不停服务更新应用,典型的应用场景是在OSGI里面,默认情况下对于已经加载的类双亲委派模型是不会重新再加载的,但这样就意味着更新了不会被及时感知,如果需要做到动态更新,那么对于已经加载的类也必须再次进行加载,并且要处理好旧实例与新实例状态数据拷贝问题,这种模式也是破坏了双亲委派机制。
原文:https://www.cnblogs.com/cyx0721/p/14782516.html