- 类加载器
- 启动类加载器BootstrapClassLoader
- 扩展类加载器ExtensionClassLoader
- 应用类加载器ApplicationClassLoader
- 自定义类加载器xxxClassLoader
- 类加载器加载的类在方法区的存储
- 双亲委派(向上委派)
- 类搜索过程
- 打破双亲委派
- 自定义类加载器
- SPI机制
- 线程上下文类加载器
- 沙箱安全
- 由C++编写,java程序获取不到为null,其他都是java编写
- 获取启动类加载器加载路径:URL[] urLs = -Launcher.getBootstrapClassPath().getURLs();
- 可以通过-Xbootclasspath指定
openJdk源码
- 通过JavaMain入口调用LoadMainClass
- 执行C++的LoadMainClass调用GetLauncherHelperClass,加载java的LauncherHellper
- 调用LauncherHellper的chechAndLoadMain
- 加载main函数所在的类,
- 然后启动扩展类加载器、应用类加载器也是在这个时候完成
int JNICALL
JavaMain(void * _args)
{
......
mainClass = LoadMainClass(env, mode, what);
......
}
static jclass
LoadMainClass(JNIEnv *env, int mode, char *name)
{
......
//加载java的sun/launcher/LauncherHelper
jclass cls = GetLauncherHelperClass(env);
NULL_CHECK0(cls);
if (JLI_IsTraceLauncher()) {
start = CounterGet();
}
NULL_CHECK0(mid = (*env)->GetStaticMethodID(env, cls,
"checkAndLoadMain",
"(ZILjava/lang/String;)Ljava/lang/Class;"));
......
}
jclass
GetLauncherHelperClass(JNIEnv *env)
{
if (helperClass == NULL) {
NULL_CHECK0(helperClass = FindBootStrapClass(env,
"sun/launcher/LauncherHelper"));
}
return helperClass;
}
扩展类加载器ExtensionClassLoader
- 加载路径: URLClassLoader urlClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader().getParent();
- URL[] urls = urlClassLoader.getURLs();
- 可以通过java.ext.dirs指定
OpenJdk源码
- 启动类加载器从C++调到java代码LauncherHelper.checkAndLoadMain
- 执行类加载scloader.loadClass(cn)时会先初始化ClassLoader
- 通过getSystemClassLoader初始化initSystemClassLoader(),调用Launcher
- Launcher.getLauncher()初始化Launcher, 创建扩展类加载器
private static final ClassLoader scloader = ClassLoader.getSystemClassLoader();
public static Class> checkAndLoadMain(boolean printToStderr,int mode, String what) {
......
//加载类
mainClass = scloader.loadClass(cn);
......
}
public static ClassLoader getSystemClassLoader() {
initSystemClassLoader();
if (scl == null) {
return null;
}
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkClassLoaderPermission(scl, Reflection.getCallerClass());
}
return scl;
}
private static synchronized void initSystemClassLoader() {
if (!sclSet) {
if (scl != null)
throw new IllegalStateException("recursive invocation");
// 加载 Launcher
sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
......
}
}
private static Launcher launcher = new Launcher();
public static Launcher getLauncher() {
return launcher;
}
public Launcher() {
ClassLoader extcl;
try {
//扩展类加载器
extcl = ExtClassLoader.getExtClassLoader();
} catch (IOException e) {
throw new InternalError("Could not create extension class loader", e);
}
try {
//应用类加载器(传入ext扩展类加载器作为父加载器)
loader = AppClassLoader.getAppClassLoader(extcl);
} catch (IOException e) {
throw new InternalError("Could not create application class loader", e);
}
// 还要为原始线程设置上下文类加载器 (SPI原理,为了打破双亲委派)
Thread.currentThread().setContextClassLoader(loader);
......
}
public static ExtClassLoader getExtClassLoader() throws IOException {
final File[] dirs = getExtDirs();
return AccessController.doPrivileged(
new PrivilegedExceptionAction() {
public ExtClassLoader run() throws IOException {
int len = dirs.length;
for (int i = 0; i < len; i++) {
MetaIndex.registerDirectory(dirs[i]);
}
//创建扩展类加载器
return new ExtClassLoader(dirs);
}
});
}
应用类加载器ApplicationClassLoader
- 默认加载用户程序的类加载器
- 加载路径: String[] urls = System.getProperty(“java.class.path”).split(“:”);
- 可以通过java.class.path指定
OpenJdk源码(扩展类加载器Launcher)
static class AppClassLoader extends URLClassLoader {
static {
ClassLoader.registerAsParallelCapable();
}
public static ClassLoader getAppClassLoader(final ClassLoader extcl)
throws IOException
{
final String s = System.getProperty("java.class.path");
final File[] path = (s == null) ? new File[0] : getClassPath(s);
return AccessController.doPrivileged(
new PrivilegedAction() {
public AppClassLoader run() {
URL[] urls =
(s == null) ? new URL[0] : pathToURLs(path);
//创建启动类加载器,传入ext加载器为父类
return new AppClassLoader(urls, extcl);
}
});
}
自定义类加载器xxxClassLoader
- 继承类java.lang.ClassLoader
- 重写findClass/loadClass
- 方法区按照类加载器进行分开存储,不同的类加载器有各自的方法区空间
- 不同类加载器加载同一个类,存储位置是不同的
- 同一个类加载器多次加载同一个文件,只加载一次
- InstanceKlass会存储在方法区各自加载器的空间上
- 如果一个类加载器收到了加载某个类的请求,则该类加载器并不会去加载该类,而是把这个请求委派给父类加载器,
- 每一个层次的类加载器都是如此,因此所有的类加载请求最终都会传送到顶端的启动类加载器;
- 只有当父类加载器在其搜索范围内无法找到所需的类,并将该结果反馈给子类加载器,子类加载器会尝试去自己加载。
优势
- 避免类的重复加载
- 保护程序安全,防止核心api被随意篡改,如自定义java.lang.String等都不会被调用(沙箱安全机制)
OpenJdk源码
protected Class> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// 添加synchronized同步锁防止多个线程调用同一个ClassLoader去加载同一个类产生冲突
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
//如果加载过,则直接return,没有加载过,则进入if判断
Class> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
//如果当前的类加载器的parent不为空,则递归调用父类的loadClass去加载类。
if (parent != null) {
c = parent.loadClass(name, false);
}
//如果此时已经是ExtClassLoader,它的parent是null,
//将会调用findBootstrapClassOrNull(name)
else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
//从非空父类加载器没有加载到(向上委托加载不到)
}
//如果向上委托调用到bootstrapClassLoader还是没有成功,返回了null,进入下面的if判断中
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
//首先现在是ExtClassLoader的findClass,再是AppClassLoader,
//最后是调用用户自定义的ClassLoader的findClass方法,
//就是层层向外去检查。如果最终还是没有找到,
//就会抛出ClassNotFoundException
c = findClass(name);
// this is the defining class loader; record the stats
//这是定义类加载器;记录统计数据
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
//解析这个class
resolveClass(c);
}
return c;
}
}
打破双亲委派
- 打破双亲委派的意思其实就是不委派、向下委派
- 因为在某些情况下父类加载器需要委托子类加载器去加载class文件。
- 受到加载范围的限制,父类加载器无法加载到需要的文件,以Driver接口为例,由于Driver接口定义在jdk当中的,而其实现由各个数据库的服务商来提供,比如mysql的就写了MySQL Connector,
- 那么问题就来了,DriverManager(也由jdk提供)要加载各个实现了Driver接口的实现类,然后进行管理,但是DriverManager由启动类加载器加载,只能记载JAVA_HOME的lib下文件,而其实现是由服务商提供的,由系统类加载器加载,这个时候就需要启动类加载器来委托子类来加载Driver实现,从而破坏了双亲委派。
- 类似这样的情况就需要打破双亲委派。
- tomcat打破源码 : org.apache.catalina.loader.WebappClassLoaderBase#loadClass(java.lang.String, boolean)
- 继承ClassLoader,重写findClass/loadClass
- 参考博客 https://blog.csdn.net/huazai30000/article/details/85296671
- 遵循双亲委派,如果使用自定义加载器去加载其他加载器可加载的文件,则不会生效
@Override
public Class> findClass(String path) {
byte[] b = loadClassData(path);
if (Objects.isNull(b)) {
return null;
}
return defineClass(path, b, 0, b.length);
}
private byte[] loadClassData(String path) {
// load the class data from the connection
File file = new File(path);
if (file.exists()) {
FileInputStream in = null;
ByteArrayOutputStream out = null;
try {
in = new FileInputStream(file);
out = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int size = 0;
while ((size = in.read(buffer)) != -1) {
out.write(buffer, 0, size);
}
return out.toByteArray();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (in != null) {
in.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
SPI机制
- 一种服务发现机制。
- 它通过在ClassPath路径下的META-INF/services文件夹查找文件,自动加载文件里所定义的类。
- 这一机制为很多框架扩展提供了可能,比如在SpringBoot自动配置能力, Dubbo, JDBC中都使用到SPI机制
- 一种特殊的类加载器,可以通过Thread获取,基于此可实现逆向委托加载
- 为了解决双亲委派的缺陷而生
- 获取 Thread.currentThread().getContextClassLoader()
- 设置Thread.currentThread().setContextClassLoader(new xxxClassLoadere());
- 自定义定义了一个类名为String所在包为java.lang,
- 这个类本来是属于jdk的,如果没有沙箱安全机制的话,这个类将会污染到我所有的String,
- 由于沙箱安全机制,所以就委托顶层的bootstrap加载器查找这个类,
- 如果没有的话就委ext,ext没有就到Aapclassloader,
- 由于String就是jdk的源代码,所以在bootstrap那里就加载到了,先找到先使用,
- 就使用bootstrap里面的String,后面的一概不能使用,这就保证了不被恶意代码污染
OpenJdk源码
- 沙箱安全检查机制方法: AccessController.doPrivileged
private static synchronized void initSystemClassLoader() {
if (!sclSet) {
if (scl != null)
throw new IllegalStateException("recursive invocation");
sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
if (l != null) {
Throwable oops = null;
scl = l.getClassLoader();
try {
//沙箱安全检查AccessController.doPrivileged
scl = AccessController.doPrivileged(
new SystemClassLoaderAction(scl));
} catch (PrivilegedActionException pae) {
oops = pae.getCause();
if (oops instanceof InvocationTargetException) {
oops = oops.getCause();
}
}
if (oops != null) {
if (oops instanceof Error) {
throw (Error) oops;
} else {
// wrap the exception
throw new Error(oops);
}
}
}
sclSet = true;
}
}



