深入理解JVM-类加载及类加载器

软件发布|下载排行|最新软件

当前位置:首页IT学院IT技术

深入理解JVM-类加载及类加载器

我不是铁杆啊   2020-02-13 我要评论

深入理解JVM

2020年02月06日22:43:09 - 记录学习过程

终于开始了。在学习这个之前,看了zhanglong老师的 java 8 和springboot

迫不及待了。先开始吧。

写在前边

论方法论

听说之前还有netty 和 kotlin 。学习风格就是,每一门课程之前,前两节课不进入主题,讲方法论。

从他人身上学习优点。加强自己的学习。从人去学习,从事去学习。我们只有亲身经历一件事情,才会产生自己的想法。从事情学习付出的成本会相对的高一点。只有一件事,你失败了,才会发现你存在什么问题。从过程中吸收一点经验,指导着你未来学习前进的方向。从人去学习来说,不是你自己亲身经历的,要学习辨别能力。为什么大家在看书的时候,看书的印象不如你自己操作的印象深刻呢?这些都是值得去思考的。更为高效的方式,还是看别人的故事,揣摩自己的人生。将别人拥有的技能转换成自己的技能,这样才是高效的学习。

学习的过程中,一定要做到两点。

  1. 输入。输入是必要的。
  2. 有输入,必定要有输出。记笔记,写博客,给别人去讲。

能给别人讲明白的前提是自己要明白。自己明白了,就一定能给别人讲明白了吗?你自己明白,给别人讲完之后,你自己也不明白了(值得反思一下,之前学习消耗的时间是否是高效的)。

每个人都值得去自己思考这个问题。为什么当时学习的时候学习的非常透彻,过一段时间跟没学过一样?产生这种问题的根源是什么呢?为什么会产生这样的问题?《后会无期》电影。它是没有标准答案的。之所以看了很多书,看了很多视频,看了很多资料,为什么没有产生持久化的效果?什么是持久化,他要最终落实到一个存储上边。在学习技术的时候,不断的在脑海中去过滤。在当时看的时候会认识的非常时刻。只有输入,没有输出,肯定保存的时间不会太长。什么是输出呢?在学习的过程中,一定要把输入的东西给吐出来,选择一种最适合你的输出方式。就像人呼吸,如果光吸气,不吐气,人早就憋死了。所以输入和输出一定是同时存在的。通过输出,把当时学习到的知识点,以文字,图表,思维导图的方式给呈现出来。做到这一点,你会很难忘掉你学过的重要的内容。做项目的过程也是很重要的输出过程,不经意间,就记住了。刻意练习,针对某一门技术有意为之。刻意输出,当你忘记的时候,回顾一下,会很快的回忆起你之前的学习过程。

即便你在多大的公司,你了解到的技术,用到的技术只占冰山一角。不要把个人绑死在一个上面。当你脱离当前工作时,你还有竞争力才是有用的。不能说项目用什么学什么。不用什么不学什么。那简直就不是程序员。学习技术,百里无一害。就算你现在用不到,你觉得重要的东西一定要去学,去输出。可以做一个实验。半天时间各学一个框架,一个记笔记,一个不记笔记。可以看一下实验结果。学习视频是一种很好的方式,不建议多次去重复的看视频。把视频中的技术要点吸收成你自己的东西才是重要的。视频不能用来检索。

印象笔记,有道云笔记,小小的工具,可能给你生活带来很大的改变。甚至改变你的一生。讲课是一件非常辛苦的事情。平时的工作中就深有体会。

论学习曲线

如果去学习JVM,每一个来学习JVM的人,都渴望成功。每一个Java开发人员的终极目标都是在日常生活中深入理解JVM的运行原理。JVM和平时的应用框架明显的区别,应用框架学习之后,可以直接拿来写项目了,就可以运行起来看到helloworld。然而对于JVM,是一个特别枯燥的事情。涵盖的内容太多了。本次根据java8来学习。必须要笔记。因为一扭头就会忘记。绝对没有速成的,突击的。有节奏,有计划的去学习。关于这门技术,范围太广,从通用的层面来进行学习。

推荐一些学习资料:一边学习视频,一边学习资料;

《深入理解Java虚拟机》 , 《深入Java虚拟机》 , R大

可能会遇到的问题:

  1. 学习了十几节课,感觉什么都没有学到。学到的东西在平时用不上。
  2. 学习完JVM之后,可能会感觉,学的越多,不知道的越多。大功已成。以前没听过的,现在听过了。
  3. 学习JVM,期间学习的文档全是英文的。阅读能力制约了你对JVM的学习。
  4. 耐心。任重而道远。看不到曙光的那种。没有案例让你来做。沉下心来,有的放矢的推进。

开发过程中遇到问题是常态。如果遇到了JVM崩溃,就算你拿到了日志,你也不能定位到问题是什么。对于原理,对于基础的学习,能够增强我们的自信心。

课程大纲

介绍:JVM是一个令人望而却步的领域,因为它博大精深,涉及到的内容与知识点非常之多。虽然Java开发者每天都在使用JVM,但对其有所研究并且研究深入的人却少之又少。然而,JVM的重要性却又是不言而喻的。基于JVM的各种动态与静态语言生态圈已经异常繁荣了,对JVM的运行机制有一定的了解不但可以提升我们的竞争力,还可以让我们在面对问题时能够沉着应对,加速问题的解决速度;同时还能够增强我们的自信心,让我们更加游刃有余。

  • JVM介绍
  • HotSpot虚拟机讲解
  • 垃圾收集方式详解
  • 垃圾收集算法详解
  • 垃圾收集器详解
  • 分代垃圾收集机制详解
  • 新生代讲解
  • 老年代讲解
  • G1收集器分析与实例
  • 常见且重要虚拟机参数示例
  • 方法区
  • 线程共享内存区
  • 根搜索算法
  • Serial收集器
  • ParNew收集器
  • 类加载机制详解
  • 类加载的双亲委托机制
  • 字节码文件生成与分析
  • 魔数
  • 常量池与方法表
  • 各种指令详解
  • 锁详解
  • 线程安全
  • 偏向锁、自旋锁与轻量级锁
  • JIT编译器
  • GC日志生成与分析
  • 虚拟机监控工具详解
  • jConsole使用方式详解
  • 何为逃逸与逃逸分析
  • 方法内联
  • 虚拟机内存模型详解
  • = = = = = = = = = = = =

以前都不知道这些工具的存在:

jConsole

Jvusualvm

jmap

类加载

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

    (类型,并不代表类产生的对象,而是类本身。类型是在程序运行期间生成出来的,run time)

  • 提供了更大的灵活性,增加了更多的可能性

    (为有创意的开发者提供了很多的功能。)

类加载器深入剖析

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

类的加载、连接与初始化

  • 加载: 查找并加载类的二进制数据
  • 连接
    • -验证:确保被加载的类的正确性
    • -准备:为类的静态变量分配内存,并将其初始化为默认值
    • -解析:把类中的符号引用转换为直接引用
    • 初始化:为类的静态变量赋予正确的初始值
  • 使用
  • 卸载
从代码来理解: 
class Test{
  public static int a = 1;
}
//我们程序中给定的是  public static int a = 1;
//但是在加载过程中的步骤如下:

1. 加载阶段
  编译文件为class文件,然后通过类加载,加载到JVM
  
2. 连接阶段
 第一步(验证):确保Class类文件没问题
  第二步(准备):先初始化为 a=0。(因为你int类型的初始值为0)
 第三步(解析):将引用转换为直接引用

3. 初始化阶段:
  通过此解析阶段,把1赋值为变量a

4. 使用阶段
  我们平时使用的对象,操作,方法调用,等等都是使用阶段
  
5. 卸载阶段
  类在卸载之后,就不能够继续new对象,平时开发很少接触到这个卸载阶段。比如-OSGI技术会使用到卸载

