目录
类加载的定义
类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,
然后在堆区创建一个这个类的java.lang.Class对象
类的加载的最终产品是位于堆区中的Class对象。
Class对象封装了类在方法区内的数据结构,
并且提供了访问方法区内的数据结构的接口。
每个 Class 对象的内部都有一个 classLoader 字段来标识自己是由哪个 ClassLoader 加载的。ClassLoader 就像一个容器,里面装了很多已经加载的 Class 对象。
加载类的方式
1)从本地系统直接加载
2)通过网络下载.class文件
3)从zip,jar等归档文件中加载.class文件
4)从专有数据库中提取.class文件
5)将Java源文件动态编译为.class文件(服务器)
类的加载过程
加载时类加载过程的第一个阶段,在加载阶段,虚拟机需要完成以下三件事情:
1、通过一个类的全限定名来获取其定义的二进制字节流。
2、将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
3、在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口。
类加载器
JVM 运行实例中会存在多个 ClassLoader,不同的 ClassLoader 会从不同的地方加载字节码文件。它可以从不同的文件目录加载,也可以从不同的 jar 文件中加载,也可以从网络上不同的静态文件服务器来下载字节码再加载。
JVM 中内置了三个重要的 ClassLoader,分别是 BootstrapClassLoader、ExtensionClassLoader 和 AppClassLoader。
- BootstrapClassLoader
启动类加载器主要加载的是JVM自身需要的类,这个类加载使用C++语言实现的,是虚拟机自身的一部分,它负责将 <JAVA_HOME>/lib
路径下的核心类库加载到内存中
- ExtensionClassLoader
负责加载<JAVA_HOME>/lib/ext
目录下的类库,开发者可以直接使用标准扩展类加载器。
- AppClassLoader
负责加载用户类路径(CLASSPATH)下的类库,一般我们编写的java类都是由这个类加载器加载,这个类加载器是CLassLoader中的getSystemClassLoader()方法的返回值,所以也称为系统类加载器.一般情况下这就是系统默认的类加载器。
双亲委派机制
而 ExtensionClassLoader 在加载一个未知的类名时,它也并不是立即搜寻 ext 路径,它会首先将类名称交给 BootstrapClassLoader 来加载,如果 BootstrapClassLoader 可以加载,那么 ExtensionClassLoader 也就不用麻烦了。否则它就会搜索 ext 路径下的 jar 包。
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 检查class是不是已经被加载了
Class<?> c = findLoadedClass(name);
if (c == null) { //如果还没被加载
long t0 = System.nanoTime();
try {
if (parent != null) { //父亲不是null,则委托父亲去加载
c = parent.loadClass(name, false);
} else { //父亲是null,其实是让BootstrapClassLoader(根加载器)加载
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;
}
}
为什么需要双亲委托机制?
那为什么需要保证 JDK 核心类库由 bootstrap 加载器加载呢?其实这主要是为了保证 JDK 核心类库都是由同一个加载器加载?那为什么要保证 JDK 核心类库都需要由同一个加载器加载呢?这是为了保证核心类库的类在 JVM 中只有一个对应实体。也就是为了保证类型关系的正确性。
不同的 ClassLoader 之间也会有合作,它们之间的合作是通过 parent 属性和双亲委派机制来完成的。parent 具有更高的加载优先级。除此之外,parent 还表达了一种共享关系,当多个子 ClassLoader 共享同一个 parent 时,那么这个 parent 里面包含的类可以认为是所有子 ClassLoader 共享的。这也是为什么 BootstrapClassLoader 被所有的类加载器视为祖先加载器,JVM 核心类库自然应该被共享。
小结 :双亲委托机制的核心思想分为两个步骤。其一,自底向上检查类是否已经加载;其二,自顶向下尝试加载类。
例子
/**
* @author: wangmingxin02
* @create: 2019-01-24
**/
public class ClassLoaderTest {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
//自定义类加载器
ClassLoader myclassloader = new ClassLoader() {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
try {
String fileaName = name.substring(name.lastIndexOf(".")+1)+".class";
InputStream is = getClass().getResourceAsStream(fileaName);
if(is==null){
return super.loadClass(name);
}
byte[] bytes = new byte[is.available()];
is.read(bytes);
return defineClass(name,bytes,0,bytes.length);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
};
Object obj = myclassloader.loadClass("pers.wmx.ClassLoaderTest").newInstance();
System.out.println(obj.getClass());
System.out.println(obj.getClass().getClassLoader());
System.out.println(obj instanceof ClassLoaderTest);
}
}
运行结果:
明明加载的也是是ClassLoaderTest,为什么返回false
System.out.println(obj.getClass().getClassLoader());
System.out.println(ClassLoaderTest.class.getClassLoader());
System.out.println(DNSNameService.class.getClassLoader());
System.out.println(DNSNameService.class.getClassLoader().getParent());
运行结果:
DNSNameService 是ExtClassLoader加载的一个类
null其实就是根加载器 BootstrapClassLoader
//可以通过以下代码得到Bootstrap ClassLoader具体的加载路径
URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();
for (int i = 0; i < urls.length; i++) {
System.out.println(urls[i].toExternalForm());
}
转载请注明:汪明鑫的个人博客 » java的类加载机制浅析
说点什么
您将是第一位评论人!