Java虚拟机(JVM)详解(三)

八、双亲委派

前置:类的加载

package com.yanqun.parents;
class MyClass{
    
    
    static int num1 = 100 ;
    static MyClass myClass = new MyClass();
    public MyClass(){
    
    
        num1 = 200 ;
        num2 = 200 ;
    }
    static int num2 = 100 ;
    public static MyClass getMyClass(){
    
    
        return myClass ;
    }

    @Override
    public String toString() {
    
    
        return this.num1 + "\t" + this.num2 ;
    }
}


public class MyClassLoader {
    
    
    public static void main(String[] args) {
    
    
        MyClass myc =  MyClass.getMyClass() ;
        System.out.println(myc);

    }
}

分析

    static int num1 = 100 ;0->100->200static MyClass myClass = new MyClass();【null】 ->【引用地址0x112231public MyClass(){
    
    
        num1 = 200 ;
        num2 = 200 ;
    }
    static int num2 = 100 ;0->200->100】

连接:static静态变量并赋默认值

初始化:给static变量 赋予正确的值

总结:在类中 给静态变量的初始化值问题,一定要注意顺序问题(静态变量 和构造方法的顺序问题)

双亲委派:

JVM自带的加载器(在JVM的内部所包含,C++)、用户自定义的加载器(独立于JVM之外的加载器,Java)

  • JVM自带的加载器

    • 根加载器,Bootstrap : 加载 jre\lib\rt.jar (包含了平时编写代码时 大部分jdk api);指定加载某一个jar( -Xbootclasspath=a.jar)
    • 扩展类加载器,Extension:C:\Java\jdk1.8.0_101\jre\lib\ext\*.jar ;指定加载某一个jar(-Djava.ext.dirs= …)
    • AppClassLoader/SystemClassLoader,系统加载器(应用加载器):加载classpath;指定加载(-Djava.class.path= 类/jar)
  • 用户自定义的加载器

    • 都是抽象类java.lang.ClassLoader的子类

双亲委派:

​ 当一个加载器要加载类的时候,自己先不加载,而是逐层向上交由双亲去加载;当双亲中的某一个加载器 加载成功后,再向下返回成功。如果所有的双亲和自己都无法加载,则报异常。

package com.yanqun.parents;
//classpath: .; ..lib,其中“.”代表当前(自己写的类)
class MyClass2{
    
    
}

public class TestParentsClassLoader {
    
    


    public static void main(String[] args) throws Exception {
    
    
       Class myClass1 =  Class.forName("java.lang.Math") ;
        ClassLoader classLoader1 = myClass1.getClassLoader();
        System.out.println(classLoader1);
        /* JDK中的官方说明:
            Some implementations may use null to represent the bootstrap class loader
         */
       Class myClass2 =  Class.forName("com.yanqun.parents.MyClass2") ;
        ClassLoader classLoader2 = myClass2.getClassLoader();
        System.out.println(classLoader2);
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a8TBgUzz-1606381470118)(C:\Users\17122\AppData\Roaming\Typora\typora-user-images\image-20201124134842936.png)]

null: bootstrap class loader

小结:如果类是 rt.jar中的,则该类是被 bootstrap(根加载器)加载;如果是classpath中的类(自己编写的类),则该类是被AppClassLoader加载。

定义类加载:最终实际加载类的 加载器

初始化类加载类:直接面对加载任务的类

package com.yanqun.parents;

import java.net.URL;
import java.util.Enumeration;

class MyCL{
    
    

}
public class JVMParentsCL {
    
    
    public static void main(String[] args) throws Exception {
    
    
        Class<?> myCL = Class.forName("com.yanqun.parents.MyCL");
        ClassLoader classLoader = myCL.getClassLoader();
        System.out.println(classLoader);
        System.out.println("---");
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println(systemClassLoader);

        ClassLoader parent1 = systemClassLoader.getParent();
        System.out.println(parent1);
        ClassLoader parent2 = parent1.getParent();
        System.out.println(parent2);

        System.out.println("----");

        ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();
        Enumeration<URL> resources = appClassLoader.getResources("com/yanqun/parents/MyCL.class");// a/b/c.txt
        while(resources.hasMoreElements()){
    
    
            URL url = resources.nextElement();
            System.out.println(url);
        }

    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SX54X5PM-1606381470124)(C:\Users\17122\AppData\Roaming\Typora\typora-user-images\image-20201124134903946.png)]

1 自定义类的加载器

二进制名binary names:

   "java.lang.String"
   "javax.swing.JSpinner$DefaultEditor"
   "java.security.KeyStore$Builder$FileBuilder$1"
   "java.net.URLClassLoader$3$1"

