zoukankan      html  css  js  c++  java
  • 你知道你的类是从什么地方加载来的吗?

     

    你知道我们平常使用的JAVA类是怎么来的吗?

    1. 类加载开篇

    1.在java代码中,类的加载、连接与初始化过程都是在程序运行期间完成的;

    2.提供了更大的灵活性,增加了更多的可能性;

    3.类加载器是沙箱的第一道防线,保护代码不被恶意干扰,保护已验证的类库,代码放入有不同行为的保护域。

    2. 类加载过程

    1.加载:查找并加载类的二进制文件;

    2.连接

    验证 : 确保被加载类的准确性(运行时环境与编译时环境是否一致;是否符合规范的class文件)

    准备:为类的静态变量分配内存,并将其初始化为默认值;

    解析:把类中的符号引用转为直接引用;

    3.初始化:为类的静态变量赋予正确的初始值;

    3. 类的使用方式

    我们对类的使用方式可以分为两种:主动使用 和 被动使用

    所有的java虚拟机实现必须在每个类或接口被java程序 首次主动使用 时才初始化他们。

    4. 类的加载

    类的加载是指将 .class 文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在内存中创建一个java.lang.Class 对象 (虚拟机规范并未指出Class对象要放在哪里)用来封装类在方法区内的数据结构;

    加载class文件的方式:

    ---从本地系统中直接加载

    ---通过网络下载.calss文件

    ---从zip,jar等归档文件中加载.class文件

    ---将java源文件动态编译为.class文件

    类加载的最终产品是位于内存中的Class对象;

    Class对象封装了类在方法区内的数据结构,并且向我们提供了访问方法区内数据结构的接口;

    5. 类的初始化

    主动使用(7种)

    ---创建类的实例

    ---访问某个类或接口的静态变量,或者对该静态变量赋值

    ---调用类的静态方法

    ---反射(如 Class.forName("com.test.ClassName"))

    ---初始化一个类的子类

    ---java虚拟机启动时被表明为启动类的类

    ---jdk1.7以后提供的动态语言支持,MethodHandle实例解析结果 REF_getstatic, REF_putstatic,REF_inoveStatic句柄对应的类没有初始化,则初始化。

    6. 类加载器

    虚拟机自带了加载器

    ---根类加载器(BootStrapClassLoader)c++实现,负责加载/lib下的类,我们常用的rt.jar就是根加载器所加载的。可以通过系统属性查看所加载的jar。

    System.getProperty("sun.boot.class.path")
    -----------------------------------------
    C:Program FilesJavajdk1.8.0_162jrelib esources.jar;
    C:Program FilesJavajdk1.8.0_162jrelib t.jar;
    C:Program FilesJavajdk1.8.0_162jrelibsunrsasign.jar;
    C:Program FilesJavajdk1.8.0_162jrelibjsse.jar;
    C:Program FilesJavajdk1.8.0_162jrelibjce.jar;
    C:Program FilesJavajdk1.8.0_162jrelibcharsets.jar;
    C:Program FilesJavajdk1.8.0_162jrelibjfr.jar;
    C:Program FilesJavajdk1.8.0_162jreclasses

    ---扩展类加载器(ExtensionClassLoader) Java实现,可以在java里获取,负责加载/lib/ext下的类。可以通过系统属性获取信息。


    System.getProperty("java.ext.dirs")
    -----------------------------------------
    C:Program FilesJavajdk1.8.0_162jrelibext;
    C:windowsSunJavalibext

    ---系统(应用)类加载器(AppClassLoader)java实现,可以加载classpath下的java类。可以通过系统属性获得。


    System.getProperty("java.class.path")

    用户自定义的类加载器

    ---java.lang.ClassLoader的子类

    ---用户可以指定类的加载方式

    Spring中的类加载器

    tomcat中的类加载器

    获取ClassLoader的途径

    ---clazz.getClassLoader();

    ---Thread.currentThread.getContxtClassLoader();//线程上下文类加载器

    ---ClassLoader.getSystemClassLoader();

    ---DriverManager.getCallerClassLoader();

    类加载器的命名空间

    ---每个类加载器都有自己的命名空间,命名空间由该类加载器及所有父加载器所加载的类组成。

    ---在同一个命名空间中,不会出现类的完整名字相同的两个类

    ---在不同的命名空间中,有可能出现类的完整名字相同的两个类。

    7. 双亲委派以及破坏双亲委派

    用户自定义

    用户自定义

    启动类加载器 bootstrap classloader

    扩展类加载器 extension classloader

    应用类加载器 APP classloader

    自定义classloader

    loader1

    loader2



    ---根加载器/启动类加载器在 java程序中用 null表示。

    双亲委派机制

    在绝大多数场景下,类的加载都遵循双亲委派机制,当一个类将要被加载的时候,当前类的加载器总要委派父加载器去加载,父加载器加载不到了,才会由自己去加载。在ClassLoader中的loadClass方法中可以看到。


    if (parent != null) {
       c = parent.loadClass(name, false);
    } else {
       c = findBootstrapClassOrNull(name);
    }

    在一般的自定义类加载器中,jvm建议大家,去重写findClass方法,而非直接重写loadClass方法,等于我们的自定义类加载器在一般情况下也要遵守双亲委派机制。

    破坏双亲委派

    我们在自定义ClassLoader时,如果重写他的loadClass方法,就会对他原先的双亲委派机制进行破坏,我们可以通过自己扩展的方式去加载这个二进制流产生Class对象。

    对于rt.jar已经加载过的类,他的实现又是第三方厂商实现的类,要怎么加载呢?这就是我们经常接触到的javaSPI机制(Service Provider Interface),Thread.currentThread().currentClassLoader()就是jdk给自己开的后门,对于这些个场景,会破坏自己的双亲委派机制,进行用线程上下文加载器进行加载。


    例如java.sql.Driver,位于rt.jar下,由bootStrap加载器加载,就是一个典型的spi,会有不同的厂商去实现这个接口,而我们所引用的第三方实现包是位于classpath下的,当我们要加载时,使用双亲委派就不会起到作用了。所以他是通过线程上下文加载器去加载的。

    ServiceClassLoader是现在对spi机制的一个通用的处理。他会找到所有classpath下"META-INF/services/"对将要加载的接口的全类路径名文件的查找,找到后将文件中的实现类通过线程上下文加载器全部加载。


    // 规定前缀 service为要加载的class名字 loader为上下文加载器
    private static final String PREFIX = "META-INF/services/";
    
    String fullName = PREFIX + service.getName();
    
    configs = loader.getResources(fullName);
    
    c = Class.forName(cn, false, loader);
     

    spi机制的扩展,springboot中的自动装配机制,我们会自定义starter也会通过扫描的方式,被springFactoryLoader加载到bean容器中。这种思想与jdk有异曲同工之妙。

    8. 类加载器命名空间的应用

    ---类之间的可见性问题

    由不同类加载器加载的同一个类所产生的对象,相互是不可见的,我们用下面的例子来印证一下:


    ClassLoader loader1 = new MyClassLoader();
    ClassLoader loader2 = new MyClassLoader();

    Class clazz1 = loader1.loadClass("com.test.User");
    Class clazz2 = loader2.loadClass("com.test.User");
    User user1 = clazz1.newInstance();
    Object user2 = clazz2.newInstance();

    user1.setUser(user2);

    由于user对象被不同的类加载器所加载,loader1和loader不在同一个命名空间,所以,两次加载出来的user对象之间是不可见的。所以这两个对象在发生强制类型转换的时候,会抛出异常。

    ---类与类之间的隔离性

    我们所熟知的tomcat中,我们可以再tomcat中部署多个web应用,那么如果部署的多个应用中有类冲突,jar版本不一致等问题,tomcat又是怎么做到类与类的隔离的,这就是上述的类加载器的命名空间起到的作用。具体的tomcat类加载器实现在上下文的帮助下,也比较好理解,有兴趣的可以自行深入。

    9. 先有类加载器还是先有加载类加载器的类

    说到这里我们可能会有些疑惑了,既然我们的类是由类加载器所加载得来的,那么我们的类加载器又是被谁加载的呢。说到这里,上文中的boostrapClassLoader在我们java程序中为什么是null的原因就可以得到解释了,boostrapClassLoader是由c++编写,所以在程序启动中,会自动加载,而bootstrapClassloader又可以加载rt.jar。在rt.jar中会有一个比较关键的类会被他加载,如下是com.misc.Launcher类反编译的源码

    public class Launcher {
     private static URLStreamHandlerFactory factory = new Factory((1)null);
     private static Launcher launcher = new Launcher();
     private static String bootClassPath = System.getProperty("sun.boot.class.path");
     private ClassLoader loader;
     private static URLStreamHandler fileHandler;
     
      public Launcher() {
      ExtClassLoader var1;
      try {
       var1 = ExtClassLoader.getExtClassLoader();
      } catch (IOException var10) {
       throw new InternalError("Could not create extension class loader", var10);
      }
    
      try {
       this.loader = AppClassLoader.getAppClassLoader(var1);
      } catch (IOException var9) {
       throw new InternalError("Could not create application class loader", var9);
      }
    
      Thread.currentThread().setContextClassLoader(this.loader);
      String var2 = System.getProperty("java.security.manager");
      if (var2 != null) {
       SecurityManager var3 = null;
       if (!"".equals(var2) && !"default".equals(var2)) {
        try {
         var3 = (SecurityManager) this.loader.loadClass(var2).newInstance();
        } catch (IllegalAccessException var5) {
         ;
        } catch (InstantiationException var6) {
         ;
        } catch (ClassNotFoundException var7) {
         ;
        } catch (ClassCastException var8) {
         ;
        }
       } else {
        var3 = new SecurityManager();
       }
    
       if (var3 == null) {
        throw new InternalError("Could not create SecurityManager: " + var2);
       }
    
       System.setSecurityManager(var3);
      }
    
     }
     }

    launcher在初始化的时候,会加载ExtClassLoader和AppClassLoader,而且在加载AppClassLoader,会把上下文类加载器设置为AppClassLoader。所有先有类加载器和先有加载类加载器的问题在这里就得到了解释。

    看到这里,一个类的前世今生想必大家心里都有一些深刻的过程了。

    10. 一些关于类加载的参数

    ---想看到程序启动都加载了哪些类:-XX:+TraceClassLoading

    ---想看到程序执行会卸载哪些类 :-XX:+TraceClassUnloading

     

  • 相关阅读:
    一些特殊的矩阵快速幂 hdu5950 hdu3369 hdu 3483
    HDU
    UVA-796 Critical Links 找桥
    HDU 4612 Warm up
    2017 ICPC乌鲁木齐 A Coins 概率dp
    HDU6223 Infinite Fraction Path bfs+剪枝
    Java基础知识学习(一)
    算法(一)
    面试题整理:SQL(二)
    面试题整理:SQL(一)
  • 原文地址:https://www.cnblogs.com/leaveast/p/11724688.html
Copyright © 2011-2022 走看看