第一部分 Java基础
第二部分 Java进阶

Java类加载器面试题

1、Java的类加载器的种类都有哪些?

● 根类加载器(Bootstrap)--C++写的,看不到源码;

● 扩展类加载器(Extension)--加载位置:jre\lib\ext中;

● 系统(应用)类加载器(System\App) --加载位置:classpath中;

● 自定义加载器(必须继承ClassLoader)。

2、类什么时候被初始化?

● 创建类的实例,也就是new一个对象

● 访问某个类或接口的静态变量,或者对该静态变量赋值

● 调用类的静态方法

● 反射(Class.forName("com.lyj.load"))

● 初始化一个类的子类(会首先初始化子类的父类)

● JVM启动时标明的启动类,即文件名和类名相同的那个类只有这6中情况才会导致类的类的初始化。

● 类的初始化步骤:

如果这个类还没有被加载和链接,那先进行加载和链接;

假如这个类存在直接父类,并且这个类还没有被初始化(注意:在一个类加载器中,类只能初始化一次),那就初始化直接的父类(不适用于接口);

加入类中存在初始化语句(如static变量和static块),那就依次执行这些初始化语句。

3、Java类加载体系之ClassLoader双亲委托机制

java是一种类型安全的语言,它有四类称为安全沙箱机制的安全机制来保证语言的安全性,这四类安全沙箱分别是:

1.类加载体系

2.class文件检验器

3.内置于Java虚拟机(及语言)的安全特性

4.安全管理器及Java API

● 主要讲解类的加载体系:

java程序中的.java文件编译完会生成.class文件,而.class文件就是通过被称为类加载器的ClassLoader加载的,而ClassLoder在加载过程中会使用“双亲委派机制”来加载.class文件,图:

BootStrapClassLoader:启动类加载器,该ClassLoader是jvm在启动时创建的,用于加载$JAVA_HOME$/jre/lib下面的类库(或者通过参数-Xbootclasspath指定)。由于启动类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不能直接通过引用进行操作。

ExtClassLoader:扩展类加载器,该ClassLoader是在sun.misc.Launcher里作为一个内部类ExtClassLoader定义的(即sun.misc.Launcher$ExtClassLoader),ExtClassLoader会加载$JAVA_HOME/jre/lib/ext下的类库(或者通过参数-Djava.ext.dirs指定)。

AppClassLoader:应用程序类加载器,该ClassLoader同样是在sun.misc.Launcher里作为一个内部类,AppClassLoader定义的(即sun.misc.Launcher$AppClassLoader),AppClassLoader会加载java环境变量CLASSPATH所指定的路径下的类库,而CLASSPATH所指定的路径可以通过System.getProperty("java.class.path")获取;当然,该变量也可以覆盖,可以使用参数-cp,例如:java-cp路径(可以指定要执行的class目录)。

CustomClassLoader:自定义类加载器,该ClassLoader是指我们自定义的ClassLoader,比如tomcat的StandardClassLoader属于这一类;当然,大部分情况下使用AppClassLoader就足够了。

前面谈到了ClassLoader的几类加载器,而ClassLoader使用双亲委派机制来加载class文件的。ClassLoader的双亲委派机制是这样的(这里先忽略掉自定义类加载器CustomClassLoader):

1.当AppClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。

2.当ExtClassLoader加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader去完成。

3.如果BootStrapClassLoader加载失败(例如在$JAVA_HOME$/jre/lib里未查找到该class),会使用ExtClassLoader来尝试加载;

4.若ExtClassLoader也加载失败,则会使用AppClassLoader来加载,如果AppClassLoader也加载失败,则会报出异常ClassNotFoundException。

下面贴下ClassLoader的loadClass(String name,boolean resolve)的源码:

