深入了解Java之类加载和案例分析

在讨论JVM内存区域分析之前,先来看一下Java程序具体执行的过程:


Java程序执行过程

Java 程序的执行过程:Java 源代码文件(.Java文件)-> Java Compiler(Java编译器)->Java 字节码文件(.class文件)->类加载器(Class Loader)->Runtime Data Area(运行时数据)-> Execution Engine(执行引擎)。上篇文章已经分析了 Runtime Data Area(运行时数据) 这一块,那我们今天就来分析一下Java程序执行过程的 类加载器(Class Loader) 这一块

一、类加载器(ClassLoader)

把Java类的数据从Class文件加载到虚拟机内存中,然后对这部分数据进行验证、准备、解析、初始化,最终形成可以被虚拟机直接使用的Java类型。

学习这个有什么好处呢?

1、有助于了解Java虚拟机的执行过程

2、程序能动态的控制类加载,比如热部署、热修复等

类加载器(ClassLoader)的一些方法:

方法 说明  
getParent() 返回该类加载器的父类加载器
loadClass(String name) 加载名称为 name的类
findClass(String name) 查找名称为 name的类
findLoadedClass(String name) 查找名称为 name的已经被加载过的类
defineClass(String name, byte[] b, int off, int len) 把字节数组 b中的内容转换成 Java 类,返回的结果是 java.lang.Class类的实例。这个方法被声明为 final的

二、类加载的分类

  •  Bootstrap Loader(启动类加载器) :是用C++语言写的,是虚拟机自身的一部分,主要负责加载Java的核心类;

  •  Extended Loader(扩展类加载器) :Extended Loader的父加载器为 Bootstrap Loader,该加载器使用Java写的,它用来加载Java的扩展库;

  •  AppClass Loader(系统类加载器):AppClass Loader的父加载器为 Extended Loader,该加载器使用Java写的,它用来加载Java应用的类路径(CLASSPATH)上的类库。

三、类加载过程

寻找jre目录,寻找jvm.dll,并初始化JVM,JVM启动后,运行Bootstrap Loader,该Bootstrap Loader(启动类加载器)自动加载Extended Loader(扩展类加载器)和AppClass Loader(系统类加载器),最后AppClass Loader加载CLASSPATH目录下定义的Class。

四、双亲委派模型

如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。

代码实现:

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
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
//检查该类是否已经加载过
Class c = findLoadedClass(name);
if (c == null) {
//如果该类没有加载,则进入该分支
long t0 = System.nanoTime();
try {
if (parent != null) {
//当父类的加载器不为空,则通过父类的loadClass来加载该类
c = parent.loadClass(name, false);
} else {
//当父类的加载器为空,则调用启动类加载器来加载该类
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
//非空父类的类加载器无法找到相应的类,则抛出异常
}
if (c == null) {
//当所有父类加载器无法加载时,则调用自己findClass方法来加载该类
long t1 = System.nanoTime();
c = findClass(name); //用户可通过覆写该方法,来自定义类加载器
//用于统计类加载器相关的信息
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
//对类进行link操作
resolveClass(c);
}
return c;
}
}

“双亲委派模型”有什么作用呢?

1、这样保证了每个类都只会加载一次

2、维护系统自身类的安全

五、类加载的三种方式

1、通过JVM初始化加载

2、通过Class.forName()方法动态加载

3、通过ClassLoader.loadClass()方法动态加载

六、案例分析

基于上篇文章的”深入了解Java之虚拟机内存”和这篇”深入了解Java之类加载”的理论知识,我们用这个例子简单的来了解一下类的加载和内存分配的一些逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class TestMain {
public void TestMethod() {
Sample testSample = new Sample(" test ");
testSample.getName();
}
}
public class Sample {
private String name;
public Sample(String name) {
this.name = name;
}
public void getName() {
Log.i("Sample", "name:" + name);
}
}

过程分析

public class TestMain

通过JVM初始化加载;通过Class.forName()方法动态加载;通过ClassLoader.loadClass()方法动态加载通过以上方法加载这个类的时候,会从Bootstrap Loader(启动类加载器),Extended Loader(扩展类加载器)从上往下加载,发现都加载不了,最后由AppClass Loader从CLASSPATH中找到加载TestMain这个类,读取这个文件中的二进制数据,接着对这部分数据进行验证、准备、解析、初始化,最后会把这个类的类信息、常量、静态变量、以及编译器编译后的代码等存入运行时数据区中的方法区。

public void TestMethod()

TestMethod 也被放入方法区。

Sample testSample = new Sample(" test ");

先看”=”右边的:“new Sample(“ test “)”也是一个类跟上面一样,加载这个类的相关信息到方法区中,然后就是就是要在运行时数据区中的堆进行分配内存,方法区的类信息和堆怎么对应呢?其实堆只是存放了这个Sample实例的一个指向方法区的引用地址testSample,他主要是存放在运行时数据的Java虚拟机栈的栈帧中的局部变量表中”=”其实不是赋值,而是testSample这个变量指向了堆中的Sample实例

testSample.getName();

testSample指向了堆中Sample的实例,然后根据这个实例持有的引用拿到了方法区这个类的相关信息,其中的一个方法getName();接着执行这个方法打印数据

分析完毕,谢谢大家耐心看完,有什么不对的地方,欢迎指出,谢谢!

文章目录
  1. 1. 在讨论JVM内存区域分析之前,先来看一下Java程序具体执行的过程:
  2. 2. Java 程序的执行过程:Java 源代码文件(.Java文件)-> Java Compiler(Java编译器)->Java 字节码文件(.class文件)->类加载器(Class Loader)->Runtime Data Area(运行时数据)-> Execution Engine(执行引擎)。上篇文章已经分析了 Runtime Data Area(运行时数据) 这一块,那我们今天就来分析一下Java程序执行过程的 类加载器(Class Loader) 这一块
  • 一、类加载器(ClassLoader)
    1. 0.1. 把Java类的数据从Class文件加载到虚拟机内存中,然后对这部分数据进行验证、准备、解析、初始化,最终形成可以被虚拟机直接使用的Java类型。
  • 学习这个有什么好处呢?
  • 类加载器(ClassLoader)的一些方法:
  • 二、类加载的分类
  • 三、类加载过程
    1. 0.1. 寻找jre目录,寻找jvm.dll,并初始化JVM,JVM启动后,运行Bootstrap Loader,该Bootstrap Loader(启动类加载器)自动加载Extended Loader(扩展类加载器)和AppClass Loader(系统类加载器),最后AppClass Loader加载CLASSPATH目录下定义的Class。
  • 四、双亲委派模型
    1. 0.1. 如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。
  • 1. 代码实现:
  • “双亲委派模型”有什么作用呢?
  • 五、类加载的三种方式
  • 六、案例分析
    1. 1. 基于上篇文章的”深入了解Java之虚拟机内存”和这篇”深入了解Java之类加载”的理论知识,我们用这个例子简单的来了解一下类的加载和内存分配的一些逻辑:
  • 过程分析
  • 分析完毕,谢谢大家耐心看完,有什么不对的地方,欢迎指出,谢谢!