JVM(二)详解Class加载过程

Class Loading Linking Initializing (类编译-加载-初始化)
JVM(二)详解Class加载过程_第1张图片

一、Loading

 类加载器(classloader):

1. 任何一个class都是classloader(有上图多个加载器)把它load到内存的,load的过程是:

  ①把二进制码存入内存
  ②创建一个class对象指向这块区域
  以后使用的都是这个class对象然后指向的这块区域,这个class对象不在栈中,在metaspace(1.8之后出现的)

  上图中从下往上是父加载器,但不是继承的关系,而且仅仅是语法上才是继承!(易混淆点)
  在这里插入图片描述这样可以取到类的classloader,如果这个返回值是null则代表是最顶级的Bootstrap加载器,因为是c++实现的所以是null

2. 双亲委派


  加载时目标的class先看自定义classloader是否加载了,如果没加载委托上一级app加载器去看看加载了没,如果没加载就继续向上挨个找,如果最顶层bootstrap也没加载,则从最顶层依次向下委派让子加载器加载这个class,如果这个class不是属于这个子加载器可以加载的范围则继续向下委派,如果最后都不能加载则抛出classNotFoundException,以上过程只要有一个找到了就直接返回结果。
  面试:为什么类加载器要用双亲委派?
   答:主要安全,比如说有一个java.lang.string的包要加载,如果没有双亲委派,直接拿来最低级classloader直接加载到内存使用,就把sun公司的String覆盖了,如果客户在输密码的时候我在包里做了个记录密码的手脚就很危险,而双亲委派发现string要加载那一直找到bootstrap发现sun有了,那直接把sun的string返回;次要是之前已经加载过了就不用了再加载了。
JVM(二)详解Class加载过程_第2张图片
  类加载器中有个parent成员变量是classloader指向谁谁就是parent。
在这里插入图片描述
  上图中$后代表前面这个类的内部类

3. 类加载器的范围

JVM(二)详解Class加载过程_第3张图片

4. 自定义类加载器

JVM(二)详解Class加载过程_第4张图片

Class clazz = T005_LoadClassByHand.class.getClassLoader().loadClass("com.testJvm.jvm.c2_classloader.T002_ClassLoaderLevel");

  上为加载类,T005…为当前类名,最后参数是你想要加载的包名.类名。

T005_LoadClassByHand.class.getClassLoader().getResourceAsStream("");

  上为利用类加载器可以加载静态资源。

  classLoader源码:
    loadClass()方法(内部已经写好了双亲委派的过程)
JVM(二)详解Class加载过程_第5张图片
  上图类似递归调用,找不到就父类调用loadClass(本方法),最后也找不到就会调用findClass(),findClass方法源码中只有抛异常的一句话,是受保护的关键字修饰,具体的实现是子类中才能看到,这是钩子函数、模板函数的设计模式(写到一半,提供模板,具体子类实现)(面试设计模式可以引到classloader的源码中,加分项)!【有趣的题外话:历史上有两次jdk自己破坏了双亲委派,可以百度搜一下具体发生了啥】

  自定义类加载器:

public class T006_MSBClassLoader extends ClassLoader {
    @Override
    protected Class findClass(String name) throws ClassNotFoundException {
        File f = new File("c:/test/", name.replace(".", "/").concat(".class"));
        try {
            FileInputStream fis = new FileInputStream(f);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            int b = 0;

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

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

            return defineClass(name, bytes, 0, bytes.length);//将流转换为class对象,name:类名;bytes:内存中哪部分字节数组;off:字节数组起始位置;bytes.length:字节数组结束位置。
        } catch (Exception e) {
            e.printStackTrace();
        }
        return super.findClass(name); //throws ClassNotFoundException
    }


    public static void main(String[] args) throws Exception {
        ClassLoader l = new T006_MSBClassLoader();
        Class clazz = l.loadClass("com.testJvm.jvm.Hello");
        Class clazz1 = l.loadClass("com.testJvm.jvm.Hello");

        System.out.println(clazz == clazz1);

        Hello h = (Hello)clazz.newInstance();//反射new一个对象
        h.m();

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

        System.out.println(getSystemClassLoader());
    }
}

JVM(二)详解Class加载过程_第6张图片
  defineClass在堆中
  自定义classloader的过程:继承classloader,重写findClass()方法,在方法内找到要load进来的二进制的内容,load进内存,将这块内存转换为class对象(用defindClass()方法)。
  compiler API 可以直接把class load到内存

5. lazyloading(拓展了解,《深入jvm虚拟机》中有详细介绍)

  懒加载,什么时候用什么时候加载。
  严格来讲应该叫lazyInitializing,jvm规范没有规定何时加载。
  五种情况:
    new getstatic putstatic invokestatic指令,访问final变量除外
    java.lang.reflect对类进行反射调用时
    初始化子类的时候,父类首先初始化
    虚拟机启动时,被执行的主类必须初始化
    动态语言支持java.lang.invoke.MethodHandle解析的结果为REF_getstatic REF_putstatic REF_invokestatic的方法句柄时,该类必须初始化

6. 编译器

  三种执行方式:混合执行、编译执行、解释执行。
  java是解释和编译混合模式执行的。
  intepreter:解释器。
  JIT:编译器,有些代码会被编译成本地代码(相当于是windows下exe的,linux中的elf),热点代码编译成本地的,下次执行直接找本地的,效率提升。

  检测热点代码:-XX:CompileThreshold=10000 (也就是一万次执行是热点)
  设置不同模式的参数在edit中的vm options中写入,默认混合模式。

  问题:parent是如何指定的?
   答:在自定义classLoader时会调用父亲默认的classLoader()方法,源码中是通过 ClassLoader.getSystemClassLoader() 获取默认的classLoader当做parent(在没有手动指定的情况下)。

  问题:如何打破双亲委派?
   答:重写loadClass(),而不是重写findClass(),因为loadClass()方法写好了双亲委派的过程。

  tomcat中每一个webApplication中都有不同的classLoader,因为不同上下文中可能加载了同名的类的不同版本。
  jsp等问什么能热加载,因为重写了loadClass(),打破了双亲委派,每次给了需要加载的类名就把已有的自定义classLoader整个干掉,重新new 一个classLoader然后加载,这样每次加载的就不一样了,若补充协议loadClass()打破双亲委派,则第二次加载会找加载过的内容直接返回。
  load的类都放在metaspace方法区里,原来load的老的类,如果没有指向他的引用就会被GC回收。

你可能感兴趣的:(JVM调优,java,jvm)