首页 > 其他分享 >jvm如何打破双亲委托机制

jvm如何打破双亲委托机制

时间:2023-04-23 10:34:46浏览次数:40  
标签:lang java String 委托 name 双亲 jvm 加载


打破双亲委托机制

重写父类ClassLoader的loadClass方法

package com.morris.jvm.classloader;

public class BreakDelegateClassLoader extends MyClassLoader {

    @Override
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        // 根据类的全限定名进行加锁,确保一个类在多线程下只被加载一次
        synchronized (getClassLoadingLock(name)) {
            // 到已加载类的缓存中查看是否已经加载过,如果已加载则直接返回
            Class<?> c = findLoadedClass(name);
            if (null == c) {
                // 尝试使用自定义类加载器加载
                c = this.findClass(name);
            }

            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }
}

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

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

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

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

摘自jdk1.8 java.lang.ClassLoader

protected final Class<?> defineClass(String name, byte[] b, int off, int len,
                                         ProtectionDomain protectionDomain)
        throws ClassFormatError
    {
        protectionDomain = preDefineClass(name, protectionDomain);
        String source = defineClassSourceLocation(protectionDomain);
        Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
        postDefineClass(c, protectionDomain);
        return c;
    }
    
    private ProtectionDomain preDefineClass(String name,
                                            ProtectionDomain pd)
    {
        if (!checkName(name))
            throw new NoClassDefFoundError("IllegalName: " + name);

        // Note:  Checking logic in java.lang.invoke.MemberName.checkForTypeAlias
        // relies on the fact that spoofing is impossible if a class has a name
        // of the form "java.*"
        // 以java.开头直接抛出异常
        if ((name != null) && name.startsWith("java.")) {
            throw new SecurityException
                ("Prohibited package name: " +
                 name.substring(0, name.lastIndexOf('.')));
        }
        if (pd == null) {
            pd = defaultDomain;
        }

        if (name != null) checkCerts(name, pd.getCodeSource());

        return pd;
    }

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

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

public class Test {
    public static void main(String[] args) {
        String str = new java.lang.String();
		str.hello();
    }
}

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

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

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

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

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

命名空间与运行时包

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

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

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

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

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

package com.morris.jvm.classloader;

// 自定义类加载器必须继承ClassLoader
public class CustomerClassLoader extends ClassLoader {

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        return super.loadClass(name);
    }

}
package com.morris.jvm.classloader;

public class CustomerClassLoaderTest {

    public static void main(String[] args) throws ClassNotFoundException {
        CustomerClassLoader myClassLoader = new CustomerClassLoader();
        Class<?> clazz = myClassLoader.loadClass("java.lang.String");
        System.out.println(clazz.getClassLoader());
    }
}

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

jvm如何打破双亲委托机制_java

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

package com.morris.jvm.classloader;

public class NameSpaceTest1 {

    public static void main(String[] args) throws ClassNotFoundException {
        // 获取系统类加载器
        ClassLoader classLoader = NameSpaceTest1.class.getClassLoader();

        Class<?> aClass = classLoader.loadClass("com.morris.jvm.classloader.HelloWorld");
        Class<?> bClass = classLoader.loadClass("com.morris.jvm.classloader.HelloWorld");

        System.out.println(aClass == bClass); // true
    }
}

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

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

package com.morris.jvm.classloader;

public class NameSpaceTest2 {

    public static void main(String[] args) throws ClassNotFoundException {
        
        ClassLoader classLoader = new MyClassLoader();
        ClassLoader classLoader2 = new BreakDelegateClassLoader();

        Class<?> aClass = classLoader.loadClass("com.morris.jvm.classloader.HelloWorld");
        Class<?> bClass = classLoader2.loadClass("com.morris.jvm.classloader.HelloWorld");

        System.out.println(aClass == bClass); // false
    }
}

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

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

package com.morris.jvm.classloader;

public class NameSpaceTest3 {

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

        ClassLoader classLoader = new MyClassLoader();
        ClassLoader classLoader2 = new MyClassLoader();

