JVM详解(二)-- 第2章 类加载器子系统 (2)

类加载分为启动类加载器、扩展类加载器、应用程序类加载器(系统类加载器)、自定义加载器。如下图:

Alt

需要注意的是,它们四者并非子父类的继承关系。以下展示了如何获取类加载器:

public class ClassLoaderTest { public static void main(String[] args) { //获取系统类加载器 ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader(); System.out.println(systemClassLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2 //获取其上层:扩展类加载器 ClassLoader extClassLoader = systemClassLoader.getParent(); System.out.println(extClassLoader);//sun.misc.Launcher$ExtClassLoader@1540e19d //获取其上层:获取不到引导类加载器 ClassLoader bootstrapClassLoader = extClassLoader.getParent(); System.out.println(bootstrapClassLoader);//null //对于用户自定义类来说:默认使用系统类加载器进行加载 ClassLoader classLoader = ClassLoaderTest.class.getClassLoader(); System.out.println(classLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2 //String类使用引导类加载器进行加载的。---> Java的核心类库都是使用引导类加载器进行加载的。 ClassLoader classLoader1 = String.class.getClassLoader(); System.out.println(classLoader1);//null } }

关于几种类加载器具体的加载对象和双亲委派机制可以参考:玩命学JVM-类加载机制

4.1 用户自定义类加载器 4.1.1 为什么要自定义类加载器

隔离加载类。

修改类加载的方式。除了bootstrap classloader是必须要用到的,其它的类加载器都不必须。

扩展加载源。

防止源码泄露。java代码很容易被反编译和篡改。因此有对源码进行加密的需求,在这个过程中就会用到自己定义的类加载器去完成解密和类加载。

4.1.2 如何自定义类加载器

步骤

继承java.lang.ClassLoader的方式,实现自己的类加载器。

建议不要去覆盖loadClass(),而是重写findClass()。

如果没有太复杂的需求(解密、从不同的路径下加载),那么可直接继承URLClassLoader,这样可以避免自己去编写findClass()方法以及其获取字节码流的方式,使其自定义类加载器编写更加简洁。
样例代码

public class CustomClassLoader extends ClassLoader { @Override protected Class<?> findClass(String name) throws ClassNotFoundException { try { byte[] result = getClassFromCustomPath(name); if(result == null){ throw new FileNotFoundException(); }else{ return defineClass(name,result,0,result.length); } } catch (FileNotFoundException e) { e.printStackTrace(); } throw new ClassNotFoundException(name); } private byte[] getClassFromCustomPath(String name){ //从自定义路径中加载指定类:细节略 //如果指定路径的字节码文件进行了加密,则需要在此方法中进行解密操作。 return null; } public static void main(String[] args) { CustomClassLoader customClassLoader = new CustomClassLoader(); try { Class<?> clazz = Class.forName("One",true,customClassLoader); Object obj = clazz.newInstance(); System.out.println(obj.getClass().getClassLoader()); } catch (Exception e) { e.printStackTrace(); } } } 4.2 关于ClassLoader

它是一个抽象类,其后所有的类加载器都继承自ClassLoader(不包括启动类加载器)。
API

getParent()
返回该类加载器的超类加载器。

loadClass(String name)
加载名称为name的类,返回结果为java.lang.Class类的实例。

findClass(String name)
查找名称为name的类,返回结果为java.lang.Class类的实例。

findLoaderClass(String name)
查找名称为name的已经被加载过的类,返回结果为java.lang.Class类的实例。

defineClass(String name, byte[] b, int off, int len)
把字节数组b中的内容转换为一个Java类,返回结果为java.lang.Class类的实例。与 findClass(String name) 搭配使用

resolveClass(Class<?>c)
连接一个指定的Java类

4.3 双亲委派机制

详见 https://www.cnblogs.com/cleverziv/p/13759175.html
自定义的一个java.lang.String不能加载到的JVM中,原因:
使用自定义的java.lang.String时,首先是应用类加载器向上委托到扩展类加载器,然后扩展类加载器向上委托给引导类加载器,引导类加载接收到类的信息,发现该类的路径时“java.lang.String”,这在引导类加载器的加载范围内,因此引导类加载器开始加载“java.lang.String”,只不过此时它加载的是jdk核心类库里的“java.lang.String”。这就是双亲委派机制中的向上委托。在完成向上委托之后,如到了引导类加载器,引导类加载器发现待加载的类不属于自己加载的类范围,就会再向下委托给扩展类加载器,让下面的加载器进行类的加载。
优势

避免类的重复加载。类加载器+类本身决定了 JVM 中的类加载,双亲委派机制保证了只会有一个类加载器去加载类。

保护程序安全,防止核心api被篡改

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpxdsx.html