JVM-1

JVM学习笔记

一、类加载

  1. 在java代码中,类型的加载、连接与初始化过程都是在程序运行期间完成的

    • 类型加载:查找并加载类的二进制数据。最常见的情况,将已经编写好的class文件从磁盘加载到内存。

    • 连接: 将类与类之间的关系确定好,并且对于字节码的一些相关的处理,对于字节码的验证都是在连接过程处理(类与类的符号引用转化成直接引用)。

      • 验证:确保被加载的类的正确性

      • 准备:为类的 静态变量 分配内存,并将其初始化为默认值

        class Test{
          // 注意:在准备阶段 a 这里的初始化值不是1 而是0
          public static int a = 1; 
        }
        
 - 解析:把类中的符合引用转化为直接引用
  • 初始化过程:如对于类中的一些静态变量进行赋值,在初始化阶段完成的。

    class Test{
      // 注意:在初始化阶段 a 这里才被赋予 1 这个值
      public static int a = 1;
    }
    
  1. 提供了更大的灵活性,增加了更多的可能性

二、类加载器

  1. java虚拟机与程序的生命周期
  2. 在如下几种情况下,java虚拟机将结束生命周期
    • 在执行了System.exit()方法
    • 程序正常执行结束
    • 程序在执行过程中遇到异常或错误而异常终止
    • 由于操作系统出现错误而导致java虚拟机进程终止