图解:

  • Java程序对类的使用方式可分为两种
    • 主动使用(七种)
      • 创建类的使用
      • 访问某个类或者接口的静态变量,或者对该静态变量赋值
      • 调动类的静态方法(助记符: getstatic putstatic invokestatic )
      • 反射(如:Class.forName("com.test.Test"))
      • 初始化一个类的子类
      • Java虚拟机启动时被表明为启动类的类(Java Test)
      • JDK1.7开始提供的动态语言支持
    • 被动使用
      • 除了以上七种主动使用的情况,其他使用Java类的方式都被看做是对类的被动使用,都不会导致类的初始化。
  • 所有Java虚拟机实现必须在每个类或者接口被Java程序“首次主动使用”时才初始化他们

类的加载

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

  • 加载.class文件的方式
    • 从本地系统中直接加载
    • 通过网络下载.class文件
    • 从zip,jar等归档文件中加载.class文件
    • 从专有数据库中提取.class文件
    • 将Java源文件动态编译为.class文件(动态代理,web开发jsp转成servlet)
/*
举例说明:
    对于静态字段来说,只有直接定义了该字段的类才会被初始化;
    当一个类在初始化是,要求其父类全部都已经初始化完毕了;
    -XX:+TraceClassLoading,用于追种类的加载信息并打印出来。
    
    所有的参数都是:
    -XX:+<option> , 表示开启option选项
    -XX:+<option> ,表示关闭option选项
    -XX:+<option>=<value>  表示将option选项的值设置为value
    
*/
public class MyTest1 {
    public static void main (String[] args){
        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 = "welcome";
    static{
        System.out.println("MyChild1 static block");
    }
}

输出结果:
> Task :MyTest1.main()
MyParent1 static block
MyChild1 static block
welcome

查看类的加载信息,并打印出来。

jvm 参数介绍:
-XX:+

常量池的概念:

/*
  常量在编译阶段会存入到调用这个常量的方法所在的类的常量池中,本质上,调用类并没有直接用用到定义常量的类,因此并不会触发定义常量的类的初始化。
  注意:这里指的是将常量存放到了MyTest2的常量池中,之后MyTest2与MyParent2就没有任何关系了。
  甚至,我们可以将MyParent2的Class文件删除

*/
public class MyTest2{
  public static void main(String[] args){
    System.out.println(MyParent2.str);
  }
}

class MyParent2{
   public static final String str = "hello world";
   public static final short s = 127;
   public static final int a = 3;
   public static final int m = 6;
  
   static {
    System.out.println("Myparent2 static block");// 这一行能输出吗?不会
  }
}

反编译:

javap -c com.erwa.jvm.class.mytest2

反编译之后会有助记符。

助记符:

  • ldc表示将int、float或是String类型的常量值从常量池中推送至栈顶。

  • bipush表示将单字节(-128~127)的常量值推送至栈顶。
  • sipush表示将短整型(-32767~32768)的常量值推送至栈顶。
  • inconst_1表示将int类型1推送至栈顶 (inconst_m1 ~inconst_5)。
  • anewarray 表示创建一个引用类型(如类,接口,数组)的数组,并将其引用值推至栈顶。
  • newarray 表示创建一个指定的原始类型(如int,float,char等)的数组,并将其引用值推至栈顶。

类的初始化规则

/*
* 当一个常量的值并非编译期间可以确定的,那么其值就不会被放到调用类的常量池中,
    这是在程序运行时,会导致主动使用这个常量所在的类,显然就会导致这个类被初始化。
*/
public class MyTest3{
  public static void main(String[] args){
    System.out.println(MyParent3.str);
  }
}

class MyParent3{
   public static final String str = UUID.randomUUID().toString();
   
