zoukankan      html  css  js  c++  java
  • JVM:类加载 & 类加载器

    System virtual machine: a.k.a. full virtualization VM. Provides a substitute for a real machine (functionality needed to execute entire OS). Allows for multiple environments which are isolated from one another yet exist on the same physical machine.

     

    Process Virtual Machine: a.k.a. application virtual machine or Managed Runtime Environment.

    Process VM runs as a normal application inside a host OS and support a single process, is created when that process is started and destroyed when it exits.

    Process VM’s purpose is to provide a platform-independent programming environment that abstracts away details of the underlying hardware and operating system and allows a program to execute in the same way on any platform.

    e.g. JVM; Parrot virtual machine; .Net Framework.

     

    *使用工具:

      Source Insight - 查看openjdk源码

        -> new project->选择openjdk所在目录->add project files时选择add all->add to project中“include top level sub-dirs”和“recursively add lower sub-dirs"全勾选

      clion - 编写底层程序

      idea / netbeans - 单步调试jdk

        -> 新建项目->C++/基于现有源代码的C++项目->选定openjdk所在目录>

      hsdb

     *注:使用jclasslib时,如果源代码有改动,在idea中先build再在jclasslib中点刷新以更新字节码。

    jps, jstack, jconsole等。

    写JVM的步骤顺序:内存池 -> 垃圾回收器 -> 让JVM能跑单线程的Java程序 -> 让JVM能跑多线程的Java程序

    类加载

    Klass

    Klass: (存在于元空间,)Java的每个类的对象在JVM中都有一个对应的Klass类实例,用于存储类的元信息(e.g. 常量池,属性信息,方法信息)

    *元信息包含字面量 (e.g. 类名、方属性名、属性签名、方法名等)

    Klass的继承结构

    MasterspaceObj

        |- Metadata

            |- Klass

                |- InstanceKlass

                    |- InstanceMirrorKlass

                    |- InstanceRefKlass

                    |- InstanceClassLoaderKlass

                |- ArrayKlass

                    |- TyperArrayKlass

                    |- ObjArrayKlass

    InstaneKlass: 表示普通(非数组)Java类。类加载器将.class文件加载进系统,将.class文件解析生成类的元信息,存储在InstanceKlass中。子类包括InstanceMirrorKlass、InstanceRefKlass和InstanceClassLoaderKlass。

    InstanceMirrorKlass:镜像类,表示Java代码中的java.lang.Class类,存储在堆区。 

    * 因为静态变量存储在InstanceMirrorKlass中,所以静态变量也存储在堆区。

    InstanceRefKlass: 表示java.lang.ref.Reference类的子类。

    InstanceClassLoaeder: 用于遍历某个加载器加载的类。

    Java中的数组不是静态数据类型(e.g. JVM内置的8种数据类型),是动态数据类型(i.e. 在运行期生成的)。

    ArrayKlass: 存储数组类的元信息。

    TyperArrayKlass: 表示基本类型的数组。

    ObjArrayKlass: 表示引用类型的数组。

     

    实验

    证明java数组是动态数据类型

    -> java main方法中new一个int/对象数组,编译运行

    -> IDEA使用插件jclasslib查看字节码(idea->view->show bytecode with Jclasslib),main中显示newarray/anewarray,对应字节码手册中含义:“创建一个原始类型/引用型数组并将其引用至压入栈顶”。

    查看java类对应的klass

    -> HSDB -> Tools/Class Browser->找到目标类名即可找到对应内存地址

    -> HSDB->Tools/Inspector->输入内存地址->可查看java类在内存中对应的klass类

    or

    -> while true维持程序运行,terminal输入jps –l获取当前运行进程id;

    -> HSDB->file/attach to hotspot process->输入目标进程ID

    -> 选中main线程,工具栏第二个按钮查看线程堆栈->可查看java对象底层内存地址

    -> 复制内存地址->HSBD->tool/inspector->可查看java对象底层的实现类

    HSDB attach后记得detach。

    类加载的过程

    * openjdk源码中ClassState记录class的状态(分配内存、加载、链接、正在初始化、完全初始化、初始化出错)。

    /openjdk/hotspot/src/share/vm/oops/instanceKlass.hpp

    enum ClassState {
        allocated,                          // allocated (but not yet linked)
         loaded,                             // loaded and inserted in class hierarchy (but not linked yet)
        linked,                             // successfully linked/verified (but not initialized yet)
        being_initialized,                  // currently running class initializer
        fully_initialized,                  // initialized (successfull final state)
        initialization_error                // error happened during initialization
    
    };

    加载 -- 可以随便使用任何语言实现类加载器,只要能够达到这三个效果。

     -> 通过类的全限定名获取存储该类的class文件(没有指明必须从哪获取)

     -> 解析成运行时数据(instanceKlass实例),存放在方法区;

     -> 在堆区生成该类的Class对象(instanceMirrorKlass实例)。

    *方法区包含了ExtClassLoader区域和AppClassLoader区域,因为一般是AppClassLoader加载,类加载器解析时将得到的类的元信息存放在方法区的AppClassLoader区域。

    JVM加载类是懒加载模式。-- 根加载器加载jar文件时并没有把其中所有的类都进行加载,而是只加载了一部分(预加载模式,只先加载常用的String,Thread,Integer等类)。

    类加载的时机? --主动使用时

      1) new, getstatic, putstatic, invokestatic字节码 i.e. java代码中使用new关键字实例化对象、读取或设置类的静态字段(final修饰的常量除外)、调用类的静态方法

      2) 反射

      3) 初始化子类时会去加载其父类

      4) 启动类(main函数所在类)

      5) 当使用JDK1.7动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。

     

    从哪加载? - 因为没有指明从哪获取class文件,可采用的思路:

    1) 从压缩包中读取 e.g. jar, war

    2) 从网络中获取 e.g. Web Applet

    3) 动态生成 e.g. 动态代理、CGLIB

    4) 由其他文件生成 e.g. JSP

    5) 从数据库读取

    6) 从加密文件读取

     

    验证 – 检查klass文件是否符合规范,判断版本, jvm能否正常运行klass,…etc.

    1. 文件格式验证

    2. 元数据验证

    3. 字节码验证

    4. 符号引用验证

    //参考《深入理解java虚拟机》

     

    准备

    为静态变量分配内存和赋初值。实例变量没有赋初值一说,而是在创建对象时完成赋值。

    例外:如果被Final修饰,编译时会给添加ConstantValue属性,准备阶段直接完成赋值,没有赋初值步骤。

    不同数据类型对应不同的初值:

    解析 – 间接引用转为直接引用

    *常量池:包括静态常量池(class文件常量池)、运行时常量池(可在HSDB中查看)和字符串常量池(StringTable)。

    字符串存储在StringTable,在堆区。

    间接引用a.k.a. 符号引用:指向运行时常量池的引用

    直接引用:内存地址

    1. 类或接口的解析

    2. 字段解析

    3. 方法解析

    4. 接口方法解析

    解析后的信息存储在ConstantPoolCache类实例中。

    何时解析? -- 思路有:

    1) 加载阶段解析常量池时

    2) 用时 // openjdk采用,在执行特定字节码指令(e.g. anewarray, checkcast, getfield, …)前进行解析。

     

    初始化 – 执行静态代码段,完成静态变量的赋值。

    java代码中定义一个static属性(静态字段、静态代码段),编译时字节码层面就会自动生成clint方法(只有一个)

    clint方法(i.e.字节码中生成的静态块)中语句顺序跟定义静态属性的java代码的编写顺序是保持一致的。

    E.g. java代码:

    public static int a=10;
    public static int b=10;

    字节码:

     

    实验

    证明final成员没有赋初值而是直接赋值

    -> 演示类中声明成员static final int a=10, static int b=10;

    -> 查看字节码,a中有属性ConstantValue,代表没有赋初值,准备阶段就完成赋值。(b在准备阶段被赋初值0)

    查看常量池

    -> idea terminal切换到classes目录下, javap –verbose 对象全限定名(全限定名可在ide中选中类名右键copy reference得),输出的Constant pool:部分表示静态常量池

    e.g. 静态常量池中Class对应#25,翻阅下方对应当前类名字符串。’#25’即为一个符号引用(指向常量池的引用)。

    -> 运行程序,HSDB attach到目标进程,class browser点击目标对象,下方可查看动态常量池

    e.g. 动态常量池中Class不再指向常量池,而是指向内存地址@0x000….,即为一个直接引用。//解析后符号引用转为直接引用

    初始化实验1

    public class Test {
      public static void main(String[] args) {
          TestA obj=TestA.getInstance();
          System.out.println(TestA.val1);
          System.out.println(TestA.val2);
      }
    }
    class TestA {
      public static int val1;
      public static val2=1;
      public static TestA instance=new TestA();
      TestA() {
          val1++;
          val2++;
      }
      public static TestA getInstance() {
          return instance;
      }
    }

      输出1  2

      原因:初始化时执行静态代码段,val1赋初值为0,val1赋值为1;执行构造函数后都+1;输出1 2.

    初始化实验2 //将static val2定义移到构造方法后

    public class Test {
      public static void main(String[] args) {
          TestA obj=TestA.getInstance();
          System.out.println(TestA.val1);
          System.out.println(TestA.val2);
      }
    }
    class TestA {
      public static int val1;
      public static TestA instance=new TestA();
      TestA() {
          val1++;
          val2++;
      }
      public static val2=1;
      public static TestA getInstance() {
          return instance;
      }
    }

      输出:1  1

      原因:生成的静态块中语句顺序跟定义静态属性的java代码的编写顺序是保持一致的,所以val2经过构造函数后被定义语句覆盖回1;

               程序的执行顺序: 1) clint方法 2) 默认构造方法(执行完++后val1=1,val2=1);静态块(val2又被赋值为1)。

    加载试验1 

    public class Test {
      public static void main(String[] args) {
          System.out.printf(TestB.str);
      }
    }
    class TestA {
      public static String str=”A str”;
      static {
          System.out.println(“A Static Block”);
      }
    }
    class TestB extends TestA {
      static {
          System.out.println(“B Static Block”);
      }
    }

      输出:Astatic Astr

      原因:A是B的父类,会被主动加载;B没有被使用,不会被加载

    加载试验2 

    public class Test {
      public static void main(String[] args) {
          System.out.printf(new TestB().str); //new了B对象
      }
    }
    class TestA {
      public String str=”A str”;//去掉static
      static {
          System.out.println(“A Static Block”);
      }
    }
    class TestB extends TestA {
      static {
            System.out.println(“B Static Block”);
      }
    }

    输出:Astatic Bstatic Astr

    原因:AB都被使用,都会被加载(主动使用子类,就是间接在主动使用父类)

    加载试验3 

    public class Test {
      public static void main(String[] args) {
          System.out.printf(new TestB().str); 
      }
    }
    class TestA {
      static {
          System.out.println(“A Static Block”);
      }
    }
    class TestB extends TestA {
      public String str=”A str”;//str从A移到B
      static {
          System.out.println(“B Static Block”);
      }
    }

    输出:Astatic Bstatic Astr

    原因:同上

    加载试验4 

    public class Test {
      public static void main(String[] args) {
          System.out.printf(testB.str); 
      }
    }
    class TestA {
      static {
          System.out.println(“A Static Block”);
      }
    }
    class TestB extends TestA {
      public static String str=”B str”; //static成员
      static {
          System.out.println(“B Static Block”);
      }
    }

    输出:Astatic Bstatic Bstr

    原因:静态字段在子类里,子类会被加载

    加载试验5 

    public class Test {
      public static void main(String[] args) {
          System.out.println(TestA.str);
      }
    }
    class TestA {
      public static final String str=”A Str”;
      static {
          System.out.println(“A Static Block”);
      }
    }

    输出:AStr

    原因:虽然AStr在A类里,但是是被final修饰的常量,此常量被写入到Test类的常量池中

    加载试验6 

    public class Test {
      public static void main(String[] args) {
          System.out.println(TestA.uuid);
      }
    }
    class TestA {
      public static final String uuid=UUID.randomUUID().toString();
      static {
          System.out.println(“A Static Block”);
      }
    }

    输出:Astatic uuid

    原因: 虽然uuid是final修饰,但randomUUID().toString()是动态运行的,uuid需要动态生成,不能写入到Test的常量池。所以类A会被加载。

    加载试验7

    public class Test2 {
      static {
        System.out.println(“Test2 Static Block”);
      }
      public static void main(String[] args) throws ClassNotFoundException {
        Class<?> clazz=Class.forName(“com.xxxx.Test1”);
      }
    }

    输出:Test2Static Test1Static

    原因:因为反射,类12都会被加载

    加载试验8

    public class Test {
      public static void main(String[] args) {
          System.out.printf(B.str);
      }
    }
    class A {
      public static String str=”str”;
      static {
          System.out.println(“A Static Block”);
      }
    }
    class B extends A {
      static {
          str+=”###”;
          System.out.println(“B Static Block”);
      }
    }

    输出:Astatic str

    原因:JVM先判断是否加载,后面才会有初始化动作发生。案例中B的内部没有任何东西被使用,所以没有加载B,B的静态块不会被执行。

     

    读取静态变量的底层实现  涉及InstanceKlass, instanceMirrorKlass, ConstantPoolCache

    实验 证明静态属性存储在镜像类中

    public class Test {
      public static void main(String[] args) {
          System.out.printf(B.str);
          while (true) {}
      }
    }
    class A {
      public static String str=”A str”;
      static {
          System.out.println(“A Static Block”);
      }
    }
    class B extends A {
      static {
          System.out.println(“B Static Block”);
      }
    }

    输出:Astatic Astr

    -> 运行,查出进程ID,在HSDB中attach

    -> HSDB classbrowser找到类A的内存地址,输入到inspector

    -> 可以在inspector类A中找到静态属性str是存储在oop Klass: java.mirror中。说明jdk8静态属性是存储在镜像类(instanceMirrorKlass)中的。(而不是存储在instanceKlass, jdk6之前是)

    -> 同样操作在inspector类B的oop Klass:java.mirror中并没有找到静态属性str,说明静态属性str只存放在父类A。

    - 既然静态属性str存放在父类A中,main中调用B.str是怎么找到它的?

    - 两种实现思路:

      1) 先从子类B的镜像类中取,如果有直接返回,没有则沿着继承链往上找;-- O(n)

      2) 借助另外的数据结构,使用K-V格式存储。-- O(1)。

      Hotspot采用的就是思路2,借助另外的数据结构ConstantPoolCache,常量池类ConstantPool有属性_cache指向该结构,每条数据对应一个类ConstantPoolCacheEntry。

    * ConstantPoolCache: 用于存储某些字节码指令所需的解析(resolve)好的常量项,例如给[get|put]static, [get|put]field, invoke[static|special|virtual|interface|dynamic]等指令对应的常量池项用。

    ConstantPoolCacheEntry的获取?

    参考openjdkhotspotsrcsharevmoopcpCache.hpp通过ConstantPoolCache的地址加上偏移量

    类加载器

    JVM的类加载器包含两种类型:

    1) 由C++编写的;-- 启动类加载器(Bootstrap Class Loader)

    2) 由Java编写的 --其他继承java.lang.ClassLoader的类加载器

    JVM也支持自定义类加载器。

     

    各种类加载器之间逻辑上的父子关系不是真正的父子关系,没有直接从属关系。

     启动类加载器

    启动类加载器:JVM将C++处理类的一套逻辑定义为启动类加载器。启动类加载器没有实体。

    因为由C++编写,无法被Java程序调用,在Java程序中显示null。

    启动类加载器的加载路径

    URL[] urLs = Launcher.getBootstrapClassPath().getURLs();
    for (URL urL : urLs) {
        System.out.println(urL);
    }

    openjdk源码

    JavaMain中调用了LoadMainClass,启动类加载器就是在这时加载的。

    Openjdk/jdk/src/share/bin/java.c / JavaMain()

    JavaMain(void * _args) {
        …
        mainClass = LoadMainClass(env, mode, what);
        …
    }

    LoadMainClass中需要先找到Launcherhelper类。启动类加载器所做的事情就是加载类”sun/launcher/LauncherHelper”,checkAndLoadMain就是在LauncherHelper类里面。

    Openjdk/jdk/src/share/java.c / GetLauncherHelperClass()

    GetLauncherHelperClass(JNIEnv *env) {
        …
        NULL_CHECK0(helperClass = FindBootStrapClass(env,
                "sun/launcher/LauncherHelper"));
        …
    }

    GetLauncherHelperClass主要调用FindBootStrapClass。FindBootStrapClass中用GetProcessAddress调用JVM动态链接库的JVM_FindClassFromBootLoader方法。

    openjdk/jdk/src/windows/bin/java_md.c / FindBootStrapClass()

    jclass FindBootStrapClass(JNIEnv *env, const char *classname) {
        …
        findBootClass = (FindClassFromBootLoader_t *)GetProcAddress(hJvm,
                    "JVM_FindClassFromBootLoader");
        …
    } 

    找到LauncherHelper类后,通过JNI执行LauncherHelper类的checkAndLoadMain方法。

    用于加载main class(main方法所在的类),三种类加载器的父子链(->启动扩展类加载器->应用类加载器)也是在这次调用中完成的。

    LoadMainClass返回的result其实就是调用checkAndLoadMain的结果 – main class。

    Openjdk/jdk/src/share/bin/java.c / LoadMainClass()

    LoadMainClass(JNIEnv *env, int mode, char *name) {
        …
        jclass cls = GetLauncherHelperClass(env);
        …
        NULL_CHECK0(mid = (*env)->GetStaticMethodID(env, cls,
                    "checkAndLoadMain",
                    "(ZILjava/lang/String;)Ljava/lang/Class;"));
        …
    }

    从checkAndLoadeMain开始都是java代码。checkAndLoadMain方法检查了运行模式,如果是class就直接命名,如果是jar包则从jar包中找,其他则报错。然后调用scloader.loadClass方法获得main class来返回。

    openjdk/jdk/src/share/classes/sun/launcher/LauncherHelper.java / checkAndLoadMain()

    public static Class<?> checkAndLoadMain(boolean printToStderr,
                                                int mode,
                                                String what) {
        …
            switch (mode) {
                case LM_CLASS:
                    cn = what;
                    break;
                case LM_JAR:
                    cn = getMainClassFromJar(what);
                    break;
                default:
                    // should never happen
                    throw new InternalError("" + mode + ": Unknown launch mode");
            }
        …
        mainClass = scloader.loadClass(cn);
        …
    }

    scloader是由ClassLoader.getSystemClassLoader()得到的,其中调用了initSystemClassLoader方法。

    Openjdk/jdk/src/share/classes/java/lang /ClassLoader.java / getSystemClassLoader()

    public static ClassLoader getSystemClassLoader() {
        initSystemClassLoader();
        …
    }

    initSystemClassLoader中调用了Launcher.getLauncher方法。

    Openjdk/jdk/src/share/classes/java/lang /ClassLoader.java / initSystemClassLoader

    private static synchronized void initSystemClassLoader() {
        …
        sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
        …
    }

    Launcher构造函数中初始化了ExtClassLoader,再以ext为参数初始化appClassLoader。Ext其实就是parent。

    线程上下文类加载器contextClassLoader也是在这时赋值的。

    openjdk/jdk/src/share/classes/sun/misc/Launcher.java

    public class Launcher {
        …
        private static Launcher launcher = new Launcher();
        …
        public static Launcher getLauncher() {
            return launcher;
        }
        public Launcher() {
            …
            // Create the extension class loader
            extcl = ExtClassLoader.getExtClassLoader();
            …
            // Now create the class loader to use to launch the application
            loader = AppClassLoader.getAppClassLoader(extcl);
            …
           Thread.currentThread().setContextClassLoader(loader);
        }
        …
    }

    从getAppClassLoader()和其中调用的AppClassLoader构造函数可看出传入的extcl参数为parent。

    openjdk/jdk/src/share/classes/sun/misc/Launcher.java /AppClassLoader

    static class AppClassLoader extends URLClassLoader {
        public static ClassLoader getAppClassLoader(final ClassLoader extcl) throws IOException {
            …
            return new AppClassLoader(urls, extcl);
        }
        AppClassLoader(URL[] urls, ClassLoader parent) {…}
    }

    为什么Ext的parent是null?

    从ExtClassLoader构造函数看出其super构造函数传入的就是null,而ExtClassLoader构造函数的super对应的形参就是parent。

    openjdk/jdk/src/share/classes/sun/misc/Launcher.java /ExtClassLoader

    static class ExtClassLoader extends URLClassLoader {
        public static ExtClassLoader getExtClassLoader() throws IOException {
            …
            return new ExtClassLoader(dirs);
        }
        public ExtClassLoader(File[] dirs) throws IOException {
            super(getExtURLs(dirs), null, factory);
            …
        }
        …
    }

    openjdk/jdk/share/classes/java/net/URLClassLoader.java

    URLClassLoader(URL[] urls, ClassLoader parent, AccessControlContext acc) {…}

    顺序:jvm的目的是要去加载main所在类->启动boot加载器->启动ext-加载器>启动app加载器->通过app加载器去加载main class

    所以这不是继承上的父子关系,而是加载链逻辑上的父子关系。逻辑上的父子关系目的就是为了双亲委派。

     

     

    扩展类加载器

    扩展类加载器的加载路径是通过向System.getProperty()传入参数”java.ext.dirs”

    String[] urls = System.getProperty(“java.ext.dirs”).split(“:”);
    for (String url : urls)
        System.out.println(url);
    ClassLoader classLoader = ClassLoader.getSystemClassLoader().getParent();
    
    URLClassLoader urlClassLoader = (URLClassLoader) classLoader;
    URL[] urls = urlClassLoader.getURLs();
    for (URL url : urls)
        System.out.println(url);

    向System.getProperty()传入参数”java.ext.dirs”的做法也可以在openjdk源码中看到:

    openjdk/jdk/src/share/classes/sun/misc/Launcher.java / ExtClassLoader

    public ExtClassLoader(File[] dirs) throws IOException {
        super(getExtURLs(dirs), null, factory);
        …
    }
    private static File[] getExtDirs() {
        String s = System.getProperty("java.ext.dirs");
        …
    }

    不同的类加载器加载同一个类,相等吗?

    不相等。方法区是按照类加载器进行分开存储的。每个类加载器在方法区里都有一块独立的区域,虽然加载的是同一份文件,但是不会在同一个空间里。

    同一个类加载器加载同一个文件多次,实际上会加载几次?

    一次。因为加载前会(根据全限定名)去判断空间里是否已经有这个类。

     

     

     

    双亲委派

    双亲委派:需要查找某个类时,先判断在当前类加载器是否已经加载(能在其空间中找到),如果已经加载则直接返回,没有则向上委托给其父类加载器。

    *系统已加载的class信息存储在SystemDictionary类中。

    e.g. 查找某个类

    -> 判断当前最下层的自定义的类加载器是否已加载该类,是则直接返回,否则往上委托给父类AppClassLoader;

    -> 判断在AppClassLoader中是否已经加载,是则直接返回,否则再往上委托给父类ExtClassLoader;

    -> … 委托给BootstrapClassLoader…

    -> 如果BootstrapClassLoader也没有加载直接报错

    局限性:无法做到不委派或向下委派

    e.g. 数据库需要实现的driver接口是由启动类加载器加载。而第三方数据(如mysql)的相关实现类需要由应用类加载器加载,启动类加载器不能加载,需要向下委派。

     

    什么叫打破双亲委派?

    两种思路。a) 不委派 –只用当前类加载器去加载 –实现方式:自定义类加载器

             或 b) 向下委派 (SPI机制中的一部分)

    SPI: 一种服务发现机制,通过在ClassPath路径下的META-INF/services文件夹查找文件,自动加载文件里所定义的类

    e.g. SPI Demo(该案例不算打破双亲委派,因为所有类都是启动类加载器加载的)

    PayService是自定义的一个接口,有两个实现AlipayService和WxpayService。

    Public interface PayService {
        void pay();
    }

    main中使用线程上下文加载器加载PayService类。

    public static void main(String[] args) {
        ServiceLoader<PayService> services=ServiceLoader.load(PayService.class);
        for (PayService service : services)
            service.pay();
    }

    通过接口调用的pay方法调用的是哪一个实现类是看pom.xml/<dependencies>/<artifactId>中指定加载的是哪一个模块。

    Pom.xml (gateway-main)

    <dependencies>
        <dependency><artifactId>pay-wx</artifactId></dependency></dependencies>

    每个实现类底下的/src/main/resources/META-INF.services/目录下的文件中指定了实现类的全限定名。SPI从而找到需要加载的实现类。

    /pay-wx/src/main/resources/META-INF.services/com.luban.common.service.PayService

        com.luban.pay.AlipayService

    实现向下委派?

    需要使用ServiceLoader。

    e.g. Driver中的SPI机制。JDBI的底层实现用到serviceloader,也是一种SPI。driver通过向下委派来打破双亲委派。

    ServiceLoader<Driver> loadedDrivers=ServiceLoader.load(Driver.class);
    Iterator<Driver> driversIterator=loadedDrivers.iterator();

    线程上下文类加载器

    ServiceLoader的底层是由线程上下文类加载器ContextClassLoader实现的,在SPI中向下委派中有应用。

    ContextClassLoader可通过Thread.currentThread().setContextClassLoader()进行设置。

    在checkAndLoadMain时已经进行设置,默认为AppClassLoader。

    openjdk/jdk/src/share/classes/sun/misc/Launcher.java

    public Launcher() {
        …
        // Now create the class loader to use to launch the application
        loader = AppClassLoader.getAppClassLoader(extcl);
        …
        Thread.currentThread().setContextClassLoader(loader);
    }

    自定义类加载器

    如何实现自定义类加载器?

    extends ClassLoader类,重写findClass方法。

    实验1:自定义加载器重写findClass时返回null,能否加载成功

    public class ClassLoader1 extends ClassLoader {
      public void main(String[] args) throws Exception {
          Classloader1 classloader1=new Classloader1();
          Class<?> clazz=classloader1.loadClass(“com.experiment.classloader.A”);
          System.out.println(“clazz hashcode: “+clazz.hashCode());
      }
      @Override
      protected Class<?> findClass(String className) throws ClassNotFoundException {
          return null;
      }
    }

    结果:可以加载

    原因:双亲委派。可通过 System.out.println(clazz1.getClassLoader())打印出sun.misc.Launcher$AppClassLoader证明clazz1是由AppClassLoader加载的。

    实验2

    public class ClassLoader1 extends ClassLoader {
      public void main(String[] args) throws Exception {
          Classloader1 classloader1=new Classloader1(), classloader2=new Classloader1();
          Class<?> clazz1=classloader1.loadClass(“com.experiment.classloader.A”);
          Class<?> clazz2=classloader2..loadClass(“com.experiment.classloader.A”);
          System.out.println(clazz1==clazz2)
      }
      @Override
      protected Class<?> findClass(String className) throws ClassNotFoundException {…}
    }

    结果:true

    原因:用同一个classLoader类加载同一个类,得到的Class是同一个类。可通过System.out.println(clazz1.hashCode())证明clazz1和clazz2的hashCode是一样的。

     

    自定义类加载器如何打破双亲委派

    classLoadder源码中:

    Class(String)底层了调用loadClass(String, boolean)

    public Class<?> loadClass(String name) throws ClassNotFoundException {
        return this.loadClass(name, false);
    }

    loadClass(String, Boolean)中的双亲委派逻辑 

    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { //name为全限定名
        …
        Class<?> c = this.findLoadedClass(name); //在自己空间中搜索该类
        if (c == null) { 
            try {
                if (this.parent! = null)
                    c = this.parent.loadClass(name, false); //如果空间中没有,双亲委派
                else
                    c = this.findBootstrapClassOrNull(name);
            } catch (ClassNotFoundException) {}    
        }
        if (c == null)
            c = this.findClass(name); //如果双亲委派仍未加载到,调用自己的findClass加载一次
        //此后再不能加载到将直接报错
        if (resolve)//判断有无解析,进行解析
            this.resolveClass(c);
        return c;
    }

    可通过重写loadClass(String, Boolean)打破双亲委派: 对指定包下的类的加载做不委派处理

    @Override
    protected Class<?> loadClass(String name, Boolean resolve) throws ClassNotFoundException {
        …
        if (c == null) {
            if (name.startsWith(“com.experiment”))
                c = findClass(name);
            else
                c = this.getParent().loadClass(name);
        }
    }

    沙箱安全


    checkAndLoadMain底层有调用到initSystemClassLoader。initClassLoader中所做的AccessController.doPrivileged判断就是一种沙箱安全机制。

    Openjdk/jdk/src/share/classes/java/lang /ClassLoader.java / initSystemClassLoader

    private static synchronized void initSystemClassLoader() {
        …
        scl = AccessController.doPrivileged(
                            new SystemClassLoaderAction(scl));
        …
    }

    实验: 沙箱安全

    在自创java.lang包下定义String类,在main中调用String的方法会报错。

    public class String {
      public static void main(String[] args) {
          String.show();
      }
      public static void show() {
          System.out.println(“String show function”);
      }
    }

    结果:报错“在类中找不到main方法”

    原因:沙箱安全防止打破双亲委派修改系统类,保护核心类库。 

  • 相关阅读:
    com.jsj.dao====新闻发布系统(4)
    java类加载器,getClassLoader()
    类名.class的理解
    static{}和{}===静态模块和普通模块的理解
    com.jsj.factory====新闻发布系统(3)
    怎么做(我直接敲代码,有解释)===新闻发布系统(2)
    (8)小项目--界面的代码
    (7) 小项目
    2 Servlet(1)
    RMQ之ST算法模板
  • 原文地址:https://www.cnblogs.com/RDaneelOlivaw/p/13494532.html
Copyright © 2011-2022 走看看