类加载器ClassLoader,是JVM提供给应用程序去获取类和接口的字节码数据的。
上面的类加载器对JVM进行了本地接口调用。本地接口即JNI,Java Native Interface,允许Java调用其他语言写的方法,如HostSpot类加载器调用JVM中用c++写的一些方法。
两类:
使用Arthas来查看所有的类加载器:
//执行
classloader
除了上面提到的三种类加载器,DelegatingClassLoader是JDK底层用来提升反射效率的加载器
启动类加载器(Bootstrap ClassLoader)是由Hotspot虚拟机提供的、使用C++编写的类加载器。 默认加载Java安装目录/jre/lib下的类文件,比如rt.jar(String、Integer、Date类都在这儿)、tools.jar、resources.jar
//尝试获取启动类的加载器
//相当于在Java程序(上层)中获取JVM(底层)的类加载器
public class BootstrapLoaderTest {
public static void main(String[] args) throws IOException {
ClassLoader classLoader = String.class.getClassLoader();
System.out.println(classLoader);
}
}
//返回null,从安全的角度考虑,这是合理的
继续用Arthas,sc指令查看某个类的详细信息
sc -d java.lang.String
发现这个基础类的加载器为空:
既然Java程序中获取不到启动类加载器,那如果后期我需要做扩展的jar包,如何让启动类加载器去加载我的jar呢:
jre/lib
下进行扩展:不推荐改JDK目录,放入也可能因为名称不会被正常加载-Xbootclasspath/a:jar包目录/jar包名
,这里的a即add的含义这里随便创建个模块,里面只有一个测试类MyLoad:
public class MyLoad {
static {
System.out.println("jar包中的Load类被加载了");
}
}
mvn package打包后去另一个模块中添加JVM参数,
另一个模块中写个测试代码:
public class BootstrapLoaderTest {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Class<?> clazz = Class.forName("com.plat.domain.MyLoad");
System.out.println(clazz);
ClassLoader classLoader = clazz.getClassLoader();
System.out.println(classLoader);
}
}
运行,可以看到MyLoad类加载成功,且是被启动类加载器加载的:
以上的应用场景是:需要开发一些偏底层的类时,并需要用启动类加载器去加载它时,可考虑添加这个JVM参数,并把jar包拷贝到对应目录。如以镜像方式打包时,Dockerfile的cmd中可加ADD 操作,并添加JVM参数即可。
扩展类加载器负责加载那些通用,但不是很重要的类:Java安装目录下/jre/lib/ext下的类文件
比如上面的nashorn.jar,尝试获取这个jar中一个类ScriptEnvironment的类加载器:
public class LoaderTest {
public static void main(String[] args) throws ClassNotFoundException {
ClassLoader classLoader = ScriptEnvironment.class.getClassLoader();
System.out.println(classLoader);
}
}
//返回结果是扩展类加载器
sun.misc.Launcher$ExtClassLoader@1d44bcfa
如果以后的工作用写了一些通用,但不重要的类,可考虑让扩展类加载器去加载,扩展方式:
-Djava.ext.dirs=jar包目录
//-Djava.ext.dirs=jar包目录会覆盖原始的ext扩展目录
//正确用法为:
-Djava.ext.dirs=jar包目录;(windows的目录):(macos/linux的目录)
注意上面dirs路径中有空格时,运行报错,需要给dirs加上双引号,如:
-Djava.ext.dirs="C:\Program Files\jdk8\jre\lib\ext;D:\tmp\"
测试代码:
public class LoaderTest {
public static void main(String[] args) throws ClassNotFoundException {
//原有的
ClassLoader classLoader = ScriptEnvironment.class.getClassLoader();
System.out.println(classLoader);
//让扩展类加载器去加载的jar
Class<?> clazz = Class.forName("com.plat.domain.MyLoad");
ClassLoader classLoader2 = clazz.getClassLoader();
System.out.println(classLoader2);
}
}
运行结果:
sun.misc.Launcher$ExtClassLoadera45ee12a7
sun.misc.Launcher$ExtClassLoader@45ee12a7
可以发现,JDK原来自己的ext目录下的类和我新加的jar包下的类,其类加载器都是扩展类加载器。保持了原扩展目录的同时,新加了自己的jar的类
应用程序类加载器加载的是classPath下的类文件,包括项目中自己编写的类和接口的字节码文件,以及引入的第三方jar包中的类和接口的字节码文件
public class LoaderTest {
public static void main(String[] args) throws ClassNotFoundException {
//自己写的类
ClassLoader classLoader1 = TestJvm.class.getClassLoader();
System.out.println(classLoader1);
//引入的jar的
ClassLoader classLoader2 = FileUtil.class.getClassLoader();
System.out.println(classLoader2);
}
}
运行:
继续用Arthas工具,查看某个类加载器加载的路径全部打印出来
classloader –c hash值