JVM类加载

类加载过程

类加载示意图(顺序开始,不一定顺序结束)

在这里插入图片描述

加载(Loading)
预加载

虚拟机启动时的加载,加载的是JAVA_HOME/lib/下的rt.jar下的.class文件,此jar包下有java.lang.、java.util.、java.io.*等常用类。

运行时加载
  1. 虚拟机在用到一个.class文件时,会去内存中查看此.class文件是否被加载,如果没有就会按照类的全限定名(包名+类名)来加载这个类。
  2. 加载过程
    获取.class文件的二进制流;
    在这里插入图片描述
    将类信息,静态变量,字节码,常量等.class文件中的信息放入方法区中;
    在内存中生成一个代表这个.class文件的java.lang.Class对象,作为访问入口。一般这个Class在堆里,但HotSpot虚拟机将其放到方法区中;
验证
  1. 目的:确保.class文件的字节流中包含的信息符合当前虚拟机的要求,且不危害虚拟机的安全。
  2. 原因:因为.class文件未必要从Java源码编译而来,可以使用任何途径产生,虚拟机如果不检查输入的字节流,对其完全信任的话,很可能会因为载入了有害的字节流而导致系统崩溃,所以验证是虚拟机对自身保护的一项重要工作。
  3. 过程
    文件格式验证;
    元数据验证;
    字节码验证;
    符号引用验证
准备
  1. 准备阶段是正式为类变量(static修饰的)分配内存并设置其初始值的阶段,这些变量所使用的内存都将在方法区中分配(因此,即使不在后面的初始化阶段为类变量赋指定值,也不会报错,因为此时已经赋予了一个初始值),如public static int a = 123,在准备阶段过后,a的值为0,而不是123。
  2. 此阶段赋初值的变量是指非final修饰的static变量(final修饰的在编译阶段就已赋值)。
  3. 格式据类型初值
    在这里插入图片描述
解析(在某些情况下可能在初始化后才开始)

虚拟机将常量池中的符号引用替换为直接引用。

初始化(类或接口被使用前的最后一项工作)
  1. 此阶段给static变量赋予用户指定的值,并执行静态代码块,就是执行类构造器方法<clinit>()的过程。
    在这里插入图片描述
  2. 虚拟机会保证类的初始化在多线程环境中被正确的加锁,同步。
  3. 多个线程同时初始化一个类,只有一个线程去执行类的类构造器()方法,其他线程都会阻塞等待,直到活动线程执行()方法完毕,执行完毕后,其他阻塞的线程也不会再执行()方法,在一个类加载器下,一个类只初始化一次。
类一定初始化的场景(主动引用)
  1. 使用new实例化对象;读取或设置一个类的非final修饰的静态变量;调用一个类的静态方法的时候。
  2. 使用java.lang.reflect包中的方法对类进行反射调用的时候.
  3. 初始化一个类,发现其父类还没初始化过的时候,会对父类进行初始化。
  4. 启动虚拟机的时候,虚拟机会首先初始化包含main()方法的类。
类不初始化的场景(被动引用)
  1. 子类通过类名直接调用父类的静态字段。
    在这里插入图片描述
    在这里插入图片描述
  2. 通过数组定义引用类,不会触发此类的初始化。
    在这里插入图片描述
    在这里插入图片描述
  3. 调用final修饰的静态常量时,因为常量在编译期就会存入类的常量池中。
    在这里插入图片描述
    在这里插入图片描述

类加载器

类加载器的作用

通过类的全限定名加载此类的二进制流。

如何确定类在虚拟机中的唯一性?

通过加载它的类加载器和这个类本身确定此类的唯一性。

类加载器模型
结构图

在这里插入图片描述

类加载器分类
  • 引导类(启动类)加载器
  1. 描述:嵌入虚拟机内核中的加载器,由c++编写。
  2. 作用:负责加载的是JAVA_HOME/lib下的类库,无法被java程序直接调用。
  • 自定义类加载器
  1. 扩展类加载器
    描述:java语言实现,独立于虚拟机外部。
    作用:负责用于加载JAVA_HOME/lib/ext目录中的,或者被java.ext.dirs系统变量所指定的路径中所有类库。
    java代码调用演示:
    public class Test{
    	public static void main(String[] args){
    		System.out.println(System.getProperty("java.ext.dirs"));
    	}
    }
    

在这里插入图片描述

  1. 应用程序类(系统类)加载器
    描述:java语言实现,独立于虚拟机外部。
    作用:加载项目bin目录下的.class文件。
    java代码调用演示:
    public class Test{
    	public static void main(String[] args){
    		System.out.println(ClassLoader.getSystemClassLoader());
    	}
    }
    

