zoukankan      html  css  js  c++  java
  • JVM类加载

    JVM加载class文件的原理机制 

       Java中的所有类,都需要由类加载器装载到JVM中才能运行。类加载器本身也是一个类,而它的工作就是把class文件从硬盘读取到内存中。在写程序的时候,我们几乎不需要关心类的加载,因为这些都是隐式装载的,除非我们有特殊的用法,像是反射,就需要显式的加载所需要的类。

      类装载方式,有两种 
          1.隐式装载, 程序在运行过程中当碰到通过new 等方式生成对象时,隐式调用类装载器加载对应的类到jvm中,
          2.显式装载, 通过class.forname()等方法,显式加载需要的类 
        隐式加载与显式加载的区别:两者本质是一样? 

         Java类的加载是动态的,它并不会一次性将所有类全部加载后再运行,而是保证程序运行的基础类(像是基类)完全加载到jvm中,至于其他类,则在需要的时候才加载。这当然就是为了节省内存开销。

       Java的类加载器有三个,对应Java的三种类:(java中的类大致分为三种:   1.系统类   2.扩展类 3.由程序员自定义的类 )

         Bootstrap Loader  // 负责加载系统类 (指的是内置类,像是String,对应于C#中的System类和C/C++标准库中的类)
                | 
              - - ExtClassLoader   // 负责加载扩展类(就是继承类和实现类)
                              | 
                          - - AppClassLoader   // 负责加载应用类(程序员自定义的类)

     三个加载器各自完成自己的工作,但它们是如何协调工作呢?哪一个类该由哪个类加载器完成呢?为了解决这个问题,Java采用了委托模型机制。

    委托模型机制的工作原理很简单:当类加载器需要加载类的时候,先请示其Parent(即上一层加载器)在其搜索路径载入,如果找不到,才在自己的搜索路径搜索该类。这样的顺序其实就是加载器层次上自顶而下的搜索,因为加载器必须保证基础类的加载。之所以是这种机制,还有一个安全上的考虑:如果某人将一个恶意的基础类加载到jvm,委托模型机制会搜索其父类加载器,显然是不可能找到的,自然就不会将该类加载进来。

          我们可以通过这样的代码来获取类加载器:

    1
    2
    ClassLoader loader = ClassName.class.getClassLoader();
    ClassLoader ParentLoader = loader.getParent();

    注意一个很重要的问题,就是Java在逻辑上并不存在BootstrapKLoader的实体!因为它是用C++编写的,所以打印其内容将会得到null。
          

    前面是对类加载器的简单介绍,它的原理机制非常简单,就是下面几个步骤:

    1.装载:查找和导入class文件;

    2.连接:

          (1)检查:检查载入的class文件数据的正确性;

          (2)准备:为类的静态变量分配存储空间;

          (3)解析:将符号引用转换成直接引用(这一步是可选的)

    3.初始化:初始化静态变量,静态代码块。

          这样的过程在程序调用类的静态成员的时候开始执行,所以静态方法main()才会成为一般程序的入口方法。类的构造器也会引发该动作。

    加载机制:

    JVM中类的加载是由类加载器(ClassLoader)和它的子类来实现的,Java中的类加载器是一个重要的Java运行时系统组件,它负责在运行时查找和装入类文件中的类。

    由于Java的跨平台性,经过编译的Java源程序并不是一个可执行程序,而是一个或多个类文件。当Java程序需要使用某个类时,JVM会确保这个类已经被加载、连接(验证、准备和解析)和初始化

    类的加载是指把类的.class文件中的数据读入到内存中,通常是创建一个字节数组读入.class文件,然后产生与所加载类对应的Class对象。加载完成后,Class对象还不完整,所以此时的类还不可用。

    当类被加载后就进入连接阶段,这一阶段包括

    验证:为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。

    准备:为静态变量分配内存并设置默认的初始值。

    解析:将符号引用替换为直接引用。

    最后JVM对类进行初始化,包括:1)如果类存在直接的父类并且这个类还没有被初始化,那么就先初始化父类;2)如果类中存在初始化语句,就依次执行这些初始化语句。

    类的加载是由类加载器完成的,类加载器包括:启动类加载器(BootStrap)、扩展类加载器(Extension)、应用程序类加载器(Application)。      

    从Java 2(JDK 1.2)开始,类加载过程采取了双亲委派模型(PDM)。PDM更好的保证了Java平台的安全性,在该机制中,JVM自带的Bootstrap是启动类加载器,其他的加载器都有且仅有一个父类加载器。类的加载首先请求父类加载器加载,父类加载器无能为力时才由其子类加载器自行加载。JVM不会向Java程序提供对Bootstrap的引用。

    双亲委派模型:要求除了顶层的启动类加载器外,其余加载器都应当有自己的父类加载器。类加载器之间的父子关系,一般不会以继承的关系来实现,而是通过组合关系复用父加载器的代码。

      工作过程:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器完成。

    每个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,

    只有到父加载器反馈自己无法完成这个加载请求(它的搜索范围没有找到所需的类)时,子加载器才会尝试自己去加载。

      为什么要使用:Java类随着它的类加载器一起具备了一种带优先级的层次关系。

    比如java.lang.Object,它存放在rt.jar中,无论哪个类加载器要加载这个类,最终都是委派给启动类加载器进行加载,

    因此Object类在程序的各个类加载器环境中,都是同一个类。

    自己编写一个与rt.jar类库中已有类重名的java类,可以正常编译,但无法被加载运行。

     

    委托机制的意义 — 防止内存中出现多份同样的字节码 
    比如两个类A和类B都要加载System类:

    如果不用委托而是自己加载自己的,那么类A就会加载一份System字节码,然后类B又会加载一份System字节码,这样内存中就出现了两份System字节码。
    如果使用委托机制,会递归的向父类查找,也就是首选用Bootstrap尝试加载,如果找不到再向下。这里的System就能在Bootstrap中找到然后加载,如果此时类B也要加载System,也从Bootstrap开始,此时Bootstrap发现已经加载过了System那么直接返回内存中的System即可而不需要重新加载,这样内存中就只有一份System的字节码了。

     

    能不能自己写个类叫java.lang.System?

    答案:通常不可以,但可以采取另类方法达到这个需求。 
    解释:为了不让我们写System类,类加载采用委托机制,这样可以保证父类加载器优先,父类加载器能找到的类,子加载器就没有机会加载。而System类是Bootstrap加载器加载的,就算自己重写,也总是使用Java系统提供的System,自己写的System类根本没有机会得到加载。

    但是,我们可以自己定义一个类加载器来达到这个目的,为了避免双亲委托机制,这个类加载器也必须是特殊的。由于系统自带的三个类加载器都加载特定目录下的类,如果我们自己的类加载器放在一个特殊的目录,那么系统的加载器就无法加载,也就是最终还是由我们自己的加载器加载。

    参考:https://www.cnblogs.com/williamjie/p/11167920.html

  • 相关阅读:
    一款HTML5网页网络检测工具--LibreSpeed
    远程连接Linux中的Mysql数据库
    Ubuntu安装python-rrdtool模块
    Ubuntu 18.04 lxd和lxd-client导致版本过低无法apt安装
    7.JVM调优-方法区,堆,栈调优详解
    6.堆和GC
    5.java内存模型详细解析
    4.自定义类加载器实现及在tomcat中的应用
    3.代码实现自定义类加载器
    2.双亲委派机制详细解析及原理
  • 原文地址:https://www.cnblogs.com/xiao-lin-unit/p/13655789.html
Copyright © 2011-2022 走看看