   static {
    System.out.println("Myparent3 static block");  // 这一行能输出吗?会
  }
}

为什么第二个例子不会输出,第三个例子就输出了呢? 
因为第三个例子的值,是只有当运行期才会被确定的值。而第二个例子的值,是编译时就能被确定的值。
public class MyTest4{
  public static void main(String[] args){
   MyParent4 myParent4 = new MyParent4(); 
  }
}

class MyParent4{
   static {
    System.out.println("Myparent4 static block");  // 这一行能输出吗?会
  }
}
因为MyParent4 myParent4 = new MyParent4();  属于主动使用的第一条,类的使用。
/**
    对于数组实例来说,其类型是由JVM在运行期间动态生成的,表示为 [Lcom.erwa.MyTest4
    这种形式,动态生成的类型,其父类型就是Object
    
*/
public class MyTest4{
  public static void main(String[] args){
   MyParent4[]  myParent4s = new MyParent4[1]; 
        System.out.println(myParent4s.getClass());
        System.out.println(myParent4s.getClass().getSuperclass());
  }
}

class MyParent4{
   static {
    System.out.println("Myparent4 static block");  // 这一行能输出吗?不会
  }
}

因为 MyParent4[]  myParent4s = new MyParent4[1];  并不属于主动使用的方式。
  
> Task :MyTest4.main()输出结果为:
class [Lcom.erwa.jvm.MyParent4;
class java.lang.Object
                int[] ints = new int[1];
        System.out.println(ints.getClass());

        boolean[] booleans = new boolean[1];
        System.out.println(booleans.getClass());

        short[] shorts = new short[1];
        System.out.println(shorts.getClass());

        double[] doubles = new double[1];
        System.out.println(doubles.getClass());

class [I
class [Z
class [S
class [D

接口的初始化规则

接口本身的成员变量 都是 public static final 的

/**
    当一个接口在初始化时,并不要求其父接口都完成了初始化。
    只有在真正使用到父接口的时候(如 引用接口中所定义的常量时),才会初始化。
*/
public class MyTest5{
  public static void main(String[] args){
    System.out.println(myParent5.b);
  }
}

interface MyParent5{
  public static int a = 5;
}

interface MyChild5 extends MyParent5{
  public static int b = 6;
}

/**
class MyChild5 implements MyParent5{
  public static int b = 6;
}
**/

类的初始化顺序

/*
    这个例子很好的阐述了类初始化的顺序问题。
*/
public class MyTest6{
    public static void main(String[] args) {
        Singleton singleton = Singleton.getInstance();
        System.out.println("counter1:"+Singleton.counter1);
        System.out.println("counter2:"+Singleton.counter2);
    }
}

class Singleton{
    public static int counter1 = 1;
  
    private static Singleton singleton= new Singleton();
    private Singleton(){
        counter1++;
        counter2++;   // 准备阶段的重要意义
        System.out.println(counter1);
        System.out.println(counter2);
    }
  
    public static int counter2 = 0;
  
    public static Singleton getInstance(){
        return singleton;
    }
}

> Task :MyTest6.main()
2
1  
counter1:2
counter2:0

类加载的加载顺序:

类加载的概念回顾

  • 类的加载

  • 类的加载的最终产品是位于内存中的Class对象
  • Class对象封装了类在方法去内的数据结构并且向Java程序员提供了访问方法区内的数据结构的接口。
  • 有两种类型的类加载器

  • Java虚拟机自带的加载器
    • 根类加载器(BootStrap)(BootClassLoader)
    • 扩展类加载器(Extension)(ExtClassLoader)
    • 系统(应用)类加载器(System)(AppClassLoader)
  • 用户自定义的类加载器
    • Java.long.ClassLoader的子类
    • 用户可以定制类的加载方式
  • 类加载器并不需要等到某个类被“首次使用”时再加载它。

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

依次执行初始化语句:按照CLass类文件中的顺序加载静态方法和静态代码块。

重点介绍一下上图的概念。举例说明。

  • 在初始化一个接口时,并不会先初始化它的父接口。
// 当一个类初始化时,它实现的接口是不会被初始化的。
public class MyTest5{
  public static void main(String[] args){
    System.out.println(MyChild5.b);
  }
}

interface MyParent5{
  public static Thread thread = new Thread(){
    // 每次被实例化的时候都会执行下方的代码块。 如果是 static{} 的时候,只会被加载一次。
    {
      System.out.println("myParent5 invoked");
    }    
  }
}

class MyChild5 implements MyParent5{
  public static int b = 6;
}
// 在初始化一个接口时,并不会先初始化它的父接口
public class MyTest5{
  public static void main(String[] args){
    System.out.println(MyParent5_1.thread);
  }
}

interface MyParent5{
  public static Thread thread = new Thread(){
    {
      System.out.println("myParent5 invoked");
    }    
  }
}

interface MyParent5_1 extends MyGrandpa5_1{
  public static Thread thread = new Thread(){
    {
      System.out.println("myParent5 invoked");
    }    
  }
}

调用CLassLoader类的loadCLass方法加载一个类,并不是对类的主动使用,不会导致类的初始化。

类加载器

类加载器深度剖析

下图:不是继承关系,是包含关系。

graph TD

根类加载器 --> 扩展类加载器

扩展类加载器 --> 系统类加载器

系统类加载器 --> 用户自定义类加载器

线程上下文类加载器 作用:打破双亲委托机制。加载SPI提供的类。

类加载器的双亲委托机制

一层一层的 让父类去加载,最顶层父类不能加载往下数,依次类推。

能够成功加载自定义类加载器的系统类加载器 被称为定义类加载器

所有能够返回Class对象引用的类加载器都被称为初始类加载器

public class MyTest7 {
    public static void main(String[] args) throws ClassNotFoundException {
        Class<?> c = Class.forName("java.lang.String");
        System.out.println(c.getClassLoader()); // 返回针对于这个类的类加载器

        Class<?> c1 = Class.forName("com.erwa.jvm.C");
        System.out.println(c1.getClassLoader());
    }
}

class C{

}

> 输出结果为:
> Task :MyTest7.main()
null 
sun.misc.Launcher$AppClassLoader@659e0bfd
getClassLoader()的 Javadoc: 
java.lang.Class<T> public ClassLoader getClassLoader()
Returns the class loader for the class. Some implementations may use null to represent the bootstrap class loader. This method will return null in such implementations if this class was loaded by the bootstrap class loader.
  
 // 返回类的类加载器。一些实现可能使用null来表示引导类加载器。如果这个类是由引导类加载器加载的,那么这个方法在这样的实现中将返回null。
  
If a security manager is present, and the caller s class loader is not null and the caller s class loader is not the same as or an ancestor of the class loader for the class whose class loader is requested, then this method calls the security manager s checkPermission method with a RuntimePermission("getClassLoader") permission to ensure its ok to access the class loader for the class.
If this object represents a primitive type or void, null is returned.
//如果此对象表示基本类型或void,则返回null。

Returns:
the class loader that loaded the class or interface represented by this object.
Throws:
SecurityException – if a security manager exists and its checkPermission method denies access to the class loader for the class.

对于final的理解深入

class FinalTest{
    public static final int x=3;  // final的话 就是编译期间的常量
  static {
    System.out.println("FinalTest stataic block");  // 会输出吗?不会
  }
}

public class MyTest8 {
    public static void main(String[] args){
        System.out.println(FinalTest.x); 
    }
}

类的初始化顺序举例练习

class Parent {
    static int a = 3;
    static {
        System.out.println("parent stataic block ");
    }
}

class Child extends Parent{
    static int b = 4;
    static {
        System.out.println("child static block ");
    }
}

public class MyTest9 {
    static {
        System.out.println("Mytest9 static block ");
    }
    public static void main(String[] args){
        System.out.println(Child.b);
    }
}

> 输出结果:
> Task :MyTest9.main()
Mytest9 static block 
parent stataic block 
child static block 
4
  
类的加载顺序: 
1. MyTest9
2. Parent 
3. Child 
// 这是一个好例子。  明白了。 类初始化的顺序    的练习。

// 每一个类,在初始化的时候,只会被初始化一次。
class Parent2 {
    static int a = 3;
    static {
        System.out.println("parent2 stataic block ");
    }
}

class Child2 extends Parent2{
    static int b = 4;
    static {
        System.out.println("child2 static block ");
    }
}

public class MyTest10 {
    static {
        System.out.println("MyTest10 static block ");
    }
    public static void main(String[] args){
      Parent2 parent2;  // 不属于主动使用
      System.out.println("- - -- - - ");
        parent2 = new Parent2();  // 主动使用, 初始化 Parent2 
      System.out.println("- - -- - - ");
        System.out.println(parent2.a);
      System.out.println("- - -- - - ");
        System.out.println(Child2.b);
    }
}

> 输出结果:
> Task :MyTest10.main()
MyTest10 static block 
- - -- - - 
parent2 stataic block 
- - -- - - 
3
- - -- - - 
child2 static block 
4 
// 使用子类,访问父类中的方法。 等于是主动使用父类,而不是主动使用子类。
class Parent3 {
    static int a = 3;
    static {
        System.out.println("parent3 stataic block ");
    }
    static void doSomething(){
        System.out.println("do something ")
    }
}

class Child3 extends Parent3{
    static int b = 4;
    static {
        System.out.println("child3 static block ");
    }
}

public class MyTest11 {
  
    public static void main(String[] args){
      System.out.println(Child3.a);  // a 是定义在父类中的, 等于是 主动使用父类
      Child3.doSomething();
    }
}

> 预计结果:
> Task :MyTest11.main()
parent3 static block 
do something 
child3 static block
3
do something 

> 实际结果:  
> Task :MyTest11.main()
parent3 stataic block 
3
do something 
  
错误原因:  a 是定义在父类中的, 等于是 主动使用父类;并没有主动使用Child
//  调用ClassLoader类的loadClass方法加载一个类,并不是对类的主动使用,不会导致类的初始化
//  反射,是对类的主动使用,会初始化类
class CL{
    static {
        System.out.println("Class CL ");
    }
}

public class MyTest12 {

    public static void main(String[] args) throws ClassNotFoundException {
        ClassLoader loader = ClassLoader.getSystemClassLoader(); 
        Class<?> clazz = loader.loadClass("com.erwa.jvm.CL");// 不会初始化CL类
        System.out.println(clazz);
        System.out.println("- - - - - -");
        clazz = Class.forName("com.erwa.jvm.CL"); // 反射会初始化CL类
        System.out.println(clazz);
    }
}

> 输出结果:
> Task :MyTest12.main()
class com.erwa.jvm.CL
- - - - - -
Class CL 
class com.erwa.jvm.CL

2020年02月09日09:41:01 继续吧。--- 今天是周末。原来说的要加班。不加了。学习

类加载器的层次关系

public class MyTest13{
  psvm{
    ClassLoader classLoader = ClassLoader.getSystemClassLoader();
    sout(classLoader);
    while (null != classLoader){
      classLoader = classLoader.getParent();
      sout(classLoader);
    }
  }
}

> 输出结果:
> Task :MyTest13.main()
sun.misc.Launcher$APPClassLoader    应用类加载器
sun.misc.Launcher$ExtClassLoader  扩展类加载器
null  根类加载器
//如何通过给定的字节码路径,打印出类的信息
public class MyTest14{
  psvm{   //上下文类加载器是由当前的线程提供的
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    String resourceName = "com/erwa/jvm/MyTest13.class";
    
    Enumeration<URL> urls = classLoader.getResources(resourceName);
    while(urls.hasMoreElements()){
      URL url = urls.nextElement();
      sout(url); // 输出结果为 资源在本机上的完整路径。
    }
  }
}

获得ClassLoader的途径

  • 获得当前类的CLassLoader

    clazz.getClassLoader();

  • 获得当前线程上下文的ClassLoader

    Thread.currnetThread().getContextClassLoader();

  • 获得系统的ClassLoader

    CLassLoader.getSystemClassLoader();

  • 获得调用者的ClassLoader

    DriberManager.getCallerClassLoader();

sun.misc.Launcher$APPClassLoader    应用类加载器
sun.misc.Launcher$ExtClassLoader  扩展类加载器
null  根类加载器

  比如: Class<?> clazz = String.class; 
       sout(clazz.getClassLoader()); //打印结果为 null . 因为是系统的类。
             Class<?> clazz = MyTest14.class; 
       sout(clazz.getClassLoader()); //打印结果为 APPClassLoader . 因为自定义的类。

ClassLoader类源码剖析

ClassLoader的Javadoc:
java.lang public abstract class ClassLoader  extends  Object
   
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.
/* 
一个类加载器是一个对象,用于加载类。 ClassLoader是一个抽象类,如果我们给了二进制的名字,ClassLoader应该尝试生成一些数据(构成了这个类定义的数据)。 一种典型的策略是将二进制名字转换成文件名字,然后从系统中读取这个文件。
*/
Every Class object contains a reference to the ClassLoader that defined it.
// 每一个Class对象都会包含 定义这个类的类加载器对象
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对象,并不是由类加载器创建的,而是由Java运行时根据需要自动创建的(其他类是由类加载器创建的)(数组类型不会导致类的初始化)。对于一个数组类的ClassLoader来说,返回结果是数组当中元素的结果是一样的。如果数组中的元素是原生类型的,那么此时数组类是没有类加载器的。
应用程序实现类装入器的子类,以扩展Java虚拟机动态加载类的方式。
*/
  
- - - - -- - 数组类的类加载器举例: - - - -- - - 
public class MyTest15{
  psvm{
    String[] strings = new String[2];
    sout(strings.getClass().getClassLoader()); // 输出结果为null ; 因为是String 的类加载器(此时的null代表根类加载器的类型);
    MyTest15[] mytest15 = new MyTest15[2];
    sout(mytest15.getClass().getClassLoader());// 结果为 AppClassLoader ; 因为是自自定义的类
    Integer[] integet = new Integer[2];
    sout(integet.getClass().getClassLoader()); // 输出结果为null;因为是原生类型。(此时的null不代表根类加载器的类型,就是null)
  }
}

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 machines 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.
/*
ClassLoader使用双亲委托模型来寻找资源。ClassLoader的每一个实例都有一个关联的父的ClassLoader。当要求ClassLoader去需要资源时,ClassLoader在自己寻找资源之前,会将资源寻找委托给它的父ClassLoader。层层往上递进。虚拟机内荐的类加载器叫做启动类加载器,它本身是没有双亲的,但是它可以当做是其他类加载器的双亲。
*/
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).
/*
并行的ClassLoader注册。
支持类的并发加载的类加载器称为支持并行的类加载器,需要通过调用类加载器在类初始化时注册它们自己。registerAsParallelCapable方法。注意,默认情况下ClassLoader类被注册为支持并行的。但是,它的子类仍然需要注册它们自己,如果它们是并行的。在委托模型没有严格层次结构的环境中,类装入器需要具有并行能力,否则类装入可能会导致死锁,因为装入器锁在类装入过程期间一直持有(请参阅loadClass方法)。
*/  
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.
 /*
通常情况下,jvm会从本地文件系统中去加载类。  例如在unix 上,jvm会从CLasspath环境变量定义的目录中去加载。
*/ 
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.
/*
然而,,一些类并不是来源于文件。  如通过网络,动态代理等。 
这种情况下,定义类会将字节数组转换成class的一个实例。这个新定义的实例可能通过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.
/*
类加载器创建的对象的方法和构造函数可以引用其他类。为了确定引用的类,Java虚拟机调用最初创建类的类加载器的loadClass方法。
*/  
For example, an application could create a network class loader to download class files from a server. Sample code might look like:
// 例如,应用程序可以创建一个网络类装入器来从服务器下载类文件。示例代码可能如下:
     ClassLoader loader = new NetworkClassLoader(host, port);
     Object main = loader.loadClass("Main", true).newInstance();
          . . .
   
The network class loader subclass must define the methods findClass and loadClassData to load a class from the network. Once it has downloaded the bytes that make up the class, it should use the method defineClass to create a class instance. A sample implementation is:
//网络类加载器子类必须定义findClass和loadClassData方法,以便从网络加载类。一旦下载了构成类的字节,就应该使用defineClass方法来创建类实例。一个示例实现是:
       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
                . . .
           }
       }
   
Binary names  // `二进制的名字` 介绍: 
Any class name provided as a String parameter to methods in ClassLoader must be a binary name as defined by The Java™ Language Specification.
Examples of valid class names include:
     "java.lang.String"   // 字符串的类 
     "javax.swing.JSpinner$DefaultEditor"  // DefaultEditor是JSpinner的一个内部类
     "java.security.KeyStore$Builder$FileBuilder$1" // FileBuilder里的第一个匿名内部类
     "java.net.URLClassLoader$3$1" //  URLClassLoader里的第三个匿名内部类中的第一个匿名内部类

自定义一个类加载器

package com.erwa.jvm;

import java.io.*;

public class MyTest16 extends ClassLoader {
    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 "MyTest16{" +
                "classLoaderName='" + classLoaderName + '\'' +
                ", fileExtension='" + fileExtension + '\'' +
                '}';
    }

    @Override
    protected Class<?> findClass(String classname) throws ClassNotFoundException {
        // 重写findClass
        byte[] data = this.loadClassDate(classname);
        return this.defineClass(classname, data, 0, data.length);
    }

    private byte[] loadClassDate(String name){
        InputStream is = null;
        byte[] data = null;
        ByteArrayOutputStream baos = null;

        try{
            this.classLoaderName = this.classLoaderName.replace(".","/");

            is = new FileInputStream(new File(name + this.fileExtension));
            baos = new ByteArrayOutputStream();

            int ch = 0;
            while (-1 != (ch = is.read())) {
                baos.write(ch);
            }

            data = baos.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                assert is != null;
                is.close();
                assert baos != null;
                baos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return data;
    }

    public static void test(ClassLoader classLoader) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        Class<?> clazz = classLoader.loadClass("com.erwa.jvm.MyTest1");
        //创建类的实例
        Object object = clazz.newInstance();
        System.out.println(object);
    }

    public static void main(String[] args) throws IllegalAccessException, InstantiationException, ClassNotFoundException {
        MyTest16 loader1 = new MyTest16("loader1");
        test(loader1);
    }
}

> 输出结果: 
> Task :MyTest16.main()
com.erwa.jvm.MyTest1@6d06d69c
  
  定义的第一个自定义的类加载器结束。

类加载器重要方法详解

启动类加载器

扩展类加载器

应用类加载器

自定义类加载器

拒绝低效率的学习

通过defineClass,将byte转换成Class的对象。
protected final Class<?> defineClass(String name, byte[] b, int off, int len){}

案例结果如下:

类加载器的命名空间

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

输出结果一样,说明loader1 和loader2成为了父子关系,在同一命名空间中。

不同类加载器的命名空间关系

类的卸载

类的卸载举例:

也可以通过一个小工具,来查看 jvisualvm

自定义类加载器在复杂类加载情况下的运行分析

public class MyCat{
    public MyCat(){
        System.out.println("MyCat is loaded by : "+ this.getClass().getClassLoader());
    }
}

public class MySample{
    public MySample(){
        System.out.println("MySample is loaded by : "+ this.getClass().getClassLoader());
        new MyCat();
    }
}

public class MyTest17{
    public static void main(String[] args) throws Exception{
        MyTest16 loader1 = new MyTest16("loader1");
        Class<?> clazz = loader1.loadClass("com.erwa.jvm.MySample");
        System.out.println(clazz.hashCode());
      
      // 如果注释掉该行,那么并不会实例化MySample对象,即MySample构造方法不会被调用
      //因此不会实例化MyCat对象,即没有对MyCat进行主动使用。这里就不会加载MyCat的CLass
        Object object = clazz.newInstance();
    }
}

> 输出结果
> Task :MyTest17.main()
2018699554
MySample is loaded by : sun.misc.Launcher$AppClassLoader@659e0bfd
MyCat is loaded by : sun.misc.Launcher$AppClassLoader@659e0bfd
  
这个例子有多种情况,结果是不同的。
ClassPath 中删除 MySample
ClassPath 中删除 MyCat
ClassPath 中删除 MyCat  MySample  

》 总体来说,就是命名空间的概念。
- 子加载器所加载的类能够访问父加载器所加载的类
- 父加载器加载的类无法访问子加载器所加载的类

2020年02月10日21:36:38

对于类加载器,具体是从哪个地方加载的?

系统类加载器:sun.boot.class.path (加载系统的包,包含jdk核心库里的类)

扩展类加载器:java.ext.dirs(加载扩展jar包中的类)

应用类加载器:java.class.path(加载你编写的类,编译后的类)

​ 可以打印出各个加载器加载的目录: System.getProperty("上述的地址");

public class MyPerson{
  private MyPerson myperson;
  public void setMyPerson(Object object){
    this.myperson = (MyPerson) object
  }
}

public class MyTest20{
  psvm{
    MyTest16 loader1 = new MyTest16("loader1");
    MyTest16 loader2 = new MyTest16("loader2");
    
    Class<?> clazz1 = loader1.loadClass("com.erwa.jvm.MyPerson");//系统类加载器加载MyPerson的类
    Class<?> clazz2 = loader2.loadClass("com.erwa.jvm.MyPerson");//系统类加载器已经加载过此类。就不会再次加载,直接返回之前加载过的对象。 所以下方的结果为true.

    sout(clazz1 == clazz2);//结果为true 
    //原因是: loader1的父亲为系统类加载,系统类加载器可以加载MyPerson的类。 到了loader2时,加载时,系统类加载器已经加载过此类。就不会再次加载,直接返回之前加载过的对象。 所以结果为true.
    //并不是因为 loader1 和loader2 是由MyTest16类生成的同样的实例。
    
    Object object1 = clazz1.newInstance();
    Object object2 = clazz2.newInstance();

    //发射,取到MyPerson中的set方法
    Method methos = clazz1.getMethod("setMyPerson",Object.class);
    method.invoke(object1,object2);// 这个地方可以正常运行
  }
}
// 命名空间深度剖析
//命名空间,由该加载器及所有父加载器加载的类组成
public class MyTest20{
  psvm{
    MyTest16 loader1 = new MyTest16("loader1");
    MyTest16 loader2 = new MyTest16("loader2");
    
    loader1.setPath("user/erwa/Desktop/");
    loader2.setPath("user/erwa/Desktop/");
    //然后把编译生成的class文件剪贴到桌面上。此时,执行此类的输出结果是什么?
    
   Class<?> clazz1 = loader1.loadClass("com.erwa.jvm.MyPerson");//自定义加载器1加载MyPerson的类
   Class<?> clazz2 = loader2.loadClass("com.erwa.jvm.MyPerson");//自定义加载器2加载MyPerson的类
    // clazz1 和clazz2加载的文件不在同一个命名空间中。
    
    sout(clazz1 == clazz2);//结果为false
    //原因是: loader1的父亲为系统类加载,系统类加载器不能加载MyPerson的类,由自定义加载器加载。 到了loader2时,加载时同loader1,由自定义加载器的实例再次加载,直接返回之前加载过的对象。 所以结果为true.
    //并不是因为 loader1 和loader2 是由MyTest16类生成的同样的实例。
    
    Object object1 = clazz1.newInstance();
    Object object2 = clazz2.newInstance();

    //发射,取到MyPerson中的set方法
    Method methos = clazz1.getMethod("setMyPerson",Object.class);
    method.invoke(object1,object2);  //并且这个地方抛异常
  }
}

类加载器命名空间总结

类加载器的双亲委托模型的好处:

  1. 可以确保Java核心库的类型安全:(jdk自带的rt.jar等)所有的Java应用都至少会引用Java.lang.Object类,也就是说,在运行期,这个类会被加载器到Java虚拟机中;如果这个加载过程是由Java应用自己提供的类加载器完成的,那么就很可能在jvm中存在多个Java.lang.Object类的版本,而且这些类之间还是不兼容的,相互不可见的(正是命名空间在发挥着作用)。借助于双亲委托机制,Java核心类库中的类的加载工作都是由启动类(根类)加载器来统一完成。从而确保了Java应用使用的都是同一个版本的Java核心类库,他们之间是相互兼容的。
  2. 可以确保Java核心类库所提供的类,不会被自定义的类所替代。
  3. 不同的类加载器可以被相同名称(binary name)的类创建额外的命名空间;相同名称的类可以并存在Java虚拟机中,只需要用不同的类加载器加载他们即可。不同类加载器所加载的类之间是不兼容的,这就相当于在Java虚拟机中创建了一个又一个相互隔离的Java类空间,这类技术在多框架中都得到了实际应用。

扩展类加载器类特性介绍

public class MyTest22{
  static{
    sout("MyTest22 initializer");
  }
  
  psvm{
    sout(MyTest22.class.getClassLoader());
    sout(MyTest1.class.getClassLoader());
  }
}
> 1.此时的输出结果是什么?
  AppClassLoader
  AppClassLoader

> 2.修改本地的扩展类加载器的加载路径。
  1. 进入到当前编译的 out路径
  2. java -Djava.ext.dirs=./ com.erwa.jvm.MyTest22
  
> 3.此时的输出结果是什么?
  AppClassLoader
  AppClassLoader
 
4.还是一样的,为什么不是扩赞类加载器加载的呢?因为扩展类加载的类还不能是Class文件的形式,要以jar包的形式加载。
  
> 5.将当前的com打成jar包 
  jar cvf com.erwa.jvm
  
> 6.此时的输出结果是什么?
  AppClassLoader
  ExtClassLoader

> 7.  java -Djava.ext.dirs=/ com.erwa.jvm.MyTest22
 
> 8.此时的输出结果是什么?
  AppClassLoader
  AppClassLoader

关于命名空间的补充:

在运行期,一个Java类是由该类的完全限定名(binary name,二进制名)和用于加载该类的定义类加载器共同确定的。

如果同样的名字(即相同的完全限定名)的类是由两个不同的加载器所加载,那么这些类就是不同的。即便.class文件的字节码完全一样,并且从相同的位置加载亦如此。

在Oracle的Hotspot实现中,系统属性sun.boot.class.path如果修改错了,则运行时会出错,信息下图:

类加载器的特例,数组。数组不是由类加载器的加载的,是由JVM加载的。

先有鸡还是先有蛋?

类加载器本身也是一个Java类,他们是怎么加载到JVM当中的呢?

他们是由启动类加载器加载的。BootStrapClassLoader加载的。他不是由Java代码写的,由C++代码编写的。启动的时候回自动加载出启动类加载器的实例。

  1. 内建与JVM中的启动类加载器会加载java.lang.ClassLoader以及其他的Java类加载器。

  2. 当JVM启动时,一块特殊的机器码会执行,它会加载类加载器与系统类加载器,这块特殊的机器码叫做启动类加载器(BootStarp)
  3. 启动类加载器并不是Java类,而其他的加载器则都是Java类
  4. 启动类加载器是特定于平台的机器指令,它负责开启整个加载过程
  5. 所有类加载器(除了启动类加载器)都被时限为Java类。不过,中国要有一个组件来加载第一个Java类加载器,从而让整个过程能够顺利的进行下去,加载第一个纯Java类加载器就是启动类加载器的职责。
  6. 启动类加载器还会负责加载供JRE正常运行所需要的基本组件,这包括java.util与java.lang包中的内容。

sout(ClassLoader.class.getClassLoader());
> 输出结果:
  null    // 因为ClassLoader是由启动类加载器的加载的。
  
ExtClassLoader 和 APPCLassLoader 位于 Launcher.class中的静态内部类。

sout(Launcher.class.getClassLoader());
> 输出结果:
  null    // 因为扩展类加载器与应用类加载器也是由启动类加载器的加载的。

自定义系统类加载器

在自定义类加载器中加一个父类的构造方法,就可以正常的运行了。

用Java命令行的话,和idea里边执行的结果有时候类加载器的是不一样的。

因为idea也重新定义了类加载器的路径。

getSystemClassLoader();
java.lang.ClassLoader public static ClassLoader getSystemClassLoader()
Returns the system class loader for delegation. This is the default delegation parent for new ClassLoader instances, and is typically the class loader used to start the application.
 // 通常 系统类加载器用来加载我们的应用。
This method is first invoked early in the runtimes startup sequence, at which point it creates the system class loader and sets it as the context class loader of the invoking Thread.
// 这个方法会创建系统类加载器,并将AppClassLoader设置为上下文类加载器
The default system class loader is an implementation-dependent instance of this class.
  
If the system property "java.system.class.loader" is defined when this method is first invoked then the value of that property is taken to be the name of a class that will be returned as the system class loader. The class is loaded using the default system class loader and must define a public constructor that takes a single parameter of type ClassLoader which is used as the delegation parent. An instance is then created using this constructor with the default system class loader as the parameter. The resulting class loader is defined to be the system class loader.
  // 如果我们自定义了 java.system.class.loader。 那么就会把这个值当做系统类加载器。这个类是被AppClassLoader加载器加载器的。这个自定义的系统类加载器必须要有一个 带有父加载器参数的构造方法。
If a security manager is present, and the invoker's class loader is not null and the invoker's class loader is not the same as or an ancestor of the system class loader, then this method invokes the security managers checkPermission method with a RuntimePermission("getClassLoader") permission to verify access to the system class loader. If not, a SecurityException will be thrown.

   @CallerSensitive
    public static ClassLoader getSystemClassLoader() {
        initSystemClassLoader();
        if (scl == null) {
            return null;
        }
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkClassLoaderPermission(scl, Reflection.getCallerClass());
        }
        return scl;
    }