protected synchronized Class<?> loadClass(String name,boolean resolve)throws ClassNotFoundException{
        // 首先找缓存是否有 class
        Class c=findLoadedClass(name);
        if(c==null){
        //没有判断有没有父类
        try{
        if(parent!=null){
        //有的话,用父类递归获取 class
        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){
        // 如果还是没有找到,调用 findClass(name)去找这个类
        c=findClass(name);}
        }
        if(resolve){
        resolveClass(c);}
        return c;
        }

代码很明朗:

首先找缓存(findLoadedClass),没有的话就判断有没有parent,有的话就用parent来递归的loadClass,然而ExtClassLoader并没有设置parent,则会通过findBootstrapClassOrNull来加载class,而findBootstrapClassOrNull则会通过JNI方法”private native Class findBootstrapClass(String name)”来使用BootStrapClassLoader来加载class。

如果parent未找到class,则会调用findClass来加载class,findClass是一个protected的空方法,可以覆盖它以便自定义class加载过程。另外,虽然ClassLoader加载类是使用loadClass方法,但是鼓励用ClassLoader的子类重写findClass(String),而不是重写loadClass,这样就不会覆盖了类加载默认的双亲委派机制。

双亲委派托机制为什么安全:

举个例子,ClassLoader加载的class文件来源很多,比如编译器编译生成的class、或者网络下载的字节码。而一些来源的class文件是不可靠的,比如我可以自定义一个java.lang.Integer类来覆盖jdk中默认的Integer类,例如下面这样:

package java.lang;
public class Integer {
    public Integer(int value) {
        System.exit(0);
    }
}

初始化这个Integer的构造器是会退出JVM,破坏应用程序的正常进行,如果使用双亲委派机制的话该Integer类永远不会被调用,以为委托BootStrapClassLoader加载后会加载JDK中的Integer类而不会加载自定义的这个,可以看下下面这测试个用例:

public static void main(String...args){
        Integer i=new Integer(1);
        System.err.println(i);
}

执行时JVM并未在new Integer(1)时退出,说明未使用自定义的Integer,于是就保证了安全性。

4、描述一下 JVM 加载 class?

JVM中类的装载是由类加载器(ClassLoader)和它的子类来实现的,Java中的类加载器是一个重要的Java运行时系统组件,它负责在运行时查找和装入类文件中的类。

1.由于Java的跨平台性,经过编译的Java源程序并不是一个可执行程序,而是一个或多个类文件。

2.当Java程序需要使用某个类时,JVM会确保这个类已经被加载、连接(验证、准备和解析)和初始化。类的加载是指把类的.class文件中的数据读入到内存中,通常是创建一个字节数组读入.class文件,然后产生与所加载类对应的Class对象。加载完成后,Class对象还不完整,所以此时的类还不可用。

3.当类被加载后就进入连接阶段,这一阶段包括验证、准备(为静态变量分配内存并设置默认的初始值)和解析(将符号引用替换为直接引用)三个步骤。最后JVM对类进行初始化,包括:如果类存在直接的父类并且这个类还没有被初始化,那么就先初始化父类;如果类中存在初始化语句,就依次执行这些初始化语句。

4.类的加载是由类加载器完成的,类加载器包括:根加载器(BootStrap)、扩展加载器(Extension)、系统加载器(System)和用户自定义类加载器(java.lang.ClassLoader的子类)。

5.从Java 2(JDK 1.2)开始,类加载过程采取了父亲委托机制(PDM)。PDM更好的保证了Java平台的安全性,在该机制中,JVM自带的Bootstrap是根加载器,其他的加载器都有且仅有一个父类加载器。类的加载首先请求父类加载器加载,父类加载器无能为力时才由其子类加载器自行加载。JVM不会向Java程序提供对Bootstrap的引用。

● 下面是关于几个类加载器的说明:

1.Bootstrap:一般用本地代码实现,负责加载JVM基础核心类库(rt.jar);

2.Extension:从java.ext.dirs系统属性所指定的目录中加载类库,它的父加载器是Bootstrap;

3.System:又叫应用类加载器,其父类是Extension。它是应用最广泛的类加载器。它从环境变量classpath或者系统属性java.class.path所指定的目录中记载类,是用户自定义加载器的默认父加载器。

5、获得一个类对象有哪些方式?

● 类型.class,例如:String.class;

● 对象.getClass(),例如:”hello”.getClass();

● Class.forName(),例如:Class.forName(“java.lang.String”);