Welcome everyone

java的类加载机制浅析

java 汪明鑫 928浏览 0评论

类加载的定义

类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,
然后在堆区创建一个这个类的java.lang.Class对象
类的加载的最终产品是位于堆区中的Class对象。
Class对象封装了类在方法区内的数据结构,
并且提供了访问方法区内的数据结构的接口。

 

 

每个 Class 对象的内部都有一个 classLoader 字段来标识自己是由哪个 ClassLoader 加载的。ClassLoader 就像一个容器,里面装了很多已经加载的 Class 对象。

 

加载类的方式

1)从本地系统直接加载
2)通过网络下载.class文件
3)从zip,jar等归档文件中加载.class文件
4)从专有数据库中提取.class文件
5)将Java源文件动态编译为.class文件(服务器)

 

类的加载过程

加载时类加载过程的第一个阶段,在加载阶段,虚拟机需要完成以下三件事情:

1、通过一个类的全限定名来获取其定义的二进制字节流。

2、将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。

3、在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口。

 

类的全限定名,比如 java.lang.Object
相对于类加载的其他阶段而言,加载阶段(准确地说,是加载阶段获取类的二进制字节流的动作)是可控性最强的阶段,因为开发人员既可以使用系统提供的类加载器来完成加载,也可以自定义自己的类加载器
– 加载阶段完成后,虚拟机外部的 二进制字节流就按照虚拟机所需的格式存储在方法区之中,而且在Java堆中也创建一个java.lang.Class类的对象,这样便可以通过该对象访问方法区中的这些数据。载器来完成加载。

 

类加载器

JVM 运行实例中会存在多个 ClassLoader,不同的 ClassLoader 会从不同的地方加载字节码文件。它可以从不同的文件目录加载,也可以从不同的 jar 文件中加载,也可以从网络上不同的静态文件服务器来下载字节码再加载。

JVM 中内置了三个重要的 ClassLoader,分别是 BootstrapClassLoader、ExtensionClassLoader 和 AppClassLoader。

 

  • BootstrapClassLoader

启动类加载器主要加载的是JVM自身需要的类,这个类加载使用C++语言实现的,是虚拟机自身的一部分,它负责将 <JAVA_HOME>/lib路径下的核心类库加载到内存中

 

  • ExtensionClassLoader

负责加载<JAVA_HOME>/lib/ext目录下的类库,开发者可以直接使用标准扩展类加载器。

 

  • AppClassLoader

负责加载用户类路径(CLASSPATH)下的类库,一般我们编写的java类都是由这个类加载器加载,这个类加载器是CLassLoader中的getSystemClassLoader()方法的返回值,所以也称为系统类加载器.一般情况下这就是系统默认的类加载器。

 

 

 

双亲委派机制

AppClassLoader 在加载一个未知的类名时,它并不是立即去搜寻 Classpath,它会首先将这个类名称交给 ExtensionClassLoader 来加载,如果 ExtensionClassLoader 可以加载,那么 AppClassLoader 就不用麻烦了。否则它就会搜索 Classpath 。

而 ExtensionClassLoader 在加载一个未知的类名时,它也并不是立即搜寻 ext 路径,它会首先将类名称交给 BootstrapClassLoader 来加载,如果 BootstrapClassLoader 可以加载,那么 ExtensionClassLoader 也就不用麻烦了。否则它就会搜索 ext 路径下的 jar 包。

 

上面的三个 ClassLoader 之间形成了级联的父子关系,每个 ClassLoader 都很懒,尽量把工作交给父亲做,父亲干不了了自己才会干。每个 ClassLoader 对象内部都会有一个 parent 属性指向它的父加载器。

 

 

看下双亲委派在源码中的体现:
protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 检查class是不是已经被加载了
            Class<?> c = findLoadedClass(name);
            if (c == null) {    //如果还没被加载 
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {   //父亲不是null,则委托父亲去加载
                        c = parent.loadClass(name, false);
                    } else {    //父亲是null,其实是让BootstrapClassLoader(根加载器)加载 
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {    //父亲们不给力,都找不到,那就我自己来找把
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);   //找起来 。。。 

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

 

为什么需要双亲委托机制?

为了保证 JDK 核心类库都是由 bootstrap 加载器加载。
那为什么需要保证 JDK 核心类库由 bootstrap 加载器加载呢?其实这主要是为了保证 JDK 核心类库都是由同一个加载器加载?那为什么要保证 JDK 核心类库都需要由同一个加载器加载呢?这是为了保证核心类库的类在 JVM 中只有一个对应实体。也就是为了保证类型关系的正确性。
ClassLoader 的意义,它相当于类的命名空间,起到了类隔离的作用。位于同一个 ClassLoader 里面的类名是唯一的,不同的 ClassLoader 可以持有同名的类。ClassLoader 是类名称的容器,是类的沙箱。

不同的 ClassLoader 之间也会有合作,它们之间的合作是通过 parent 属性和双亲委派机制来完成的。parent 具有更高的加载优先级。除此之外,parent 还表达了一种共享关系,当多个子 ClassLoader 共享同一个 parent 时,那么这个 parent 里面包含的类可以认为是所有子 ClassLoader 共享的。这也是为什么 BootstrapClassLoader 被所有的类加载器视为祖先加载器,JVM 核心类库自然应该被共享。

 

小结 :双亲委托机制的核心思想分为两个步骤。其一,自底向上检查类是否已经加载;其二,自顶向下尝试加载类。

 

例子

/**
 * @author: wangmingxin02
 * @create: 2019-01-24
 **/
public class ClassLoaderTest {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {

      //自定义类加载器
      ClassLoader myclassloader = new ClassLoader() {
          @Override
          public Class<?> loadClass(String name) throws ClassNotFoundException {

              try {
                  String fileaName = name.substring(name.lastIndexOf(".")+1)+".class";
                  InputStream is = getClass().getResourceAsStream(fileaName);
                  if(is==null){
                      return super.loadClass(name);
                  }
                  byte[] bytes = new byte[is.available()];
                  is.read(bytes);
                  return defineClass(name,bytes,0,bytes.length);
              } catch (IOException e) {
                  e.printStackTrace();
              }

              return null;
          }
      };

      Object obj = myclassloader.loadClass("pers.wmx.ClassLoaderTest").newInstance();
      
      System.out.println(obj.getClass());
      System.out.println(obj.getClass().getClassLoader());
      System.out.println(obj instanceof ClassLoaderTest);


    }
}

运行结果:

明明加载的也是是ClassLoaderTest,为什么返回false

原因:虚拟机中有两个ClassLoaderTest类,一个是由系统应用程序类加载器加载的
另外一个是由我们自定义的类加载器加载的,是两个独立的类,因此返回false
类加载器就好比命名空间,不同的类加载器加载的类可以同名。

 

在上面的例子中再加几行代码:
System.out.println(obj.getClass().getClassLoader());
System.out.println(ClassLoaderTest.class.getClassLoader());
System.out.println(DNSNameService.class.getClassLoader());
System.out.println(DNSNameService.class.getClassLoader().getParent());

 

运行结果:

DNSNameService 是ExtClassLoader加载的一个类

null其实就是根加载器 BootstrapClassLoader

 

//可以通过以下代码得到Bootstrap ClassLoader具体的加载路径
        URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();
        for (int i = 0; i < urls.length; i++) {
            System.out.println(urls[i].toExternalForm());
        }

转载请注明:汪明鑫的个人博客 » java的类加载机制浅析

喜欢 (0)

说点什么

您将是第一位评论人!

提醒
avatar
wpDiscuz