$代表内部类:

$数字:第几个匿名内部类

The class loader for an array class, as returned by {
    
    @link* Class#getClassLoader()} is the same as the class loader for its element* type; if the element type is a primitive type, then the array class has no* class loader.

1.数组的加载器类型 和数组元素的加载器类型 是相同

2.原声类型的数组 是没有类加载器的

如果加载的结果是null: 可能是此类没有加载器(int[]) , 也可能是 加载类型是“根加载器”

<p> However, some classes may not originate from a file; they may originate* from other sources, such as the network, or they could be constructed by an* application.  The method {@link #defineClass(String, byte[], int, int)* <tt>defineClass</tt>} converts an array of bytes into an instance of class* <tt>Class</tt>. Instances of this newly defined class can be created using* {@link Class#newInstance <tt>Class.newInstance</tt>}.

xxx.class文件可能是在本地存在,也可能是来自于网络 或者在运行时动态产生(jsp)

<p> The network class loader subclass must define the methods {
    
    @link
 * #findClass <tt>findClass</tt>} and <tt>loadClassData</tt> to load a class
 * from the network.  Once it has downloaded the bytes that make up the class,
 * it should use the method {
    
    @link #defineClass <tt>defineClass</tt>} to
 * create a class instance.  A sample implementation is:
 *
 * <blockquote><pre>
 *     class NetworkClassLoader extends ClassLoader {
    
    
 *         String host;
 *         int port;
 *
 *         public Class findClass(String name) {
    
    
 *             byte[] b = loadClassData(name);
 *             return defineClass(name, b, 0, b.length);
 *         }
 *
 *         private byte[] loadClassData(String name) {
    
    
 *             // load the class data from the connection
 *             &nbsp;.&nbsp;.&nbsp;.
 *         }
 *     }

如果class文件来自原Network,则加载器中必须重写findClas()和loadClassData().

2 自定义类加载器的实现

重写findClas()和loadClassData()

package com.yanqun.parents;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;

//public class MyException extends Exception{...}
public class MyClassLoaderImpl  extends ClassLoader{
    
    
        //优先使用的类加载器是:getSystemClassLoader()
        public MyClassLoaderImpl(){
    
    
            super();
        }

        public MyClassLoaderImpl(ClassLoader parent){
    
    //扩展类加载器
            super(parent);
        }
        //com.yq.xx.class
        public Class findClass(String name) {
    
    
            System.out.println(name);
              byte[] b = loadClassData(name);
              return defineClass(name, b, 0, b.length);
          }

          //“com/yq/xxx.class” ->  byte[]
          private byte[] loadClassData(String name)  {
    
    

              name =  dotToSplit("out.production.MyJVM."+name)+".class" ;
              byte[] result = null ;
              FileInputStream inputStream = null ;
              ByteArrayOutputStream output = null ;
              try {
    
    
                 inputStream = new FileInputStream( new File(  name)  );
                //inputStream -> byte[]
                 output = new ByteArrayOutputStream();

                byte[] buf = new byte[2];
                int len = -1;
                while ((len = inputStream.read(buf)) != -1) {
    
    
                    output.write(buf, 0, len);
                }
                result = output.toByteArray();
            }catch (Exception e){
    
    
                    e.printStackTrace(); ;
            }finally {
    
    
                  try {
    
    
                      if(inputStream != null )inputStream.close();
                      if(output != null ) output.close();
                  }catch (Exception e){
    
    
                      e.printStackTrace();
                  }
            }
            return result ;
          }

    public static void main(String[] args) throws Exception{
    
    
            //自定义加载器的对象
        MyClassLoaderImpl myClassLoader = new MyClassLoaderImpl();//默认在双亲委派时,会根据正规流程:系统——》扩展->根
//        MyClassLoaderImpl myClassLoader = new MyClassLoaderImpl();//直接指定某个 具体的的委派
        Class<?> aClass = myClassLoader.loadClass("com.yanqun.parents.MyDefineCL");
        System.out.println(aClass.getClassLoader());

        MyDefineCL myDefineCL =  (MyDefineCL)(aClass.newInstance() );
        myDefineCL.say();
    }

    public static String dotToSplit(String clssName){
    
    
        return clssName.replace(".","/") ;
    }

}


class MyDefineCL{
    
    
    public void say(){
    
    
        System.out.println("Say...");
    }
}
实现流程:

1.public class MyClassLoaderImpl extends ClassLoader

2.findClass(String name){…} :直接复制文档中的NetworkClassLoader中的即可

3.loadClassData(String name){…} :name所代表的文件内容->byte[]

4.细节:

loadClassData(String name): 是文件形式的字符串a/b/c.class,并且开头out.production..

findClass(String name):是全类名的形式 a.b.c.class,并且开头 是: 包名.类名.class

操作思路:

要先将 .class文件从classpath中删除,之后才可能用到 自定义类加载器;否在classpath中的.class会被 APPClassLoader加载

package com.yanqun.parents;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;

//public class MyException extends Exception{...}
public class MyClassLoaderImpl  extends ClassLoader{
    
    
    private String path ; //null
        //优先使用的类加载器是:getSystemClassLoader()
        public MyClassLoaderImpl(){
    
    
            super();
        }

        public MyClassLoaderImpl(ClassLoader parent){
    
    //扩展类加载器
            super(parent);
        }
        //com.yq.xx.class
        public Class findClass(String name) {
    
    
            System.out.println("findClass...");
              byte[] b = loadClassData(name);
              return defineClass(name, b, 0, b.length);
          }

          //“com/yq/xxx.class” ->  byte[]
          private byte[] loadClassData(String name)  {
    
    
              System.out.println("加载loadClassData...");
              if(path != null){
    
    //name: com.yanqun.parents.MyDefineCL
                  name = path+ name.substring(  name.lastIndexOf(".")+1  )+".class" ;
              }else{
    
    
                  //classpath ->APPClassLoader
                  name =  dotToSplit("out.production.MyJVM."+name)+".class" ;
              }




              byte[] result = null ;
              FileInputStream inputStream = null ;
              ByteArrayOutputStream output = null ;
              try {
    
    
                 inputStream = new FileInputStream( new File(  name)  );
                //inputStream -> byte[]
                 output = new ByteArrayOutputStream();

                byte[] buf = new byte[2];
                int len = -1;
                while ((len = inputStream.read(buf)) != -1) {
    
    
                    output.write(buf, 0, len);
                }
                result = output.toByteArray();
            }catch (Exception e){
    
    
                    e.printStackTrace(); ;
            }finally {
    
    
                  try {
    
    
                      if(inputStream != null )inputStream.close();
                      if(output != null ) output.close();
                  }catch (Exception e){
    
    
                      e.printStackTrace();
                  }
            }
            return result ;
          }

    public static void main(String[] args) throws Exception {
    
    
        System.out.println("main...");
        //自定义加载器的对象
        MyClassLoaderImpl myClassLoader = new MyClassLoaderImpl();//默认在双亲委派时,会根据正规流程:系统——》扩展->根

        myClassLoader.path = "d:/" ;

        //MyClassLoaderImpl myClassLoader = new MyClassLoaderImpl();//直接指定某个 具体的的委派
        Class<?> aClass = myClassLoader.loadClass("com.yanqun.parents.MyDefineCL");
        System.out.println(aClass.getClassLoader());
//        MyDefineCL myDefineCL = (MyDefineCL)( aClass.newInstance()) ;
    }

    public static String dotToSplit(String clssName){
    
      return clssName.replace(".","/") ;  }

}


class MyDefineCL{
    
    
    public void say(){
    
    
        System.out.println("Say...");
    }
}

代码流程:

loadClass() ->findClass()->loadClassData()

一般而言,启动类加载loadClass();

实现自定义加载器,只需要:

1.继承ClassLoader

2重写的 findClass()

情况一:用APPClassLoader

classpath中的MyDefineCL.class文件:

1163157884
1163157884

d盘中的MyDefineCL.class文件:

356573597

说明:类加载器 只会把同一个类 加载一次; 同一个class文件 加载后的位置

结论:

自定义加载器 加载.class文件的流程:

先委托APPClassLoader加载,APPClassLoader会在classpath中寻找是否存在,如果存在 则直接加载;如果不存在,才有可能交给 自定义加载器加载。

package com.yanqun.parents;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;

//public class MyException extends Exception{...}
public class MyClassLoaderImpl  extends ClassLoader{
    
    
    private String path ; //null
        //优先使用的类加载器是:getSystemClassLoader()
        public MyClassLoaderImpl(){
    
    
            super();
        }

        public MyClassLoaderImpl(ClassLoader parent){
    
    //扩展类加载器
            super(parent);
        }
        //com.yq.xx.class
        public Class findClass(String name) {
    
    
//            System.out.println("findClass...");
              byte[] b = loadClassData(name);
              return defineClass(name, b, 0, b.length);
          }

          //“com/yq/xxx.class” ->  byte[]
          private byte[] loadClassData(String name)  {
    
    
//              System.out.println("加载loadClassData...");
              if(path != null){
    
    //name: com.yanqun.parents.MyDefineCL
//                  System.out.println("去D盘加载;;");
                  name = path+ name.substring(  name.lastIndexOf(".")+1  )+".class" ;
              }

              byte[] result = null ;
              FileInputStream inputStream = null ;
              ByteArrayOutputStream output = null ;
              try {
    
    
                 inputStream = new FileInputStream( new File(  name)  );
                //inputStream -> byte[]
                 output = new ByteArrayOutputStream();

                byte[] buf = new byte[2];
                int len = -1;
                while ((len = inputStream.read(buf)) != -1) {
    
    
                    output.write(buf, 0, len);
                }
                result = output.toByteArray();
            }catch (Exception e){
    
    
                    e.printStackTrace(); ;
            }finally {
    
    
                  try {
    
    
                      if(inputStream != null )inputStream.close();
                      if(output != null ) output.close();
                  }catch (Exception e){
    
    
                      e.printStackTrace();
                  }
            }
            return result ;
          }

    public static void main(String[] args) throws Exception {
    
    
//        System.out.println("main...");
        //自定义加载器的对象
//        MyClassLoaderImpl myClassLoader = new MyClassLoaderImpl();//默认在双亲委派时,会根据正规流程:系统——》扩展->根
//        myClassLoader.path = "d:/" ;
//        Class<?> aClass = myClassLoader.loadClass("com.yanqun.parents.MyDefineCL");
//        System.out.println(aClass.hashCode());

        MyClassLoaderImpl myClassLoader2 = new MyClassLoaderImpl();//默认在双亲委派时,会根据正规流程:系统——》扩展->根
        Class<?> aClass2 = myClassLoader2.loadClass("com.yanqun.parents.MyDefineCL");
        System.out.println(aClass2.hashCode());


//        System.out.println(aClass.getClassLoader());
//        MyDefineCL myDefineCL = (MyDefineCL)( aClass.newInstance()) ;
    }

    public static String dotToSplit(String clssName){
    
      return clssName.replace(".","/") ;  }

}


class MyDefineCL{
    
    
    public void say(){
    
    
        System.out.println("Say...");
    }
}

​ 通过以下源码可知,在双亲委派体系中,“下面”的加载器 是通过parent引用 “上面”的加载器。即在双亲委派体系中,各个加载器之间不是继承关系。

public abstract class ClassLoader {
    
    

    private static native void registerNatives();
    static {
    
    
        registerNatives();
    }

    // The parent class loader for delegation
    // Note: VM hardcoded the offset of this field, thus all new fields
    // must be added *after* it.
    private final ClassLoader parent;

ClassLoader源码解读

    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) {
    
    
                long t0 = System.nanoTime();
                try {
    
    
                   //如果“父类”不为空,则委托“父类”加载
                    if (parent != null) {
    
    
                        c = parent.loadClass(name, false);
                    } else {
    
    
                        //如果“父类”为空,说明是双亲委派的顶层了,就调用顶层的加载器(BootstrapClassLoader)
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
    
    
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }
				//如果“父类”加载失败,则只能自己加载(自定义加载器中的findClass()方法)
                if (c == null) {
    
    
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
    
    
                resolveClass(c);
            }
            return c;
        }
    }

3 双亲委派机制优势:

可以防止用户自定义的类 和 rt.jar中的类重名,而造成的混乱

自定义一个java.lang.Math(和jdk中rt.jar中的类重名)

package java.lang;

public class Math {
    
    
    public static void main(String[] args) {
    
    
        System.out.println("hello Math...");
    }
}

运行结果:
在这里插入图片描述

原因:

根据双亲委派, 越上层的加载器越优先执行。

最顶层的加载器是 根加载器,根加载器就会加载rt.jar中的类。

因此rt.jar中的Math会被优先加载。

即程序最终加载的是不是我们自己写的Math,而是jdk/rt.jar中 内置的Math;

而内置的Math根本没有提供main()方法,因此报 无法找到main()。

4 实验:

​ 将相关联的类A.class和B.class分别用 不同的类加载器加载

A和B是继承关系
public class B{
    
    
    public B(){
    
    
        System.out.println("B被加载了,加载器是: "+ this.getClass().getClassLoader());//对象使用之前,必然先把此对象对应的类加载
    }
}

public class A extends  B
{
    
    
    public A(){
    
    
        super();
        System.out.println("A被加载了,加载器是: "+ this.getClass().getClassLoader());//对象使用之前,必然先把此对象对应的类加载
    }
}
//AppClassLoader.class : TestMyClassLoader2
//自定义加载器: A.class/B.class
public class TestMyClassLoader2 {
    
    
    public static void main(String[] args) throws Exception{
    
    
        MyClassLoaderImpl myClassLoader = new MyClassLoaderImpl() ;
        //自定义加载路径
        myClassLoader.path = "d:/" ;
        Class<?> aClass = myClassLoader.loadClass("com.yanqun.parents.A");
        Object aObject = aClass.newInstance();//newInstance()会调用 该类的构造方法(new 构造方法())
        System.out.println(aObject);
    }
}

A和B不是继承关系
public class Y {
    
    
    public Y(){
    
    
        System.out.println("Y被加载了,加载器是: "+ this.getClass().getClassLoader());//对象使用之前,必然先把此对象对应的类加载
    }
}
public class X {
    
    
    public X(){
    
    
        new Y() ;//加载Y(系统加载器)
        System.out.println("X被加载了,加载器是: "+ this.getClass().getClassLoader());//对象使用之前,必然先把此对象对应的类加载
    }
}

//AppClassLoader.class : TestMyClassLoader2
//自定义加载器: A.class/B.class
public class TestMyClassLoader3 {
    
    
    public static void main(String[] args) throws Exception{
    
    
        MyClassLoaderImpl myClassLoader = new MyClassLoaderImpl() ;
        //自定义加载路径
        myClassLoader.path = "d:/" ;
        //程序第一次加载时(X),使用的是  自定义加载器
        Class<?> aClass = myClassLoader.loadClass("com.yanqun.parents.X");



        Object aObject = aClass.newInstance();//newInstance()会调用 该类的构造方法(new 构造方法())
        System.out.println(aObject);
    }
}
存在继承关系

A.class:  classpath
B.class:   classpath
原因
同一个AppClassLoader 会同时加载A.class和B.class

--
A.class:   d:\

B.class:   classpath
原因
A.class:自定义加载器加载
B.class:被AppClassLoader加载
因此,加载A.class和B.class的不是同一个加载器


IllegalAccess
---
A.class:    classpath

B.class:    d:\	
NoClassDefFoundError
原因:
A.class: 被AppClassLoader加载  
B.class: 自定义加载器加载
因此,加载A.class和B.class的不是同一个加载器

--
A.class	d:\
B.class d:\
TestMyClassLoader2 can not access a member of class com.yanqun.parents.A with modifiers "public"
A.class/B.class: 自定义加载器加载
原因是 main()方法所在类在 工程中(APPClassLoader),而A和B不在工程中(自定义加载器)。


造成这些异常的核心原因: 命名空间(不是由同一个类加载器所加载)


----
没有继承关系

X.class:  D:		自定义加载器
Y.class:  classpath	系统加载器

Y被加载了,加载器是: sun.misc.Launcher$AppClassLoader@18b4aac2
X被加载了,加载器是: com.yanqun.parents.MyClassLoaderImpl@74a14482


---

X.class:  classpath  系统加载器
Y.class:  D:	    自定义加载器

java.lang.NoClassDefFoundError: com/yanqun/parents/Y

--

<img src="C:\Users\17122\AppData\Roaming\Typora\typora-user-images\image-20201126164624873.png" alt="image-20201126164624873" style="zoom:67%;" /

  • 如果存在继承关系:

    继承的双方(父类、子类)都必须是同一个加载器,否则出错;

  • 如果不存在继承关系:

    子类加载器可以访问父类加载器加载的类(自定义加载器,可以访问到 系统加载器加载的Y类);反之不行(父类加载器 不能访问子类加载器)

核心: 双亲委派

如果都在同一个加载器 ,则不存在加载问题; 如果不是同一个,就需要双亲委派。

如果想实现各个加载器之间的自定义依赖,可以使用ogsi规范
在这里插入图片描述
OSGi:

1.网状结构的加载结构

2.屏蔽掉硬件的异构性。例如,可以将项目部署在网络上,可以在A节点上 远程操作B节点。在操作上,可以对硬件无感。也可以在A节点上 对B节点上的项目进行运维、部署,并且立项情况下 在维护的期间,不需要暂时、重启。

5 类的卸载

1.系统自带(系统加载器、扩展加载器、根加载器):这些加载器加载的类 是不会被卸载。

2.用户自定义的加载器,会被GC卸载GC

猜你喜欢

转载自blog.csdn.net/Mr_YanMingXin/article/details/110196919