封面来源:微信公众号 杂乱无章--树熊
受多种情况的影响,又开始看JVM 方面的知识。
1、Java 实在过于内卷,没法不往深了学。
2、面试题问的多,被迫学习。
3、纯粹的好奇。 很喜欢一句话:“八小时内谋生活,八小时外谋发展。” --- 望别日与君相见时,君已有所成。
共勉
一、概述
Java类加载器(英语:Java Classloader)是Java运行时环境(Java Runtime Environment)的一部分,负责动态加载Java类到Java虚拟机的内存空间中。类通常是按需加载,即第一次使用该类时才加载。由于有了类加载器,Java运行时系统不需要知道文件与文件系统。
ClassLoader只负责class文件的加载,至于它是否可以运行,则由Execution Engine(执行引擎)决定。
==每个Java类必须由某个类加载器装入到内存。==
图示:
加载的类信息存放于一块称为方法区的内存空间。除了类的信息外,方法区中还会存放运行时常量池信息,可能还包括字符串字面量和数字常量(这部分常量信息是Class文件中常量池部分的内存映射)
二、类加载器的分类
JVM支持两种类型的类加载器 。分别为引导类加载器(Bootstrap ClassLoader)和自定义类加载器(User-Defined ClassLoader) 从概念上来讲,自定义类加载器一般指的是程序中由开发人员自定义的一类类加载器,但是Java虚拟机规范却没有这么定义,而是将所有派生于抽象类ClassLoader的类加载器都划分为自定义类加载器
类加载器结构图:
2.1、Bootstrap ClassLoader(根类加载器)
又可称之为 根类加载器、引导类加载器、启动类加载器,看到这些都要知道说的是 ==Bootstrap ClassLoader==。
- 此类加载器是Java虚拟机的一部分,使用native代码(C++)编写。
所以它是获取不到的
- 加载位于/jre/lib目录中的或者被参数-Xbootclasspath所指定的目录下的核心Java类库。
- 如图:
- 如上图:用来
加载Java的核心库
,rt.jar这个jar包就是Bootstrap根类加载器负责加载的,其中包含了java各种核心的类如java.lang,java.io,java.util,java.sql等 , - Bootstrap ClassLoader 并
不继承自java.lang.ClassLoader
,没有父加载器
- 加载扩展类和应用程序类加载器,并作为他们的父类加载器(当他俩的爹)
- Bootstrap启动类加载器只加载包名为
java、javax、sun
等开头的类
代码测试:
public class BootstrapClassLoaderTest { public static void main(String[] args) { String s = new String(); ClassLoader loader = s.getClass().getClassLoader(); System.out.println("让我们一起来看看 String 是由加载的吧 ==>"+loader); //Bootstrap ClassLoader ==>null Integer integer = 2; ClassLoader classLoader1 = integer.getClass().getClassLoader(); System.out.println("让我们一起来看看 Integer 是由加载的吧 "+classLoader1); //Bootstrap ClassLoader ==>null } }
通过测试,我们可以发现全部都是 null ,其实 BootstrapClassLoader 我们是获取不到,它是由C++编写的。
测试小结:
我们可以证明,java、javax、sun
等开头的类 的类确实是由启动类加载器加载的。 其他的大家也都可以试一试。
大家也会想,如果我们建项目时,也创建 一个java.lang 包,然后在底下写一个String类 或者写一个自定义类(MyUser)。启动类加载器会加载它吗?
答案:是什么???? 见文末。
2.2、Extension ClassLoader(扩展类加载器)
- ClassLoader 的派生类
- 父类加载器为启动类加载器(Bootstrap ClassLoader )
- 用来在/jre/lib/ext,或java.ext.dirs中指明的目录中加载 Java的扩展库。
- Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。
- 该类由sun.misc.Launcher$ExtClassLoader实现。
- Java语言编写
环境要jdk 8 。 我的环境是 jdk11。所以底下代码是从 blog.csdn.net/sj158149630… 搬过来的。
代码测试:
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 } }
System.out.println(classLoader); //null 再次证明我们无法获取到启动类加载器
**********启动类加载器************** file:/Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/resources.jar file:/Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/rt.jar file:/Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/sunrsasign.jar file:/Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/jsse.jar file:/Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/jce.jar file:/Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/charsets.jar file:/Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/jfr.jar file:/Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/classes null ***********扩展类加载器************* /Users/xiexu/Library/Java/Extensions:/Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/ext:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java sun.misc.Launcher$ExtClassLoader@d716361
2.3、AppClassLoader (应用程序类加载器 系统类加载器)
- 同样是 Java 语言编写。
- 由sun.misc.LaunchersAppClassLoader实现 (后有代码实现查看)
- 派生于ClassLoader类
该类加载是程序中默认的类加载器
,一般来说,Java应用的类都是由它来完成加载通过classLoader.getSystemclassLoader()
方法可以获取到该类加载器- 加载用户路径(ClassPath)上所指定的类库
代码测试:
public class AppClassLoaderTest { public static void main(String[] args) { BootstrapClassLoaderTest loaderTest = new BootstrapClassLoaderTest(); ClassLoader classLoader = loaderTest.getClass().getClassLoader(); System.out.println(classLoader); //jdk.internal.loader.ClassLoaders$AppClassLoader@2437c6dc } }
==小结==:所以一般来说,Java应用的类都是由它来完成加载
2.4、User-Defined ClassLoader (自定义类加载器)
可以通过继承java.lang.ClassLoader类的方式实现自己的类加载器,以满足一些特殊的需求而不需要完全了解Java虚拟机的类加载的细节。
可用于:
- 运行时装载或卸载类。这常用于:
- 改变Java字节码的装入,例如,可用于Java类字节码的加密装入。)
- 修改已装入的字节码weavingof aspects when usingaspect-oriented programming)。
2.5、关于ClassLoader类
==ClassLoader类,它是一个抽象类
,其后所有的类加载器都继承自ClassLoader(不包括启动类加载器
)==
如何获取到ClassLoader 呢?
总共有下面四种方式:
1、获取当前类的Classloader
2、获取当前线程上下文的ClassLoader
3、获取系统的ClassLoader
4、获取调用者的ClassLoader DriverManager.getCallerClassLoader()
public static void main(String[] args) { //1、获取当前类的ClassLoader BootstrapClassLoaderTest classLoaderTest = new BootstrapClassLoaderTest(); ClassLoader classLoader = classLoaderTest.getClass().getClassLoader(); System.out.println(classLoader); //2、获取当前线程上下文的ClassLoader new Thread(){ @Override public void run() { System.out.println(Thread.currentThread()); ClassLoader classLoader1 = Thread.currentThread().getContextClassLoader(); System.out.println(classLoader); } }.run(); //3、获取系统的ClassLoader ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader(); System.out.println(systemClassLoader); }
2.6、加载流程图
下图来自于:图
四、答案
我也亲自去尝试了,全部都是报错。直接报 关于包的错误。创立String 类型也是一样的。
代码及结构:
错误:一、报的直接是说 此java程序包 已存在另一个模块中 。二、报启动类加载器 找不到。
自言自语
每天的焦虑生活,让人无比疲惫,却又无可奈何。