笔记来源:尚硅谷 JVM 全套教程,百万播放,全网巅峰(宋红康详解 java 虚拟机)
如果自己想手写一个 Java 虚拟机的话,主要考虑哪些结构呢?
类加载器子系统作用
类加载器 ClasLoader 角色
类的加载过程
/**
*示例代码
*/
public class HelloLoader {
public static void main(String[] args) {
System.out.println("Hello World!");
}
}
用流程图表示上述示例代码:
**备注:**JDK8中 Class对象存放在堆区,类的元数据存在方法区(元空间),元数据≠
类的Class对象!
Class对象是加载的最终产品,类的方法代码,变量名,方法名,访问权限,返回值等等在方法区(元空间)
补充:加载 class 文件的方式
链接的准备阶段为类变量分配内存并设置默认初始值,初始化阶段进行显式初始化.(不包括final修饰的static变量)
对于final修饰的static变量,在编译阶段分配内存,在链接的准备阶段就会被设置成指定的值.(无默认初始化)
JVM 支持两种类型的类加载器 。分别为引导类加载器(Bootstrap ClassLoader)和自定义类加载器(User-Defined ClassLoader)。
从概念上来讲,自定义类加载器一般指的是程序中由开发人员自定义的一类类加载器,但是 Java 虚拟机规范却没有这么定义,而是将所有派生于抽象类 ClassLoader 的类加载器都划分为自定义类加载器。
无论类加载器的类型如何划分,在程序中我们最常见的类加载器始终只有 3 个,如下所示:
这里的四者之间的关系是包含关系。不是上层下层,也不是子父类的继承关系。
启动类加载器(引导类加载器,Bootstrap ClassLoader)
扩展类加载器(Extension ClassLoader)
应用程序类加载器(系统类加载器,AppClassLoader)
代码演示
/**
* @author shkstart
* @create 2020 上午 9:22
*/
public class ClassLoaderTest {
public static void main(String[] args) {
//获取系统类加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2
//获取其上层:扩展类加载器
ClassLoader extClassLoader = systemClassLoader.getParent();
System.out.println(extClassLoader);//sun.misc.Launcher$ExtClassLoader@1b6d3586
//获取其上层:获取不到引导类加载器(因为底层使用C/C++编写)
ClassLoader bootstrapClassLoader = extClassLoader.getParent();
System.out.println(bootstrapClassLoader);//null
//对于用户自定义类来说:默认使用系统类加载器进行加载
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
System.out.println(classLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2
//String类使用引导类加载器进行加载的。---> Java的核心类库都是使用引导类加载器进行加载的。
ClassLoader classLoader1 = String.class.getClassLoader();
System.out.println(classLoader1);//null 引导类加载器无法获取
}
}
/**
* @author shkstart
* @create 2020 上午 12:02
*/
public class ClassLoaderTest1 {
public static void main(String[] args) {
System.out.println("**********启动类加载器**************");
//获取BootstrapClassLoader能够加载的api的路径
URL[] urLs = sun.misc.Launcher.getBootstrapClassPath().getURLs();
for (URL element : urLs) {
System.out.println(element.toExternalForm());
}
//从上面的路径中随意选择一个类,来看看他的类加载器是什么:引导类加载器
ClassLoader classLoader = Provider.class.getClassLoader();
System.out.println(classLoader);//null
System.out.println("***********扩展类加载器*************");
String extDirs = System.getProperty("java.ext.dirs");
for (String path : extDirs.split(";")) {
System.out.println(path);
}
//从上面的路径中随意选择一个类,来看看他的类加载器是什么:扩展类加载器
ClassLoader classLoader1 = CurveDB.class.getClassLoader();
System.out.println(classLoader1);//sun.misc.Launcher$ExtClassLoader@1540e19d
}
}
在 Java 的日常应用程序开发中,类的加载几乎是由上述 3 种类加载器相互配合执行的,在必要时,我们还可以自定义类加载器,来定制类的加载方式。 为什么要自定义类加载器?
用户自定义类加载器实现步骤:
代码演示:
/**
* 自定义用户类加载器
* @author shkstart
* @create 2019 下午 12:21
*/
public class CustomClassLoader extends ClassLoader {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] result = getClassFromCustomPath(name);
if(result == null){
throw new FileNotFoundException();
}else{
return defineClass(name,result,0,result.length);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
throw new ClassNotFoundException(name);
}
//获取字节码文件的字节流
private byte[] getClassFromCustomPath(String name){
//从自定义路径中加载指定类:细节略
//如果指定路径的字节码文件进行了加密,则需要在此方法中进行解密操作。
return null;
}
public static void main(String[] args) {
CustomClassLoader customClassLoader = new CustomClassLoader();
try {
Class<?> clazz = Class.forName("One",true,customClassLoader);
Object obj = clazz.newInstance();
System.out.println(obj.getClass().getClassLoader());
} catch (Exception e) {
e.printStackTrace();
}
}
}
ClassLoader 类是一个抽象类,其后所有的类加载器都继承自 ClassLoader(不包括启动类加载器)
常见方法如下:
sun.misc.Launcher 它是一个 java 虚拟机的入口应用
获取 ClassLoader 的途径
方式一:获取当前 ClassLoader
clazz.getClassLoader()
方式二:获取当前线程上下文的 ClassLoader
Thread.currentThread().getContextClassLoader()
方式三:获取系统的 ClassLoader
ClassLoader.getSystemClassLoader()
方式四:获取调用者的 ClassLoader
DriverManager.getCallerClassLoader()
代码演示:
/**
* @author shkstart
* @create 2020 上午 10:59
*/
public class ClassLoaderTest2 {
public static void main(String[] args) {
try {
//1.
ClassLoader classLoader = Class.forName("java.lang.String").getClassLoader();
System.out.println(classLoader);//null
//2.
ClassLoader classLoader1 = Thread.currentThread().getContextClassLoader();
System.out.println(classLoader1);//sun.misc.Launcher$AppClassLoader@18b4aac2
//3.
ClassLoader classLoader2 = ClassLoader.getSystemClassLoader().getParent();
System.out.println(classLoader2);//sun.misc.Launcher$ExtClassLoader@1b6d3586
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
Java 虚拟机对 class 文件采用的是**按需加载的方式,也就是说当需要使用该类时才会将它的 class 文件加载到内存生成 class 对象。而且加载某个类的 class 文件时,Java 虚拟机采用的是双亲委派模式**,即把请求交由父类处理,它是一种任务委派模式。
工作原理
代码演示:
项目下建立java.lang包,里面创建String类
public class String {
static{
System.out.println("我是自定义的String类的静态代码块");
}
}
在java1包下创建StringTest类
public class StringTest {
public static void main(String[] args) {
java.lang.String str = new java.lang.String();
System.out.println("hello,atguigu.com");
StringTest test = new StringTest();
System.out.println(test.getClass().getClassLoader());
}
}
执行StringTest方法
在String类中增加Main方法,执行观察结果:
举例
当我们加载 jdbc.jar 用于实现数据库连接的时候,首先我们需要知道的是 jdbc.jar 是基于 SPI 接口进行实现的,所以在加载的时候,会进行双亲委派,最终从根加载器(启动类)中加载 SPI 核心类.然后在加载 SPI 接口类,接着在进行反向委派,通过线程上下文类加载器进行实现类 jdbc.jar 的加载。
**补充:**spi是引导类加载器加载,第三方的类库实现是系统类加载器加载。当使用spi时,是无法访问系统类加载器加载,所以引入了线程上下文类加载器,由线程上下文类加载器来加载第三方的库,这样就能使用到第三方的库了
优势
沙箱安全机制
自定义 String 类,但是在加载自定义 String 类的时候会率先使用引导类加载器加载,而引导类加载器在加载的过程中会先加载 jdk 自带的文件(rt.jar 包中 java\lang\String.class),报错信息说没有 main 方法,就是因为加载的是 rt.jar 包中的 string 类。这样可以保证对 java 核心源代码的保护,这就是沙箱安全机制。
如何判断两个 class 对象是否相同
在 JVM 中表示两个 class 对象是否为同一个类存在两个必要条件:
换句话说,在 JVM 中,即使这两个类对象(class 对象)来源同一个 Class 文件,被同一个虚拟机所加载,但只要加载它们的 ClassLoader 实例对象不同,那么这两个类对象也是不相等的。
对类加载器的引用
JVM 必须知道一个类型是由启动加载器加载的还是由用户类加载器加载的。如果一个类型是由用户类加载器加载的,那么 JVM 会将这个类加载器的一个引用作为类型信息的一部分保存在方法区中。当解析一个类型到另一个类型的引用的时候,JVM 需要保证这两个类型的类加载器是相同的。
类的主动使用和被动使用
Java 程序对类的使用方式分为:主动使用和被动使用。
主动使用,又分为七种情况:
创建类的实例
访问某个类或接口的静态变量,或者对该静态变量赋值
调用类的静态方法
反射(比如:Class.forName(“com.atguigu.Test”))
初始化一个类的子类
Java 虚拟机启动时被标明为启动类的类
JDK 7 开始提供的动态语言支持:
java.lang.invoke.MethodHandle 实例的解析结果
REF_getStatic、REF_putStatic、REF_invokeStatic 句柄对应的类没有初始化,则初始化
除了以上七种情况,其他使用 Java 类的方式都被看作是对类的被动使用,都不会导致类的初始化。
如果有收获!!! 希望老铁们来个三连,点赞、收藏、转发。
创作不易,别忘点个赞,可以让更多的人看到这篇文章,顺便鼓励我写出更好的博客