    private static synchronized void initSystemClassLoader() {
        if (!sclSet) {
            if (scl != null)
                throw new IllegalStateException("recursive invocation");
            sun.misc.Launcher l = sun.misc.Launcher.getLauncher(); // 这个地方值得深入理解一下
            if (l != null) {
                Throwable oops = null;
                scl = l.getClassLoader(); // 返回了AppClassLoader
                try {
                  // 已经获取到了 scl 为什么还要进行一步这样的操作呢?
                  // 加载自定义系统类加载器的属性。如果自定义了系统类加载器,在这里进行了处理,如果没有的话,则使用默认的系统类加载器。(可以点进去看看,里边的run方法摘抄出来放下了下方)
                    scl = AccessController.doPrivileged(
                        new SystemClassLoaderAction(scl));
                } catch (PrivilegedActionException pae) {
                    oops = pae.getCause();
                    if (oops instanceof InvocationTargetException) {
                        oops = oops.getCause();
                    }
                }
                if (oops != null) {
                    if (oops instanceof Error) {
                        throw (Error) oops;
                    } else {
                        // wrap the exception
                        throw new Error(oops);
                    }
                }
            }
            sclSet = true;  //设置成功了
        }
    }

// 处理自定义加载器的方法。
 public ClassLoader run() throws Exception {
        String cls = System.getProperty("java.system.class.loader");
        if (cls == null) {// 如果设置了自定义加载器的属性,则不为null,进行后续的操作。
            return parent;
        }

   // 在下方,会单独讲解一下 Class.forName方法的使用。
        Constructor<?> ctor = Class.forName(cls, true, parent)
            .getDeclaredConstructor(new Class<?>[] { ClassLoader.class });
        ClassLoader sys = (ClassLoader) ctor.newInstance( //获取到自定义的类加载器。返回
            new Object[] { parent });
        Thread.currentThread().setContextClassLoader(sys);
        return sys;
    }

grepcode.com 在线查看源代码的网站。

为什么扩展类加载的路径是java.ext.dirs 源代码里边写的。

Launcher();
//双亲处理的机制。精髓
public Launcher() {
        // Create the extension class loader  创建扩展类加载器
        ClassLoader extcl;
        try {
            extcl = ExtClassLoader.getExtClassLoader();
        } catch (IOException e) {
            throw new InternalError(
                "Could not create extension class loader", e);
        }

        // Now create the class loader to use to launch the application 创建应用类加载器
        try {
            loader = AppClassLoader.getAppClassLoader(extcl);
        } catch (IOException e) {
            throw new InternalError(
                "Could not create application class loader", e);
        }

        // Also set the context class loader for the primordial thread.
  // 为当前的执行线程设置上下文类加载器。(重要。重要。重要。)
 // 如果没有通过setContextCLassLoader(ClassLoader cl)进行设置的话,线程将继承其父线程的上下文类加载器。Java应用运行时的初始线程的上下文类加载器是系统类加载器
        Thread.currentThread().setContextClassLoader(loader);

        // Finally, install a security manager if requested
        String s = System.getProperty("java.security.manager");
        if (s != null) {
            // init FileSystem machinery before SecurityManager installation
            sun.nio.fs.DefaultFileSystemProvider.create();

            SecurityManager sm = null;
            if ("".equals(s) || "default".equals(s)) {
                sm = new java.lang.SecurityManager();
            } else {
                try {
                    sm = (SecurityManager)loader.loadClass(s).newInstance();
                } catch (IllegalAccessException e) {
                } catch (InstantiationException e) {
                } catch (ClassNotFoundException e) {
                } catch (ClassCastException e) {
                }
            }
            if (sm != null) {
                System.setSecurityManager(sm);
            } else {
                throw new InternalError(
                    "Could not create SecurityManager: " + s);
            }
        }
    }
Class.forName();

第一次使用这个方法的时候,是在学习JDBC的时候获取数据库连接。那个时候只用到了一个参数的构造方法

java.lang.Class<T> public static Class<?> forName(@NonNls String name,
                               boolean initialize,
                               ClassLoader loader)  throws ClassNotFoundException
Returns the Class object associated with the class or interface with the given string name, using the given class loader. Given the fully qualified name for a class or interface (in the same format returned by getName) this method attempts to locate, load, and link the class or interface. The specified class loader is used to load the class or interface. If the parameter loader is null, the class is loaded through the bootstrap class loader. The class is initialized only if the initialize parameter is true and if it has not been initialized earlier.
//使用给定的类加载器返回与具有给定字符串名称的类或接口关联的Class对象。 给定类或接口的完全限定名称(采用getName返回的相同格式),此方法尝试查找,加载和链接该类或接口。 指定的类加载器用于加载类或接口。 如果参数加载器为null,则通过引导类加载器加载该类。 仅当initialize参数为true且之前尚未初始化时,才对类进行初始化。
If name denotes a primitive type or void, an attempt will be made to locate a user-defined class in the unnamed package whose name is name. Therefore, this method cannot be used to obtain any of the Class objects representing primitive types or void.
If name denotes an array class, the component type of the array class is loaded but not initialized.
//如果name表示原始类型或void,则将尝试在名称为name的未命名包中定位用户定义的类。 因此,该方法不能用于获取表示原始类型或void的任何Class对象。如果name表示数组类,则将加载但不初始化该数组类的组件类型。
For example, in an instance method the expression:
Class.forName("Foo")
is equivalent to:
Class.forName("Foo", true, this.getClass().getClassLoader())
Note that this method throws errors related to loading, linking or initializing as specified in Sections 12.2, 12.3 and 12.4 of The Java Language Specification. Note that this method does not check whether the requested class is accessible to its caller.
If the loader is null, and a security manager is present, and the caller's class loader is not null, then this method calls the security manager's checkPermission method with a RuntimePermission("getClassLoader") permission to ensure it's ok to access the bootstrap class loader.
/*例如,在实例方法中,表达式为:
Class.forName(“ Foo”)
等效于:
Class.forName(“ Foo”,yes,this.getClass().getClassLoader())
请注意,此方法会引发与Java语言规范的12.2、12.3和12.4节中指定的加载,链接或初始化有关的错误。 请注意,此方法不检查其调用者是否可以访问所请求的类。
如果加载程序为null,并且存在安全管理器,并且调用方的类加载器不为null,则此方法使用RuntimePermission(“ getClassLoader”)权限调用安全管理器的checkPermission方法,以确保可以访问引导程序类加载器 。*/
Params:
name – fully qualified name of the desired class
initialize – if true the class will be initialized. See Section 12.4 of The Java Language Specification.
loader – class loader from which the class must be loaded
/*参数:
名称-所需类别的完全限定名称
初始化–如果为true,则将初始化该类。 参见Java语言规范的12.4节。
loader –必须从中加载类的类加载器*/
  @CallerSensitive
    public static Class<?> forName(String name, boolean initialize,
                                   ClassLoader loader)
        throws ClassNotFoundException
    {
        Class<?> caller = null;
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            // Reflective call to get caller class is only needed if a security manager
            // is present.  Avoid the overhead of making this call otherwise.
            caller = Reflection.getCallerClass();
            if (sun.misc.VM.isSystemDomainLoader(loader)) {
                ClassLoader ccl = ClassLoader.getClassLoader(caller);
                if (!sun.misc.VM.isSystemDomainLoader(ccl)) {
                    sm.checkPermission(
                        SecurityConstants.GET_CLASSLOADER_PERMISSION);
                }
            }
        }
        return forName0(name, initialize, loader, caller);
    }
  
  
     @CallerSensitive
    public static Class<?> forName(String className)
                throws ClassNotFoundException {
        Class<?> caller = Reflection.getCallerClass();
        return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
    }