在这里插入图片描述

  1. 自定义类加载器
    为什么要自定义类加载器:
    【代码加密】: 对代码加密防止反编译,此时就需要自定义的类加载器在加载时,先进行解密。
    【从非标准来源加载代码】: 如加载云端、数据库等非标准来源的字节码的时候,需要自定义类加载器。
    自定义加载器的实现:
    【MyClassLoader的实现】:

    public class MyClassLoader extends ClassLoader{
        //文件路径
        private String path;
        //文件类型
        private String type;
    
        public MyClassLoader(String path, String type) {
            this.path = path;
            this.type = type;
        }
    
        /**
         * 重写findClass方法
         * @param s 类的全限定名
         * @return
         * @throws ClassNotFoundException
         */
        @Override
        protected Class<?> findClass(String s) throws ClassNotFoundException {
            try {
                byte[] result = getClassFromCustomPath(path+s);
                if (result == null) {
                    throw new FileNotFoundException();
                } else {
                    return defineClass(s,result,0,result.length);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            return super.findClass(s);
        }
    
        private byte[] getClassFromCustomPath(String name) throws Exception {
            //将全限定名中的.替换为/
            name = name.replaceAll("\\.","/");
            name = name+type;
            FileInputStream fis = new FileInputStream(name);
            FileChannel fc = fis.getChannel();
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            WritableByteChannel wbc = Channels.newChannel(baos);
            ByteBuffer by = ByteBuffer.allocate(1024);
    
            while (true){
                int i = fc.read(by);
                if (i == 0 || i == -1)
                    break;
                by.flip();
                wbc.write(by);
                by.clear();
            }
            fis.close();
            return baos.toByteArray();
        }
    }
    

    【准备字节码文件】:
    在这里插入图片描述
    在此文件夹下,创建一个测试用的java文件,然后使用javac命令编译字节码文件即可。

    【测试】:

    public class Test{
    	public static void main(String[] args){
    		//设置文件路径(全限定名之前的路径)和文件类型
    		MyClassLoader classLoader = new MyClassLoader("D:/work/",".class");
    		try {
    			//使用全限定名
    			Class<?> clazz = Class.forName("com.classLoader.test.ClassLoaderTest",true,classLoader);
    			Object obj = clazz.newInstance();
    			System.out.println(obj);
    			System.out.println(obj.getClass().getClassLoader());
    		} catch (Exception e) {
    			e.printStackTrace();
    		}
    	}
    }
    

    【结果】:
    在这里插入图片描述
    结果显示使用了自定义的类加载器。

    【注意】:
    如果读取的字节码文件是idea里自动生成的(即系统类加载器可以读取到),使用的类加载器就变为系统类加载器了(双亲委派模型)。
    在这里插入图片描述
    自定义加载器实现字节码加密解密:
    【使用加密算法对上述测试的字节码文件加密】:

    public class EncryptUtils {
        public static void main(String[] args) throws FileNotFoundException{
            File file = new File("D:/work/com/classLoader/test/ClassLoaderTest.class");
            encrypt(file);
        }
        /**
         * 对指定文件进行编码(伪加密,Base64实际上是一个编码算法,并不是加密算法)
         *
         * @param file
         */
        public static void encrypt(File file) throws FileNotFoundException {
            InputStream input = null;
            OutputStream output = null;
            String originalPath = file.getPath();
            File targetFile = new File(originalPath + ".enc");
            try {
                input = new FileInputStream(file);
                output = new FileOutputStream(targetFile);
                transfer(input, output);
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (input != null) {
                    try {
                        input.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (output != null) {
                    try {
                        output.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
            //删除原文件
            file.delete();
            //将暂存文件,重命名为原来的文件名
            targetFile.renameTo(new File(originalPath));
        }
    
        /**
         * 将每个字节与11111111进行异或运算,模拟加密过程
         *
         * @param input
         * @param output
         * @throws IOException
         */
        public static void transfer(InputStream input, OutputStream output) throws IOException {
            int ch;
            while (-1 != (ch = input.read())) {
                //将每个字节和1111 1111 进行异或(模拟加密过程)
                ch = ch ^ 0xff;
                output.write(ch);
            }
        }
    }
    

    还是使用上述的自定义加载器进行加载,发现无法加载了。
    在这里插入图片描述
    【自定义解密的加载器】:

    public class DecryptClassLoader extends ClassLoader{
        //文件路径
        private String path;
        //文件类型
        private String type;
    
        public DecryptClassLoader(String path, String type) {
            this.path = path;
            this.type = type;
        }
    
        /**
         * 重写findClass方法
         * @param s 类的全限定名
         * @return
         * @throws ClassNotFoundException
         */
        @Override
        protected Class<?> findClass(String s) throws ClassNotFoundException {
            try {
                byte[] result = decodeFile(path+s);
                if (result == null) {
                    throw new FileNotFoundException();
                } else {
                    return defineClass(s,result,0,result.length);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            return super.findClass(s);
        }
    
        private byte[] decodeFile(String name) {
            byte[] data = new byte[0];
            InputStream input = null;
            ByteArrayOutputStream output = null;
            //将全限定名中的.替换为/
            name = name.replaceAll("\\.", "/");
            name = name + type;
            try {
                input = new FileInputStream(name);
                output = new ByteArrayOutputStream();
                //将读入的文件流进行解密(对之前异或的结果再次进行异或操作,即可得到原来的值),并写入缓存中
                EncryptUtils.transfer(input, output);
                data = output.toByteArray();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (input != null) {
                    try {
                        input.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (output != null) {
                    try {
                        output.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
            return data;
        }
    }
    

    使用解密类加载器加载

    public class Test{
    	public static void main(String[] args){
    		//设置文件路径(全限定名之前的路径)和文件类型
    		DecryptClassLoader classLoader = new DecryptClassLoader("D:/work/",".class");
    		try {
    			//使用全限定名
    			Class<?> clazz = Class.forName("com.classLoader.test.ClassLoaderTest",true,classLoader);
    			Object obj = clazz.newInstance();
    			System.out.println(obj);
    			System.out.println(obj.getClass().getClassLoader());
    		} catch (Exception e) {
    			e.printStackTrace();
    		}
    	}
    }
    

    结果显示加载成功!
    在这里插入图片描述

双亲委派模型
  • 机制
    一个类加载器收到加载类的请求时,都会把请求委托给父类加载器,直到父类无法完成加载请求(搜索范围中没有找到请求的类),子类加载器才会自己加载。
  • 过程
    在这里插入图片描述
  • 优势
  1. 避免类重复加载
  2. 保护java核心API,自定义的与核心API相同包名下的类无法加载。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值