jvm如何打破双亲委托机制

x33g5p2x  于2021-12-18 转载在 其他  
字(5.3k)|赞(0)|评价(0)|浏览(564)

打破双亲委托机制

重写父类ClassLoader的loadClass方法

  1. package com.morris.jvm.classloader;
  2. public class BreakDelegateClassLoader extends MyClassLoader {
  3. @Override
  4. protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
  5. // 根据类的全限定名进行加锁,确保一个类在多线程下只被加载一次
  6. synchronized (getClassLoadingLock(name)) {
  7. // 到已加载类的缓存中查看是否已经加载过,如果已加载则直接返回
  8. Class<?> c = findLoadedClass(name);
  9. if (null == c) {
  10. // 尝试使用自定义类加载器加载
  11. c = this.findClass(name);
  12. }
  13. if (resolve) {
  14. resolveClass(c);
  15. }
  16. return c;
  17. }
  18. }
  19. }

是否可以加载自定义java.lang.String类

当我们尝试加载自定义的java.lang.String类时,会抛出如下异常:

  1. Exception in thread "main" java.lang.SecurityException: Prohibited package name: java.lang
  2. at java.lang.ClassLoader.preDefineClass(ClassLoader.java:662)
  3. at java.lang.ClassLoader.defineClass(ClassLoader.java:761)
  4. at java.lang.ClassLoader.defineClass(ClassLoader.java:642)
  5. at com.morris.jvm.load.MyClassLoader.findClass(MyClassLoader.java:33)
  6. at com.morris.jvm.load.BreakDelegateClassLoader.loadClass(BreakDelegateClassLoader.java:14)
  7. at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
  8. at com.morris.jvm.load.StringClassLoaderTest.main(StringClassLoaderTest.java:7)

JVM出于安全考虑,禁止使用以java开头的包名。以下为相关源码:

摘自jdk1.8 java.lang.ClassLoader

  1. protected final Class<?> defineClass(String name, byte[] b, int off, int len,
  2. ProtectionDomain protectionDomain)
  3. throws ClassFormatError
  4. {
  5. protectionDomain = preDefineClass(name, protectionDomain);
  6. String source = defineClassSourceLocation(protectionDomain);
  7. Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
  8. postDefineClass(c, protectionDomain);
  9. return c;
  10. }
  11. private ProtectionDomain preDefineClass(String name,
  12. ProtectionDomain pd)
  13. {
  14. if (!checkName(name))
  15. throw new NoClassDefFoundError("IllegalName: " + name);
  16. // Note: Checking logic in java.lang.invoke.MemberName.checkForTypeAlias
  17. // relies on the fact that spoofing is impossible if a class has a name
  18. // of the form "java.*"
  19. // 以java.开头直接抛出异常
  20. if ((name != null) && name.startsWith("java.")) {
  21. throw new SecurityException
  22. ("Prohibited package name: " +
  23. name.substring(0, name.lastIndexOf('.')));
  24. }
  25. if (pd == null) {
  26. pd = defaultDomain;
  27. }
  28. if (name != null) checkCerts(name, pd.getCodeSource());
  29. return pd;
  30. }

强制使用根加载器加载自定义java.lang.String类

从JDK中复制String.java的源码,添加hello()方法后编译,放入D:\classloader\java\lang目录,在Test方法中调用hello()方法。

  1. public class Test {
  2. public static void main(String[] args) {
  3. String str = new java.lang.String();
  4. str.hello();
  5. }
  6. }

使用-Xbootclasspath参数指定自定义java.lang.String的class文件的路径,具体如下:

  1. $ java "-Xbootclasspath/p:D:\classloader" Test
  2. hello string

在调试过程中可以添加虚拟机参数-XX:+TraceClassLoading来监控类的加载情况。

注意一定要复制JDK中的String.java类,并在其基础上添加方法,否则运行会报错,因为jvm在启动过程中会调用String类的方法,如果自定义的String类没有原生类的方法就会报错。

另外如果我们要加载自定义的java.util.HashMap(不在java.lang包下)类,可以使用endorsed技术,将自己的HashMap类,打包成一个jar包,然后放到-Djava.endorsed.dirs指定的目录中。注意类名和包名,应该和JDK自带的是一样的。

