三大类加载器

1.启动类加载器-BootstrapClassLoader

启动类加载器是由一个系统参数定义:sun.boot.class.path,其确定加载哪些路径的jar包或class文件。
代码示例:

  1. public static void main(String[] args) {
  2. System.out.println(String.class.getClassLoader());// java中获取不到根类加载器 输出null
  3. String path = System.getProperty("sun.boot.class.path");
  4. for (String p : path.split(";")) {
  5. System.out.println(p);
  6. }
  7. }

输出

null C:\Program Files\jdk\jre\lib\resources.jar C:\Program Files\jdk\jre\lib\rt.jar C:\Program Files\jdk\jre\lib\sunrsasign.jar C:\Program Files\jdk\jre\lib\jsse.jar C:\Program Files\jdk\jre\lib\jce.jar C:\Program Files\jdk\jre\lib\charsets.jar C:\Program Files\jdk\jre\lib\jfr.jar C:\Program Files\jdk\jre\classes

我们可以加在启动参数中通过-Xbootclasspath参数修改其加载路径

  1. -Xbootclasspath:路径 替换系统指定路径 — 不推荐
  2. -Xbootclasspath/a:路径 指定的路径会在jdk核心配置之后搜索 — 可用,推荐
  3. -Xbootclasspath/p:路径 指定的路径会在jdk核心配置之前搜索 — 可用,不推荐

示例:

a.启动参数加-Xbootclasspath/a:D:/tmp/commons-beanutils-1.9.4.jar

pom中增加依赖

  1. <dependencies>
  2. <dependency>
  3. <groupId>commons-beanutils</groupId>
  4. <artifactId>commons-beanutils</artifactId>
  5. <version>1.9.4</version>
  6. </dependency>
  7. </dependencies>

同时,复制jar包到D:/tmp/目录下。

  1. public static void main(String[] args) {
  2. System.out.println(BeanUtils.class.getClassLoader());
  3. String path = System.getProperty("sun.boot.class.path");
  4. for (String p : path.split(";")) {
  5. System.out.println(p);
  6. }
  7. }

输出:

null C:\Program Files\jdk\jre\lib\resources.jar C:\Program Files\jdk\jre\lib\rt.jar C:\Program Files\jdk\jre\lib\sunrsasign.jar C:\Program Files\jdk\jre\lib\jsse.jar C:\Program Files\jdk\jre\lib\jce.jar C:\Program Files\jdk\jre\lib\charsets.jar C:\Program Files\jdk\jre\lib\jfr.jar C:\Program Files\jdk\jre\classes D:/tmp/commons-beanutils-1.9.4.jar

可以看到BeanUtils的类加载器时根类加载器(null),同时搜索路径的最后增加了D:/tmp/commons-beanutils-1.9.4.jar

b.启动参数增加-Xbootclasspath/p:D:/tmp/commons-beanutils-1.9.4.jar

上面代码不变,只修改启动参数,输出

null D:/tmp/commons-beanutils-1.9.4.jar C:\Program Files\jdk\jre\lib\resources.jar C:\Program Files\jdk\jre\lib\rt.jar C:\Program Files\jdk\jre\lib\sunrsasign.jar C:\Program Files\jdk\jre\lib\jsse.jar C:\Program Files\jdk\jre\lib\jce.jar C:\Program Files\jdk\jre\lib\charsets.jar C:\Program Files\jdk\jre\lib\jfr.jar C:\Program Files\jdk\jre\classes

  1. 可见自定义的搜索路径在原搜索路径之前,也就是先搜索自定义路径,再搜索jdk核心路径,这种方式不安全,不推荐使用。

2.扩展类加载器-ExtendClassLoader

根据上小接我们类比应该知道,扩展类加载器是由-Djava.ext.dirs=目录参数配置的目录确定(替换)。
示例:
启动参数:-Djava.ext.dirs=D:/tmp

  1. public class ClassLoaderDemo {
  2. public static void main(String[] args) {
  3. System.out.println(BeanUtils.class.getClassLoader());
  4. String path = System.getProperty("java.ext.dirs");
  5. for (String p : path.split(";")) {
  6. System.out.println(p);
  7. }
  8. }
  9. }

输出:

sun.misc.Launcher$ExtClassLoader@66d3c617 D:/tmp

原加载目录都被替换掉了

3.系统类加载器-AppClassLoader

系统类加载器会加载所有的classpath下的类,类路径目录由参数java.class.path=路径指定。
加载类路径下的类

  1. public class ClassLoaderDemo {
  2. public static void main(String[] args) {
  3. System.out.println(ClassLoaderDemo.class.getClassLoader());
  4. String path = System.getProperty("java.class.path");
  5. for (String p : path.split(";")) {
  6. System.out.println(p);
  7. }
  8. }
  9. }

输出:

sun.misc.Launcher$AppClassLoader@18b4aac2 C:\Program Files\jdk\jre\lib\charsets.jar …省略 D:\projects\jvm\target\classes C:\Users\leber.m2\repository\commons-beanutils\commons-beanutils\1.9.4\commons-beanutils-1.9.4.jar C:\Users\leber.m2\repository\commons-logging\commons-logging\1.2\commons-logging-1.2.jar C:\Users\leber.m2\repository\commons-collections\commons-collections\3.2.2\commons-collections-3.2.2.jar D:\ideaIU-2021.3.2.win\lib\idea_rt.jar

