ZB-035-02类加载和ClassLoader

Class对象的生命周期

不止一份说明书

你的程序扩展了

此时 你有了如下多的类型

  • Cat
  • Dog
  • Pig
  • 或者内置的 ArrayList

它们都是从 对应的说明书创建的?

问题来了?这个 说明书,这个Class对象 那里来的

  • 绝不可能是凭空出现,一定是从什么地方拿到的

Class对象的生命周期

  • 它在第一次被使用的时候加载

Class对象就是一个 类型(如Cat) 的说明书

上篇文章的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
Animal
-|Cat
--|WhiteCat

在内存中初始的时候,
一个 WhiteCat 它的一部分区域是 Cat,在一部分是 Animal

意思就是
你要创建一个 WhiteCat 就必须 先创建 Cat,创建 Cat 就必须 先创建Animal

换句话说,
你想要一个 WhiteCat,你首先要用到 Cat,然后创建 Cat 又必须用到 Animal
这个过程是一层一层递归的

这就是:类加载的时机 在它第一次被使用的时候

HelloWorld 运行过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
public class Main {
public static void main(String[] args) {
System.out.println("HelloWorld");
new WhiteCat();
// new WhiteCat() 的时候必须要有 Cat说明书
// new Cat()的时候 必须有 Animal 说明书
// new Animal() 的时候 必须有 Object 说明书
// java.lang.Object 是排在第一个被加载的 它是jre自带的
// 然后造出来 obj -> animal -> cat -> whiteCat
}
}

// IDEA里 右键运行

你以为的
运行------》 HelloWorld

实际上

运行---》
第一步 编译 从 Main.java--> Main.class
第二步 帮你拼了一个 java命令 运行你的 Main.class
打印出 HelloWorld

// 复制这行拼接的命令
在终端里执行,你发现跟你点 运行 没区别

// 我们可以给这个命令行加参数

让他打印 类加载 顺序

-verbose:class

// 在这个后面
/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/bin/java -verbose:class ...

但是非常啰嗦

你可以在命令的最后 加上 管道操作

/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/bin/java -verbose:class
...

| grep 过滤词(我们自己类的包名如 com.xxx)


你会看到 形如
[Loaded com.io.demo.Animal from file:/Users/xxx/Desktop/xxx/target/classes/]
[Loaded com.io.demo.Cat from file:/Users/xxx/Desktop/xxx/target/classes/]
[Loaded com.io.demo.WhiteCat from file:/Users/xxx/Desktop/xxx/target/classes/]

意思就是这个类 从那里来

回到刚才的问题 我想创建一个 Cat/Dog/ArrayList,说明书放在一个地方存着。说明书从那里来的?

上面的过程解释了,说明书什么时候被加载。

那它是那里来的呢?

答案是你自己写的类 从你编写的 xxx.java 编译后的 xxx.class里来(target目录里)

你看到的是瞬间加载你的类,实际做了很多事

1
2
3
4
5
6
7
8
9
10
11
12
13
加载之前是 xxx.class 文件

加载 Loading (被jvm读取)===>
进行一系列操作

(链接Linking 包含 验证、准备、解析)
验证==》防止恶意代码搞坏jvm
准备==》
解析===》
初始化
(最终)
把一个 class文件 变到 内存里
使得根据对应的说明书 可以创建对应的对象

第三个问题?说明书被谁加载的

答案就是 Classloader

Class和 Classloader

Classloader 负责从外部系统中加载一个类

  • 这个类对应的Java文件并不需要存在 你可以没有Cat.java,你可以通过Cat.class
  • 这个类(字节码))并不一定需要存在你也可以没有Cat.class,你可以从网上下载,文件的本质是字节流,可以动态生成(内存里凭空捏造一个,它可以不对应一个 .class文件)
  • 这是java世界丰富多彩的基石

    1
    2
    3
    4
    5
    6
    比如我想要一个 五条腿的猫
    动态创建一个 Cat5
    Cat5 extends Cat
    这个Cat5的说明书 你可以直接在内存里捏造出来,不需要和外界打交道

    这个操作就是 动态字节码增强
  • Classloader的双亲委派加载模型

    • 首先 Classloader 负责从外部系统中加载一个类
    • 外部是不安全的,总会有恶意的代码,比如 他想搞坏你的String
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      // src/main下建立如下代码
      package java.lang;

      public class String {
      // 我是一个坏人
      // 我在自己定义的 String 里 做坏事

      public String() {
      throw new IllegalStateException();
      }
      }

      //
      package com.xxx.demo;

      public class Main {
      public static void main(String[] args) {
      new String();
      // 此时 点进去发现 会优先选择官方的 String
      }
      }

      // 答案就是 双亲委派加载模型

双亲委派加载模型

  • 每次加载类的时候 都会调用 ClassLoader.loadClass() 你可以给这行 加上断点 debug 运行一下
    • 当前的对象叫做 Launcher$AppClassLoader 启动类加载器
    • AppClassLoader它负责从外部系统 把说明书加载到jvm内部的人
    • AppClassLoader它的父亲是 Launcher$ExtClassLoader 扩展类加载器
      • ExtClassLoader 的父亲 是 null
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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 {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}

// 父亲加载失败,尝试自己加载
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;
}
}

这个模型的好处?

比如刚刚的new String();这样的基础的类,出于安全性的考虑都是由最顶端的启动类加载器加载的,这样就保证了安全

所以我们自造的恶意的 类无法被加载。

  • 每次加载一个 类的时候 都先问他的父亲 加载了吗
  • 如果父亲加载了,就不会让你在继续加载,这就保证了 无法恶意的 覆盖修改 String 类

Java语言规范(JLS)和Java虚拟机规范(JVMS)

  • java 和 jvm实际是分离的,它们的联系就是字节码,而字节码可以不由java生成
  • 这种分离提供了JVM上运行其他语言的可能

查看字节码 命令

1
javap xxx