JVM——类加载器总结

JVM类加载器

类加载器是什么

类加载阶段中的 “通过一个类的全限定名来获取描述此类的二进制字节流” 这个动作在JVM外部实现,有利于让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块就称为“类加载器”。

类加载器的作用

1.实现类的加载过程中加载这个动作

2.确保这个被加载的类在JVM中的唯一性

注意:只有同一个类被同一个类加载器加载,这两个被加载的类才相等,也就是只有这样它们的equals,isAssignableFrom和isInstance方法返回的结果才为true。

类加载器的分类

  • JVM的角度

    • 启动类加载器,使用C++实现,是虚拟机的一部分
    • 其他的类加载器,由Java实现,在虚拟机外部,全都继承自抽象类java.lang.ClassLoader
  • java程序猿角度

    • 启动(引导)类加载器,是最顶层的类加载器,java无法直接引用此类加载器。负责加载<JAVA_HOME>\lib目录下的,或-Xbootclasspath参数指定的路径中的并且是虚拟机识别的类库。

      注意:在编写自定义类加载器时,若需把加载请求委派给引导类加载器,直接使用null代替即可,如下java.lang.ClassLoader.getClassLoader()方法所示:

      public ClassLoader getClassLoader() { 
          ClassLoader cl = getClassLoader0(); 
          if (cl == null) 
              return null; 
          SecurityManager sm = System.getSecurityManager(); 
          if (sm != null) { 
              ClassLoader.checkClassLoaderPermission(cl, Reflection.getCallerClass()); 
          } 
          return cl; 
      }
      

      这里也侧面说明:当通过getClassLoader方法来获取一个类的类加载器时,如果它返回的是null,表示加载它的类加载器是启动类加载器,而不是没有类加载器来加载它。

    • 扩展类加载器,主要负责加载目录 %JRE_HOME%/lib/ext 目录下的jar包和类,或被 java.ext.dirs 系统变量所指定的路径下的所有类库。开发者可以直接使用扩展类加载器。

    • 应用程序(系统)类加载器,负责加载当前用户路径classpath下指定的类库。如果程序中没有自定义类加载器,通常默认使用这个类加载器。

双亲委派模型

1.双亲委派模型的实现

实现双亲委派的代码都集中在java.lang.ClassLoader的loadClass方法中,如下所示:

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{
    	synchronized (getClassLoadingLock(name)) {
        Class<?> c = findLoadedClass(name);	  //先检查这个类是否已经被加载过
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                if (parent != null) {    //如果父加载器不为空,先尝试调用父类加载器加载
                    c = parent.loadClass(name, false);
                } else {       //否则使用启动类加载器加载这个类
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
               
            }
            
            if (c == null) {
                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;
    }
}

将上面的代码转化为下面具体的图片更容易理解,左边的文字应该改为:向上询问父类是否类已经加载,右边的文字改为:向下尝试让子类加载类

双亲委派模型

2.双亲委派模型的优点

  • Java类随着它的类加载器一起具备了一种带优先级的层次关系
  • 可以保证java程序稳定运行,防止重复加载一个类。

例如,无论哪一个类加载器要加载 java.lang.Object这个类,最终需委派给模型顶端的启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。若没有使用双亲委派模型,由各个类加载器自行去加载,用户编写了一个java.lang.Object的类放在ClassPath中,那系统中将出现多个不同的Object类,Java体系中最基础的行为就无法保证,应用程序将变得一片混乱。

自定义类加载器

自定义类加载器主要是通过继承自ClassLoader类来实现的,JDK1.2后推荐重写findClass方法而不是loadClass方法,因为这样可以保证双亲委派模型不被破坏。

public class MyClassLoaderTest {
    public static void main(String[] args) throws Exception {
        MyClassLoader myClassLoader = new MyClassLoader();
        Class<?> clazz = myClassLoader.loadClass("practice.leetcode.offer.TestClass");  //这里还是调用loadClass方法,传入全类名作为参数
        TestClass testClass = (TestClass) clazz.newInstance();
        testClass.sayHello();
    }
}

class MyClassLoader extends ClassLoader {
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        String className = null;
        //下面是测试类编译后的字节码文件存储路径
        String path = "C:\\Users\\腾腾娃发光的板砖\\Desktop\\java\\out\\production\\java\\";
        byte[] arr = new byte[0];
        try {
            name = name.replace(".", "//");
            FileInputStream is = new FileInputStream(new File(path + name + ".class"));
            arr = new byte[is.available()];
            is.read(arr);
            is.close();
        } catch (Exception e) {
            e.printStackTrace();
        }

        defineClass(className, arr, 0, arr.length);
        return null;
    }
}

//测试类
public class TestClass {
    public void sayHello() {
        System.out.println("你好,自定义类加载器~");
    }
}

//测试结果:
你好,自定义类加载器~

参考资料

《深度理解java虚拟机》

Java虚拟机学习笔记(五)——委派模型、ClassLoader

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值