我们写的.java
文件通过编译成字节码文件.class
文件,然后再通过我们的类加载器:Class Loader,反射以后,类模板存在方法区,把实例化的对象存在堆里;
对象的hashcode值
car1.hashCode(); car2.hashCode(); car3.hashCode()
得到的hashCode码是不一样的(说明通过new关键字得到的对象不一样);car1.getClass();car2.getClass();car3.getClass();
得到的类模板是一样的。(说明是从同一个类模板new出来对象)总结:
. getClass()
->获得 类模板;. getClassLoader()
->获得 类加载器类加载器主要分为四种:
扩展类加载器(Extension ClassLoader)和 应用程序类加载器(Application ClassLoader)是继承自抽象类java.lang.ClassLoader。
需要注意的是:各个类加载器之间是组合关系,并非继承关系
那么到底该有哪个类去加载呢 ?所以我们在这引出双亲委派机制;
一句话:向上委派,上面能加载就会加载;
App -> Platform -> Bootstrap
直至到根加载器-BootstrapBootstrap
会检查是否能加载当前这个类,(比如java.lang包下的所有类,根加载器都能加载);
Bootstrap -> Platform -> App
直至有加载器来加载,不然就会报错;比如ClassNotFound一般来说,Java 应用的类都是由AppClassLoader来完成加载的
总结:
如果一个类加载器收到了类加载的请求,它首先不会自己尝试去加载这个类,而是把这个请求委派给父类加载器,每一个层次的类加载器都是加此,因此所有的加载请求最终到达顶层的启动类加载器,只有当父类加载器反馈自己无法完成加载请求时,子类加载器才会尝试自己去加载。
从上图中我们就更容易理解了:
当一个.class
这样的文件要被加载时。不考虑我们自定义类加载器,首先会在AppClassLoader
中检查是否加载过,如果有那就无需再加载了。
如果没有,那么会拿到父加载器,然后调用父加载器的loadClass
方法。父类中同理也会先检查自己是否已经加载过,如果没有再往上。
注意这个类似递归的过程,直到到达Bootstrap classLoader
之前,都是在检查是否加载过,并不会选择自己去加载。直到BootstrapClassLoader
,已经没有父加载器了,这时候开始考虑自己是否能加载了,如果自己无法加载,会下沉到子加载器去加载,一直到最底层,如果没有任何加载器能加载,就会抛出ClassNotFoundException
。
那么有人就有下面这种疑问了?
为什么要有这种机制?
这种设计的好处:
String.java
,篡改它的实现。双亲委派机制可以保证所有的Java类库都是由启动类加载器加载Bootstrap classLoader
,从一定程度上防止了危险代码的植入;面试常问
问: 比如我们自己重新定义一个完全和String
同包名的类: java.lang.String类
,那么此时jdk加载的是系统自己的String还是我们的String?
答案:当然是系统自己的String类, 也就是说:我们自己写的java.lang.String的类,是不可以替换调JDK本身的类。
为什么呢?虽然和双亲委派机制有关系,但不是非常的准确。因为双亲委托派机制是可以打破的,我们完全可以自己写一个classLoader
来加载自己写的java.lang.String
类,但是我们会发现也不会加载成功;
真正的原因:
因为针对java.*
开头的类,jvm
的实现中已经保证了必须由Bootstrap classLoader
,也就是我们的根加载器来加载。
具体描述:
在加载某个类时,优先使用父类加载器加载。如果我们自定义了java.lang.String
这个类, 那么该自定义String类使用的加载器应是是AppClassLoader
,根据双亲委派原理,AppClassLoader
加载器的父类为ExtClassLoader
,所以这时加载String使用的类加载器是ExtClassLoader, 但是类加载器ExtClassLoader在jre/lib/ext目录下没有找到String.class类。 然后使用ExtClassLoader父类的加载器BootStrap
, 父类加载器BootStrap在jre/lib目录的rt.jar找到了String.class,将其加载到内存中。而jre/lib目录下的 String类 就是我们jdk自己的类。
问: 既然JVM已经提供了默认的类加载器,为什么还要自已来定义类加载器呢?
答: 因为Java中提供的默认ClassLoader,只加载指定目录下的jar和class,如果我们想加载其它位置的类或jar时,比如:
我们想要加载网络上的一个.class
文件,加载到内存之后,我们想要调用这个类中的方法实现自己的业务逻辑。在这样的情况下,默认的ClassLoader就不能满足我们的需求了,所以需要定义自己的ClassLoader。
定义自已的类加载器分为两步:
继承java.lang.ClassLoader
重写父类的findClass方法
问: 为什么只重写findClass方法?
答: 因为JDK已经在loadClass方法中帮我们实现了ClassLoader搜索类的算法,当在loadClass方法中搜索不到类时,loadClass方法就会调用findClass方法来搜索类,所以我们只需重写该方法即可。
但是,如没有特殊的要求,一般不建议重写loadClass搜索类的算法。