类加载器子系统与SPI

193 阅读7分钟

初始化加锁

初始化 并发环境 死锁:

类加载器初始化阶段 JVM底层会加锁

输出:

class A init class B init

public class InitDeadLock {

public static void main(String[] args) {
    new Thread(() -> A.test()).start();
    new Thread(() -> B.test()).start();
}
}

class A {
static {
    System.out.println("class A init");

    try {
        TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    new B();
}

public static void test() {
    System.out.println("aaa");
}
}

class B{

static {
    System.out.println("class B init");

    new A();
}


public static void test() {
    System.out.println("bbb");
}
}

检查死锁

visualvim jstack jconsole

案例输出: A Static Block A str

JVM先判断是否加载 后面才有初始化动作发生。

public class Test_10 {
public static void main(String[] args) {
    System.out.printf(Test_10_B.str);
}
}

class Test_10_A {
public static String str = "A str";

static {
    System.out.println("A Static Block");
}
}

class Test_10_B extends Test_10_A {
static {
    str += "#11";

    System.out.println("B Static Block");
}
}

类加载器加载JVM中

InstanceClass 普通类的元信息在JVM的存在形式

元信息:属性信息 方法信息

InstanceMirroClass 镜像类

class对象 Java程序反射

三层类加载器以及父子关系的建立

avatar

启动加载器 Bootstrap

没有实体的

将那一段加载逻辑定义为启动类加载器

加载路径:

URL[] urLs = Launcher.getBootstrapClassPath().getURLs();
for (URL urL : urLs) {
System.out.println(urL);
}

启动类加载器是什么

加载:

main=LoadMainClass(enry,mode,what)

底层调用checkAndLoadMain(加载main函数所在的类 父子链接)

核心:

找到 sun.launcher.LauncherHelper

代码分析:

1 LoadMainClass

int JNICALL
JavaMain(void * _args)
{
……
mainClass = LoadMainClass(env, mode, what);
……
}

2 checkAndLoadMain

static jclass
LoadMainClass(JNIEnv *env, int mode, char *name)
{
jmethodID mid;
jstring str;
jobject result;
jlong start, end;
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;"));

str = NewPlatformString(env, name);
CHECK_JNI_RETURN_0(
    result = (*env)->CallStaticObjectMethod(
        env, cls, mid, USE_STDERR, mode, str));

if (JLI_IsTraceLauncher()) {
    end   = CounterGet();
    printf("%ld micro seconds to load main class\n",
           (long)(jint)Counter2Micros(end-start));
    printf("----%s----\n", JLDEBUG_ENV_ENTRY);
}

return (jclass)result;

}

3 GetLauncherHelperClass 核心

jclass
GetLauncherHelperClass(JNIEnv *env)
{
if (helperClass == NULL) {
    NULL_CHECK0(helperClass = FindBootStrapClass(env,
            "sun/launcher/LauncherHelper"));
}
return helperClass;
}

4 FindBootStrapClass dlsym

jclass
(JNIEnv *env, const char* classname)
{
 if (findBootClass == NULL) {
   findBootClass = (FindClassFromBootLoader_t *)dlsym(RTLD_DEFAULT,
      "JVM_FindClassFromBootLoader");
   if (findBootClass == NULL) {
       JLI_ReportErrorMessage(DLL_ERROR4,
           "JVM_FindClassFromBootLoader");
       return NULL;
   }
}
return findBootClass(env, classname);
}

5

JVM_ENTRY(jclass, JVM_FindClassFromBootLoader(JNIEnv* env,
                                          const char* name))
JVMWrapper2("JVM_FindClassFromBootLoader %s", name);

