java中类的加载时机和类加载器
一、类加载时机
jvm的类加载机制:jvm把描述类的数据从class文件中加载到内存,并对数据进行校验,转换解析,和初始化,最终形成被jvm 使用的Java类型。
加载时机有以下几种情景:
- 使用new关键字实例化对象
- 读取一个类的静态字段(被final修饰、已在编译期把结果放在常量池的静态字段除外)
- 设置一个类的静态字段(被final修饰、已在编译期把结果放在常量池的静态字段除外)
- 调用一个类的静态方法
- 被final修饰静态字段在操作使用时,不会使类进行初始化,因为在编译期已经将此常量放在常量池。
- 子类调用或者设置父类的静态字段或者调用父类的静态方法时仅仅初始化父类,而不初始化子类。同样读取final修饰的常量不会进行类的初始化。
二、类加载器
2.1 类加载器的类型
类加载器是负责将可能是网络上、也可能是磁盘上的class文件加载到内存中。并为其生成对应的java.lang.class对象。一旦一个类被载入JVM了,同一个类就不会被再次加载。那么怎样才算是同一个类?在JAVA中一个类用其全限定类名(包名和类名)作为其唯一标识,但是在JVM中,一个类用其全限定类名和其类加载器作为其唯一标识。也就是说,在JAVA中的同一个类,如果用不同的类加载器加载,则生成的class对象认为是不同的。
当JVM启动时,会形成由三个类加载器组成的初始类加载器层次结构,如下:
- 启动类加载器BootstrapClassLoader:
是嵌在JVM内核中的加载器,该加载器是用C++语言写的,主要负载加载JAVA_HOME/lib下的类库,启动类加载器无法被应用程序直接使用。 - 扩展类加载器Extension ClassLoader:
该加载器器是用JAVA编写,且它的父类加载器是Bootstrap,主要加载JAVA_HOME/lib/ext目录中的类库。开发者可以这几使用扩展类加载器。我们知道java中系统属性java.ext.dirs指定的目录由ExtClassLoader加载器加载,如果程序中没有指定该系统属性(-Djava.ext.dirs=sss/lib)那么该加载器默认加载$JAVA_HOME/lib/ext目录下的所有jar文件,通过程序来看下系统变量java.ext.dirs所指定的路径:
public class Test
{
public static void main(String[] args)
{
System.out.println(System.getProperty("java.ext.dirs"));
}
}
结果:
C:\Program Files (x86)\Java\jdk1.6.0_43\jre\lib\ext;C:\Windows\Sun\Java\lib\ext
- 系统类加载器App ClassLoader:
系统类加载器,也称为应用程序类加载器,负责加载应用程序classpath目录下的所有jar和class文件。它的父加载器为Ext ClassLoader。
public class Test
{
public static void main(String[] args)
{
System.out.println(ClassLoader.getSystemClassLoader());
}
}
结果:
sun.misc.Launcher$AppClassLoader@addbf1
程序中的方法是返回委托的系统类加载器,通过执行结果,可以知道,系统类加载器是通过sun.misc.Launcher$AppClassLoader实现的。
2.2 类加载器的关系
三种类加载器关系如下所示:
类加载器的体系并不是“继承”体系,而是委派体系,大多数类加载器首先会到自己的parent中查找类或者资源,如果找不到才会到自己本地查找。类加载器的委托行为动机是为了避免相同的类被加载多次。
程序验证:
public static void main(String[] args)
{
System.out.println(ClassLoader.getSystemClassLoader().getParent());
}
结果:sun.misc.Launcher$ExtClassLoader@42e816
在这里可以看到,Application ClassLoader的父加载器确实是ExtClassLoader。
我们在往上走一层,如果猜想没错的话,ExtClassLoader的父加载器应该是BootStrap ClassLoader
public static void main(String[] args)
{
System.out.println(ClassLoader.getSystemClassLoader().getParent().getParent());
}
结果: null
这里不是说ExtClassLoader没有父加载器,而是因为Bootstrap ClassLoader使用C++写的。
2.3 双亲委派模型
如果一个类加载器收到了一个类加载请求,它不会自己去尝试加载这个类,而是把这个请求转交给父类加载器去完成。每一个层次的类加载器都是如此。因此所有的类加载请求都应该传递到最顶层的启动类加载器中,只有到父类加载器反馈自己无法完成这个加载请求(在它的搜索范围没有找到这个类)时,子类加载器才会尝试自己去加载。委派的好处就是避免有些类被重复加载。
看一下代码:
protected synchronized Class<?> loadClass(String paramString, boolean paramBoolean)
throws ClassNotFoundException
{
//检查是否被加载过
Class localClass = findLoadedClass(paramString);
//如果没有加载,则调用父类加载器
if (localClass == null) {
try {
//父类加载器不为空
if (this.parent != null)
localClass = this.parent.loadClass(paramString, false);
else {
//父类加载器为空,则使用启动类加载器
localClass = findBootstrapClass0(paramString);
}
}
catch (ClassNotFoundException localClassNotFoundException)
{
//如果父类加载失败,则使用自己的findClass方法进行加载
localClass = findClass(paramString);
}
}
if (paramBoolean) {
resolveClass(localClass);
}
return localClass;
}
先检查是否已经被加载过,若没有加载则调用父类加载器的loadClass方法,若父类加载器不存在,则使用启动类加载器。如果父类加载器加载失败,则抛出异常之后看,再调用自己的findClass方法进行加载。
》》》博主长期更新学习心得,推荐点赞关注!!!
》》》若有错误之处,请在评论区留言,谢谢!!!