三、类的加载、连接与初始化

  1. java程序对的使用方式可分为两种

    • 主动使用

      • 创建类的实例

      • 访问某个类或接口的静态变量,或者对该静态变量赋值(字节码层面:访问静态变量 getstatic助记符 静态变量赋值 putstatic助记符 完成类的操作)

      • 调用类的静态方法(字节码层面:invokestatic 助记符来完成操作)

      • 反射 如 :

        Class.forName("com.test.Test")
        
      • 初始化一个类的子类

        class Parent{}
        // 当对 Child进行初始化时 也会对Parent进行初始化 如果Parent上也有父类 以此类推
        class Child extends Parent{}
        
 - java虚拟机启动时被标明为启动类的类(Java Test 包含main 方法时)

 - JDK1.7开始提供的动态语言支持:java.lang.invoke.MethodHandle实例的解析结果 REF_getStatic,REF_putStatic,REF_invokeStatic句柄对应的类没有初始化则初始化
  • 被动使用(除了以上七种情况,其他使用java类的方式都被看作是对类的被动使用,都不会导致类的 初始化

    //主动使用和被动使用之间的关联关系
    
    /*
        对于静态字段来说,只有直接定义了该字段的类才会被初始化
        当一个类在初始化时要求其父类全部都已经初始化完毕了
        -XX:+TraceClassLoading  用于追踪类的加载信息并打印
     */
    public class MyTest1 {
    
        public static void main(String[] args) {
            // 谁定义的静态变量就表示对谁的主动使用
            System.out.println(MyChild1.str);
            //System.out.println(MyChild1.str2);
        }
    }
    
    class MyParent1 {
        public static  String str = "hello world";
    
        // 静态代码块 初始化执行
        static {
            System.out.println("MyParent1 static block");
        }
    }
    
    class MyChild1 extends  MyParent1 {
    
        public static  String str2 = "hello";
    
        static {
            System.out.println("MyChild1 static block");
        }
    }
    
  1. 所有的java虚拟机实现必须在每个类或接口被java程序 首次主动使用时才初始化他们

  2. 类的加载:指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区,然后在内存中创建一个java.lang.Class对象(规范并未说明Class对象位于哪里,HotSpot虚拟机将其放在了方法区中)用来封装类在方法区内的数据结构

    1. 加载.class文件的方式
      • 从本地系统中直接加载
      • 通过网络下载.class文件
      • 从zip,jar等归档文件中加载.class文件
      • 从专有数据库中提取.class文件
      • 将java源文件动态编译为.class文件(动态代理)
  3. JVM参数

    1. 格式: -XX: 开头
    2. -XX:+<option> 表示开启option选项如
      • -XX:+TraceClassLoading 用于追踪类的加载信息并打印
    3. -XX:-<option> 表示关闭option选项
    4. -XX:<option>=<value> 表示将option选择的值设置为value
  4. 助记符

    • ldc 表示将int,float或String类型的常量值从常量池推送至栈顶
    • bipush 表示将单字节 (-128 - 127)的常量值推送到栈顶
    • sipush 表示将一个短整型常量值 (-32768 - 32767)推送至栈顶
    • iconst_1 表示将int类型 1 推送至栈顶 (iconst_m1 - iconst_5)
    • anewarray 表示创建一个引用类型的(如类 接口 数组)数组,并将其引用值压如栈顶
    • newarray 表示创建一个指定的原始类型(int float chart)数组,并将其引用值压入栈顶
  5. 助记符说明(实例)

    • 助记符这一块用语言直接去描述还是有点晦涩所以直接通过代码以及反编译之后的字节码更能直观的说明问题。(javap -c 文件)

      /*
          常量在编译阶段会被存入到调用这个常量的方法所在类的常量池中
          本质上,调用类并没有直接引用到定义常量的类,因此并不会触发定义常量的类的初始化
          注意:这里指的是将常量存放到了MyTest2的常量池中,之后MyTest2与MyParent2就
          没有任何关系了,甚至可以将MyParent2的class文件删除
       */
      /*
          助记符:
          ldc 表示将int,float或String类型的常量值从常量池推送至栈顶
          bipush 表示将单字节 (-128 - 127)的常量值推送到栈顶
          sipush 表示将一个短整型常量值 (-32768 - 32767)推送至栈顶
          iconst_1 表示将int类型 1 推送至栈顶 iconst_1 - iconst_5
      
       */
      public class MyTest2 {
          public static void main(String[] args) {
              System.out.println(MyParent2.s);
              System.out.println(MyParent2.str);
              System.out.println(MyParent2.i);
              System.out.println(MyParent2.m);
          }
      }
      class MyParent2 {
          //public static String str = "hello";
          public static final String str = "hello"; // 增加final 关键字后 出现了变化
          // 在编译阶段的会被放置在调用这个方法所在的类的常量池中
          public static final short s = 7;
          public static final int i = 128;
          public static final int m = 1;
          static {
              System.out.println("MyParent2 static block");
          }
      }
      
      

      反编译之后的内容:

      Compiled from "MyTest2.java"
      public class com.lc.jvm.classloader.MyTest2 {
        public com.lc.jvm.classloader.MyTest2();
          Code:
             0: aload_0
             1: invokespecial #1                  // Method java/lang/Object."<init>":()V
             4: return
      
        public static void main(java.lang.String[]);
          Code:
             0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
             3: bipush        7
             5: invokevirtual #4                  // Method java/io/PrintStream.println:(I)V
             8: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
            11: ldc           #5                  // String hello
            13: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
            16: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
            19: sipush        128
            22: invokevirtual #4                  // Method java/io/PrintStream.println:(I)V
            25: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
            28: iconst_1
            29: invokevirtual #4                  // Method java/io/PrintStream.println:(I)V
            32: return
      }
      
      

四、接口初始化规则与类加载器意义分析

  1. 当一个接口在初始化时,并不要求其父接口都完成了初始化

  2. 只有在真正使用到父接口的时候(如引用接口中所定义的常量时),才会初始化

  3. 接口中所定义的常量 默认是 public static final

  4. 类加载器 准备阶段以及初始化 阶段的意义

    public class MyTest6 {
    
        public static void main(String[] args) {
            Signleton signleton = Signleton.getInstance();
            System.out.println("counter1:"+Signleton.counter1);
            System.out.println("counter2:"+Signleton.counter2);
        }
    }
    /*
        分析
        准备阶段-> counter1 默认值0 signleton null  counter2 0
        需要注意初始化阶段 是按照我们申明的变量 从上往下执行
        counter1 =>1
        signleton=>new Signleton()
        new Signleton()=>执行私有构造方法  counter1++ => 2 ;counter2++;=>1
        System.out.println(counter1);
        System.out.println(counter2);
        counter2 赋值 0
     */
    class Signleton {
        public static int counter1 = 1;
        private static Signleton signleton = new Signleton();
        private Signleton(){
            counter1++;
            counter2++; // 准备阶段的重要意义
            System.out.println(counter1);
            System.out.println(counter2);
        }
        public static int counter2 = 0;
    
    
        public static Signleton getInstance(){
            return signleton;
        }
    
    }
    

​ 输出的结果:

2
1
counter1:2
counter2:0
  1. 类加载流程图


加载:

过程:

  • 加载:把二进制形式的java类型读入java虚拟机中

  • 验证:对文件进行验证

  • 准备:为类变量分配内存,设置默认值。但是在到达初始化之前,类变量都没有初始化为真正的初始值

  • 解析:解析过程就是类型在常量池中寻找类、接口、字段和方法的符号引用,把这些符号引用替换成直接引用的过程

  • 初始化:为类变量赋予正确的初始值

  • 类实例化:为新的对象分配内存;为实例变量赋予默认值;为实例变量赋正确的初始值;java编译器为它编译的每一个类都至少生成一个实例初始化方法,在java的class文件中,这个实例初始化方法被称为"<init>"。针对源代码中的每一个类的构造方法,java编译器都产生了一个<init>方法

  1. 类的加载

    • 类的加载的最终产品是位于内存中的class对象
    • Class对象封装了类在方法区内的数据结构,并且向java程序员提供了访问方法区内的数据结构的接口
  2. 类加载器的类型

    • java虚拟机自带的加载器
      • 根加载器(Bootstrap)
        • 该加载器没有父类加载器。它负责加载虚拟机的核心类库,如java.lang.*等。例如从Sample.java可以看出java.lang.Object就是由根类加载器加载的,根类加载器从系统属性sun.boot.class.path所指定的目录中加载类库。根类加载器的实现依赖于底层操作系统,属于虚拟机实现的一部分,它并没有继承java.lang.ClassLoader类
      • 扩展类加载器(Extension)
        • 它的父加载器为根类加载器。它从java.ext.dirs系统属性所指定的目录中加载类库,或者从JDK安装的目录的jre\lib\ext子目录(扩张目录)下加载类库,如果把用户创建的JAR文件放在这个目录下,也会自动由扩展类加载器加载。扩展类加载器是纯java类 是java.lang.ClassLoader类的子类
      • 系统(应用)类加载器(System)
        • 它的父加载器为扩展类加载器。它从环境变量classpath或者系统属性java.lang.path所指定的目录中加载类,它是用户自定义加载器的默认父加载器。系统类加载器是纯java类,是java.lang.ClassLoader类的子类
    • 用户自定义的类加载器
      • java.lang.ClassLoader的子类
      • 用户可以定制类的加载方式(继承 ClassLoader类)
  3. 类加载器

    • 类加载器用来把类加载到java虚拟机中,从JDK1.2版本开始,类的加载过程采用父亲委托机制,这种机制能更好地保证java平台的安全。在此委托机制中,除了java虚拟机自带的根类加载器以外,其余的类加载器都有且只有一个父加载器。当java程序请求加载器loader加载Sample类时,loader首先委托自己的父加载器去加载Sample类,若父加载器能加载,则由父加载器完成加载任务,否则才由加载器loader本身加载Sample类
  4. 类加载器并不需要等到某个类被 首次主动使用 时再加载它

    • JVM规范允许类加载器在预料某个类将要被使用时加预先加载它,如果在预先加载的过程中遇到了 .class 文件缺失或存在错误,类加载器必须在 程序首次主动 使用该类时才报告错误 (LinkageError错误)
    • 如果这个类一直没有被程序主动使用,那么 类加载器就不会报告错误
  5. 类的验证

    • 类被加载后,就进入连接阶段。连接就是将已经读入到内存的类的二进制数据合并到虚拟机的运行时环境中去
    • 类的验证 主要内容(jdk版本不一样验证相应也会有区别)
      • 类文件的结构检查
      • 语义检查
      • 字节码验证
      • 二进制兼容性的验证
      • 魔数验证
  6. 类的准备

    • 在准备阶段java虚拟机为类的静态变量分配内存,并设置默认初始值。例如 对于以下 Sample类,在准备阶段,将为int类型的静态变量a分配4个字节的内存空间,并且赋予默认值0,为long类型的静态变量b分配8个字节的内存空间,并且赋予默认值0
    public class Sample{
         private static int a = 1;
         private static long b;
         static {
          b = 2;
        }
         ...
    }
    
    • 在初始化阶段,java虚拟机执行类的初始化语句,为类的静态变量赋予初始值。在程序中,静态变量的初始化有两种途径:1. 在静态变量的声明处进行初始化 2. 在静态代码块中进行初始化。例如在以下代码中,静态变量a和b都是显示的初始化,而静态变量c没有被显示的初始化,它将保持默认值0
    public class Sample{
         private static int a = 1; //在静态变量声明处进行初始化
         private static long b;
         private static long c;
         static {
          b = 2; // 在静态代码块中进行初始化
        }
         ...
    }
    
    • 静态变量的声明语句,以及静态代码块都被看做类的初始化语句,java虚拟机会按照初始化语句在类文件中的先后顺序依次执行他们。例如当一下Sample类被初始化后,它的静态变量a的取值为4
    public class Sample{
      static int a = 1;
      static {
        a = 2;
      }
      static {
        a = 4;
      }
      public static void main(String[] args){
        System.out.println("a="+a); //输出 a=4
      }
    }
    
  7. 类的初始化

    • 类的初始化步骤
      • 假如这个类还没有被加载和连接,那就先进行加载和连接
      • 假如类存在直接父类,并且这个父类还没有被初始化,那就先初始化直接父类
      • 假如类中存在初始化语句,那就依次执行这些初始化语句
  8. 类的初始化时机

    • 创建类的实例(new 一个实例)

    • 访问某个类或接口的静态变量,或者对该静态变量赋值(对应到助记符 getstatic putstatic)

    • 调用类的静态方法(对应到助记符 invokestatic)

    • 反射(如Class.forName("com.test.Test") )

    • 初始化一个类的子类

    • java虚拟机启动时被标明为启动类的类(java Test)

    • JDK1.7开始提供的动态语言支持:java.lang.invoke.MethodHandle实例的解析结果 REF_getStatic REF_putStatic REF_invokeStatic 句柄对应的类没有初始化则初始化

    • 注意: 除了上面的七种情形,其他使用java类的方式都被看做是被动使用,不会导致类的初始化。

    • 当java虚拟机初始化一个类时,要求它的所有父类都已经被初始化,但是这条规则并不适用于接口。

      • 在初始化一个类时,并不会先初始化它所有实现的接口

      • 在初始化一个接口时,并不会先初始化它的父接口

      • 因此,一个父接口并不会因为它的子接口或者实现类的初始化而初始化。只有当程序首次使用特定接口的静态变量时,才会导致该接口的初始化。

      • 通过代码方式证明上述的结论

        /*
            当一个接口在初始化时,并不要求其父接口都完成了初始化
             只有在真正使用到父接口的时候(如引用接口中所定义的常量时),才会初始化
         */
        public class MyTest7 {
            public static void main(String[] args) {
                // System.out.println(MyChild7.b ); // 在初始化一个类时,并不会先初始化它所有实现的接口
                System.out.println(IMyChild7.thread);  // 验证 在初始化一个接口时,并不会先初始化它的父接口
            }
        }
        
        interface MyParent7 {
           public static Thread thread = new Thread(){
               // 这是实例代码块 在对象实例化时 执行。和静态代码块有很大区别 静态代码块只执行一次
                {
                    System.out.println("MyParent7 invoke");
                }
            };
        }
        class MyChild7  implements  MyParent7{
            public static    int b = new Random().nextInt(3);
        }
        interface IMyChild7 extends MyParent7 {
            public static Thread thread = new Thread(){
                {
                    System.out.println("IMyChild7 invoke");
                }
            };
        }
        
        
  • 只有当程序访问的静态变量或静态方法确实在当前类或当前接口中定义时,才可以认为是对类或接口的主动使用

  • 通过子类去调用父类的静态变量或者父类的静态方法本质上都是对于父类的主动使用,而不是对于自己的主动使用

    /*
        通过子类去调用父类的静态变量或者父类的静态方法本质上都是对于父类的主动使用
        而不是对于自己的主动使用。
     */
    public class MyTest11 {
        public static void main(String[] args) {
            System.out.println(Child11.a);
        }
    }
    class Parent11{
        static  int a = 1;
        static {
            System.out.println("Parenr11 static block");
        }
    
        static void doSomething(){
            System.out.println("do something");
        }
    }
    class Child11 extends  Parent11{
        static int b = 2;
        static {
            System.out.println("Child11 static block");
        }
    }
    
    /*
       输出:
       Parenr11 static block
       1
    */
    
  • 调用ClassLoader类的loadClass方法加载一个类,并不是对类的主动使用,不会导致类的初始化 (阅读 ClassLoader javadoc文档)

     public class MyTest12 {
        public static void main(String[] args) throws Exception {
            ClassLoader classLoader = ClassLoader.getSystemClassLoader();
            Class<?> aClass = classLoader.loadClass("com.lc.jvm.classloader.CL");
            System.out.println(aClass);
            System.out.println("--------");
            Class<?> aClass1 = Class.forName("com.lc.jvm.classloader.CL");//通过反射 会导致类的初始化
            System.out.println(aClass1);
        }
    }
    class  CL {
        static {
            System.out.println("Class CL");
        }
    }
    /*
    输出:
    class com.lc.jvm.classloader.CL
    --------
    Class CL
    class com.lc.jvm.classloader.CL
    */
    
  1. 类加载器的双亲委托机制

    • 在双亲委托机制中,各个加载器按照父子关系形成 树形结构,除了根类加载器之外,其余的类加载器都有且只有一个父加载器
    • 若有一个类加载器能够成功加载Test类,那么这个类加载器被称为 定义类加载器,所有能成功返回Class对象引用的类加载器(包括定义类加载器)都被称为 初始类加载器
  2. 类加载器的层次关系

    public class MyTest14 {
    
        public static void main(String[] args) throws  Exception{
            // 获取当前线程的上下文类加载器
            ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
            // 定义资源
            String resoureName = "com/lc/jvm/classloader/MyTest13.class";
            // 获取资源
            Enumeration<URL> resources = classLoader.getResources(resoureName);
            while (resources.hasMoreElements()) {
                URL url = resources.nextElement();
                System.out.println(url);
            }
        }
    }
    
    1. 获取ClassLoader的途径

      • 获取当前类的ClassLoader : clazz.getClassLoader();
      • 获取当前线程上下文的ClassLoader : Thread.currentThread().getContextClassLoader();
      • 获取系统的ClassLoader: ClassLoader.getSystemClassLoader();
      • 获取调用者的ClassLoader: DriverManager.getCallerClassLoader();
    2. ClassLoader javadoc文档

      • A class loader is an object that is responsible for loading classes. The class ClassLoader is an abstract class. Given the binary name of a class, a class loader should attempt to locate or generate data that constitutes a definition for the class. A typical strategy is to transform the name into a file name and then read a "class file" of that name from a file system.
        Every Class object contains a reference to the ClassLoader that defined it.
        Class objects for array classes are not created by class loaders, but are created automatically as required by the Java runtime. The class loader for an array class, as returned by 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.
        Applications implement subclasses of ClassLoader in order to extend the manner in which the Java virtual machine dynamically loads classes.
        Class loaders may typically be used by security managers to indicate security domains.
        The ClassLoader class uses a delegation model to search for classes and resources. Each instance of ClassLoader has an associated parent class loader. When requested to find a class or resource, a ClassLoader instance will delegate the search for the class or resource to its parent class loader before attempting to find the class or resource itself. The virtual machine's built-in class loader, called the "bootstrap class loader", does not itself have a parent but may serve as the parent of a ClassLoader instance.
        Class loaders that support concurrent loading of classes are known as parallel capable class loaders and are required to register themselves at their class initialization time by invoking the ClassLoader.registerAsParallelCapable method. Note that the ClassLoader class is registered as parallel capable by default. However, its subclasses still need to register themselves if they are parallel capable. In environments in which the delegation model is not strictly hierarchical, class loaders need to be parallel capable, otherwise class loading can lead to deadlocks because the loader lock is held for the duration of the class loading process (see loadClass methods).
        Normally, the Java virtual machine loads classes from the local file system in a platform-dependent manner. For example, on UNIX systems, the virtual machine loads classes from the directory defined by the CLASSPATH environment variable.
        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 defineClass converts an array of bytes into an instance of class Class. Instances of this newly defined class can be created using Class.newInstance.
        The methods and constructors of objects created by a class loader may reference other classes. To determine the class(es) referred to, the Java virtual machine invokes the loadClass method of the class loader that originally created the class

      • 对应说明:类加载器是负责加载类的对象。 ClassLoader类是一个抽象类。给定类的二进制名称,类加载器应尝试定位或生成构成类定义的数据。典型的策略是将名称转换为文件名,然后从文件系统中读取该名称的“类文件”。
        每个Class对象都包含对定义它的ClassLoader的引用。
        数组类的类对象不是由类加载器创建的,而是根据Java运行时的需要自动创建的。 Class.getClassLoader()返回的数组类的类加载器与其元素类型的类加载器相同;如果元素类型是基本类型,则数组类没有类加载器。
        应用程序实现ClassLoader的子类,以便扩展Java虚拟机动态加载类的方式。
        安全管理器通常可以使用类加载器来指示安全域。
        ClassLoader类使用委派模型来搜索类和资源。 ClassLoader的每个实例都有一个关联的父类加载器。当请求查找类或资源时,ClassLoader实例会在尝试查找类或资源本身之前,将对类或资源的搜索委托给其父类加载器。虚拟机的内置类加载器(称为“引导类加载器”)本身不具有父级,但可以作为ClassLoader实例的父级。
        支持并发加载类的类加载器称为并行加载类加载器,需要通过调用ClassLoader.registerAsParallelCapable方法在类初始化时注册自己。请注意,ClassLoader类默认注册为并行。但是,如果它们具有并行能力,它的子类仍然需要注册自己。在委托模型不是严格分层的环境中,类加载器需要具有并行能力,否则类加载会导致死锁,因为加载器锁在类加载过程的持续时间内保持(请参阅loadClass方法)。
        通常,Java虚拟机以与平台相关的方式从本地文件系统加载类。例如,在UNIX系统上,虚拟机从CLASSPATH环境变量定义的目录中加载类。
        但是,某些类可能不是源自文件;它们可能来自其他来源,例如网络,或者它们可以由应用程序构建。方法defineClass将字节数组转换为类Class的实例。可以使用Class.newInstance创建此新定义的类的实例。
        由类加载器创建的对象的方法和构造函数可以引用其他类。要确定所引用的类,Java虚拟机将调用最初创建该类的类加载器的loadClass方法

      • 定义自己的类加载器

        /*
            自定义类加载器
         */
        public class MyTest16 extends  ClassLoader{
            // 定义classLoaderName
            private String classLoaderName;
            // 定义文件后缀
            private final String fileExtension = ".class";
        
            public MyTest16(String classLoaderName){
                super();// 将系统类加载器当做该类加载器的父加载器
                this.classLoaderName = classLoaderName;
            }
        
            public MyTest16(ClassLoader parent,String classLoaderName){
                super(parent);// 显示指定该类加载器的父类加载器
                this.classLoaderName = classLoaderName;
            }
        
            @Override
            public String toString() {
                return "["+this.classLoaderName+"]";
            }
        
            @Override
            protected Class<?> findClass(String name) throws ClassNotFoundException {
                byte[] bytes = loadClassData(name);
                return this.defineClass(name,bytes,0,bytes.length);
            }
        
            private byte[] loadClassData(String name){
                InputStream is = null;
                byte[] bytes = null;
                ByteArrayOutputStream baos = null;
        
                try{
                    //this.classLoaderName = this.classLoaderName.replace(".","/");
                    name = name.replace(".","/");
                    is = new FileInputStream(new File(name+this.fileExtension));
                    baos = new ByteArrayOutputStream();
                    int ch = 0;
                    while (-1 != (ch = is.read())){
                        baos.write(ch);
                    }
                    bytes = baos.toByteArray();
                }catch (Exception e) {
                    e.printStackTrace();
                }finally {
                    try{
                        is.close();
                        baos.close();
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }
                return bytes;
            }
        
            public static void main(String[] args) throws Exception {
                MyTest16 load = new MyTest16(new MyTest16("parent"),"load1");
                test(load);
            }
        
            public static void test(ClassLoader classLoader) throws Exception{
                Class<?> aClass = classLoader.loadClass("com.lc.jvm.classloader.MyTest1");
                Object o = aClass.newInstance();
                System.out.println(o);
                System.out.println(aClass.getClassLoader());
            }
        }
        
    3. 命名空间

      • 每个类加载器都有自己的命名空间,命名空间由该加载器及所有父加载器所加载的类组成
      • 在同一个命名空间中,不会出现类的完整名字(包括类的包名)相同的两个类
      • 在不同的命名空间,有可能出现类的完整名字(包括类的包名)相同的两个类
    4. 类的卸载

      • 当MySample类被加载、连接和初始化后,它的生命周期就开始了。当代表MySample类的Class对象不再引用,即不可触及时,Class对象就会结束生命周期,MySample类在方法区内的数据也会被卸载。从而结束MySample类的生命周期
      • 一个类何时结束生命周期,取决于代表它的Class对象何时结束生命周期
      • 由java虚拟机自带的类加载器所加载的类,在虚拟机的生命周期中,始终不会被卸载。前面已经介绍过,java虚拟机自带的类加载器包括根类加载器、扩展类加载器、系统类加载器。java虚拟机本省会始终引用这些类加载器,而这些类加载器则会始终引用他们所加载的类的Class对象,因此这些Class对象始终是可触及的。
      • 由用户自定义的类加载器所加载的类可以被卸载
      • 说明:运行以上程序时,MySample类有loader加载,在类加载器的内部实现中,用一个java集合来存放所加载类的引用。另一方面,一个Class对象总会引用它的类加载器,调用Class对象的getClassLoader()方法,就能获取到它的类加载器。由此可见,代表Sample类的Class实例与Loader之间为双向关联关系。一个类的实例总是引用代表这个类的Class对象,在Object类中定义了getClass() 方法,这个方法返回代表对象所属类的Class对象的引用,此外,所有的java类都有一个静态属性class,它引用代表这个类的Class对象

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章