命名空间与运行时包

每个类加载器都有自己的命名空间。同一个命名空间内的类是相互可见的,命名空间由该加载器及所有父加载器所加载的类组成。

子加载器的命名空间包含所有父加载器的命名空间。因此由子加载器加载的类能看见父加载器的类。例如系统类加载器加载的类能看见根类加载器加载的类。由父亲加载器加载的类不能看见子加载器加载的类。如果两个加载器之间没有直接或间接的父子关系,那么它们各自加载类相互不可见。

在同一个命名空间中,不会出现类的完整名字(包括类的包名)相同的两个类;在不同的命名空间中,有可能会出现类的完整名字(包括类的包名)相同的两个类。

在类的加载过程中,所有参与的类加载器,即时没有亲自加载过该类,都会被标识为该类的初始类加载器,实际加载类的加载器被称为定义类加载器。

运行时包:由类加载器的命名空间和类的全限定名组成。

  1. package com.morris.jvm.classloader;
  2. // 自定义类加载器必须继承ClassLoader
  3. public class CustomerClassLoader extends ClassLoader {
  4. @Override
  5. protected Class<?> findClass(String name) throws ClassNotFoundException {
  6. return super.loadClass(name);
  7. }
  8. }
  1. package com.morris.jvm.classloader;
  2. public class CustomerClassLoaderTest {
  3. public static void main(String[] args) throws ClassNotFoundException {
  4. CustomerClassLoader myClassLoader = new CustomerClassLoader();
  5. Class<?> clazz = myClassLoader.loadClass("java.lang.String");
  6. System.out.println(clazz.getClassLoader());
  7. }
  8. }

在上面例子中,java.lang.String依次经过了CustomerClassLoader类加载器、系统类加载器、扩展类加载器、根类加载器,这些加载器都是java.lang.String的初始类加载器,而根类加载是java.lang.String的定义类加载器,JVM会在每一个类加载器维护的列表中添加该类型。如下图所示:

同一个类加载器实例加载同一个class

  1. package com.morris.jvm.classloader;
  2. public class NameSpaceTest1 {
  3. public static void main(String[] args) throws ClassNotFoundException {
  4. // 获取系统类加载器
  5. ClassLoader classLoader = NameSpaceTest1.class.getClassLoader();
  6. Class<?> aClass = classLoader.loadClass("com.morris.jvm.classloader.HelloWorld");
  7. Class<?> bClass = classLoader.loadClass("com.morris.jvm.classloader.HelloWorld");
  8. System.out.println(aClass == bClass); // true
  9. }
  10. }

使用同一个类加载器实例不论load多少次,都只会返回同一个class对象。

不同类加载器加载同一个class

  1. package com.morris.jvm.classloader;
  2. public class NameSpaceTest2 {
  3. public static void main(String[] args) throws ClassNotFoundException {
  4. ClassLoader classLoader = new MyClassLoader();
  5. ClassLoader classLoader2 = new BreakDelegateClassLoader();
  6. Class<?> aClass = classLoader.loadClass("com.morris.jvm.classloader.HelloWorld");
  7. Class<?> bClass = classLoader2.loadClass("com.morris.jvm.classloader.HelloWorld");
  8. System.out.println(aClass == bClass); // false
  9. }
  10. }

使用不同的类加载器实例加载同一个class,会在堆内存产生多个class对象。

相同类加载器加载同一个class

  1. package com.morris.jvm.classloader;
  2. public class NameSpaceTest3 {
  3. public static void main(String[] args) throws ClassNotFoundException {
  4. ClassLoader classLoader = new MyClassLoader();
  5. ClassLoader classLoader2 = new MyClassLoader();
  6. Class<?> aClass = classLoader.loadClass("com.morris.jvm.classloader.HelloWorld");
  7. System.out.println(aClass.getClassLoader());
  8. Class<?> bClass = classLoader2.loadClass("com.morris.jvm.classloader.HelloWorld");
  9. System.out.println(bClass.getClassLoader());
  10. System.out.println(aClass == bClass); // false
  11. }
  12. }

使用相同的类加载器实例加载同一个class,会在堆内存产生多个class对象。

相关文章