JVM类加载
目录1.内存结构概述
2.类加载子系统概述
3.类的加载过程
2.1加载
2.2Linking
2.2.1验证(Verify)
- 确保Class文件的字节流中的信息正确,保证类加载的正确性
- 主要包括四种验证:文件格式验证,元数据验证,字节码验证,符号引用验证
- 例如下面一个元数据的验证,java字节码文件初始文件都是CA FE BA BE
2.2.2准备(Prepare)
2.2.3解析(Resolve)
-
将常量池内的符号引用,转化为直接引用。
例:Object类的引用
2.3初始化(Initlization)
举例:
-
定义父类
public class initTest { public static int A = 1; static { A=2; } }
-
定义子类
public class Son extends initTest { public static int B =A; public static void main(String[] args) { System.out.println(B); } }
-
编译运行
-
查看字节码文件,可以看到加载B的时候先调了父类的clinit,加载了A
举例:
-
编写被调用的class
public class initTest { static { //这里使用循环,目的是为了卡住<clinit>()方法,让别的线程等待 if (true){ System.out.println(Thread.currentThread().getName()+"进来了"); while (true){ } } } }
-
编写两个线程,两个线程都加载initTest,因为对于JVM来说,同一个类只会被加载一次,加载以后类信息等存放在方法区中
public class Son { public static void main(String[] args) { Runnable r = () ->{ System.out.println(Thread.currentThread().getName()+"开始"); initTest initTest = new inittest(); System.out.println(Thread.currentThread().getName()+"结束"); }; Thread t1 = new Thread(r,"线程1"); Thread t2 = new Thread(r,"线程2"); t1.start(); t2.start(); } }
-
编译运行,第一个加载initTest的线程会进去,而另一个加载initTest的线程则会在后面等待
4.类加载器的分类
JVM支持两种类型的类加载器,分别为引导类加载器(Bootstrap ClassLoader),和自定义类加载器(user-defined ClassLoader)
JVM将所有派生于抽象类ClassLoader的类加载器都划分为自定义类加载器
可能你会疑惑,拓展类加载器,和系统类加载器是什么类型,其实他们都是派生于ClassLoader类的,JVM均视为自定义类加载器
引导类加载器,拓展类加载器,系统类加载器,用户自定义加载器,这四者的关系是包含关系。不是子父继承的关系
3.1虚拟机自带的加载器
3.1.1引导类加载器(Bootstrap ClassLoader)
- 这个类加载器使用c/c++实现,嵌套与JVM内部
- 它用来加载Java的核心库(JAVA_HOME/jre/lib/rt.jar、resource.jar或者 sun.boot.class.path 路径下的内容),用于提供JVM自身需要的类
- 它并不继承与java.lang.classLoader,c语言编写,无父加载器
- 它用来加载拓展类加载器和系统类加载器(应用程序类加载器),并指定Bootstrap为他们的父加载器
- 出于安全考虑,Bootstrap ClassLoader只加载包名为java,javax,sun等开头的类
3.1.2拓展类加载器(Extension ClassLoader)
- java语言编写,为sun.misc.Launcher的一个内部类
- 派生于ClassLoader类
- 父加载器为引导类加载器
- 从java.ext.dirs系统属性所指定的目录中加载类库,或从JDK安装目录的jre/lib/ext子目录下加载类库,如果用户创建的JAR放在此目录下,也会由拓展类加载器自动加载。
3.1.3系统类加载器(应用程序类加载器 AppClassLoader)
- java语言编写,为sun.misc.Launcher的内部类
- 派生于ClassLoader类
- 父类加载器为拓展类加载器
- 它负责加载环境遍历classpath或者是系统属性java.class.path指定路径下的类库
- 该类加载器是程序中默认的类加载器,一般来说,我们编写的Java应用的类都是由它来完成加载
- 通过ClassLoader#getSystemClassLoader()的方法,可以获取到该类加载器
举例:
import sun.misc.Launcher;
import java.net.URL;
import java.security.Provider;
public class ClassLoaderTestFyp {
public static void main(String[] args) {
// 获取引导类加载器加载的路径
System.out.println("====================引导类加载器加载的路径==========================");
URL[] urLs = Launcher.getBootstrapClasspath().getURLs();
for (URL urL : urLs) {
System.out.println(urL.toExternalForm());
}
//从上面的路径中随意选择一个类,看一下这个类的类加载器是什么
System.out.println("====================获取引导类加载器===============================");
ClassLoader classLoader = Provider.class.getClassLoader();
System.out.println(classLoader); //应该为null,因为引导类加载器是c和c++编写,我们无法获取到
// 拓展类加载器
System.out.println("====================拓展类加载器加载的路径==========================");
String extDirs = System.getProperty("java.ext.dirs");
System.out.println(extDirs);
// 系统类加载器classpath
System.out.println("====================系统类加载器加载的路径==========================");
String classpath = System.getProperty("java.class.path");
for (String s : classpath.split(";")) {
System.out.println(s);
}
}
}
3.1.4用户自定义类加载器
为什么要自定义类的加载器:
自定义类加载器实现步骤:
- 继承java.lang.classLoader抽象类
- 在jdk1.2之后,建议把自定义的类加载器逻辑写在findclass当中
- 如果没有太过于复杂的需求,可以继承urlclassloader,可以避免自己去读取字节码流的方式,避免自己写urlclassloader
5.ClassLoader介绍
ClassLoader是一个抽象类,其后所有的类加载器都继承自ClassLoader(除引导类加载器)
@H_269_404@
获取ClassLoader的途径
- 方式一:获取当前ClassLoader
clazz.getClassLoader()
- 方式二:获取当前线程上下文的ClassLoader
Thread.currentThread().getContextClassLoader()
- 方式三:获取系统的ClassLoader
ClassLoader.getSystemClassLoader()
DriverManager.getCallerClassLoader()
6.双亲委派机制
Java虚拟机对class文件采用的按需加载的方式,也就是说当需要使用此类的时候,才会把这个类的class文件加载到内存,生成class对象。且,在加载某个类的class文件的时候,Java虚拟机采用的是双亲委派模式,即把请求交给父类加载器处理,它是一种任务委派模式。
工作原理
- 如果一个类加载器收到了类加载的请求,它并不会先自己去加载这个类,而是把这个请求委托给父类的加载器去执行
- 如果父类加载器还存在其父类加载器,则依次向上委托,请求最终将到达顶层的引导类加载器
- 如果父类加载器可以完成类加载的任务,则就成功返回,如果父类加载器无法完成类加载的任务,则子加载器才会自己尝试去加载类,这就是双亲委派模式。
举例:
当我们加载jdbc.jar包用于实现数据库连接的时候,首先我们要知道的是jdbc.jar是基于SPI接口进行实现的,所以在加载的时候会进行双亲委派,从引导类加载器加载SPI核心的类,然后再加载SPI接口类,接着在进行反向委派,通过线程上下文类加载器进行实现类jdbc.jar的加载。
优势
-
避免类的重复加载
-
保护程序安全,防止核心的API被随意的修改
举例:假设我们自己实现了一个java.lang.String,如果给这个类写个main方法,去运行,是不行的,因为当类加载器收到类加载请求的时候会向上委托,会加载核心的String类,而核心的String类无此方法,所以会报错为方法找不到,这也称为沙箱安全机制。
7.其他
-
如何判断两个Class对象是否相等
在JVM中表示两个Class对象是否为同一个类,有两个必要的条件
- 类的全类名必须相同
- 加载这个类的ClassLoader必须相同
8.类的主动使用和被动使用
java程序对类的使用方式分为:主动使用和被动使用
主动使用,又分为7种情况:
除了以上7种情况,其他使用java类的方式,都看作是对类的被动使用,不会导致类的初始化。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。