类加载器

Posted 走出自己的未来

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了类加载器相关的知识,希望对你有一定的参考价值。

之前(已经n年之久)介绍过类加载的基本流程,虚拟机类加载过程,现在来重点关注一下其中几个比较重要的流程节点。

本文主要介绍类加载器,了解类加载器主要有哪些、加载流程、双亲委派以及打破双亲委派

1、类加载器种类

从java虚拟机角度来分,类加载器可以分为两种:

   一种是启动类加载器,使用C++语言实现,是虚拟机自身的一部分。

   一种是所有其他的类加载器,由java语言实现,独立于虚拟机外部,并且都继承自抽象类java.lang.ClassLoader。

从java开发人员的角度分,类加载器分为三种:

   启动类加载器,主要加载<JAVA_HOME>\\lib 目录下的核心类

   扩展类加载器,由sun.misc.Launcher$ExtClassLoader实现,主要加载<JAVA_HOME>\\lib\\ext 目录下的jar包

   应用程序类加载器,由sun.misc.Launcher$AppClassLoader实现,主要加载classpath路径下的jar文件。

 可以通过如下代码查看具体加载的jar:

 static void testLoaderScope() 
        String bootLoader = System.getProperty("sun.boot.class.path");
        System.out.println(bootLoader.replace(";", System.lineSeparator()));

        System.out.println("==================");

        String extLoader = System.getProperty("java.ext.dirs");
        System.out.println(extLoader.replace(";", System.lineSeparator()));

        System.out.println("==================");
        String appLoader = System.getProperty("java.class.path");
        System.out.println(appLoader.replace(";", System.lineSeparator()));
 

2、双亲委派

前面已经介绍了类加载器的种类,那在类加载的过程中,这些又如何工作的呢? 

 上图就是双亲委派的模型,在进行类加载时,首先是从底部开始,检查类是否已经加载,如果加载则直接返回类结果;如果没有,则向上由父类进行检查。如果都没有加载过,则由Bootstrap类加器进行加载,加载不到则交给子类加载器进行加载。如果都无法加载,则会抛出ClassNotFund异常。具体流程如下:

双亲委派的设计作用在于保证类加载的安全性,这里需要注意的是,虽然说的是父子类加载器,但是类加载器之间的父子关系并不是以继承关系来实现,而是以组合关系来复用父类加载器的代码。

 

3、自定义类加载器

实现自定义类加载器,需要继承ClassLoader,然后重写findClass方法去加载自定义的类文件即可。

自定义类加载器代码如下:

package com.wpb.jvm;

import com.wpb.jvm.test.HelloJvm;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;

public class MyClassLoader extends ClassLoader 

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException 
        // 此目录为编译后的class文件路径
        File file = new File("d:/", name.replace(".", "/").concat(".class"));
        try 
            FileInputStream fis = new FileInputStream(file);

            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            int b = 0;
            while ((b = fis.read()) != 0) 
                baos.write(b);
            

            byte[] bytes = baos.toByteArray();
            baos.close();
            fis.close();

            return defineClass(name, bytes, 0 , bytes.length);

         catch (Exception e) 
            e.printStackTrace();
        

        return super.findClass(name);
    

    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException 

        ClassLoader myClassLoader = new MyClassLoader();
        Class<?> class1 = myClassLoader.loadClass("com.wpb.jvm.test.HelloJvm");
        HelloJvm helloJvm = (HelloJvm) class1.newInstance();
        helloJvm.sayHello();

    


自定义类信息如下:

package com.wpb.jvm.test;

public class HelloJvm 

    public void sayHello() 
        System.out.println("hello my custom class loader");
    


 

4、打破双亲委派

了解什么是双亲委派并且会自定义类加载器之后,我们来看下如何打破双亲委派模式,使用自己定义的加载来进行加载类文件。

自定义类加载器是重写findClass,而要想打破双亲委派,需要重写loadClass方法,在加载的时候就直接使用自己定义的方法进行加载,避免使用默认的方法,这样就不会走双亲委派模式。

自定义类加载器并重写loadClass方法:

package com.wpb.jvm;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;

public class MyClassLoader2 extends ClassLoader 

    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException 
        // 注意路径
        File file = new File("G:/IdeaPrictise/jvm/out/production/jvm/", name.replace(".", "/").concat(".class"));
        if (!file.exists()) 
            return super.loadClass(name);
        

        try 
            InputStream is = new FileInputStream(file);
            byte[] b = new byte[is.available()];
            is.read(b);
            return defineClass(name, b, 0, b.length);
         catch (Exception e) 
            e.printStackTrace();
        

        return super.loadClass(name);
    

    public static void main(String[] args) throws ClassNotFoundException 

        MyClassLoader2 classLoader1 = new MyClassLoader2();
        Class<?> class1 = classLoader1.loadClass("com.wpb.jvm.test.HelloJvm");

        classLoader1 = new MyClassLoader2();
        Class<?> class2 = classLoader1.loadClass("com.wpb.jvm.test.HelloJvm");

        System.out.println(class1 == class2);  // 应该返回false

    

加载的自定义类还是上述的HelloJvm对象。 

此时最终的结果应该输出false。

 

类加载器的介绍就到这里了,下篇会从源码的角度来介绍下加载流程,以及为啥就能做到打破双亲委派。

以上是关于类加载器的主要内容,如果未能解决你的问题,请参考以下文章

深入理解Java类加载器:Java类加载原理解析

Java内存管理-掌握虚拟机类加载器

类加载器

类加载器

类加载器的理解——基于Launcher类

从源码透彻理解JVM类加载机制