 // Java libraries should ensure that name is never null...
 if (name == NULL || (int)strlen(name) > Symbol::max_length()) {
// It's impossible to create this class;  the name cannot fit
// into the constant pool.
return NULL;
 }

TempNewSymbol h_name = SymbolTable::new_symbol(name, CHECK_NULL);
Klass* k = SystemDictionary::resolve_or_null(h_name, CHECK_NULL);
if (k == NULL) {
return NULL;
 }

if (TraceClassResolution) {
trace_class_resolution(k);
}
return (jclass) JNIHandles::make_local(env, k->java_mirror());
JVM_END

dlsym 动态链接

这套逻辑做的事情就是通过启动类加载器加载类sun.launcher.LauncherHelper,执行该类的方法checkAndLoadMain,加载main函数所在的类,启动扩展类加载器、应用类加载器也是在这个时候完成的

C++逻辑转Java逻辑

扩展类记载器路径

public static void main(String[] args) {
    ClassLoader classLoader = ClassLoader.getSystemClassLoader().getParent();

    URLClassLoader urlClassLoader = (URLClassLoader) classLoader;

    URL[] urls = urlClassLoader.getURLs();
    for (URL url : urls) {
        System.out.println(url);
    }
}

可以通过java.ext.dirs指定

应用类记载器路径

main函数所在的类用的是应用类加载器加载

public static void main(String[] args) {
    String[] urls = System.getProperty("java.class.path").split(":");

    for (String url : urls) {
        System.out.println(url);
    }

    System.out.println("================================");

    URLClassLoader classLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();

    URL[] urls1 = classLoader.getURLs();
    for (URL url : urls1) {
        System.out.println(url);
    }
}

三种类加载器父子关系链如何建立

加载顺序:

jvm去加载main函数所在的类

boot->ext->app

调用链接生成

1、\openjdk\jdk\src\share\classes\sun\launcher\LauncherHelper.java

核心代码:ClassLoader.getSystemClassLoader();

public enum LauncherHelper {

private static final ClassLoader scloader = ClassLoader.getSystemClassLoader();

public static Class<?> checkAndLoadMain(boolean printToStderr,
                                        int mode,
                                        String what) {
    ……
    mainClass = scloader.loadClass(cn);
	……

2、\openjdk\jdk\src\share\classes\java\lang\ClassLoader.java

核心代码:sun.misc.Launcher.getLauncher();

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");
        sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
    ……

3 \openjdk\jdk\src\share\classes\sun\misc\Launcher.java

核心代码:

• private static Launcher launcher = new Launcher();

扩展类加载器

• extcl = ExtClassLoader.getExtClassLoader();

应用类加载器

• loader = AppClassLoader.getAppClassLoader(extcl);

线程上下文加载器 • Thread.currentThread().setContextClassLoader(loader);

extcl 其实是parents

public class Launcher { private static URLStreamHandlerFactory factory = new Factory(); private static Launcher launcher = new Launcher(); private static String bootClassPath = System.getProperty("sun.boot.class.path");

public static Launcher getLauncher() {
    return launcher;
}

private ClassLoader loader;

public Launcher() {
    // Create the extension class loader
    ClassLoader extcl;
    try {
        extcl = ExtClassLoader.getExtClassLoader();
    } catch (IOException e) {
        throw new InternalError(
            "Could not create extension class loader", e);
    }

    // Now create the class loader to use to launch the application
    try {
        loader = AppClassLoader.getAppClassLoader(extcl);
    } catch (IOException e) {
        throw new InternalError(
            "Could not create application class loader", e);
    }

    // Also set the context class loader for the primordial thread.
    Thread.currentThread().setContextClassLoader(loader);
……

4 扩展类加载器的创建流程 ExtClassLoader

public static ExtClassLoader getExtClassLoader() throws IOException
    {
   ……
                        return new ExtClassLoader(dirs);
}

	public ExtClassLoader(File[] dirs) throws IOException {
        super(getExtURLs(dirs), null, factory);
    }
    
 URLClassLoader(URL[] urls, ClassLoader parent,
               AccessControlContext acc) {

第二个参数传的是null,其实就是parent=null

5 应用类加载器的创建流程

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);

// Note: on bugid 4256530
// Prior implementations of this doPrivileged() block supplied
// a rather restrictive ACC via a call to the private method
// AppClassLoader.getContext(). This proved overly restrictive
// when loading  classes. Specifically it prevent
// accessClassInPackage.sun.* grants from being honored.
//
return AccessController.doPrivileged(
    new PrivilegedAction<AppClassLoader>() {
        public AppClassLoader run() {
            URL[] urls =
                (s == null) ? new URL[0] : pathToURLs(path);
            return new AppClassLoader(urls, extcl);
        }
    });
}
    

AppClassLoader(URL[] urls, ClassLoader parent) { super(urls, parent, factory); }

启动类加载器

没有实体的 C++

类加载器加载的类如何存储

不同的加载器加载同一个类不相等。

原因:空间不一样。

avatar

方法区

按照类加载器进行分开存储

同一个加载器加载同一个文件多次。实际上加载一次

InstanceClass Java类在JVM的存在形式。

类存储在方法区

双亲委派(向上委派)

如果一个类加载器收到了加载某个类的请求,则该类加载器并不会去加载该类,而是把这个请求委派给父类加载器,每一个层次的类加载器都是如此,因此所有的类加载请求最终都会传送到顶端的启动类加载器;只有当父类加载器在其搜索范围内无法找到所需的类,并将该结果反馈给子类加载器,子类加载器会尝试去自己加载。

avatar

局限性:不能向下委派 或者 不委派

AppClassLoader加载一个类

不会自己加载

判断自己的空间有无 有 直接返回 没有 委托ext去记载

ext 有 直接返回 没有 委托boot去记载

boot 有 直接返回 没有 异常 class not found

打破双亲委派

因为在某些情况下父类加载器需要委托子类加载器去加载class文件。受到加载范围的限制,父类加载器无法加载到需要的文件,以Driver接口为例,由于Driver接口定义在jdk当中的,而其实现由各个数据库的服务商来提供,比如mysql的就写了MySQL Connector,那么问题就来了,DriverManager(也由jdk提供)要加载各个实现了Driver接口的实现类,然后进行管理,但是DriverManager由启动类加载器加载,只能记载JAVA_HOME的lib下文件,而其实现是由服务商提供的,由系统类加载器加载,这个时候就需要启动类加载器来委托子类来加载Driver实现,从而破坏了双亲委派。 类似这样的情况就需要打破双亲委派。

Driver接口 启动类加载器加载

调用mysql/oracal相关实现类 应用类记载器

需要向下委派:线程上下文加载器ServiceLoader(底层ContextLoader)

打破双亲委派的意思其实就是不委派、向下委派

自定义类加载器

继承ClassLoader 重写findClass

public class Classloader_2 extends ClassLoader {

public static void main(String[] args) throws ClassNotFoundException {
    Classloader_2 classloader1 = new Classloader_2();
    Class<?> clazz1 = classloader1.loadClass("com.qimingnan.classloader.Classloader_1");
    System.out.println("clazz1 hashcode: " + clazz1.hashCode());

    Classloader_2 classloader2 = new Classloader_2();
    Class<?> clazz2 = classloader2.loadClass("com.qimingnan.classloader.Classloader_1");
    System.out.println("clazz2 hashcode: " + clazz2.hashCode());
}

@Override
protected Class<?> findClass(String className) throws ClassNotFoundException {
    System.out.println("Classloader_2 findClass");
}
}

hashCode 内存地址

String可以重写hashCode

双亲委派逻辑

try {
if (this.parent != null) {
    c = this.parent.loadClass(name, false);
} else {
    c = this.findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException var10) {
            
}

打破双亲委派

if (name.startsWith("com.luban")) { 
// com.luban包下的所有类不委派
	c = findClass(name);
} else {
	c = this.getParent().loadClass(name);
}
SPI 向下委派
是一种服务发现机制。

它通过在ClassPath路径下的META-INF/services文件夹查找文件,自动加载文件里所定义的类。 

这一机制为很多框架扩展提供了可能,比如在Dubbo、JDBC中都使用到了SPI机制

Driver SPI机制:

ServiceLoader loadedDrivers = ServiceLoader.load(Driver.class); Iterator driversIterator = loadedDrivers.iterator();

SPI-Demo 启动类加载器加载的不算打破

public interface PayService {

void pay();
}

实现

public class AlipayService implements PayService {
public void pay() {
    System.out.println("AlipayService");
}
}

public class WxpayService implements PayService {
public void pay() {
    System.out.println("WxpayService");
}
}

Main函数

public class MainApplication {

public static void main(String[] args) {
    ServiceLoader<PayService> services = ServiceLoader.load(PayService.class);

    for (PayService service : services) {
        service.pay();
    }
}
}

<dependencies>
    <dependency>
        <groupId>org.example</groupId>
        <artifactId>pay-ali</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
</dependencies>

线程上下文类加载器

ServiceLoader SPI机制(向下委派)

在checkAndLoadMain使用过

1、是什么

一种特殊的类加载器,可以通过Thread获取,基于此可实现逆向委托加载

2、为什么

为了解决双亲委派的缺陷而生

3、怎么做

//获取 Thread.currentThread().getContextClassLoader()

// 设置 Thread.currentThread().setContextClassLoader(new Classloader_4());

沙箱安全

防止打破双亲委派 修改系统类。 保护核心类库

avatar

avatar

AccessController.doPrivileged( new SystemClassLoaderAction(scl));

比如我定义了一个类名为String所在包为java.lang,因为这个类本来是属于jdk的,如果没有沙箱安全机制的话,这个类将会污染到我所有的String,但是由于沙箱安全机制,所以就委托顶层的bootstrap加载器查找这个类,如果没有的话就委托extsion,extsion没有就到aapclassloader,但是由于String就是jdk的源代码,所以在bootstrap那里就加载到了,先找到先使用,所以就使用bootstrap里面的String,后面的一概不能使用,这就保证了不被恶意代码污染

向上委派

Class<?> c = this.findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();

try {
    if (this.parent != null) {
        c = this.parent.loadClass(name, false);
    } else {
        c = this.findBootstrapClassOrNull(name);
    }
} catch (ClassNotFoundException var10) {
}

if (c == null) {
    long t1 = System.nanoTime();
    c = this.findClass(name);
    PerfCounter.getParentDelegationTime().addTime(t1 - t0);
    PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
    PerfCounter.getFindClasses().increment();
}
}

if (resolve) {
this.resolveClass(c);
}

return c;