1.类加载器
来源:此系列章节内容来源尚硅谷康师傅出品,b站视频直通车请点击👉:https://www.bilibili.com/video/BV1PJ411n7xZ?p=1
后续章节不再声明出处。
1.类加载器
1.1类加载器作用
定义:类加载器会根据指定class文件的全限定名称,将其加载到JVM内存,转为Class对象。
类加载器子系统负责从文件系统或网络中加载Class文件,class文件在文件开头有特定的文件标识。
ClassLoader只负责class文件的加载,至于它是否可以运行,则由Execution Engine决定。
加载的类信息存放于一块称位方法区的内存空间。除了类的信息外,方法区中还会存放运行时常量池信息,可能还包括字符串字面量和数字常量(这部分常量信息是Class文件中常量池部分的内存映射)
1.2类加载器ClassLoader
1.class file 存在于本地磁盘上,可以理解为设计师画在纸上的模板,而最终这个模板在执行的时候是要加载到JVM当中来根据这个文件实例化出n个一模一样的实例。
2.class file 加载到JVM中,被称为DNA元数据模板,放在方法区。
3.在.class文件 -> JVM -> 最终成为元数据模板,此过程就要一个运输工具(类装载器Class Loader),扮演一个快递员的角色。
ClassLoader类介绍:
ClassLoader类,它是一个抽象类,其后所有的类加载器都继承自ClassLoader(不包括启动类加载器)
方法名称 | 描述 |
---|---|
gatParent() | 返回该类加载器的超类加载器 |
loadClass(String name) | 加载名称为name的类,返回结果为java.lang.Class类的实例 |
findClass(String name) | 查找名称为name的类,返回结果为java.lang.Class类的实例 |
findLoadedClass(String name) | 查找名称为name的已经被加载过的类,返回结果为java.lang.Class类的实例 |
defineClass(String name,byte[] b,int off,int len) | 把字节数组b中的内容转换为一个Java类,返回结果为java。lang.Class类的实例 |
resolveClass(Class<?> c) | 连接指定的一个Java类 |
1.3类的加载过程
①加载
1.通过一个类的全限定名获取定义此类的二进制字节流
2.将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
3.在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
加载.class文件的方式:
1.从本地系统中直接加载 2.通过网络获取,典型场景:Web Applet 3.从zip压缩包中读取,成为日后jar、war格式的基础 4.运行时计算生成,使用最多的是:动态代理技术 5.由其他文件生成,典型场景:JSP应用 6.从专有数据库中提取.class文件,比较少见 7.从加密文件中获取,典型的防Class文件被反编译的保护措施
②链接
验证(Verify):
1.目的在于确保Class文件的字节流中包含信息符合当前虚拟机要求,保证被加载类的正确性,不会危害虚拟机自身安全。
2.主要包括四种验证:文件格式验证,元数据验证,字节码验证,符号引用验证。
准备(Prepare):
1.为 static 静态变量(类变量)分配内存,并为其设置初始值,即零值。(是被static修饰的变量,不是final的不是常量,只有static修饰的)
-
private static int age = 26; // 类变量age会在准备阶段过后为 其分配四个(int四个字节)字节的空间,并且设置初始值为0,而不是26。
-
注:这些内存都将在方法区内分配内存,实例变量在堆内存中,而且实例变量是在对象初始化时才赋值
2.这里不包含用final修饰的static,若是final的,则在编译期就会设置上最终值。
3.这里不会为实例变量分配初始化,类变量会分配在方法区中,而实例变量是会随着对象一起分配到java堆中。
解析(Resolve):
1.将常量池内的符号引用转换为直接引用的过程。
- 例如 import xxx.xxx.xxx 属于符号引用,而通过指针或者对象地址引用就是直接引用
2.事实上,解析操作往往会伴随着JVM在执行完初始化(Initialization)之后再执行。
3.符号引用就是一组符号来描述所引用的目标。符号引用的字面量形式明确定义在《Java虚拟机规范》的Class文件格式中。直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。
4.解析动作主要针对类或接口、字段、类方法、接口方法、方法类型等。对应常量池中的CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info等。
5.将符号引用转为直接引用的过程。在编译时,Java类不知道引用的类的实际地址,只能用符号引用来代替,类结构文件的常量池中存储了符号引用,包括类和接口的全限定名、类引用、方法引用以及成员变量引用等。如果要使用这些类和方法,就需要把它们转化为 JVM可以直接获取的内存地址或指针,即直接引用。
查看字节码文件idea插件:jclasslib
③初始化
1.初始化阶段就是执行类构造器方法<clinit>()
的过程。比如准备阶段的那个age初始值是0,到这一步就设置为26。
2.此方法不需定义,是javac编译器自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并而来。若类中没有定义静态代码块或静态变量,则不会出现<clinit>
3.构造器方法中指令按语句在源文件中出现的顺序执行。
4.<clinit>()
不同于类的构造器。构造器是虚拟机视角下的<init>()
。
5.若该类具有父类,JVM会保证子类的<clinit>()
执行前,父类的<clinit>()
已经执行完毕。
6.虚拟机必须保证一个类的<clinit>()
方法在多线程下被同步加锁。
7.初始化会对变量进行赋值,即对最初的零值,进行显式初始化,例如 static int num = 0
变成了 static int num = 3
,这些工作都会在类构造器 <clinit>()
方法中执行。而且虚拟机保证了会先去执行父类 <clinit>()
方法 。如果在静态代码块中修改了静态变量的值,会对前面的显示初始化的值进行覆盖
public class DeadThreadTest { public static void main(String[] args) { Runnable r = () -> { System.out.println(Thread.currentThread().getName() + "开始"); DeadThread dead = new DeadThread(); System.out.println(Thread.currentThread().getName() + "结束"); }; Thread t1 = new Thread(r,"线程1"); Thread t2 = new Thread(r,"线程2"); t1.start(); t2.start(); } } class DeadThread{ static{ if(true){ System.out.println(Thread.currentThread().getName() + "初始化当前类"); while(true){ } } } }
输出结果:一个类只会被加载一次,只会被其中一个线程所执行,其中一个线程还没加载完,另一个线程因为同步加锁而不会加载。
大佬地址: https://www.javazhiyin.com/84681.html https://www.javazhiyin.com/80670.html
本文来自博客园,作者:Lz_蚂蚱,转载请注明原文链接:https://www.cnblogs.com/leizia/p/15315504.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步