b站尚硅谷的JVM学习笔记--https://www.bilibili.com/video/BV1PJ411n7xZ
非常建议去看看,小白看笔记很难明白的
类加载器ClassLoader
在java程序运行时的位置:
- 字节码文件存在于本地硬盘上,在执行的时候是要加载到JVM当中根据其进行实例化一个实例。
- 字节码文件加载到JVM中后,被称为DNA元数据模板,存放于一块称为方法区的内存空间,除了类的信息外,方法区中还会存放运行时常量池信息,可能还包括字符串字面量和数字常量(这部分常量信息是Class文件中常量池部分的内存映射)
- 在.class文件-> JVM ->最终成为元数据模板,此过程就要一个运输工具(Class Loader),扮演一个快递员的角色。ClassLoader只负贵class文件的加载,至于它是否可以运行,则由ExecutiorEngine决定。|
加载器的三个阶段
加载阶段
- 通过一个类的全限定名获取定义此类的二进制字节流
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
- 在内存中生成一个代表这个类的java. lang.Class对象,作为方法区这个类的各种数据的访问入口
链接阶段(验证,准备,解析)
验证(Verify) :
- 确保class文件的字节流中包含信息符合当前虚拟机要求,保证被加载类的正确性,不会危害虚拟机自身安全。主要包括四种验证,文件格式验证,元数据验证,字节码验证,符号引用验证。
准备(Prepare) :
- 为类变量分配内存并且设置该类变量的默认初始值,即零值。
- 这里不包含用final修饰的static,因为final在编译的时候就会分配了,准备阶段会显式初始化;
- 这里不会为实例变量分配初始化,类变量会分配在方法区中,而实例变量是会随着对象一起分配到Java堆中。
解析(Resolve) :
- 将常量池内的符号引用转换为直接引用的过程。
- 事实上,解析操作往往会伴随着JVM在执行完初始化之后再执行。
- 符号引用就是一组符号来描述所引用的目标。符号引用的字面量形式明确定义在的Class文件格式中。直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。
- 解析动作主要针对类或接口、字段、类方法、接口方法、方法类型等。
值得注意的是在JDK8之前,静态成员(静态变量(保存对象的位置)和静态方法)都是存储在方法区(也称永久代)中的静态区中。但在JDK8之后,永久代被移除了,取而代之的是元空间(metaspace)依然是方法区。但元空间中存储的主要是.class文件的元数据信息,静态成员的存储位置知由方法区转到了堆内存(heap)中。注意:不管是8前还是8后,静态变量的实例都存储在堆中。而且不管是普通方法和静态方法都是在栈中执行的,这个后边会讲
初始化阶段
- 初始化阶段就是执行类构造器方法
<clinit>()
的过程。此方法不需定义,是javac编译器自动收集类的所有类变量的赋值动作和静态代码块中的语句合并而来。 - 构造器方法中指令按语句在源文件中出现的顺序执行。
<clinit>()
不同于类的构造器。(关联:构造器是虚拟机视角下的<init>()
)- 若该类具有父类,JVM会 保证子类的()执行前,父类的
<clinit>()
已经执行完毕。 - 虚拟机必须保证一个类的
<clinit> ()
方法在多线程下被同步加锁
加载器分类
加载器分为引导类加载器和自定义加载器,自定义加载器都是间接或直接继承ClassLoader类的
获取ClassLoader
方式一:获取当前类的ClassLoader
clazz.getClassLoader()
方式二:获取当前线程上下文的ClassLoader
Threap.currentThread(). getContextClassLoader()
方式三:获取系统的ClassLoader
ClassLoader.getSystemClassLoader()
方式四:获取调用者的ClassLoader
DriverManager.getCallerClassLoader()
获取某加载器的上层加载器
ClassLoader.getParent();
其中引导类加载器时获取不到的,当获取某个类的加载器为null时,我们就要知道这个类的加载器是由引导类加载器而加载的,而java的核心类库几乎是由引导类加载器而加载。
详解加载器:
引导类加载器
用于加载核心类
扩展类加载器
- 派生于ClassLoader类
- 父类加载器为引导类加载器
- 从java. ext. dirs系统属性所指定的目录中加载类库,或从JDK的安装目录的jre/lib/ext子自录(扩展目录)下加载类库。如果用户创建的JAR放在此目录下,也会自动由扩展类加载器加载
应用程序类加载器
- 派生于ClassLoader类
- 父类加载器为扩展类加载器
- 它负责加载环境变量classpath或系统属性java.class.path指定路径下的类库
- 该类加载是程序中默认的类加载器,一般来说,Java应用的类都是由它来完成加载
- 通过ClassLoader.getSystemClassLoader ()方法可以获取到该类加载器
自定义类加载器
为什么要自定义加载器
- 隔离加载类
- 修改类加载方式
- 扩展加载源
- 防止源码泄漏
自定义过程
继承ClassLoader重写方法,获取二进制流,转换成Class(
没有复杂需求则可继承URLClassLoader。)
public class MyClassLoader extends ClassLoader{
private String classpath;
public MyClassLoader(String classpath) {
this.classpath = classpath;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte [] classDate=getDate(name);
if(classDate==null){}
else{
//defineClass方法将字节码转化为类
return defineClass(name,classDate,0,classDate.length);
}
} catch (IOException e) {
e.printStackTrace();
}
return super.findClass(name);
}
//返回类的字节码
private byte[] getDate(String className) throws IOException{
InputStream in = null;
ByteArrayOutputStream out = null;
String path=classpath + File.separatorChar +
className.replace('.',File.separatorChar)+".class";
try {
in=new FileInputStream(path);
out=new ByteArrayOutputStream();
byte[] buffer=new byte[2048];
int len=0;
while((len=in.read(buffer))!=-1){
out.write(buffer,0,len);
}
return out.toByteArray();
}
catch (FileNotFoundException e) {
e.printStackTrace();
}
finally{
in.close();
out.close();
}
return null;
}
}
类加载器的------双亲委派机制
Java虛拟机需要使用某类时才会将它的class文件加载到内存生成class对象。而且加载某个类的class文件时,Java虚拟机采用的是双亲委派模式,即把请求交由父类处理它是一种任务委派模式。
工作原理
- 如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行;
- 如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归请求最终将到达顶层的启动类加载器
- 如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式。
JVM必须知道一个类型是由启动加载器加载的还是由用户类加载器加载的。如果一个类型是由用户类加载器加载的,那么JVM会将这个类加载器的一个引用作为类型信息的一部分保存在方法区中。当解
析一个类型到另一个类型的引用的时候,JVM需要保证这两个类型的类加载器是相同的。
结论:
如果我们自定义一个与java核心类同名且同包名的类(如java.lang.String),加载器就层层向上委托到引导类加载器,然后引导类加载器会去指定目录找,结果找到的肯定是核心类,而不会加载我们自定义的,这样就保护了程序安全。这使类有了个层次关系,避免类加载错误,防止核心类篡改,因此判定两个类是否相同的必要条件就是同类名,同包名,同加载器。
如何查看字节码
需要进行解析,而javap是jdk自带的解析工具。
用法:javap <options> <classes>
,更多详细信息可通过javap -help
查看
一般常用的选项有:
- -v,不仅会输出行号、本地变量表信息、,还会输出当前类用到的常量池等信息
- -l 会输出行号和本地变量表信息。
- -c 会对当前class字节码进行解析
如: javap -l -c Test.class