10

理解和运用 ClassLoader 该篇文章就够了

 3 years ago
source link: http://www.cnblogs.com/liferecord/p/14337329.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

定义

根据《深入理解Java虚拟机》提到“通过一个类的全限定名(packageName.ClassName)来获取描述此类的二进制字节(class文件字节)这个动作的代码模块就叫做类加载器(ClassLoader)”。

作用

1、通常类加载器的作用是加载资源(字节码文件)到java虚拟机中,想要在一个jvm 进程中唯一确认一个类,除了类的全限定名外,还需要指定它是由哪个类加载器加载的。

2、比如我们的类库需要通过远程网络获取,可以通过自定义类加载器从远程加载字节码文件。

3、java的字节码文件很容易反编译出来,一些核心的代码不想被反编译出来,可以对字节码进行加密,然后通过自定义的类加载器加载这些字节码,然后进行解码返回给虚拟机。

4、比如jvm的热加载和热部署等功能也需要自定义类加载器来完成。

.......

java类加载器的种类

1、Bootstrap ClassLoader : 该加载器是最顶层的类加载器,它是加载放在{Java_home}\lib目录 或者-Xbootclasspath指定路径下类库。

2、Extension ClassLoader : 该类加载器负载加载{Java_home}/lib\ext目录 或者System.getenv("java.ext.dirs")系统变量路径下的类库。

3、Application ClassLoader : 该类加载器加载用户程序类路径下的类库,它是默认的程序的类加载器。

双亲委派机制

1、双亲委派机制,双亲委派除了启动类加载器(Bootstrap ClassLoader)外,其他的类加载器都应该有自己父加载器。它们的实现不是通过继承来实现的,而是通过组合的方式。当加载某个Class时,当前类加载器会把这个加载请求委派给其父类加载器加载,同理父类加载器同样委派其它的父类加载器加载,直到无其父类类加载器加载为止,如果父类加载器加载失败,才会由其子类加载。

2、使用双亲委派机制,有个明显的特征是:Java类随着它的类加载器一起具备了一种优先级的层次关系。比如rt.jar包中的java.lang.Object,由于它所在位置是由启动类加载器加载,所以Object类在程序的各种类加载器环境中都是同一个类。

3、双亲委派模型如下:

UNvuEn3.png!mobile

ClassLoader

可以看下ClassLoader 双亲委派模型的大致代码框架如下:

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 1、查看该类是否加载
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
 		    // 2、如果未加载,委托给父类加载器加载
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                    //3、没有父类加载器,委托给BootstrapClassLoader
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // 父类加载器没有加载到,则自己加载
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    //  记录该类加载的状态Stat. 
                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
 	    // resolve :true,需要对类进行链接(链接阶段包括:准备,解析,初始化类)
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

1、通过以上可以知道,我们可以继承ClassLoader 来实现自己的类加载器,然后重写findClass()方法,这些还是保存了双亲委派机制。

2、当我们重写findClass()方法时,得到该类的字节码后,需要调用defineClass()来放回Class<?>对象。

URLClassLoader

1、一般自定义的类加载器可以直接继承该类,该类加载器通过添加url路径来获取类。

2、可以在其构造函数上URLClassLoader(URL[] urls, ClassLoader parent)直接进行添加其URL。

3、也可以通过addURL(URL)添加其URL。

自定义类加载器

1、首先假设main-project为我们自己编写的工程,其依赖某一api:service-spi。而service-spi的实现有很多,project-spi-impl是其中的一个。

2、当main-project仅依赖service-spi,而project-spi-impl不在工程的类加载路径下。所以需要自定义类加载器,从某个路径下的jar加载进来。CoreClassLoader就是自定义的类加载器。

3、CoreClassLoader继承自URLClassLoader,然后把相关搜索路径添加到该类加载器即可。

4.github: https://github.com/zhvqee/class-loader

SpringBoot 对类加载器的运用

1、SpringBoot 工程通过spring-boot-maven-plugin插件打包。把相关资源和依赖包都打到一个jar包中(all in one)。其包的结构如下:

rqINbme.png!mobile

BOOT-INF/classes:存放的是本工程的class文件

BOOT-INF/lib:存放的是本工程依赖的二方包和三方包。

META-INF/MANIFEST.MF:该文件记录了程序启动入口等。

o.s.b.loader包下就是springboot自定义的类加载器。

2、当我们执行java -jar xxxx 时,会读取MANIFEST.MF下

Main-Class: org.springframework.boot.loader.JarLauncher,

该配置就是程序的路口。而我们编写的Main方法不是真正的启动的入口。

3、当执行Launcher#launch()方法时,会把SpringBoot自定义的类加载器LaunchedURLClassLoader设置线程的上下文中,并通过该自定义类加载器加载我们自己编写的main方法所在的类,然后利用反射调用main方法。代码片段如下:

Class<?> mainClass = Thread.currentThread().getContextClassLoader().loadClass(this.mainClassName);
Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
mainMethod.invoke(null, new Object[] { this.args });

4、LaunchedURLClassLoader 类加载器继承URLClassLoader类加载器,它加载的路径就是可执行jar下BOOT-INF下的class文件和二方包、三方包。

Class.forName() 和 ClassLoader.load()

1、JVM 加载的类主要经过以下几个步骤:加载,链接,初始化,试用,卸载。

2、Class.forName()默认是需要对加载的类进行初始化。

3、ClassLoader.load实际调用的是ClassLoader.load(className,false),false:表示不进行链接,不进行链接也就代表不会进行初始化的操作,类的静态块和静态对象都不会执行。

该文章


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK