类加载器是负责搬运.class到JVM中去的,这些.class文件开头都有特定的标识,把.class文件中的字节码内容加载到内存中去,并把这些内容转换成方法区中的运行时数据结构,注意它只负责加载.class文件
2、parent属性| 类加载器 | 描述 |
|---|---|
| Bootstrap | parent是null,因为是c++实现的,java没法直接引用 |
| Application | parent的Extension ClassLoader |
| Extension | parent是null |
从类加载到释放内存,总共经过了7个步骤:加载,连接(验证、准备、解析),初始化,使用,卸载。
1、加载 ①将.class文件载入内存;
②将静态数据结构转化成方法区中的运行时数据结构;
③在堆上生成一个代表这个类的java.lang.Class对象作为数据访问入口。
(1)验证
确保加载的类符合JVM规范、安全,保证被校验类的方法在运行时不会做危害虚拟机的事,就一个安全检查;
(2)准备
为static变量在方法区中分配内存空间,设置变量初始值,如:“static int count = 3;”。
(3)解析
JVM将常量池中的符号引用替换为直接引用。比如“import java.util.List;”,这就是符号引用,直接引用就是指针或者对象地址,引用对象是在内存中进行的。
其实就是赋值,它会执行一个类构造器的
GC把无用对象从内存中清除。
三、类加载器的加载顺序加载Class类是有优先级的。
其中BootStrap ClassLoader在虚拟机中,其他都在外部,用java实现,继承抽象类 java.lang.ClassLoader。
非BootStrap的类加载器,全部继承java.lang.classLoader,Java提供了两种实现:Extension ClassLoader、Application ClassLoader。
1、启动类加载器(BootStrap ClassLoader)如rt.jar。该加载器无法直接被Java程序引用,用户自定义类加载器时,使用null代替可以把加载请求委派给该类加载器。
2、扩展类加载器(Extention ClassLoader)加载扩展的jar包,ExtClassLoader(sun.misc.Launcher$ExtClassLoader)实现的,开发者可以直接使用扩展类加载器。
3、应用程序类加载器(Application ClassLoader)由 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的。由于这个类加载器是 ClassLoader 中的 getSystemClassLoader() 方法的返回值,因此一般称为系统类加载器。开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
4、自定义类加载器(Customer ClassLoader)| 类加载器 | 加载路径 | 虚拟机内外部 |
|---|---|---|
| 启动类加载器(Bootstrap ClassLoader) | 内部 | |
| 扩展类加载器(Extention ClassLoader) | 外部 | |
| 应用程序类加载器(Application ClassLoader) | 用户类路径(ClassPath)上所指定的类库 | 外部 |
| 自定义类加载器(Customer ClassLoader) | 外部 |
我们的应用程序都是由上述这三种类加载器互相配合从而实现类加载,如果有必要,还可以加入自己定义的类加载器。
四、双亲委派机制委派什么?谁委派谁?为什么要委派?
类收到加载请求时,不会先自己尝试加载,而是委托给父类,只有父类加载器无法完成(找不到加载所需class)时,子类才会自己加载。可知,所有加载请求都会达到最顶层的Bootstrap ClassLoader。
1、父类加载器先尝试加载的好处听上去有些奇怪,但是有很明显的优势。
(1)优先级的层次关系,从而使得基础类得到统一
比如java.lang.Object存放在rt.jar中,如果又写了一个存到class path中,那么,程序中用到的Object是哪个呢,答案是rt.jar中的那个,前面提到,rt.jar在Bootstrap ClassLoader中,而ClassPath在Application ClassLoader中,Bootstrap优先级更高。
即使是同一个.class,用不同类加载器加载,会得到两个类,因为每个类加载器都有独立的类名称空间。
(2)内存宝贵
没必要弄两份浪费,同一个Class对象,多个地方用,没必要加载多份,用同一份即可,但如果没有委派机制,就没办法去找父加载器中获取,可能父级有,这样就有多份了。
其实现主要在java.lang.ClassLoader 的 loadClass() :
2、java.lang.ClassLoader loadClass()源码
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.
long t1 = System.nanoTime();
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) {
resolveClass(c);
}
return c;
}
}
简单讲下这个方法,当被调用的时候,先看下自己有没有加载过这个类,加载过了就直接返回,没加载过就先调用父类的loadClass(),让父类去加载。
如果父类发现自己没加载过,就去找爷爷,一直到parent是null,说明已经走到BootStrap ClassLoader了,就调用findBootstrapClassOrNull()(native方法)让它试试。
如果类是classpath下的,那么Bootstrap和Extension都会加载失败,最后回到自己这里,然后才会调用findClass(),自己去加载。
3、破坏双亲委派机制场景常见于:SPI、OSGi(java模块化规范,热拔插、热部署、模块化,会有模块间类加载的问题)、Java Web服务器(Tomcat)。
常见SPI(Service Provider Interfaces),java为服务器提供的接口,JDBC、JNDI、JAXP等,Spring Boot的Ioc容器加载页用到了类似机制。
(1)jdbc第三方服务商提供的jar,都是放在classpath下的,所以Bootstrap ClassLoader也爱莫能助。
所以在java.lang.Thread类里定义了一个线程上下文类加载器(Thread Context ClassLoader,TCCL),JVM主线程里这个就是默认的Application ClassLoader。去琢磨源码就会发现,居然出现了可以让父级类加载器,去使用子级类加载器(Application ClassLoader)。
(2)TomcatWeb服务器需要运行多个web应用程序,这些应用依赖相同类库不同版本,需要把各个应用依赖的类库隔离。
好了,不细说了。



