JVM虚拟机-01、类加载机制分析

JVM虚拟机-01、类加载机制分析

java虚拟机(java virtual machine,JVM),一种能够运行java字节码的虚拟机。作为一种编程语言的虚拟机,实际上不只是专用于Java语言,只要生成的编译文件匹配JVM对加载编译文件格式要求,任何语言都可以由JVM编译运行。比如kotlin、scala等。

1、jvm的基本结构

JVM 由三个部分组成

  • 类加载系统
  • 运行时数据区
  • java运行执行引擎

22

2、类加载机制

2.1类的生命周期

多个java文件经过编译打包生成可运行jar包(或者war包),最终由java命令运行某个主类的main函数启动程序,首先需要通过类加载器把主类加载到JVM。主类在运行过程中如果使用到其它类,会逐步加载这些类。注意,jar包里的类不是一次性全部加载的,是使用到时才加载。

1

  1. 加载(Load 查找和导入class文件)
  • 通过一个类的全限定名获取定义此类的二进制字节流
  • 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
  • 在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口
  1. 链接(Link)

2.1 验证(Verify 保证加载类的正确性)

  • 文件格式验证
  • 元数据验证
  • 字节码验证
  • 符号引用验证

2.2 准备(Prepare 为类的静态变量分配内存,并将其初始化为默认值)

2.3 解析(Resolve 类装载器装入类所引用的其它所有类)
3 初始化(Initialize 对类的静态变量初始化为指定的值,执行静态代码块)
4 使用
5 卸载

3. 类加载器和双亲委派机制

3.1 类加载器的种类

  • 启动类加载器(Bootstrap ClassLoader)

负责加载JRE的核心类库,如JRE目标下的rt.jar,charsets.jar等

  • 扩展类加载器(Extension ClassLoader)

负责加载JRE扩展目录ext中jar类包

  • 系统类加载器(Application ClassLoader)

负责加载ClassPath路径下的类包

  • 用户自定义加载器(User ClassLoader)

负责加载用户自定义路径下的类包

111

类加载器列子

import com.sun.crypto.provider.DESKeyFactory;

/**
 * @program: bloom
 * @description: ${description}
 * @author: hao.yu
 * @create: 2019-03-08 14:04
 **/

public class Test {

    @org.junit.Test
    public void test(){
        System.out.println(String.class.getClassLoader());
        System.out.println(DESKeyFactory.class.getClassLoader().getClass().getName());
        System.out.println(Test.class.getClassLoader().getClass().getName());
        System.out.println(ClassLoader.getSystemClassLoader().getClass().getName());
    }
}

运行结果

null
sun.misc.Launcher$ExtClassLoader
sun.misc.Launcher$AppClassLoader
sun.misc.Launcher$AppClassLoader

3.2 类加载器的加载原则

检查某个类是否已经加载:顺序是自底向上,从Custom ClassLoader到BootStrap ClassLoader逐层检查,只要某个Classloader已加载,就视为已加载此类,保证此类只所有ClassLoader加载一次。加载的顺序:加载的顺序是自顶向下,也就是由上层来逐层尝试加载此类。

双亲委派机制

如果一个类加载器在接到加载类的请求时,它首先不会自己尝试去加载这个类,而是把这个请求任务委托给父类加载器去完成,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。

  • 优势:Java类随着加载它的类加载器一起具备了一种带有优先级的层次关系。比如,Java中的String类,它存放在rt.jar之中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此String在各种类加载环境中都是同一个类。如果不采用双亲委派模型,那么由各个类加载器自己取加载的话,那么系统中会存在多种不同的String类。(沙箱安全机制:自己写的java.lang.String.class类不会被加载,避免类的重复加载:当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次,保证被加载类的唯一性)
  • 破坏:可以继承ClassLoader类,然后重写其中的loadClass方法.

3.3 自定义类加载器

自定义类加载器只需要继承java.lang.ClassLoader类,该类有两个核心方法,一个是loadClass(String,boolean),实现了双亲委派机制,大体逻辑

  1. 首先,检查一下指定名称的类是否已经加载过,如果加载过了,就不需要再加载,直接返回。
  2. 如果此类没有加载过,那么,再判断一下是否有父加载器;如果有父加载器,则由父加载器加载(即调用parent.loadClass(name,false))或者是调用bootstrap类加载器来加载。
  3. 如果父加载器及bootstrap类加载器都没有找到指定的类,那么调用当前类加载器的findClass方法来完成类加载。

还有一个方法是findClass,默认实现是抛出异常,所以我们自定义类加载器主要是重写findClass方法

import java.io.FileInputStream;
import java.lang.reflect.Method;

public class MyClassLoaderTest {
    static class MyClassLoader extends ClassLoader {
        private String classPath;

        public MyClassLoader(String classPath) {
            this.classPath = classPath;
        }

        private byte[] loadByte(String name) throws Exception {
            name = name.replaceAll("\\.", "/");
            FileInputStream fis = new FileInputStream(classPath + "/" + name
                    + ".class");
            int len = fis.available();
            byte[] data = new byte[len];
            fis.read(data);
            fis.close();
            return data;
        }

        protected Class<?> findClass(String name) throws ClassNotFoundException {
            try {
                byte[] data = loadByte(name);
                //defineClass将一个字节数组转为Class对象,这个字节数组是class文件读取后最终的字节数组。
                return defineClass(name, data, 0, data.length);
            } catch (Exception e) {
                e.printStackTrace();
                throw new ClassNotFoundException();
            }
        }

    }

    public static void main(String args[]) throws Exception {
        MyClassLoader classLoader = new MyClassLoader("D:/test");
        Class clazz = classLoader.loadClass("com.bloom.newexam.model.QuizBankQuestion");
        Object obj = clazz.newInstance();
        Method method= clazz.getDeclaredMethod("sout", null);
        method.invoke(obj, null);
        System.out.println(clazz.getClassLoader().getClass().getName());
    }
}
运行结果:
=======自己的加载器加载类调用方法=======
com.bloom.jvm.MyClassLoaderTest$MyClassLoader
posted @ 2020-04-14 15:46  teago  阅读(151)  评论(0)    收藏  举报