2-详解class加载过程

一.class加载过程

Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验/准备/解析初始化,最终

形成可以被虚拟机直接使用的Java类型,这个过程被称作虚拟机的类加载机制

loading → linking (verification-> preparation → resolution )-> initializing

loading:把class文件load到内存中,采用双亲委派,主要是为了安全性

verification:校验class文件是否符合标准

preparation:静态变量分配内存并设初始值的阶段(不包括实例变量)

resolution:把符号引用转换为直接引用

initializing:静态变量赋初始值

遇到new、getstatic、putstatic或invokestatic这四条字节码指令时,如果类型没有进行过初始

化,则需要先触发其初始化阶段。

能够生成这四条指令的典型Java代码场景有:

1.使用new关键字实例化对象。

2.读取或设置一个类型的静态字段。(被final修饰、已在编译期把结果放入常量池的静态字段除外,但是

final static int i=10;如果没有=10还是会在准备阶段给它赋默认值,在初始化的时候赋值。)

3.调用一个类型的静态方法。

4.使用java.lang.reflect包的方法对类型进行反射调用。

5.虚拟机启动的时候被执行的主类必须初始化。

6.初始化子类时,父类首先初始化。

二.类加载器

类加载器实现了loading这个动作,把class文件加载到内存。

1.启动类加载器(BootstrapClassLoader):负责加载存放在lib/rt.jar charset.jar 等目录下的核心类,由c++实现。启动类加载器无法被Java程序直接引用(classLoader的loadClass方法中,若parent为 null则使用启动类加载器。

2.扩展类加载器(ExtensionClassLoader):负责加载lib/ext目录下的类,在类sun.misc.Launcher$ExtClassLoader 中以Java代码的形式实现 。

3.应用程序类加载器(ApplicationClassLoader):负责加载用户类路径(ClassPath)下所有的类库。由sun.misc.Launcher$AppClassLoader实现 ,是ClassLoader类中的getSystemClassLoader()方法的返回值。如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

双亲委派模型

双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这 个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载 请求最终都应该传送到最顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求 (它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去完成加载。

源码实现:

protected Class loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        // First, check if the class has already been loaded
        // 查询缓存 是否已经加载过这个类
        Class c = findLoadedClass(name);
        if (c == null) {//没加载则去调用父亲
            long t0 = System.nanoTime();
            try {
                if (parent != null) {//调用父亲(不为BootstrapClassloader)去查缓存
                    c = parent.loadClass(name, false);
                } else {//父亲为BootstrapClassloader
                    c = findBootstrapClassOrNull(name);//查缓存和加载/lib/rt/下的核心类
                }
            } 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;
    }
}

图解如下:

三.自定义类加载器
  1. 继承ClassLoader抽象类 extends ClassLoader

2.重写findClass() 方法,读取class文件,并调用defineClass(),override findClass() → defineClass(byte[] b, int off, int len)

3.加密(可以对class字节码文件进行加密处理,可选)

源码实现如下:

package com.tc.javabase.jvm;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;

/**
 * @Classname MyClassLoader
 * @Description 自定义类加载器
 *
 * 1. 继承ClassLoader抽象类 extends ClassLoader
 * 2.重写findClass() 方法,读取class文件,并调用defineClass(),override findClass() -> defineClass(byte[] b, int off, int len)
 * 3.加密(可以对class字节码文件进行加密处理,可选)
 * @Date 2020/7/29 00:17
 * @Created by zhangtianci
 */
public class MyClassLoader extends ClassLoader{

    public static int seed = 0B10110110;

    @Override
    protected Class findClass(String name) throws ClassNotFoundException {
        File f = new File("/Users/zhangtianci/work/personal-projects/javabase/src/main/java/", name.replace('.', '/').concat(".ztcclass"));

        try {
            FileInputStream fis = new FileInputStream(f);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            int b = 0;

            while ((b=fis.read()) !=0) {
                baos.write(b ^ seed);
            }

            byte[] bytes = baos.toByteArray();
            baos.close();
            fis.close();//可以写的更加严谨

            return defineClass(name, bytes, 0, bytes.length);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return super.findClass(name); //throws ClassNotFoundException
    }

    public static void main(String[] args) throws Exception {

        encFile("com.tc.javabase.jvm.Hello");

        ClassLoader l = new MyClassLoader();
        Class clazz = l.loadClass("com.tc.javabase.jvm.Hello");
        Hello h = (Hello)clazz.newInstance();
        h.sayHello();

        System.out.println(l.getClass().getClassLoader());
        System.out.println(l.getParent());
    }

    private static void encFile(String name) throws Exception {
        File f = new File("/Users/zhangtianci/work/personal-projects/javabase/src/main/java/", name.replace('.', '/').concat(".class"));
        FileInputStream fis = new FileInputStream(f);
        FileOutputStream fos = new FileOutputStream(new File("/Users/zhangtianci/work/personal-projects/javabase/src/main/java/", name.replace(".", "/").concat(".ztcclass")));
        int b = 0;

        while((b = fis.read()) != -1) {
            fos.write(b ^ seed);
        }

        fis.close();
        fos.close();
    }
}
四.破坏双亲委派模型

1.如何破坏双亲委派模型?

通过重写loadClass()方法。

2.什么时候破坏过双亲委派模型?

- JDK1.2之前,自定义classLoader都必须重写loadClass ()方法。(缺陷)
- ThreadContextClassLoader可以实现基础类调用实现类的代码
- 热启动/热部署  tomcat 都有自己的模块指定classLoader(可以加载同一类库的不同版本)

破坏双亲委派模型的简单实现:

/**
 * @Classname MyClassLoader3
 * @Description 自定义类加载机制  打破双亲委派机制(重写classLoad() )
 *
 *  当重新new 一个自定义加载器实例时 可以重新加载目标class文件到内存中 有一个新的目标class对象
 *  *  否则 内存中只会加载一次
 *  *
 *  *  <----重写loadClass()  打破双亲委派机制时----->
 *  *  当同一个自定义加载器实例 想要加载目标class文件到内存两次(调用defineClass()方法) 会报错
 * @Date 2020/7/29 17:27
 * @Created by zhangtianci
 */
public class MyClassLoader3 extends ClassLoader{
    @Override
    public Class loadClass(String name) throws ClassNotFoundException {

        File f = new File("/Users/zhangtianci/work/personal-projects/javabase/src/main/java/" + name.replace(".", "/").concat(".class"));

        if(!f.exists()) return super.loadClass(name);

        try {

            InputStream is = new FileInputStream(f);

            byte[] b = new byte[is.available()];
            is.read(b);
            return defineClass(name, b, 0, b.length);
        } catch (IOException e) {
            e.printStackTrace();
        }

        return super.loadClass(name);
    }
}
五.混合模式

1.解释器: bytecode intepreter

2.jit:just in-time compiler

3.混合模式:

  • 混合使用解释器+热点代码编译
  • 起始阶段采用解释执行
  • 热点代码检测 -多次被调用的代码 - 多次被调用的循环 -进行编译
  • -Xmixd :默认为混合模式,启动速度较快,对热点代码实行检测和编译; -Xint:使用纯解释模式,启动很快,执行稍慢 ; -Xcomp:  使用纯编译模式,执行很快,启动稍慢。

你可能感兴趣的:(2-详解class加载过程)