线程上下文类加载器

当前类加载器(Current ClassLadert):加载当前类的加载器。

每一个类都会尝试使用自身的类加载器(即加载自身的类加载器)来加载其他的类(指的是所依赖的类)。

如: 如果CLassx引用了ClassY,那么ClassX的类加载器就会去加载CLassY(前提是ClassY尚未被加载)

线程上下文类加载器(Context CLassloader)

线程上下文类加载器是从jdk1.2开始引入的,类Thread中的getContextClassLoader();与setContextClassLoadert();分别用来获取和设置上下文类加载器。

如果没有通过setContextCLassLoader(ClassLoader cl)进行设置的话,线程将继承其父线程的上下文类加载器。Java应用运行时的初始线程的上下文类加载器是系统类加载器。在线程中运行的代码可以通过该类加载器来加载类与资源。

线程上下文类加载器的重要性:

SPI(Service Proider Interface) 服务提供厂商;

父ClassLoader可以使用当前线程Thread.currentThread().getContextClassLoader()所指定的ClassLoader加载的类,这就改变了父ClassLoader不能使用子ClassLoader或者是其他没有直接父子关系的ClassLoader加载的类的情况,即这就改变了双亲委托模型。

线程上下文类加载器就是当前线程的Current ClassLoader。

在双亲委托模型下,类加载是由下至上的,即下层的类加载器会委托上层进行加载。但是对于SPI来说,有些类是由Java核心库提供的。而Java核心库是由启动类加载器来加载的,而这些接口的实现却来源于不同的jar包(不同的厂商提供),Java的启动类加载器是不会加载其他来源的jar包,这样传统的双亲委托模型就无法满足SPI的要求,而通过给当前线程设置上下文类加载器,就可以由上下文类加载器来实现对于接口实现类的加载。

Thread.currentThread().setContextClassLoader(loader);

public class MyTest24{
  psvm{
    sout(Thread.currentThread().getContextClassLoader());
    sout(Thread.class.getClassLoader());
  }
}

输出结果:
  AppClassLoader
  null 
  

数据库厂商肯定会满足jdbc规范的接口。

Tomcat和spring的类加载机制和jdk的不同。

/**
如果没有通过setContextCLassLoader(ClassLoader cl)进行设置的话,线程将继承其父线程的上下文类加载器。Java应用运行时的初始线程的上下文类加载器是系统类加载器。在线程中运行的代码可以通过该类加载器来加载类与资源。
**/
public class MyTest25 implements Runnable {
    private Thread thread;

    public MyTest25() {
        thread = new Thread(this);
        thread.start();
    }

    @Override
    public void run() {
        ClassLoader classLoader = this.thread.getContextClassLoader();
        this.thread.setContextClassLoader(classLoader);
        System.out.println("class:"+ classLoader.getClass());
        System.out.println("Parent:"+ classLoader.getParent().getClass());
    }

    public static void main(String[] args) {
       new  MyTest25();
    }
}

> Task :MyTest25.main()
class:class sun.misc.Launcher$AppClassLoader
Parent:class sun.misc.Launcher$ExtClassLoader

