类加载器
1.背景:jdk,jre,jvm
我们利用jdk来开发属于我们自己的java程序,通过jdk的编译器(javac)将我们的java文件编译成java字节码,在jre上运行这些java字节码,使用jre内嵌的jvm解析这些字节码,映射到cpu指令集或操作系统的调用
2.类加载器介绍
class loaders属于jre的一部分,负责在运行时将java类动态加载到jvm,得益于class loaders,jvm在无需知晓底层文件或文件系统时就可以运行java程序
此外java类是按需加载,并不会一次全部加载到内存中
java类由java.lang.ClassLoader的实例进行加载,但是class loader本身也是java类,它又由谁加载呢?这就是bootstrap class loader的用处了,它主要负责加载jdk的核心类库
bootstrap class loader:负责加载jdk核心类库,$JAVA_HOME/jre/lib下的核心库 + rt.jar,native代码编写,不会以java类的形式体现,在不同jvm中行为会有所不同。
extension class loader:加载核心库之外的扩展类,JAVA_HOME/lib/ext,正因如此所有的应用程序都能运行在java平台上。
app class loader:负责加载所有应用程序级别的类(自己代码写的类),classpath环境变量或 -classpath以及-cp命令行参数中指定的文件
3.ClassLoaders是如何工作的
当jvm请求一个类时,class loaders会通过java.lang.ClassLoader.loadClass()方法负责通过类的全限定名将class definition加载到runtime,如果类尚未加载,class loader会将加载请求委派给父类加载器,如果父类加载器最终没有找到该类,子加载器将调用 java.net.URLClassLoader.findClass() 方法从文件系统中加载该类(递归)。如果最终子加载器也无法加载该类,将抛出 java.lang.NoClassDefFoundError 或 java.lang.ClassNotFoundException。
java.lang.ClassNotFoundException: com.baeldung.classloader.SampleClassLoader
at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:348)
4.自定义ClassLoader
自定义classLoader不仅仅只用于在运行时加载类,还有这么一些场景
1.用于更新已存在的字节码
2.根据需求动态创建类
3.在加载具有相应类名,包名的类的字节码时可以实现类的版本控制机制
public class CustomClassLoader extends ClassLoader {
public CustomClassLoader(ClassLoader parent) {
super(parent);
}
public Class getClass(String name) throws ClassNotFoundException {
byte[] b = loadClassFromFTP(name);
return defineClass(name, b, 0, b.length);
}
@Override
public Class loadClass(String name) throws ClassNotFoundException {
if (name.startsWith("com.baeldung")) {
System.out.println("Loading Class from Custom Class Loader");
return getClass(name);
}
return super.loadClass(name);
}
private byte[] loadClassFromFTP(String fileName) {
// Returns a byte array from specified file.
}
}
自定义的classLoader用于从包com.baeldung加载文件,扩展了class loader
5.理解java.lang.ClassLoader
loadClass方法:通过完全限定类名加载类
resolve == true,jvm将执行loadClass()解析该类,然而我们一般只想判断类是否存在
1.执行findLoadedClass(String)判断类是否已经加载了
2.执行父类的loadClass(String)方法
3.执行findClass(String)方法查找类
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
defineClass()方法
负责将字节数组转换为类,我们需要在使用类之前先解析类,final修饰
protected final Class<?> defineClass(
String name, byte[] b, int off, int len) throws ClassFormatError
getParent()方法
返回父加载器用于委派,null代表bootstrap class loader
@CallerSensitive
public final ClassLoader getParent() {
if (parent == null)
return null;
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
// Check access to the parent class loader
// If the caller's class loader is same as this class loader,
// permission check is performed.
checkClassLoaderPermission(parent, Reflection.getCallerClass());
}
return parent;
}
getResource()方法
用于查找给定名称的资源
首先,查找请求会委托给父加载器,如果父加载器为null,则将请求交给bootstrap class loader,如果依然失败,该方法将调用findResource(String)来查找资源,返回一个用于读取资源的URL对象,如果没有找到资源或者没有足够的权限方法资源返回null,java会从classpath路径中加载资源
public URL getResource(String name) {
URL url;
if (parent != null) {
url = parent.getResource(name);
} else {
url = getBootstrapResource(name);
}
if (url == null) {
url = findResource(name);
}
return url;
}
6.线程上下文加载器(Context ClassLoaders)
场景:当jvm核心类需要加载由开发人员提供的类或者资源时,正常双亲委派模式加载出现了问题
例子:在JNDI中,其核心功能由rt.jar中的引导类(bootstrap)实现,但是这些JNDI引导类可能需要加载由各独立服务商提供的JNDI实现类(部署在应用classpath中),这个场景需要bootstrap加载一些仅对child class loader可见的类
方案:java.lang.Thread 类有一个 getContextClassLoader 方法用于返回特定线程的ContextClassLoader。在加载资源和类时,ContextClassLoader由线程的创建者提供。