同理,我们可以在启动参数中配置classpath路径:-Djava.class.path=xxxx,如同idea启动时一样
image.png

类加载器源码

我们知道根类加载器时c++编写又jvm实现,但是扩展类加载器和系统类加载器时java层面实现,其源码我们可以查看(hotspot实现,只有通过反编译)。
在rt.jar包下有个Launcher类,其构造方法中实例化了扩展类加载器和系统类加载器

扩展类加载器

  1. public Launcher() {
  2. Launcher.ExtClassLoader var1;
  3. try {
  4. // 扩展类加载器
  5. var1 = Launcher.ExtClassLoader.getExtClassLoader();
  6. } catch (IOException var10) {
  7. throw new InternalError("Could not create extension class loader", var10);
  8. }
  9. try {
  10. // 系统类加载器,以扩展类加载器作为父加载器
  11. this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
  12. } catch (IOException var9) {
  13. throw new InternalError("Could not create application class loader", var9);
  14. }
  15. Thread.currentThread().setContextClassLoader(this.loader);
  16. // ...
  17. }

扩展类加载器定义在Launcher类中

  1. // 继承至URLClassLoader
  2. static class ExtClassLoader extends URLClassLoader {
  3. // 可知扩展类加载器是单例的
  4. private static volatile Launcher.ExtClassLoader instance;
  5. // 同步获取
  6. public static Launcher.ExtClassLoader getExtClassLoader() throws IOException {
  7. if (instance == null) {
  8. Class var0 = Launcher.ExtClassLoader.class;
  9. synchronized(Launcher.ExtClassLoader.class) {
  10. if (instance == null) {
  11. instance = createExtClassLoader();
  12. }
  13. }
  14. }
  15. return instance;
  16. }
  17. // 创建扩展类加载器
  18. private static Launcher.ExtClassLoader createExtClassLoader() throws IOException {
  19. try {
  20. return (Launcher.ExtClassLoader)AccessController.doPrivileged(new PrivilegedExceptionAction<Launcher.ExtClassLoader>() {
  21. public Launcher.ExtClassLoader run() throws IOException {
  22. // java.ext.dirs参数配置的目录
  23. File[] var1 = Launcher.ExtClassLoader.getExtDirs();
  24. int var2 = var1.length;
  25. // 以后在看
  26. for(int var3 = 0; var3 < var2; ++var3) {
  27. MetaIndex.registerDirectory(var1[var3]);
  28. }
  29. // 实例化
  30. return new Launcher.ExtClassLoader(var1);
  31. }
  32. });
  33. } catch (PrivilegedActionException var1) {
  34. throw (IOException)var1.getException();
  35. }
  36. }
  37. // 构造方法
  38. public ExtClassLoader(File[] var1) throws IOException {
  39. super(getExtURLs(var1), (ClassLoader)null, Launcher.factory);
  40. SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this);
  41. }

系统类加载器AppClassLoader

  1. static class AppClassLoader extends URLClassLoader {
  2. final URLClassPath ucp = SharedSecrets.getJavaNetAccess().getURLClassPath(this);
  3. public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException {
  4. final String var1 = System.getProperty("java.class.path");
  5. final File[] var2 = var1 == null ? new File[0] : Launcher.getClassPath(var1);
  6. return (ClassLoader)AccessController.doPrivileged(new PrivilegedAction<Launcher.AppClassLoader>() {
  7. public Launcher.AppClassLoader run() {
  8. URL[] var1x = var1 == null ? new URL[0] : Launcher.pathToURLs(var2);
  9. // 实例化 以classpath,和扩展类加载器作为参数
  10. return new Launcher.AppClassLoader(var1x, var0);
  11. }
  12. });
  13. }
  14. // 构造方法
  15. AppClassLoader(URL[] var1, ClassLoader var2) {
  16. super(var1, var2, Launcher.factory);
  17. this.ucp.initLookupCache(this);
  18. }
  19. public Class<?> loadClass(String var1, boolean var2) throws ClassNotFoundException {
  20. int var3 = var1.lastIndexOf(46);
  21. if (var3 != -1) {
  22. SecurityManager var4 = System.getSecurityManager();
  23. if (var4 != null) {
  24. var4.checkPackageAccess(var1.substring(0, var3));
  25. }
  26. }
  27. if (this.ucp.knownToNotExist(var1)) {
  28. Class var5 = this.findLoadedClass(var1);
  29. if (var5 != null) {
  30. if (var2) {
  31. this.resolveClass(var5);
  32. }
  33. return var5;
  34. } else {
  35. throw new ClassNotFoundException(var1);
  36. }
  37. } else {
  38. return super.loadClass(var1, var2);
  39. }
  40. }
  41. private void appendToClassPathForInstrumentation(String var1) {
  42. assert Thread.holdsLock(this);
  43. super.addURL(Launcher.getFileURL(new File(var1)));
  44. }
  45. private static AccessControlContext getContext(File[] var0) throws MalformedURLException {
  46. PathPermissions var1 = new PathPermissions(var0);
  47. ProtectionDomain var2 = new ProtectionDomain(new CodeSource(var1.getCodeBase(), (Certificate[])null), var1);
  48. AccessControlContext var3 = new AccessControlContext(new ProtectionDomain[]{var2});
  49. return var3;
  50. }
  51. static {
  52. // 是否可并行加载
  53. ClassLoader.registerAsParallelCapable();
  54. }
  55. }

URLClassLoader