JVM —— 类的加载


一、ClassLoader概念

    根据一个类的全限定名来读取此类的二进制字节流到JVM内部,然后转换为一个目标类对应的Java.lang.Class对象实例。



二、类加载器
    1. BootStrapClassLoader:启动类加载器,由C++语言编写并嵌套在JVM内部,主要负责加载“JAVA_HOME/lib”
                                            目录中的所有类型

    2. ExtClassLoader:主要负责加载“JAVA_HOME/lib/ext”扩展目录中的所有类型

    3. AppClassLoader:超类加载器是ExtClassLoader,主要负责加载ClassPath目录中的所有类型。

    4.CustomClassLoader:自定义类加载器,超类加载器是AppClassLoader。编写自定义类加载器来重新定义类的加载规则。

 import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
public class CustomClassLoader extends ClassLoader
{
    public static final String drive = "d:/";
    public static final String fileType = ".class";


    public static void main(String[] args) throws Exception
    {
        CustomClassLoader loader = new CustomClassLoader();
        Class objClass = loader.loadClass("HelloWorld", true);
        Object obj = objClass.newInstance();
        System.out.println(objClass.getName());
        System.out.println(objClass.getClassLoader());
        System.out.println(obj.getClass().toString());
    }


    public Class findClass(String name)
    {
        byte[] data = loadClassData(name);
        return defineClass(name, data, 0, data.length);
    }
    public byte[] loadClassData(String name)
    {
        FileInputStream fis = null;
        byte[] data = null;
        try
        {
            fis = new FileInputStream(new File(drive + name + fileType));
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            int ch = 0;
            while ((ch = fis.read()) != -1)
            {
                baos.write(ch);
            }
            data = baos.toByteArray();
        } catch (IOException e)
        {
            e.printStackTrace();
        }
        return data;
    }
}
编写过程:
1.继承抽象类ClassLoader
2.重写其findClass()
3.在程序中调用loadClass()方法实现类加载操作


一般情况下,findClass()和defineClass()通常一起组合使用。


三、双亲委派模型


    1. 双亲委派规则

        除了启动类加载器之外,程序中每一个类加载器都应该拥有一个超类加载器。当一个类加载器接收到一个类加载任务的时

 候,它并不会立即展开加载,而是将加载人物委派给它的超类加载器去执行,每一层的类加载器都采取相同的方式,直至委派

 给最顶层的启动类加载器为止。如果超类加载器无法加载委派给它的类时,便会将类的加载任务退回给它的下一级类加载器去

 执行加载。 


    2. 优点

           有效的确保一个类的全局唯一性,当程序中出现多个全限定名相同的类时,类加载器执行加载的时候,始终只会加载其中的某一个类。


    3.LoadClass源码分析

    protected Class loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 检查类是否已加载
            Class c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
//委托超类加载器执行类加载
                    if (parent != null) {
                        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) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
    //没有找到加载的class,则调用findClass()方法
                    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;
        }
    }



四、类加载过程

    1.加载

        由类加载器负责根据一个类的全限定名来读取此类的二进制字节流到JVM内部,并存储在运行时内存区中的方法区

内,  然后将其转换成一个与目标类型对应的java.lang.Class对象实例,这个Class对象在日后就会作为方法区中该类的各

 种数据的访问入口。



    2.连接

            将已加载到JVM中的二进制字节流的类数据信息合并到JVM的运行状态中,由验证、准备、和解析3个阶段构成。

                ①验证:确保类加载的正确性。验证阶段一系列操作大致划分:格式验证、语义验证、操作验证、符号验证等。

                ②准备:对存放在方法区中类数据信息的类变量执行初始化(静态变量分配内存、设置初始值)

                ③解析:将字节码常量池中的符号引用全部替换为直接引用,包括类、接口、方法、字段的符号引用。



    3.初始化

        一个类或者接口应该在首次主动使用时执行初始化操作。

    主动使用情形:
1. 为一个类型创建一个新的对象实例时(new、反射或序列化)
2. 调用一个类型的静态方法时
3. 调用一个类型或接口的静态字段,或者对这些静态字段执行赋值操作时。
不过final关键字修饰的静态字段除外,它被初始化为一个编译时的常量表达式。
4. 调用Java API 中的反射方法时
5. 初始化一个类的派生类时(超类必须提前进行初始化操作)
对于接口而言,只有在某个接口中声明的非常量字段被使用时,接口才初始化。
6. JVM启动main()方法的启动类时

         

你可能感兴趣的:(JVM —— 类的加载)