zoukankan      html  css  js  c++  java
  • 0032 Java学习笔记-类加载机制-初步

    JVM虚拟机

    • Java虚拟机有自己完善的硬件架构(处理器、堆栈、寄存器等)和指令系统
    • Java虚拟机是一种能运行Java bytecode的虚拟机
    • JVM并非专属于Java语言,只要生成的编译文件能匹配JVM对载入编译文件格式要求,任何语言都可以交由JVM运行,比如Scala、Groovy、Fantom等,见Java虚拟机维基百科
    • JVM虚拟机除了Sun开发的HotSpot外,还有BEA、IBM、微软、等公司都有开发。见《深入理解Java虚拟机(第二版)》
    • 查看自己用的JVM:cmd->java -version。我的是“Java HotSpot 64-Bit Server VM(build 25.92-b14 mixed mode)”。

    JVM和类

    • 当调用java命令运行一个java程序时,就启动了一个Java虚拟机进程,不论该程序多么复杂,占用多大的内存,始终处于该进程中
    • JVM进程合适终止?
      • 程序运行结束
      • 程序执行过程中,遇到未捕获的异常或者错误
      • 程序运行中调用了System.exit()或者Runtime.getRuntime().exit(),退出了虚拟机
      • 程序所在平台强制结束了JVM进程
    • 虚拟机何时加载一个类?
      • 第一次使用该类时
      • 预加载

    类的加载、连接、初始化

    • 当系统要使用某个类的时候,会将该类初始化,初始化依次包括加载、连接、初始化三步,一般说类的加载或类的初始化就包含了这三个步骤。
    • 类的加载
      • 概念:类加载器将.class字节码文件读入内存,并创建一个对应的java.lang.Class对象。加载进内存的每个类都有至少一个与之对象的Class对象
      • 类加载器有哪些?
        • Bootstrap ClassLoader:根类加载器,只有这个加载器不是用java语言写的;并且只有这个加载器不是ClassLoader的子类的实例
        • Extension ClassLoader:扩展类加载器
        • System ClassLoader:系统类加载器
        • 自定义类加载器:继承ClassLoader抽象类
      • 可以从哪些地方加载.class字节码文件?
        • 来源于本地文件系统
        • 来自于jar包中
        • 通过网络加载class文件
        • 把一个Java源文件动态编译,并执行加载。这个不懂?
    • 类的连接
      • 概念:负责把二进制数据合并到JRE中
      • 验证阶段:验证被加载的类是否具有正确的内部结构,并和其他类协调一致
      • 准备阶段:为类变量分配内存,并设置默认初始值
      • 解析阶段:将类的二进制数据中的符号引用替换为直接引用
    • 类的初始化
      • 类的初始化,主要是对类变量的初始化
      • 如果这个类还没有被加载和连接,那么先加载并连接。这个主要是针对父类
      • 如果这个类的直接父类还没有初始化,那就先初始化其父类
      • 依次执行类中的初始化语句
      • 因此最先被初始化的类总是java.lang.Object。参见:0023 Java学习笔记-面向对象-初始化代码块
    • JVM在何时初始化一个类
      • 一般说来,在JVM首次使用一个类时,对该类给予初始化,具体包含以下六种情况
        • 创建一个类的实例时:new操作符;反射;反序列化
        • 调用一个类的类方法
        • 访问一个类或接口的类变量
        • 用反射方式来强制创建一个类的Class对象:Class.forName("className");注意ClassLoader的loadClass()方法只会加载而不会初始化该类
        • 某个类的子类被初始化时,该类也会被初始化
        • 直接用java.exe运行某个主类,先初始化该主类。不懂?
      • 不会初始化的情况
        • 宏变量:static final变量,并且能在编译阶段就确定它的值。一个类使用另一个类的宏变量,另一个类不会被初始化。
        • 示例代码01:访问宏变量不会初始化它所在的类

    示例代码01:访问宏变量不会初始化它所在的类

    package testpack;
    public class Test1{  
        public static void main(String[] args){ 
           System.out.println(A.num);  //输出8,没有输出“A类被初始化”,A类没有被初始化
           System.out.println(B.num);  //输出“B类被初始化”;21;B类被初始化
        }
    }
    class A{
    	static{
    		System.out.println("A类被初始化");
    	}
    	public static final int num=8;  //num的值在编译阶段就能确定下来
    }
    class B{
    	static{
    		System.out.println("B类被初始化");
    	}
    	public static final int num=8+Integer.valueOf(13); //num的值不能在编译阶段确定下来
    }
    

    类的加载器

    • 如何标识一个被载入JVM的类?
      • 类名+包名+类加载器名
    • 类加载器的层次结构
      • Bootstrap ClassLoader:根类加载器,
        • 没有父加载器;由C++写成,其他加载器都是Java写成;
        • 负责加载Java核心类库;也就是系统属性sun.boot.class.path的值表示的路径下的包;
        • (HotSpot?)可以在java.exe中用-D参数指定系统属性sun.boot.class.path的值,从而加载指定的附加类;
        • 见示例02:获取根类加载器加载的核心类库
      • Extension ClassLoader:扩展类加载器
        • 没有父加载器(实际上就是根类加载器?);由Java写成,是ClassLoader的子类;
        • 负责加载扩展目录JAR包中的类,即系统属性java.ext.dirs或者%JAVA_HOME%/jre/lib/ext
        • 因此可以把自己开发的类,打包成JAR包,放在该目录下
        • 见示例03:扩展类加载器的加载目录,父加载器
      • System ClassLoader:系统类加载器
        • 父加载器是扩展类加载器;由Java写成,是ClassLoader的子类;
        • 也是用户自定义的类加载器的默认父加载器,如果不特别指定的话。
        • 负责加载系统属性java.class.path系统CLASSPATH环境变量指定的目录中的类;
        • Java命令的-classpath参数可以临时指定CLASSPATH的路径
        • 示例04:系统类加载器的加载路径
      • 自定义类加载器:
        • 继承ClassLoader抽象类。
        • 默认的父加载器是系统类加载器;在自定义的时候,可以在一个方法中指定。见java的类加载器ClassLoader
      • 上面说的父加载器,并不是类的继承关系,而是加载器间实例间的关系,就是说在一个加载器中可以定义它的父加载器。

    示例代码02:根类加载器加载的核心类库

    package testpack;
    import java.net.URL;
    public class Test1{  
        public static void main(String[] args)throws ClassNotFoundException{ 
           URL[] urls=sun.misc.Launcher.getBootstrapClassPath().getURLs(); //获取根类加载器加载的全部URL数组
           for (int i=0;i<urls.length;i++){
        	   System.out.println(urls[i].toExternalForm());
           }
           System.out.println("-------------下面是系统属性(sun.boot.class.path)的值---------------");
           System.out.println(System.getProperty("sun.boot.class.path"));
        }
    }
    

    输出:

    file:/C:/Java/jdk1.8.0_92/jre/lib/resources.jar
    file:/C:/Java/jdk1.8.0_92/jre/lib/rt.jar //核心类库java.lang.*位于该jar包中
    file:/C:/Java/jdk1.8.0_92/jre/lib/sunrsasign.jar
    file:/C:/Java/jdk1.8.0_92/jre/lib/jsse.jar
    file:/C:/Java/jdk1.8.0_92/jre/lib/jce.jar
    file:/C:/Java/jdk1.8.0_92/jre/lib/charsets.jar
    file:/C:/Java/jdk1.8.0_92/jre/lib/jfr.jar
    file:/C:/Java/jdk1.8.0_92/jre/classes
    -------------下面是系统属性(sun.boot.class.path)的值---------------
    file:/C:/Java/jdk1.8.0_92/jre/lib/resources.jar
    file:/C:/Java/jdk1.8.0_92/jre/lib/rt.jar
    file:/C:/Java/jdk1.8.0_92/jre/lib/sunrsasign.jar
    file:/C:/Java/jdk1.8.0_92/jre/lib/jsse.jar
    file:/C:/Java/jdk1.8.0_92/jre/lib/jce.jar
    file:/C:/Java/jdk1.8.0_92/jre/lib/charsets.jar
    file:/C:/Java/jdk1.8.0_92/jre/lib/jfr.jar
    file:/C:/Java/jdk1.8.0_92/jre/classes

    示例03:扩展类加载器的加载目录,父加载器

    package testpack;
    public class Test1 {  
        public static void main(String[] args){
        	ClassLoader systemLoader=ClassLoader.getSystemClassLoader();   //获取系统类加载器
        	System.out.println("这是系统类加载器: "+systemLoader);        //输出系统类加载器
        	ClassLoader extensionLoader=systemLoader.getParent();          //获取扩展类加载器
        	System.out.println("这是扩展类加载器: "+extensionLoader);     //输出扩展类加载器
        	System.out.println("扩展类的父加载器: "+extensionLoader.getParent());  //获取扩展类加载器的父加载器null
        	System.out.println("扩展类的加载路径: "+System.getProperty("java.ext.dirs"));  //获取系统属性java.ext.dirs的值
        } 
    }
    

    输出:

    这是系统类加载器: sun.misc.Launcher$AppClassLoader@73d16e93 //说明系统类加载器是AppClassLoader的实例
    这是扩展类加载器: sun.misc.Launcher$ExtClassLoader@15db9742 //说明扩展类加载器是ExtClassLoader的实例
    扩展类的父加载器: null //以上二者都是URLClassLoader的实例
    扩展类的加载路径: C:Javajdk1.8.0_92jrelibext;C:windowsSunJavalibext

    示例04:系统类加载器的加载路径

    package testpack;
    
    import java.io.IOException;
    import java.net.URL;
    import java.util.Enumeration;
    import java.lang.ClassLoader;
    
    public class Test1 {  
        public static void main(String[] args)throws IOException{ 
         ClassLoader systemLoader=ClassLoader.getSystemClassLoader();  //获取系统类加载器
         System.out.println("系统类加载器: "+systemLoader);           //输出系统类加载器
         Enumeration<URL> eml=systemLoader.getResources("");           //遍历其加载路径
         while(eml.hasMoreElements()){
             System.out.println(eml.nextElement());
         }
         System.out.println("系统属性java.class.path的值: "+System.getProperty("java.class.path"));
        }
    }
    

    输出:

    系统类加载器: sun.misc.Launcher$AppClassLoader@73d16e93
    file:/D:/JavaWorkspace/Test/bin/
    系统属性java.class.path的值: D:JavaWorkspaceTestin

    类的加载机制

    • 全盘负责制:
      • 当一个类加载器加载一个类时,该类依赖和引用的其他类,也有这个类加载器负责
    • 父类委托:
      • 先让父加载器加载这个类,如果加载不了,再从自己的类路径中加载。不懂?,一般说来,不同的加载器的加载路径都不同吗?
    • 缓存机制:
      • 要使用一个类时,先从缓存中查找,如果已经有了,就不另行加载,直接返回;如果没有,再加载

    类加载器的8个步骤

    • 要加载的类是否已被加载?
        • 如果父类加载器不存在,那么请求使用根类加载器加载
          • 成功:返回Class对象
          • 失败:抛出ClassNotFoundException
        • 如果父类加载器存在,请求使用父加载器加载
          • 成功:返回Class对象
          • 失败:当前类加载器载入类
            • 当前类寻找加载
              • 成功:返回Class对象
              • 失败:抛出ClassNotFoundException
      • 是:返回其Class对象
    • 总的来说,先在缓存中找;再让父加载器加载,一直到没有父加载器,就用根加载器加载,如果父加载器失败,那么自己加载,还失败,那就抛出异常:ClassNotFoundException

    自定义类加载器

    • 继承结构
    • java.lang.Object
      • java.lang.ClassLoader
        • java.security.SecureClassLoader
          • java.net.URLClassLoader
            • sun.misc.Launcher$ExtClassLoader //扩展类加载器就是这个类的实例
            • sun.misc.Launcher$AppClassLoader //系统类加载器就是这个类的实例
    • 所有的类加载器中,除了根加载器外,都是ClassLoader子类的实例。
    • ClassLoader类包含了一些protected和static方法
    • ClassLoader的主要方法:
      • protected Class<?> loadClass(String name,boolean resolve);
        • 根据指定名称加载类,返回指定类的Class对象
        • 自定义类加载器时,最好不要重写该方法
        • 执行步骤:
          • 用findLoadedClass(String)检查是否已加载
          • 在父加载器上调用loadClass()方法,若父加载器为null,则用根加载器加载
          • 用findClass(String)查找类
      • protected Class<?> findClass(String name);
        • 根据名称查找类
        • 自定义类加载器时,一般只重写这个方法
      • protected final Class<?> defineClass(String name, byte[] b, int off, int len)
        • 将指定的字节码文件读入byte数组,转换为Class对象
        • final,不可重写
      • protected final Class<?> findSystemClass(String name)
        • 从本地文件系统装入字节码文件
      • static ClassLoader getSystemClassLoader()
        • 返回系统类加载器
      • final ClassLoader getParent()
        • 返回该加载器的父加载器
        • final,不可重写
      • protected final void resolveClass(Class<?> c)
        • 链接指定的类c
        • 不懂?
      • protected final Class<?> findLoadedClass(String name)
        • 如果已经加载了名为name的类,则返回其Class实例

    其他

    • 关于类加载器实例、类加载器实例的类、Class对象、普通类、普通类的对象
      • 简单的说,加载器是实例,他们的类有AppClassLoader和ExtClassLoader,都是URLClassLoader的子类
      • ExtClassLoader类的实例就是扩展类加载器,ExtClassLoader自身在加载的时候也有Class对象
      • AppClassLoader类的实例就是系统类加载器,AppClassLoader自身在加载的时候也有Class对象
      • ExtClassLoader和AppClassLoader二者没有继承关系,只是前者的实例是后者的实例的父加载器
      • 系统类加载器,加载一个普通类,创建对应的Class对象,连接,初始该普通类,再创建实例
      • 看下面的示例代码
    • 什么情况下需要自定义类加载器?见 JVM——自定义类加载器 CSDN
    • 参考:深入探讨Java类加载器 IBM 成富
    package testpack;
    public class Test1 {  
        public static void main(String[] args){ 
        	A a=new A();          //创建普通类A的实例a
        	a.show();             //调用a的show()方法
        	ClassLoader cl=a.getClass().getClassLoader(); //通过实例a得到A类的Class对象,再获得该Class对象的加载器
        	System.out.println("实例a的类A的Class对象的加载器是: "+cl);  //输出加载器,是AppClassLoader类的实例
        	Class appClazz=cl.getClass();  //通过系统类加载器实例获得它的类的Class对象
        	while(appClazz!=null){
        		System.out.println(appClazz);  //输出该Class对象
        		appClazz=appClazz.getSuperclass();   //获得这个Class对象的父类的Class对象,一直到Object没有父类
        	}
        }
    }
    class A{
    	public void show(){
    		System.out.println("这是A类的实例");
    	}
    }
    

    参考资料:

  • 相关阅读:
    Chrome 中的彩蛋,一款小游戏,你知道吗?
    Json对象与Json字符串互转(4种转换方式)
    [PHP自动化-进阶]005.Snoopy采集框架介绍
    [PHP自动化-进阶]004.Snoopy VS CURL 模拟Discuz.net登陆
    [PHP自动化-进阶]003.CURL处理Https请求访问
    [PHP自动化-进阶]002.CURL模拟登录带有验证码的网站
    [PHP自动化-进阶]001.CURL模拟登录并采集数据
    [注]2015中国程序员生存报告,你苦你先看@^@
    [JavaWeb基础] 016.Struts2 国际化配置
    [工具推荐]_iOS音频批量转换
  • 原文地址:https://www.cnblogs.com/sonng/p/6110123.html
Copyright © 2011-2022 走看看