首页 > 编程语言 > 详细

Java类加载机制

时间:2021-05-18 23:03:55      阅读:24      评论:0      收藏:0      [点我收藏+]

技术分享图片

 

技术分享图片

 

 

 

类的加载过程:首先通过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. 当一个类加载器接收到类加载任务时,先查缓存里有没有,如果没有,将任务委托给它的父加载器去执行。
  2. 父加载器也做同样的事情,一层一层往上委托,直到最顶层的启动类加载器为止。
  3. 如果启动类加载器没有找到所需加载的类,便将此加载任务退回给下一级类加载器去执行,而下一级的类加载器也做同样的事情。
  4. 如果最底层类加载器仍然没有找到所需要的class文件,则抛出异常。

技术分享图片

 

应用类加载器也叫系统类加载器。

 

 

 

如何破坏双亲委派模式?

1、通过自定义一个类加载器,然后破坏双亲委托模型,最后在重写defineClass方法(在这个native方法里面也有检查限制),绕过Java语言的各种限制,是可以达到目标的,但其实这里面存在很大安全隐患的,对于java开头的包里面的基础数据类型是没有任何理由去破坏的,这种行为属于破坏双亲委托模型的最顶级行为

 

 

2、最左边的类也就是最底层的类,可以访问到顶层的类加载的类,但是反过来却不行,但在实际开发情况下,可能会遇到,顶级的加载器需要回调低级加载器加载的实现类。为了克服这个问题,双亲委派模型中又引入了ThreadContextClassLoader,可以通过Thread的setContextClassLoader和getContextClassLoader获取底层的加载器,从而通过底层加载器来加载该类来避免这个问题。

举个常见的例子: Java里面的SPI机制,或者java.sql的驱动实例化的例子,他们的核心接口都是由Java的引导类加载器加载的,但是他们的实现却是各个厂商提供的或者根据约定设置的,这种情况下引导类加载器是看不到底层加载器的(classpath)的类的,所以只能通过底层加载器本身来加载,这个时候相当于顶层加载器需要使用底层加载器加载的类,从而间接的破坏了双亲委托模型,相当于走了后门。

 

3、另外一种破坏双亲委托模型的例子是热加载模式,为了解决不停机或者不停服务更新应用,典型的应用场景是在OSGI里面,默认情况下对于已经加载的类双亲委派模型是不会重新再加载的,但这样就意味着更新了不会被及时感知,如果需要做到动态更新,那么对于已经加载的类也必须再次进行加载,并且要处理好旧实例与新实例状态数据拷贝问题,这种模式也是破坏了双亲委派机制。

 

Java类加载机制

原文:https://www.cnblogs.com/cyx0721/p/14782516.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!