前言
语雀地址:https://www.yuque.com/yangxiaofei-vquku/wmp1zm/dmz2gd一、类加载器相关概念
1.Classloader的作用
Classloader是java的核心组件,所有的Class都是又Classloader进行加载的,Classloader负责通过各种方式将Class信息的二进制数据流读入JVM内部,转换为一个与目标类对应的java.lang.Class对象实例。然后交给java虚拟机进行连接、初始化等操作。因此,Classloader在整个装载阶段,只能影响到类的加载,而无法通过Classloader去改变类的连接和初始化行为,至于它是否可以运行,则有Execution Engine决定。
2.类加载的分类
类的加载可以分为显示加载vs隐式加载
- 显示加载:是指在代码中显示使用Class.forName(),或者Classloader.loaderClass()加载class
- 隐式加载:是指A类中引用了B类(import)则在A类加载时会隐式加载B类
3.类加载器的必要性
一般情况下,java开发人员并不需要在程序中显示的使用类加载器,但是了解类加载器的加载机制去显得至关重要。
- 避免开发中遇到java.lang.ClassNotFoundException异常或java.lang.NoClassDefFoundError异常时手足无措。只有了解类加载器的加载机制才能在出现异常时快速定位问题。
ClassNotfoundException和NoClassDefFoundError两者的本质区别就是:
ClassNotfoundException时在编译时JVM加载不到类或者找不到类导致的;
而NoClassDefFoundError是在运行时JVM加载不到类或者找不到类。
NoClassDefFoundError出现举例:在父类加载器加载的A类中调用了子类加载器加载的B类,B类对父类在的器不可见,
此时会出现NoClassDefFoundError。
- 需要支持类的动态加载或者需要对编译后的字节码进行加密解密操作是,就需要和类加载器打交道了
注:源码之下无秘密,在java的类加载器层面上处理加密解密还是比较容易被破解的,建议使用openjdk重写defineClass1的native实现完成解密操作,这样相对于在java层面上更安全一些 - 开发人员可以在程序编写自定义类加载器来重新定义加载规则
4.命名空间
命名空间总结:定义类加载器和类本身一同确定其在虚拟机中的唯一性。
①.何为类的唯一性
对于任意一个类,都需要由加载他的类加载器和这个类本身一同确认其在Java虚拟机中的唯一性。每一个类加载器,都拥有一个独立的命令空间:比较两个类是否相等,只有在这两个类是由同一个类加载器加载的前提下才有意义。否则,即便这两个类源自同一个Class文件,被同一个虚拟机加载,只要他们的类加载器不同,那这两个类就必定不相等。
②.命名空间
- 每个类加载器都有自己的命名空间,命名空间由该加载器及其所有的父加载器的类组成
- 同一命名空间中,不会出现类完整名字(包括类的包名)相同的两个类
注:这必须是在双亲委派机制的前提下,例如:使用自定义类加载器调用findClass可以再次加载系统类加载器加载过的类,但是一个类加载器不能重复使用findClass加载一个类两次。不考虑双亲委派的话应该说同一个类加载器加载的类中不会出现完整名字相同的两个类。 - 在不同的命名空间中有可能会出现类的完整名字相同的两个类
③.有关命名空间的例子
例子1:不同类加载器加载的同一个class文件会产生两个Class对象
public class MyClassloaderTest1 {
final static String path="/Users/yangxiaofei/lago/learning-notes/JVM/jvmDemo/src/main/java/";
public static void main(String[] args) throws Exception{
MyClassloader myClassloader1=new MyClassloader(path);
MyClassloader myClassloader2=new MyClassloader(path);
// 主要此处不能用loadClass,使用loadClass就会由系统类加载器去加载target下的User.class了
Class<?> userClass2 = myClassloader1.findClass("jvm.memory.classloader.User");
Class<?> userClass1 = myClassloader2.findClass("jvm.memory.classloader.User");
System.out.println(userClass2==userClass1);
System.out.println("userClass1的类加载器是"+userClass1.getClassLoader());
System.out.println("userClass2的类加载器是"+userClass2.getClassLoader());
}
}
打印结果:
false
userClass1的类加载器是jvm.memory.classloader.MyClassloader@7c75222b
userClass2的类加载器是jvm.memory.classloader.MyClassloader@1cd072a9
例子2:打破双亲委派的情况下,MyClassloader可以加载系统类加载器已经加载过的类(定义类加载器和类本身一同确定其在虚拟机中的唯一性,下面情况由于打破了双亲委托致使定义类加载器不同,所以不足以确定类的唯一性)
/**
* 打破双亲委派的情况下,MyClassloader可以加载系统类加载器已经加载过的类
*/
public class MyClassloaderTest2 {
final static String path="/Users/yangxiaofei/lago/learning-notes/JVM/jvmDemo/target/classes/";
public static void main(String[] args)throws Exception {
Field classes = ClassLoader.class.getDeclaredField("classes");
classes.setAccessible(true);
MyClassloader myClassloader1=new MyClassloader(path);
// 使用loadClass会遵循双亲委派机制最终由系统类加载器加载User类
Class<?> userClass1 = myClassloader1.loadClass("jvm.memory.classloader.User");
System.out.println("************打印系统类加载器加载的类******************");
Vector a=(Vector)classes.get(myClassloader1.getParent());
for (Object o : a) {
System.out.println(o);
}
System.out.println("************打印MyClassloader加载器加载的类****************");
Vector b=(Vector)classes.get(myClassloader1);
for (Object o : b) {
System.out.println(o);
}
Class<?> userClass2 = myClassloader1.findClass("jvm.memory.classloader.User");
System.out.println(userClass2==userClass1);
System.out.println("userClass1的类加载器是"+userClass1.getClassLoader());
System.out.println("userClass2的类加载器是"+userClass2.getClassLoader());
System.out.println("************再次打印系统类加载器加载的类******************");
Vector aa=(Vector)classes.get(myClassloader1.getParent());
for (Object o : aa) {
System.out.println(o);
}
System.out.println("************再次打印MyClassloader加载器加载的类****************");
Vector bb=(Vector)classes.get(myClassloader1);
for (Object o : bb) {
System.out.println(o);
}
}
}
打印结果:
************打印系统类加载器加载的类******************
class com.intellij.rt.execution.application.AppMainV2$Agent
class com.intellij.rt.execution.application.AppMainV2
class com.intellij.rt.execution.application.AppMainV2$1
class jvm.memory.classloader.MyClassloaderTest2
class jvm.memory.classloader.MyClassloader
class jvm.memory.classloader.User
************打印MyClassloader加载器加载的类****************
false
userClass1的类加载器是jdk.internal.loader.ClassLoaders$AppClassLoader@2c13da15
userClass2的类加载器是jvm.memory.classloader.MyClassloader@7c30a502
************再次打印系统类加载器加载的类******************
class com.intellij.rt.execution.application.AppMainV2$Agent
class com.intellij.rt.execution.application.AppMainV2
class com.intellij.rt.execution.application.AppMainV2$1
class jvm.memory.classloader.MyClassloaderTest2
class jvm.memory.classloader.MyClassloader
class jvm.memory.classloader.User
************再次打印MyClassloader加载器加载的类****************
class jvm.memory.classloader.User
例子3:即便打破双亲委派机制,同一个类加载器也不能加载同一个类两次
/**
* 即便打破双亲委派机制,同一个类加载器也不能加载同一个类两次
*/
public class MyClassloaderTest3 {
final static String path="/Users/yangxiaofei/lago/learning-notes/JVM/jvmDemo/src/main/java/";
public static void main(String[] args) throws Exception{
MyClassloader myClassloader1=new MyClassloader(path);
// 主要此处不能用loadClass,使用loadClass会遵循双亲委派机制userClass2不会真正加载而是直接被赋予userClass1的引用
Class<?> userClass1 = myClassloader1.findClass("jvm.memory.classloader.User");
Class<?> userClass2 = myClassloader1.findClass("jvm.memory.classloader.User");
}
}
打印结果(出现LinkageError异常):
Exception in thread "main" java.lang.LinkageError: loader jvm.memory.classloader.MyClassloader @1cd072a9 (instance of jvm.memory.classloader.MyClassloader, child of 'app' jdk.internal.loader.ClassLoaders$AppClassLoader) attempted duplicate class definition for jvm.memory.classloader.User.
at java.base/java.lang.ClassLoader.defineClass1(Native Method)
at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1016)
at jvm.memory.classloader.MyClassloader.findClass(MyClassloader.java:47)
at jvm.memory.classloader.MyClassloaderTest3.main(MyClassloaderTest3.java:15)
5.类加载机制的基本特征
- 双亲委派模型。但是不是所有类加载都遵守这个模型,有时候,启动类加载器所加载的类型,是可能需要加载用户代码的比如SIP机制,具体如JDBC的驱动发现等,在这种情况下就不会用双亲委派模型去加载了,而是利用线程上下文类加载器去打破它(默认的线程上下文类加载器就是系统类加载器)。
- 可见性,子类加载器可以访问父类加载器加载的类型,但是反过来是不被允许的
- 单一性,由于父加载器加载的类对于子类加载器是可见的,所以父加载器中加载过的类型,就不会再子加载器中重复加载。但是类加载器"邻居"间(MyClassloader的两个实例),同一类型可以被多次加载,因为互相并不可见。
二、类加载器分类(java8)
JVM支持两种类型的类加载器,分别为引导类加载器(Bootstrap ClassLoader)和自定义类加载器。
从概念上来讲,自定义类加载器一般指的是程序中由开发人员自定义的一类类加载器,但是java虚拟机规范却没有这么定义,而是将所有派生与抽象类CLassLoader的类加载器都划分为自定义类加载器所有扩展类加载器和系统类加载器都算自定义类加载器。无论类加载器的类型如何划分,在程序中我们最常见的类加载器结构主要情况如下:
1.引导类加载器
- 这个类是由C/C++语言实现的,嵌套在JVM内部。
- 它用加载java的核心类库(JAVA_HOME/jre/lib/rt.jar或sun.boot.class.path路径下的内容),用于提供JVM自身需要的类。
- 并不继承自java.lang.ClassLoader,没有父类加载器
- 出于安全考虑,Bootstrap启动类加载器只能加载包名为java、javax、sun等开头的类
- 加载扩展类加载器和应用程序类加载器,并指定为他们的父类加载器
- 在java代码中没有具体实现,通常用null代表
2.扩展类加载器
- JAVA语言编写,由sun.misc.Launcher$ExtClassLoader实现
- 继承与ClassLoader类
- 父类加载器为Bootstrap启动类加载器
- 从java.ext.dirs系统属性所指定的目录中加载类库,或从JDK的安装目录的jre/lib/ext子目录下加载类库,如果用户创建的jar放在此目录下,也可以由扩展类加载器加载到
3.系统类加载器
- java语言编写,由sun.misc.launcher$AppClassLoader实现
- 继承与ClassLoader类
- 父类加载器是启动类加载器
- 它负责加载环境变量classpath或系统属性java.class.path指定路径下的类库
- 应用程序中的类加载器默认是系统类加载器
- 它是用户自定义类加载器的默认父加载器
- 通过ClassLoader.getSystemClassLoader()方法可以获取该类加载器
4.数组的类加载器(补充)
数组的Class对象,不是由类加载器去创建的,而是java运行期JVM根据需要自动创建的,数组类的Class.getClassLoader()返回的类加载器与数组中元素类型的类加载器是一样的,如果数组中是基本数据类型则没有类加载器,如果是String、Integer、Long等引用类型则为Bootstrap类加载器,如果是User等自定义的引用类型则为系统类加载器。
注:基本数据类型由虚拟机预先定义不需要类加载,不需要类加载器;引用数据类型则需要进行类加载,需要类加载器
5.初始类加载器和定义类加载器(补充)
不说废话直接上代码
public class User {
static {
Dog dog=new Dog();
System.out.println("我有一只小狗,他的初始类加载器是"+User.class.getClassLoader()+",他的定义类加载器是"+dog.getClass().getClassLoader());
}
}
/**
* 初始类加载器和定义类加载器
*/
public class MyClassloaderTest4 {
final static String path="/Users/yangxiaofei/lago/learning-notes/JVM/jvmDemo/target/classes/";
public static void main(String[] args) throws Exception {
MyClassloader myClassloader=new MyClassloader(path);
Class<?> aClass = myClassloader.findClass("jvm.memory.classloader.User");
Object o = aClass.newInstance();
}
}
打印结果:我有一只小狗,他的初始类加载器是jvm.memory.classloader.MyClassloader@1cd072a9,他的定义类加载器是jdk.internal.loader.ClassLoaders$AppClassLoader@2c13da15
因为Dog在User中被引用触发隐式加载所以User的类加载器是Dog的初始类加载器,有由于双亲委派机制的存在最终加载Dog的类是系统类加载器,所以系统类加载器是Dog类的定义类加载器。
三、双亲委派机制
首先这里的"双亲"并不是指真正的父类,子类加载器和父类加载器直接不是继承关系,而且子类加载器里面有个父类加载器的引用存放于parent变量中,从而子类加载器可以间接访问父类加载器加载的类以及父类加载器的属性和方法。
1.定义与本质
一个类加载器在接到一个类加载请求时,它首先不会自己尝试去加载这个类,而是把这个请求任务委托给父类加载器去完成,依次递归,如果父类加载器可以完成这个加载任务就成功返回,只有父类加载器不能完成加载任务时,才会自己去加载。
2.优势
- 避免类的重复加载,确保一个类的全局唯一性
- 保护程序安全,防止核心API被随意修改
在双亲委派机制下java的核心类库始终由Bootstrap启动类加载器加载,不会被系统类加载器加载,避免了全类名冲突恶意覆盖行为。
①代码支持(见loaderClass源码分析)
- 先在当前类加载器的缓存中查找有无目标类,如果有直接返回。
- 判断当前类加载器的父类加载器是否为空,如果不为空则递归调用parent.loadClass()进行加载
- 相反,如果当前类加载器为空,则调用findBootstrapClassOrNull()方法让启动类加载器进行加载
- 如果以上3条路径都未加载成功,则调用findClass进行加载,改方法最后调用definClass系列的native方法加载目标java类
②思考
如果在自定义类加载器中重新loaderClass()或直接用findClass()加载等方式打破双亲委派机制,此时来加载自定义的java.lang.String类能否加载成功破坏核心类库呢?
答案是否定的,因为JDK自定义的类加载器在加载类的最后一不都会调用defineClass(String,byte[],int,int,ProtectionDomain)方法,该方法会执行**preDefineClass()**方法,此方法中对核心类库进行了安全检查。
3.劣势
双亲委派机制的最大缺点在于检查类是否加载的过程是单向的,这个方式虽然从结构上比较清晰,使各个ClassLoader的职责非常明确,但是使得顶层的ClassLoader无法访问底层ClassLoader加载的类。
4.破坏双亲委托机制
①JDK1.2之前
双亲委派模型是在jdk1.2之后引入的,之前自然不存在。
②线程上下文类加载器
对SPI不了解的可以看下:Java的SPI机制
在SPI服务机制中就要求JDK核心类库rt.jar中的代码去调用classpath下面代码,而rt.jar中的代码的定义类加载器是Bootstrap启动类加载器,是绝对加载不到classpath中的类的,此时为了打破这个僵局推出了线程上下文类加载器,这个类加载器可以通过java.lang.Thread类的setContextClassLoader()方法进行设置,如果线程创建时未设置它将会从父线程中继承一个,如果在应用程序的全局范围内都未设置过的话,那么这个类加载器的默认就是应用程序类加载器。
③代码热替换、模块热部署
想要实现代码的热替换必须要打破双亲委派模型使用自定义的类加载器才行,比较成熟的技术有OSGI有兴趣可以自行百度一下,下面我举一个比较简单代码热替换例子。
王厨师做一道回锅肉做完后发现忘记放肉了,然后抓紧将肉加进去。直接上代码
/**
* 王厨师
*/
public class Cook_wang implements Cook {
@Override
public void cooking() {
StringBuffer stringBuffer=new StringBuffer();
stringBuffer.append("这是一道回锅肉材料有:").append("葱、").append("辣椒、").append("醋、").append("锅");
System.out.println(stringBuffer.toString());
}
}
/**
* 厨师接口
*/
public interface Cook {
void cooking();
}
/**
* 自定义类加载器
*/
public class MyClassloader extends ClassLoader{
private String path;
public MyClassloader(String path){
this.path=path;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] bytes1 = new byte[0];
FileInputStream fileInputStream=null;
ByteArrayOutputStream byteArrayOutputStream=null;
try {
String classFilePath=path+name.replace(".","/")+".class";
fileInputStream=new FileInputStream(new File(classFilePath));
byteArrayOutputStream=new ByteArrayOutputStream();
byte[] bytes=new byte[1024];
int len;
while ((len=fileInputStream.read(bytes))!=-1){
byteArrayOutputStream.write(bytes,0,len);
}
bytes1 = byteArrayOutputStream.toByteArray();
} catch (IOException e) {
throw new ClassNotFoundException(e.getMessage());
} finally {
try {
if(fileInputStream!=null){
fileInputStream.close();
}
if(byteArrayOutputStream!=null){
byteArrayOutputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return defineClass(null,bytes1,0,bytes1.length,null);
}
}
/**
* 工厂容器
*/
public class SpringBeanFactory {
private final static String path="/Users/yangxiaofei/lago/learning-notes/JVM/jvmDemo/target/classes/";
private static Map<Class,Object> beanList=new HashMap<>();
public SpringBeanFactory(){
Thread thread=new Thread(new Runnable() {
@Override
public void run() {
while (true){
try {
Thread.sleep(1000);
MyClassloader myClassloader=new MyClassloader(path);
// 这里就暂且只加载王厨师了,实战是应加载全部类
Class<?> aClass = myClassloader.findClass("jvm.memory.classloader.hotdeploy.Cook_wang");
beanList.put(Cook.class,aClass.newInstance());
} catch (Exception e) {
e.printStackTrace();
} }
}
});
thread.setDaemon(true);
thread.start();
}
public static<T> T getBean(Class<T> beanClass){
Object object = beanList.get(beanClass);
T cast = beanClass.cast(object);
return cast;
}
}
/**
* 热部署测试类
*/
public class HotDeployTest {
public static void main(String[] args) throws Exception{
// 初始化容器
SpringBeanFactory springBeanFactory=new SpringBeanFactory();
while (true){
Thread.sleep(2000);
Cook bean = springBeanFactory.getBean(Cook.class);
bean.cooking();
}
}
}
运行程序打印:
这是一道回锅肉材料有:葱、辣椒、醋、锅
这是一道回锅肉材料有:葱、辣椒、醋、锅
这是一道回锅肉材料有:葱、辣椒、醋、锅
王厨师添加了肉后
/**
* 王厨师
*/
public class Cook_wang implements Cook {
@Override
public void cooking() {
// 急忙补上肉
StringBuffer stringBuffer=new StringBuffer();
stringBuffer.append("这是一道回锅肉材料有:").append("葱、").append("辣椒、").append("醋、").append("锅、").append("肉");
System.out.println(stringBuffer.toString());
}
}
此时程序继续运行打印:
这是一道回锅肉材料有:葱、辣椒、醋、锅、肉
这是一道回锅肉材料有:葱、辣椒、醋、锅肉
代码热替换成功
四、Classloader源码解析
1.loaderClass
public Class<?> loadClass(String name) throws ClassNotFoundException {
// 第二个参数是是否进行解析的意思,默认不进行解析
return loadClass(name, false);
}
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 检查看该类是否已经被加载过,如果已经被加载过则直接返回(优先检查定义类加载器是否加载过)
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
// 如果该类从未加载过,则判断当前类加载器的父类加载器是否为null
if (parent != null) {
// 如果父类加载器不为null,则直接委托父类加载器来加载,递归调用loadClass()
c = parent.loadClass(name, false);
} else {
// 如果父类加载器为null,说明该加载器为ExtClassLoader扩展类加载器,此时调用Bootstrap启动类加载器进行加载
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// 父类加载器也不能加载,此时调用findClass自己进行加载
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
PerfCounter.getParentDelegationTime().addTime(t1 - t0);
PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
2.findClass
findClass方法在自定义类加载器是官方推荐的重新方法,此方法主要完成的工作是根据类的全限定名通过各种途径获取改类对于class文件的数据流并输出位byte[]字节数组,最后调用defineClass系列重载方法加载改类。
3.defineClass
defineClass系列重载方法最后都会调用defineClass(String name, byte[] b, int off, int len, ProtectionDomain protectionDomain)
protected final Class<?> defineClass(String name, byte[] b, int off, int len,
ProtectionDomain protectionDomain)
throws ClassFormatError
{
protectionDomain = preDefineClass(name, protectionDomain);
String source = defineClassSourceLocation(protectionDomain);
Class<?> c = defineClass1(this, name, b, off, len, protectionDomain, source);
postDefineClass(c, protectionDomain);
return c;
}
最后会调用本地方法defineClass1来加载该类获取Class实例,想要继续深入JVM源码探究的可以看下类加载时JVM在干什么,源码分析。
4.preDefineClass
对要加载的class的全限定名进行检查,以java开头的类会直接抛出异常,防止核心API被篡改。
private ProtectionDomain preDefineClass(String name,
ProtectionDomain pd)
{
if (!checkName(name))
throw new NoClassDefFoundError("IllegalName: " + name);
// Note: Checking logic in java.lang.invoke.MemberName.checkForTypeAlias
// relies on the fact that spoofing is impossible if a class has a name
// of the form "java.*"
// 检查是否已java开头
if ((name != null) && name.startsWith("java.")
&& this != getBuiltinPlatformClassLoader()) {
throw new SecurityException
("Prohibited package name: " +
name.substring(0, name.lastIndexOf('.')));
}
if (pd == null) {
pd = defaultDomain;
}
if (name != null) {
checkCerts(name, pd.getCodeSource());
}
return pd;
}
五、自定义类加载器
在没有特殊情况下自定义类加载器官方建议保留双亲委派机制,重写findClass方法即可。
/**
* 自定义类加载器
*/
public class MyClassloader extends ClassLoader{
private String path;
public MyClassloader(String path){
this.path=path;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] bytes1 = new byte[0];
FileInputStream fileInputStream=null;
ByteArrayOutputStream byteArrayOutputStream=null;
try {
// 将.换成/
String classFilePath=path+name.replace(".","/")+".class";
// 获取.class文件的输入流
fileInputStream=new FileInputStream(new File(classFilePath));
byteArrayOutputStream=new ByteArrayOutputStream();
byte[] bytes=new byte[1024];
int len;
while ((len=fileInputStream.read(bytes))!=-1){
byteArrayOutputStream.write(bytes,0,len);
}
// 获取.class文件的字节数组
bytes1 = byteArrayOutputStream.toByteArray();
} catch (IOException e) {
throw new ClassNotFoundException(e.getMessage());
} finally {
try {
if(fileInputStream!=null){
fileInputStream.close();
}
if(byteArrayOutputStream!=null){
byteArrayOutputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
// 调用defineClass加载该类
return defineClass(name,bytes1,0,bytes1.length,null);
}
}
六、java9有关类加载器的变化
- 在jdk9中移除了扩展类加载器,新增了平台类加载器代替原先扩展类加载器的位置。
- 启动类加载器新增了对应的java实现类BootClassLoader,但为了向前兼容在获取启动加载器时还是会返回null。
- 系统类加载器的实现有原先的sun.misc.launcher A p p C l a s s L o a d e r 换 成 了 j d k . i n t e r n a l . l o a d e r . C l a s s L o a d e r s AppClassLoader换成了jdk.internal.loader.ClassLoaders AppClassLoader换成了jdk.internal.loader.ClassLoadersAppClassLoader
- 移除了URLCLassLoader,所以之前版本自定义类加载器是如果直接继承了URLClassLoader的话在新的JDK9的环境下无法运行
- 双亲委派机制在,委派给父类之前,先根据包名判断该类是否能够确定归属与某个类加载器,如果定义这个归属关系则直接由归属类加载器加载。