1、类加载子系统负责从文件或者网络当中加载.class文件,通常.class文件在文件的打开头有特定的文件标识
2、Classloader只是负责class文件的加载,至于他能不能运行,交给Execution Engine决定
3、加载的类信息存放在一块叫做方法区的内存空间,除了类的信息外,方法区中还会存放运行时常量池信息,可能还包含字符串字面量或者是数字常量(这部分的常量是.class文件中常量池部分的内存映射)
what is 运行时常量池??? 就是常量池在运行时加载到内存里叫做运行时常量池
1、.class 文件存在于本地硬盘上,可以理解为设计师画在纸上的模板,最终这个模板运行的时候是加载到JVM中,根据这个文件创建n个一样的实例
2、.class文件加载到JVM当中,被称为DNA元数据模板,放在方法区
3、在.class 文件 -> JVM -> 成为元数据模板,此过程就要一个运输工具(类装载器Class Loader)扮演一个快递员的角色。
注意:这里说的生成n个一样的实例,不是说n个实例一模一样,而是属性方法都有,能调用的方法也都有,但是不是所有数据的值都是一样的。
类的装载过程图形理解
当JVM开始运行的时候,检查这个类是不是已经被加载,如果没有被加载,那么ClassLoader类加载器开始加载这个类,因为加载的过程当中也可能存在错误,所以,如果装载顺利,就开始执行下一步的链接,如果装载错误,就抛出异常。
类的加载过程有三大步:加载、链接、初始化
这个加载和刚才的加载不是一个东西,是加载三个步骤当中的一个步骤:
1、通过一个类的全限定名获取定义此类的二进制文件流
2、将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
3、在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
加载的几种方式
1、从本地系统中直接加载
2、通过网络获取,典型的应用场景:web applet
3、从zip压缩包当中读取,成为日后jar,war格式的基础
4、运行时计算生成,使用最多的是动态代理技术
5、由其他文件生成,典型场景:jsp应用
6、专有的书库当中提取,比较少见
7、从加密的文件当中获取,典型的防.class 文件被反编译的保护措施
链接分为三个小的步骤:验证、准备、解析
验证(Verify)
准备(Prepare)
解析(Resolve)
我们来使用IDEA来详细解释一下类加载过程当中的初始化过程:
1、clinit() 构造方法:
下面是一段简单的测试代码:
public class HelloLoader {
private int num;
static {
System.out.println("execute");
}
public static void main(String[] args) {
new HelloLoader();
}
}
使用jclasslib插件打开编译成功的class文件,打开是下面这个样子的:
clinit()构造方法:
跟上面描述的一样,clinit()是在类当中存在类变量的赋值,或者是否存在静态代码块。构造器方法clinit()就是这两个的合并。
2、构造器方法的顺序
**修改:**那么把刚才的这段代码再次进行修改
class Loader {
private static int x;
static {
x = 10;
}
}
public class HelloLoader extends Loader {
private int num;
static {
System.out.println("execute");
}
public static void main(String[] args) {
new HelloLoader();
}
}
那么在这次的初始化过程当中,在执行HelloLoader的clinit()的方法之前执行父类的clinit()方法。
3、保证clinit() 方法在多线程下被同步加锁
先看一下下面这些测试代码:
//ThreadTest.java 文件
public class ThreadTest {
public static void main(String[] args) {
Runnable r = () -> {
System.out.println(Thread.currentThread().getName() + " start");
new DeadThread();
System.out.println(Thread.currentThread().getName() + " end");
};
new Thread(r, "thread 1").start();
new Thread(r, "thread 2").start();
}
}
//DeadThread.java文件
public class DeadThread {
private static int num = 0;
static {
num++;
System.out.println("number : " + num);
}
}
这段代码执行的结果是:
thread 1 start
thread 2 start
number : 1
thread 2 end
thread 1 end
从结果看,无论是哪一个线程先执行,这个类只会加载一次,当一个线程在进行加载类的时候,因为同步加锁,所以其他的线程不能同时加载。
下面还有类加载器的具体解释,欢迎继续观看
JVM类加载器