• java基础-类加载


    类加载

    java的类加载过程分为加载、验证、准备、解析、初始化等阶段,加载、验证、准备、初始化的开始顺序是一定的,解析则可能会出现在初始化之后、使用过程中。

    加载

    类加载的第一个阶段就是加载类字节流,通过加载,jvm运行时数据区的方法区内就会产生该类相应的数据结构,并在heap中创建一个class对象来代表该类的数据结构,我们可以通过class对象访问方法区的类数据结构,通过反射对类进行操作。

    验证

    在加载的同时,就可以开始验证了,所谓验证,就是验证类的字节码是否符合java规范。编译为class文件时其实就包含了验证过程,但由于加载阶段加载的字节流未必是通过合法编译产生的class文件,所以jvm有责任对加载内容进行验证。当然,如果确认加载内容合法,可以选择关闭类加载的验证过程。只有通过了验证的字节序列,才会在方法区创建其对应的数据结构,之后的所有操作,都是针对该数据结构了。

    准备

    为加载、验证阶段在方法区生成的数据结构的类字段分配空间、赋零值。

    例外,对于被赋值字面量static final字段来说,准备阶段就可以确定其真实值了,这也是在初始化之前唯一执行用户逻辑的地方。

    解析

    解析就是将常量池内的符号引用替换为直接引用,比如运行下面程序最终会打印加载Inner的信息,这是因为类字段inner在解析时会引发Inner的类加载,如果注释掉inner字段,则不会打印。注意,解析的目的只是将符号引用变为直接引用,即将符号引用变为内存中的真实地址(实际是逻辑地址),而一个类的字节序列变为方法区的数据结构只需要经过加载、验证即可,所以,此处的Inner类可能只经过了加载、验证。

    1 public class Outer{
    2     private static Inner inner;
    3     private static class Inner{
    4     }
    5     //-XX:+TraceClassLoading
    6     public static void main(String[] args){
    7         System.out.println("main");
    8     }
    9 }

    解析可能发生在初始化之后,比如将Inner inner变成Object obj = new Inner()。

    解析阶段还可能发生在使用时,方法执行时都会将方法内未解析的符号引用转换为直接引用,比如执行下面程序,会打印Inner的加载信息。与前例不同的在于,new Inner()不但会引发加载、验证,还会引发解析、初始化、对象实例化等操作。如果此处只声明了Inner类型变量,而不使用Inner(所谓使用类,比如new、使用类字段、类方法等),则不会触发加载。

    1 public class Outer{
    2     private static class Inner{
    3     }
    4     //-XX:+TraceClassLoading
    5     public static void main(String[] args){
    6         Inner outer = new Inner();
    7     }
    8 }

    初始化

    注意,这里是类的初始化,要与实例的初始化区分,类的初始化就是执行用户对类字段的赋值操作。在类字段声明时赋值,以及在static块中对类字段赋值的逻辑,在java->class的编译后都会按顺序放入类初始化方法中。比如,如果实例化Test对象,输出1,但i最终会被赋值为2。

    1 public class Test{
    2     static{
    3         i = 1;
    4         System.out.println(i);//1,注意,实例块中禁止向前引用
    5     }
    6     private static int i = 2;
    7 }

    大致会生成如下的 类初始化方法:

    1 //<clinit>方法
    2 i = 1;
    3 System.out.println(i);
    4 i = 2;

    类加载器

    类加载器执行的类加载逻辑应当符合双亲委派模型,即优先父级类加载尝试加载,父级加载不成功时,子级才去尝试加载。我们自定义类加载器时,应当重写ClassLoader的findClass方法,而不是重写loadClass方法,因为loadClass方法遵守着双亲委派规则。

    ClassLoader的loadClass方法:

     1 protected Class<?> loadClass(String name, boolean resolve)
     2   throws ClassNotFoundException
     3 {
     4   synchronized (getClassLoadingLock(name)) {
     5     // First, check if the class has already been loaded
     6     Class<?> c = findLoadedClass(name);
     7     if (c == null) {
     8       long t0 = System.nanoTime();
     9       try {
    10         if (parent != null) {
    11           c = parent.loadClass(name, false);
    12         } else {
    13           c = findBootstrapClassOrNull(name);
    14         }
    15       } catch (ClassNotFoundException e) {
    16         // ClassNotFoundException thrown if class not found
    17         // from the non-null parent class loader
    18       }
    19 
    20       if (c == null) {
    21         // If still not found, then invoke findClass in order
    22         // to find the class.
    23         long t1 = System.nanoTime();
    24         c = findClass(name);
    25 
    26         // this is the defining class loader; record the stats
    27         sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
    28         sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
    29         sun.misc.PerfCounter.getFindClasses().increment();
    30       }
    31     }
    32     if (resolve) {
    33       resolveClass(c);
    34     }
    35     return c;
    36   }
    37 }
    View Code

    当然也有破坏双亲委派模型的情况出现,比如tomcat7的类加载机制

    这里的Bootstrap包括了Ext ClassLoader,common加载的class为webapps共享,webapp加载类时,首先使用SystemClassloader(遵循双亲委派),这是为了避免用户类覆盖java基础类,然后会使用webappN进行加载,如果加载失败再尝试双亲委派加载(即使用父级common加载器加载)。先webappN在common,而common是webappN的父级类加载器,这就破坏了双亲委派模型。

    下面简单说一下tomcat加载的jar

    对于System ClassLoader来说,tomcat会忽略系统变量CLASS_PATH,System ClassLoader会加载tomcat的bin目录下的相关jar:

    common ClassLoader会加载tomcat的lib目录下的jar:

    WebappClassLoader重写了loadClass方法:

      1 //WebappClassLoader.loadClass (Tomcat 7.0)  
      2   
      3   
      4     @Override  
      5     public synchronized Class<?> loadClass(String name, boolean resolve)  
      6         throws ClassNotFoundException {  
      7   
      8         if (log.isDebugEnabled())  
      9             log.debug("loadClass(" + name + ", " + resolve + ")");  
     10         Class<?> clazz = null;  
     11   
     12 //检查当前ClassLoad是否已经停止了  
     13         // Log access to stopped classloader  
     14         if (!started) {  
     15             try {  
     16                 throw new IllegalStateException();  
     17             } catch (IllegalStateException e) {  
     18                 log.info(sm.getString("webappClassLoader.stopped", name), e);  
     19             }  
     20         }  
     21    
     22 //检查缓存1,Class是否已经被当前Class"实例"装载过  
     23        // (0) Check our previously loaded local class cache  
     24         clazz = findLoadedClass0(name);  
     25         if (clazz != null) {  
     26             if (log.isDebugEnabled())  
     27                 log.debug(" Returning class from cache");  
     28             if (resolve)  
     29                 resolveClass(clazz);  
     30             return (clazz);  
     31         }  
     32 //ClassLoader检查缓存2,Class是否已经被父类"实例"装载过  
     33         // (0.1) Check our previously loaded class cache  
     34         clazz = findLoadedClass(name);  
     35         if (clazz != null) {  
     36             if (log.isDebugEnabled())  
     37                 log.debug(" Returning class from cache");  
     38             if (resolve)  
     39                 resolveClass(clazz);  
     40             return (clazz);  
     41         }  
     42   
     43 //ClassLoader装载系统classpath下面的类, 阻止webapp覆盖了J2SE的类  
     44         // (0.2) Try loading the class with the system class loader, to prevent  
     45         // the webapp from overriding J2SE classes  
     46         try {  
     47             clazz = system.loadClass(name);  
     48             if (clazz != null) {  
     49                 if (resolve)  
     50                     resolveClass(clazz);  
     51                 return (clazz);  
     52             }  
     53         } catch (ClassNotFoundException e) {  
     54             // Ignore  
     55         }  
     56   
     57 //安全检查  
     58         // (0.5) Permission to access this class when using a SecurityManager  
     59         if (securityManager != null) {  
     60             int i = name.lastIndexOf('.');  
     61             if (i >= 0) {  
     62                 try {  
     63                     securityManager.checkPackageAccess(name.substring(0,i));  
     64                 } catch (SecurityException se) {  
     65                     String error = "Security Violation, attempt to use " +  
     66                         "Restricted Class: " + name;  
     67                     log.info(error, se);  
     68                     throw new ClassNotFoundException(error, se);  
     69                 }  
     70             }  
     71         }  
     72   
     73 //默认的返回值为false,   
     74 //delegate = false  
     75 //filter(name) 通过一个List指定哪些package里面的Class使用双亲委派模式,但是默认这个List是空  
     76 //比如包名以Java开头的,就需要使用双亲委派模式,不过这一点"部分"由 (0.2) 在上面已经实现了  
     77         boolean delegateLoad = delegate || filter(name);  
     78   
     79 //针对一些明确指定的package,使用双亲委派模式加载  
     80         // (1) Delegate to our parent if requested  
     81         if (delegateLoad) {  
     82             if (log.isDebugEnabled())  
     83                 log.debug(" Delegating to parent classloader1 " + parent);  
     84             ClassLoader loader = parent;  
     85             if (loader == null)  
     86                 loader = system;  
     87             try {  
     88                 clazz = Class.forName(name, false, loader);  
     89                 if (clazz != null) {  
     90                     if (log.isDebugEnabled())  
     91                         log.debug(" Loading class from parent");  
     92                     if (resolve)  
     93                         resolveClass(clazz);  
     94                     return (clazz);  
     95                 }  
     96             } catch (ClassNotFoundException e) {  
     97                 // Ignore  
     98                //如果(1)通过双亲委派加载发生异常, 忽略异常,  
     99             }  
    100         }  
    101   
    102        
    103         //尝试从本地加载Class( 如果(1)通过双亲委派加载发生异常, 忽略异常,也会走到这一步)  
    104         // (2) Search local repositories  
    105         if (log.isDebugEnabled())  
    106             log.debug(" Searching local repositories");  
    107         try {  
    108             clazz = findClass(name);  
    109             if (clazz != null) {  
    110                 if (log.isDebugEnabled())  
    111                     log.debug(" Loading class from local repository");  
    112                 if (resolve)  
    113                     resolveClass(clazz);  
    114                 return (clazz);  
    115             }  
    116         } catch (ClassNotFoundException e) {  
    117             // Ignore  
    118         }  
    119   
    120 //最后依旧没有成功, 忽略所有配置,再次尝试一下双亲加载模式加载  
    121         // (3) Delegate to parent unconditionally  
    122         if (!delegateLoad) {  
    123             if (log.isDebugEnabled())  
    124                 log.debug(" Delegating to parent classloader at end: " + parent);  
    125             ClassLoader loader = parent;  
    126             if (loader == null)  
    127                 loader = system;  
    128             try {  
    129                 clazz = Class.forName(name, false, loader);  
    130                 if (clazz != null) {  
    131                     if (log.isDebugEnabled())  
    132                         log.debug(" Loading class from parent");  
    133                     if (resolve)  
    134                         resolveClass(clazz);  
    135                     return (clazz);  
    136                 }  
    137             } catch (ClassNotFoundException e) {  
    138                 // Ignore  
    139             }  
    140         }  
    141   
    142 //最后如果依旧没有成功,则抛出异常  
    143         throw new ClassNotFoundException(name);  
    144   
    145     }  
    View Code

    参考:http://flyfoxs.iteye.com/blog/2114149

      https://tomcat.apache.org/tomcat-7.0-doc/class-loader-howto.html

  • 相关阅读:
    Java 多态
    HDFS读写原理
    HDFS详解
    Servlet基础
    Tomcat
    HTTP简介
    JDBC技术
    final、finally和finalize
    java 中的权限修饰符
    进程、线程、线程状态、多线程实现方法
  • 原文地址:https://www.cnblogs.com/holoyong/p/7479460.html
Copyright © 2020-2023  润新知