线程上下文类加载器的一般使用模式(获取-使用-还原)

线程上下文类加载器的一般使用模式(获取-使用-还原)
 ClassLoader classloader= Thread.currentThread().getConTextCLassLoader(); // 获取
try{
  Thread.currentThread().setConTextCLassLoader(targetTccl);//使用
}finally{
  Thread.currentThread().setConTextCLassLoader(classLoader);//还原。
}

1. myMethod里边调用了Thread.currentThread().getConTextCLassLoader();获取当前线程的上下文类加载器某些事情。
2.如果一个类由类加载器A加载,那么这个类的依赖类也是由相同的类加载器加载器的(如果该依赖类没有加载过的话)
3.ContextClassLoader的作用就是为了破坏Java的类加载委托机制。
4.当高层提供了同一的接口让低层去实现,同时又要在高层加载(或者实例化)低层的类时,就必须通过上下文类加载器来帮助高层找到高层的CLassLoader找到并加载该类。
举例: 
  1、 首先加入MySQL的依赖 :  “mysql:mysql-connector-java:5.1.34 ”
public class MyTest26{
    public static void main(String[] args) {
        ServiceLoader<Driver> loader = ServiceLoader.load(Driver.class);
        Iterator<Driver> iterator = loader.iterator();
        while(iterator.hasNext()){
         Driver driver = iterator.next();    System.out.println("driver:"+driver.getClass()+",loader:"+driver.getClass().getClassLoader());
        }
        System.out.println("当前线程上下文类架加载器的对象:"+Thread.currentThread().getContextClassLoader());
        System.out.println("ServiceLoader的类加载器"+ServiceLoader.class.getClassLoader());
    }
}

> Task :MyTest26.main()
driver:class com.mysql.jdbc.Driver,loader:sun.misc.Launcher$AppClassLoader@659e0bfd
driver:class com.mysql.fabric.jdbc.FabricMySQLDriver,loader:sun.misc.Launcher$AppClassLoader@659e0bfd
当前线程上下文类架加载器的对象:sun.misc.Launcher$AppClassLoader@659e0bfd
ServiceLoader的类加载器null
ServiceLoader<Driver> loader = ServiceLoader.load(Driver.class);便能获取到驱动的加载的类。
ServiceLoader解析: 提供了很好的 对 SPI的支持。
  
java.util public final class ServiceLoader<S>
extends Object
implements Iterable<S>
  
