diff --git a/_config.yml b/_config.yml index 39e1c17..070883e 100644 --- a/_config.yml +++ b/_config.yml @@ -74,6 +74,7 @@ category_map: NAS基础: nasbase NAS服务: nasservice 技术随笔: other + Java技术: java tag_map: Linux基础: linux 网络: network diff --git a/source/_posts/other/java中的类加载器.md b/source/_posts/other/java中的类加载器.md new file mode 100644 index 0000000..1825716 --- /dev/null +++ b/source/_posts/other/java中的类加载器.md @@ -0,0 +1,138 @@ +--- +title: java中的类加载器 +tags: Java技术 +categories: 技术随笔 +abbrlink: 3639892104 +date: 2024-03-06 16:22:05 +keywords: +description: +cover: +--- +> java中的类加载器主要有三种: +> 引导类加载器 +> 扩展类加载器 +> 应用类加载器 + +# 通过代码了解类加载器 + +``` +public class TestJDKClassLoader { + public static void main(String[] args) { + //打印类加载器 + System.out.println(String.class.getClassLoader()); //不是java对象,打印null + System.out.println(DESKeyFactory.class.getClassLoader()); + System.out.println(TestJDKClassLoader.class.getClassLoader()); + //类加载器之间的关系 + System.out.println(); + ClassLoader appClassLoader = ClassLoader.getSystemClassLoader(); + ClassLoader extClassLoader = appClassLoader.getParent(); + ClassLoader bootstrapLoader = extClassLoader.getParent(); + System.out.println(appClassLoader); + System.out.println(extClassLoader); + System.out.println(bootstrapLoader); + + System.out.println(); + System.out.println("bootstrapLoader加载以下文件:"); + URL[] urLs = Launcher.getBootstrapClassPath().getURLs(); + for (URL url : urLs) { + System.out.println(url); + } + + System.out.println(); + System.out.println("extClassLoader加载以下文件:"); + System.out.println(System.getProperty("java.ext.dirs")); + + System.out.println(); + System.out.println("appClassLoader加载以下文件"); + System.out.println(System.getProperty("java.class.path")); + + } +} +``` + +# 双亲委派机制 + +JVM在进行类加载时有个双亲委派机制,当它需要去加载类时,自己先不去加载,而是委托给父加载器加载,父加载器又直接委派它的父加载器,直到找到引导类加载器时,他才会去对类进行加载,这时候如果加载不到,也就是在它所管辖的范围内找不到这个类,就会进行委托的退回,说,啊,我不行,还是你自己来吧,这样一直退回到能加载这个类的时候,才会对类进行加载。 +总结起来就是所有类都会优先使用最顶层的类加载器去加载,从而保证了系统的安全性和类加载的唯一性。 +比如我是一个黑客,在用户的系统里写了一个String类,这个类里面做了一些危害系统的代码,这时候双亲委派机制就能有效的避过这个类,他是应用类加载器要加载String时,会委派给上层,上层在委派给上上层,一直到引导类加载器,引导类加载器会直接使用Java自带的String类。 + +# 全盘负责委托机制 + +“全盘负责”是指当一个ClassLoder装载一个类时,除非显式的使用另外一个ClassLoder,否则该类所依赖及引用的类也由这个ClassLoder载入 + +# 自定义类加载器 + +自定义类加载器只需要继承`java.lang.ClassLoader`类,该类有两个核心方法,一个是`loadClass(String, boolean)`,实现了双亲委派机制,还有一个方法是`findClass`,默认实现是空方法,所以我们自定义类加载器主要是重写`findClass`方法. +打破双亲委派机制主要就是通过重写`loadClass`方法,去掉它的递归委派的代码。 + +测试代码 + +``` +public class MyClassLoaderTest { + static class MyClassLoader extends ClassLoader { + private String classPath; + public MyClassLoader(String classPath) { + this.classPath = classPath; + } + private byte[] loadByte(String name) throws Exception { + name = name.replaceAll("\\.", "/"); + FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class"); + int len = fis.available(); + byte[] data = new byte[len]; + fis.read(data); + fis.close(); + return data; + } + protected Class findClass(String name) throws ClassNotFoundException { + try { + byte[] data = loadByte(name); + // defineClass 将一个字节数组转为 Class 对象, + // 这个字节数组是 class 文件读取后最终的字节数组 + return defineClass(name, data, 0, data.length); + } catch (Exception e) { + e.printStackTrace(); + throw new ClassNotFoundException(); + } + } + //双亲委派机制 + //沙箱安全机制:自己写的java.lang.String.class类不会被加载,这样可以防止核心API库被随意篡改 + //避免类的重复加载:当父亲已经加载了该类时,就没必要子classloader再加载一次,保证被加载类的唯一性。 + //下面通过重写loadClass来打破双亲委派机制 + protected Class loadClass(String name, boolean resolve) + throws ClassNotFoundException { + synchronized (getClassLoadingLock(name)) { + // First, check if the class has already been loaded + Class c = findLoadedClass(name); + if (c == null) { + if (!name.startsWith("com.locaris")) { + c = this.getParent().loadClass(name); + } else { + c = findClass(name); + } + sun.misc.PerfCounter.getFindClasses().increment(); + } + if (resolve) { + resolveClass(c); + } + return c; + } + + } + } + public static void main(String args[]) throws Exception { + // 初始化自定义类加载器,会初始化父类 ClassLoader + // 其中会把自定义类加载器的父类加载器,设置为应用程序类加载器 AppClassLoader + MyClassLoader classLoader = new MyClassLoader("D:/test"); + // D 盘创建 test/com/swordsman/jvm 几级目录,将 User 类的复制类 User1 丢入该目录 + Class clazz = classLoader.loadClass("com.youren.jvm.User"); + Object obj = clazz.newInstance(); + Method method = clazz.getDeclaredMethod("sout", null); + method.invoke(obj, null); + + // 这里因为双亲委派机制的原因, + // 如果 User1 也在 classpath 下,类加载器就是 AppClassLoader + // 只在 D:/test 下就是自定义加载器 + System.out.println(clazz.getClassLoader().getClass().getName()); + } +} +```