JVM基础(2) - java类生命周期、类加载原理与自定义类加载器

JVM基础2 - java类加载机制

1. java类的加载过程

JVM 中类的装载是由类加载器(ClassLoader) 实现的,类加载器负责在运行时查找和装入类文件中的类。

当Java程序需要使用某个类时,JVM会确保这个类已经被加载、连接(验证、准备和解析)和初始化。

  1. 加载:指把.class文件中读入到内存中,然后产生与所加载类对应的Class对象。加载完成后,Class对象还不完整,所以此时的类还不可用。
  2. 连接:包括验证、准备(为静态变量分配内存并设置默认的初始值)和解析(将符号引用替换为直接引用)三个步骤。
  3. 类初始化:如果类存在父类且这个类没有被初始化,那么就先初始化父类;如果类中存在初始化语句,就依次执行这些初始化语句。

符号引用和直接引用: 当java类编译成class文件时,java类并不知道其引用类的实际内存地址,因此只能使用符号引用来代替。类装载器装载类时,可以通过虚拟机获取实际的内存地址,然后将符号引用替换为直接引用。

当程序执行结束,或者程序遇到异常、操作系统导致JVM进程终止、执行了 System.exit() 都会结束类的生命。
 

2. 类加载器

2.1. 类加载器

类加载器包括:根加载器(BootStrap)、扩展加载器(Extension)、系统加载器(System)和用户自定义类加载器(java.lang.ClassLoader的子类)。

在这里插入图片描述

类加载器:

  • Bootstrap:一般用本地代码实现,负责加载JVM基础核心类库(rt.jar);
  • Extension:从java.ext.dirs系统属性所指定的目录中加载类库,它的父加载器是Bootstrap;
  • System:应用类加载器,其父类是Extension。它从环境变量classpath或者系统属性java.class.path所指定的目录中记载类,是用户自定义加载器的默认父加载器。

 

2.2. 双亲委派机制

如果一个类收到了类加载的请求,它首先会将请求发送给父类去加载,以此向上,直到所有的类加载请求传递到顶层的启动类加载器中。
 
只有当父类加载器在自己的搜索范围找不到所需的类时,子加载器才会尝试自己去加载。

 
双亲委派过程

  • 当AppClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。
  • 当ExtClassLoader加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader去完成。
  • 如果BootStrapClassLoader加载失败(例如在$JAVA_HOME/jre/lib里未查找到该class),会使用ExtClassLoader来尝试加载;
  • 若ExtClassLoader也加载失败,则会使用AppClassLoader来加载,如果AppClassLoader也加载失败,则会报出异常ClassNotFoundException

 
双亲委派源码

public Class<?> loadClass(String name)throws ClassNotFoundException {
            return loadClass(name, false);
    }
    protected synchronized Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException {
            // 首先判断该类型是否已经被加载
            Class c = findLoadedClass(name);
            if (c == null) {
                //如果没有被加载,就委托给父类加载或者委派给启动类加载器加载
                try {
                    if (parent != null) {
                         //如果存在父类加载器,就委派给父类加载器加载
                        c = parent.loadClass(name, false);
                    } else {
                    //如果不存在父类加载器,就检查是否是由启动类加载器加载的类,通过调用本地方法native Class findBootstrapClass(String name)
                        c = findBootstrapClass0(name);
                    }
                } catch (ClassNotFoundException e) {
                 // 如果父类加载器和启动类加载器都不能完成加载任务,才调用自身的加载功能
                    c = findClass(name);
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }

 
双亲委派优势

  • 系统类防止内存中出现多份同样的字节码
  • 保证Java程序安全稳定运行

 

3. 自定义类加载器

场景:

当通过网络来传输 Java 类的字节码,需为保证安全性,这些字节码经过了加密处理,这时系统类加载器就无法对其进行加载,这样则需要自定义类加载器来实现。
 
自定义类加载器一般都是继承自 ClassLoader 类,从上面对 loadClass 方法来分析来看,我们只需要重写 findClass
方法即可。

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-compress</artifactId>
            <version>1.21</version>
        </dependency>
 package com.gao.jvm.classloader;

import org.apache.commons.compress.utils.IOUtils;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.WritableByteChannel;

public class MyClassLoader extends ClassLoader {

    public static void main(String[] args) throws Exception {
        MyClassLoader mcl = new MyClassLoader();
        Class<?> clazz = Class.forName("com.gao.algorithm.integer.IntegerOne", true, mcl);
        Object obj = clazz.newInstance();

        System.out.println(obj.getClass().getClassLoader());
    }

    public MyClassLoader() {

    }


    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        File file = new File("/Users/lianggao/Downloads/com/gao/algorithm/integer/IntegerOne.class");
        try {
            byte[] bytes = getClassBytes(file);
            //将二进制流字节组成的文件转换为一个java.lang.Class
            return this.defineClass(name, bytes, 0, bytes.length);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return super.findClass(name);
    }

    // 这里要读入.class的字节,因此要使用字节流
    private byte[] getClassBytes(File file) throws Exception {
        try (FileInputStream fis = new FileInputStream(file);
             ByteArrayOutputStream baos = new ByteArrayOutputStream();) {
             //我们这里可以读到之后,可以对文件进行解密
             。。。
            IOUtils.copy(fis, baos);
            return baos.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return new byte[0];
    }
}

控制台输出
//com.gao.jvm.classloader.MyClassLoader@610455d6

需要注意 :

  1. 最好不要重写loadClass方法,因为这样容易破坏双亲委托模式。
  2. 这类Test 类本身可以被 AppClassLoader 类加载,因此我们不能把com/gao/algorithm/integer/IntegerOne.class 放在类路径下。否则,由于双亲委托机制的存在,会直接导致该类由 AppClassLoader 加载,而不会通过我们自定义类加载器来加载。

 
 
https://pdai.tech/md/java/jvm/java-jvm-classload.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

roman_日积跬步-终至千里

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值