        Class<?> aClass = classLoader.loadClass("com.morris.jvm.classloader.HelloWorld");
        System.out.println(aClass.getClassLoader());
        Class<?> bClass = classLoader2.loadClass("com.morris.jvm.classloader.HelloWorld");
        System.out.println(bClass.getClassLoader());

        System.out.println(aClass == bClass); // false
    }
}

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

更多精彩内容关注本人公众号:架构师升级之路

jvm如何打破双亲委托机制_jdk_02


标签:lang,java,String,委托,name,双亲,jvm,加载
From: https://blog.51cto.com/u_6784072/6216468

相关文章

  • JVM内存模型
    JVM内存模型JVM的内存模型也就是JVM中的内存布局,不要与java的内存模型(与多线程相关)混淆。下图是jdk8jvm内存模型图:程序计数器程序计数器是当前线程所执行的字节码的行号指示器。JVM支持多个线程同时运行,每个线程都会根据CPU时间片来回切换,那么如果当前线程获得时间片了,怎么知道它......
  • Java虚拟机之JVM工具监控调优
    我是攻城师(woshigcs)前几篇我们学习了,JVM里面的运行结构,GC算法,以及各种垃圾收集器的优劣点,那么本篇我们来看下如何使用一些虚拟机性能监控工具,来监控和快速处理故障,当JVM出现一些故障时,我们通常从如下的几个方面进行着手分析,包括运行日志,异常堆栈,GC日志,线程快照(threaddump/javacor......
  • JDK,JRE,JVM之间的关系
    JDK,JRE,JVM三者之间的关系JDK=JRE+开发工具集(例如javac编译工具等)JRE=JVM+JavaSE标准类库JDK(Java开发工具包)JRE(Java运行环境)JVM(Java虚拟机)......
  • 一文回顾JVM
     ......
  • 打破双亲委派模型方法
    自定义一个继承了ClassLoader的加载器,然后重写loadClass方法。若不想打破则重写findClass方法即可。我们比较熟悉的Tomcat服务器为了能够优先加载Web应用目录下的类,然后再加载其他目录下的类,就自定义了类加载器WebAppClassLoader来打破双亲委托机制。这也是Tomcat下Web......
  • 从原理聊JVM(一):染色标记和垃圾回收算法
    作者:京东科技 康志兴1JVM运行时内存划分1.1运行时数据区域•方法区属于共享内存区域,存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。运行时常量池,属于方法区的一部分,用于存放编译期生成的各种字面量和符号引用。JDK1.8之前,Hotspot虚拟机对方法区......
  • JVM垃圾回收机制之对象回收算法
    在前面的文章中,介绍了JVM内存模型分为:堆区、虚拟机栈、方法区、本地方法区和程序计数器,其中堆区是JVM中最大的一块内存区域,在Java中的所有对象实例都保存在此区域,它能被所有线程共享。在Java中还有一个重要的机制:GC(垃圾收集器),堆是GC管理的主要区域,本文会带大家了解GC机制。GC......
  • 从原理聊JVM(一):染色标记和垃圾回收算法
    作者:京东科技 康志兴1JVM运行时内存划分1.1运行时数据区域•方法区属于共享内存区域,存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。运行时常量池,属于方法区的一部分,用于存放编译期生成的各种字面量和符号引用。JDK1.8之前,Hotspot虚拟机对......
  • JVM中的编译器
    JVM中集成了两种编译器,ClientCompiler和ServerCompiler,它们的作用也不同。ClientCompiler注重启动速度和局部的优化,ServerCompiler则更加关注全局的优化,性能会更好,但由于会进行更多的全局分析,所以启动速度会变慢。两种编译器有着不同的应用场景,在虚拟机中同时发挥作用。Clien......
  • 【Azure Spring Cloud】在Azure Spring Apps上看见 App Memory Usage 和 jvm.menory.u
    问题描述在Azure的SpringCloud服务(官名为:SpringApps)中,在Metrics页面中查看AppMemoryUsage和jvm.memory.use,发现两则在下图中出现巨大差距。AppMemoryUsage还是在逐渐上升jvm.memory.use却断崖式下降  在AppMemoryUsage在逐渐上涨的情况下,是否会最终出现OO......