A simple service-provider loading facility.
// 一个简单的服务提供商加载工具。
A service is a well-known set of interfaces and (usually abstract) classes. A service provider is a specific implementation of a service. The classes in a provider typically implement the interfaces and subclass the classes defined in the service itself. Service providers can be installed in an implementation of the Java platform in the form of extensions, that is, jar files placed into any of the usual extension directories. Providers can also be made available by adding them to the applications class path or by some other platform-specific means.
//服务是一组著名的接口和(通常是抽象的)类。 服务提供者是服务的特定实现。 提供程序中的类通常实现接口,并子类化服务本身中定义的类。 服务提供程序可以扩展的形式安装在Java平台的实现中,也就是说,将jar文件放置在任何常用扩展目录中。 也可以通过将提供者添加到应用程序类路径或通过其他一些特定于平台的方式来使提供者可用。
For the purpose of loading, a service is represented by a single type, that is, a single interface or abstract class. (A concrete class can be used, but this is not recommended.) A provider of a given service contains one or more concrete classes that extend this service type with data and code specific to the provider. The provider class is typically not the entire provider itself but rather a proxy which contains enough information to decide whether the provider is able to satisfy a particular request together with code that can create the actual provider on demand. The details of provider classes tend to be highly service-specific; no single class or interface could possibly unify them, so no such type is defined here. The only requirement enforced by this facility is that provider classes must have a zero-argument constructor so that they can be instantiated during loading.
//出于加载的目的,服务由单一类型表示,即单一接口或抽象类。 (可以使用一个具体的类,但是不建议这样做。)给定服务的提供者包含一个或多个具体类,这些具体类使用该提供者特定的数据和代码来扩展此服务类型。 提供者类通常不是整个提供者本身,而是包含足够信息以决定提供者是否能够满足特定请求以及可以按需创建实际提供者的代码的代理。 提供程序类的细节往往是高度特定于服务的; 没有单个类或接口可能会统一它们,因此此处未定义此类。 此功能强制执行的唯一要求是,提供程序类必须具有零参数构造函数,以便可以在加载期间实例化它们。
A service provider is identified by placing a provider-configuration file in the resource directory META-INF/services. The file's name is the fully-qualified binary name of the service's type. The file contains a list of fully-qualified binary names of concrete provider classes, one per line. Space and tab characters surrounding each name, as well as blank lines, are ignored. The comment character is '#' ('\u0023', NUMBER SIGN); on each line all characters following the first comment character are ignored. The file must be encoded in UTF-8.
//通过将提供者配置 文件放置在资源目录META-INF / services中来标识服务提供者。 文件名是服务类型的标准二进制名称。 该文件包含一个具体的提供程序类的标准二进制名称列表,每行一个。 每个名称周围的空格和制表符以及空白行将被忽略。 注释字符为“#”(“ \ u0023”,数字符号); 在每一行中,第一个注释字符之后的所有字符都将被忽略。 该文件必须使用UTF-8编码。
If a particular concrete provider class is named in more than one configuration file, or is named in the same configuration file more than once, then the duplicates are ignored. The configuration file naming a particular provider need not be in the same jar file or other distribution unit as the provider itself. The provider must be accessible from the same class loader that was initially queried to locate the configuration file; note that this is not necessarily the class loader from which the file was actually loaded.
//如果一个特定的具体提供程序类在多个配置文件中被命名,或者在同一配置文件中被多次命名,则重复项将被忽略。 命名特定提供程序的配置文件不必与提供程序本身位于同一jar文件或其他分发单元中。 该提供程序必须可以从最初查询定位配置文件的同一类加载程序进行访问; 请注意,这不一定是实际从中加载文件的类加载器。
Providers are located and instantiated lazily, that is, on demand. A service loader maintains a cache of the providers that have been loaded so far. Each invocation of the iterator method returns an iterator that first yields all of the elements of the cache, in instantiation order, and then lazily locates and instantiates any remaining providers, adding each one to the cache in turn. The cache can be cleared via the reload method.
//提供商的位置很懒,即按需实例化。 服务加载器维护到目前为止已加载的提供者的缓存。 每次迭代器方法的调用都会返回一个迭代器,该迭代器首先按实例化顺序生成高速缓存的所有元素,然后懒惰地定位和实例化任何剩余的提供程序,依次将每个提供程序添加到高速缓存中。 可以通过reload方法清除缓存。
Service loaders always execute in the security context of the caller. Trusted system code should typically invoke the methods in this class, and the methods of the iterators which they return, from within a privileged security context.
//服务加载程序始终在调用方的安全上下文中执行。 受信任的系统代码通常应从特权安全上下文中调用此类中的方法以及它们返回的迭代器的方法。
Instances of this class are not safe for use by multiple concurrent threads.
Unless otherwise specified, passing a null argument to any method in this class will cause a NullPointerException to be thrown.
//此类的实例不适用于多个并发线程。
//除非另有说明,否则将null参数传递给此类中的任何方法都将引发NullPointerException。
Example Suppose we have a service type com.example.CodecSet which is intended to represent sets of encoderhttps://img.qb5200.com/download-x/decoder pairs for some protocol. In this case it is an abstract class with two abstract methods:
   public abstract Encoder getEncoder(String encodingName);
   public abstract Decoder getDecoder(String encodingName);
Each method returns an appropriate object or null if the provider does not support the given encoding. Typical providers support more than one encoding.
  //每个方法都返回一个适当的对象,如果提供者不支持给定的编码,则返回null。典型的提供程序支持多种编码。
If com.example.impl.StandardCodecs is an implementation of the CodecSet service then its jar file also contains a file named
   META-INF/services/com.example.CodecSet
This file contains the single line:
   com.example.impl.StandardCodecs    # Standard codecs
The CodecSet class creates and saves a single service instance at initialization:
   private static ServiceLoader<CodecSet> codecSetLoader
       = ServiceLoader.load(CodecSet.class);
To locate an encoder for a given encoding name it defines a static factory method which iterates through the known and available providers, returning only when it has located a suitable encoder or has run out of providers.
   public static Encoder getEncoder(String encodingName) {
       for (CodecSet cp : codecSetLoader) {
           Encoder enc = cp.getEncoder(encodingName);
           if (enc != null)
               return enc;
       }
       return null;
   }
A getDecoder method is defined similarly.
Usage Note If the class path of a class loader that is used for provider loading includes remote network URLs then those URLs will be dereferenced in the process of searching for provider-configuration files.
//使用说明如果用于提供程序加载的类加载程序的类路径包括远程网络URL,则在搜索提供程序配置文件的过程中将取消引用这些URL。
This activity is normal, although it may cause puzzling entries to be created in web-server logs. If a web server is not configured correctly, however, then this activity may cause the provider-loading algorithm to fail spuriously.
//此活动是正常的,尽管它可能会导致在Web服务器日志中创建令人费解的条目。但是,如果未正确配置Web服务器,则此活动可能导致提供商加载算法错误地失败
A web server should return an HTTP 404 (Not Found) response when a requested resource does not exist. Sometimes, however, web servers are erroneously configured to return an HTTP 200 (OK) response along with a helpful HTML error page in such cases. This will cause a ServiceConfigurationError to be thrown when this class attempts to parse the HTML page as a provider-configuration file. The best solution to this problem is to fix the misconfigured web server to return the correct response code (HTTP 404) along with the HTML error page.
//当请求的资源不存在时,Web服务器应返回HTTP 404(未找到)响应。但是,在某些情况下,有时会错误地将Web服务器配置为返回HTTP 200(OK)响应以及有用的HTML错误页面。当此类尝试将HTML页面解析为提供程序配置文件时,这将引发ServiceConfigurationError。解决此问题的最佳方法是修复配置错误的Web服务器,以返回正确的响应代码(HTTP 404)和HTML错误页面。

private static final String PREFIX = "META-INF/services/";
// Cached providers, in instantiation order
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

至于为什么会输出两个jdbc的驱动;由下图所示。

听懂了,但是要不要记下来呢?? 源码里边都有,但是手速已经跟不上了。如果暂停下来去粘贴过来的话,会大量的浪费时间。所以这里就不粘贴过来了,认真的听吧。

通过jdbc驱动加载,深刻理解线程上下文类加载器的机制

分析一下 这两行代码底层是怎么实现的。已经都学习过了。

// 初始化的作用
// 线程上下文类加载器的作用。
// 精辟。   
 public class MyTest27 {
    public static void main(String[] args) throws Exception{
      // 初始化,会加载类中华的静态方法和内部类。  层层的,如果静态方法中调用静态类,会初始化内部的静态类
        Class.forName("com.mysql.jdbc.Driver");  
     
        Connection connection = DriverManager.getConnection("jdbc:localhost:8080", "username", "password");
    }
}

2020年02月11日23:12:09 。 睡觉。

2020年02月12日17:07:52

类加载器阶段性总结

总结回顾了一下之前学习的所有笔记中的内容。

论笔记的重要性。学习方式的升级。学习,遗忘,复习。笔记来做一个载体。

不要给后续留坑,前期欠的东西,以后总归是要还的。

技术学习与思维方式谈心

学习效率,学习方法,重要吗?

学习一个知识的时候太着急了。并不是一个视频学完了,这门技术就掌握了。

你学习的时候,目标是什么呢?把视频中讲解的内容给吸收理解了。

时间和效率两个因素来说,时间是固定的。我们只能从效率入手。

突击学习只会让你误以为你了解了这个技术。有节奏的学习。张龙老师的视频的准备和录制时间,是在工作之余准备和录制的。

再次复习一下之前学习的规程。

类加载器学习到此结束。开启一个新的阶段。2020年02月12日21:31:10

Copyright 2022 版权所有 软件发布 访问手机版

声明:所有软件和文章来自软件开发商或者作者 如有异议 请与本站联系 联系我们