class文件加载从java到C++的初步研究

在学习反射时,实现了自定义ClassLoader类加载器,我们都知道点击运行后.java代码文件在编译成.class字节码文件后,jvm自动加载.class文件,而在自定义类加载器后,不再是jvm自动加载,而是手动选择加载编译后的.class文件,此文是研究class文件从加载到运行中的具体过程.

本人在学习中参考的是B站up主 青空の霞光 的视频,讲的挺好,反射不明白的可以去看看,吐槽一句,人与人之间是不能比较的,在看到自定义classLoader类加载器时,up主一句"这是我高中研究出的一个玩意"把我惊到了.


测试类

ReflectTest.java是一个可以随意编写的测试类
这里是我自己写的
package test;

public class ReflectTest {
    public String name;
    public int age;
    private String id;
    public String password;
    public ReflectTest(){}
    public ReflectTest(String name){
        this.name = name;
    }
    private ReflectTest(int age){
        this.age = age;
    }
    public ReflectTest(String name,int age){
        this.name = name;
        this.age = age;
    }


    public void say() {
        System.out.println(name+"12345");
    }
    public void sayAge(){
        System.out.println("age:"+age);
    }
    public void saySome(String word){
        System.out.println("this is myword:"+word);
    }
    private void sayHello(String name){
        System.out.println("Hello "+name);
    }

    private void sayHellos(String... names){
        for (String item:names
             ) {
            System.out.println("Hello"+item);
        }
    }

    public void sayId(){
        System.out.println("id:"+id);
    }
}

第一步:执行命令javac .\src\Test\Student.java 得到Student.class字节码文件
执行命令


然后会多出一个.class文件,如下图
执行命令后的目录



我们把ReflectTest的源文件删除
删除后




自定义加载类

下面是我们加载ReflectTest的测试类,其中自定义类加载器放在Test12类中

package reflect;

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

//自定义加载器加载class文件,可运行时加载
public class Test12 {

    public static void main(String[] args) throws Exception {
        MyClassLoader myClassLoader = new MyClassLoader();

        FileInputStream stream = new FileInputStream("src\\Test\\ReflectTest.class");//填ReflectTest.class的地址
        byte[] bytes = new byte[stream.available()];
        stream.read(bytes);

        Class<?> clazz = myClassLoader.defineClass("test.ReflectTest",bytes);  //必须和我们定义的完整类名一致
        System.out.println(clazz.getName());  //加载成功
        stream.close();

        try {
            Object object = clazz.newInstance();   //用object接收ReflectTest的实例
            Method method = clazz.getMethod("saySome",String.class);
            method.invoke(object,"HuGuang");

            Field field = clazz.getField("age");  //获取属性
            field.set(object,23);       //设置age属性
            Method method1 = clazz.getMethod("sayAge");  //测试ReflectTest类的sayAge方法
            method1.invoke(object);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

	//自定义类加载器
    static class MyClassLoader extends ClassLoader{     //自定义class加载器,因为ClassLoader是抽象类不能被实例化
        public Class<?> defineClass(String className,byte[] classByte){
            return defineClass(className , classByte , 0 , classByte.length);
        }
    }
}

运行结果

可以看到在ReflcetTest.java文件被删的情况下,这个类仍然被加载运行,ReflectTest的方法测试成功,也就是说ReflectTest.class文件的确被我们的自定义类MyClassLoader加载.


自定义的加载类MyClassLoader中并没有太多内容,关键点是这句
return defineClass(className , classByte , 0 , classByte.length);

defineClass方法是继承父类ClassLoader的,ReflcetTest.class转化成字节流后最后也是传入defineClass方法中,被defineClass方法加载进JVM,所以我们要弄清楚ReflectTest.class如何被添加到JVM就是要弄清defineClass方法.




解析defineClass

下面是ClassLoader类中defineClass方法的源码
protected final Class<?> defineClass(String className, byte[] classRep, int offset, int length) throws ClassFormatError {
	return defineClass(className, classRep, offset, length, null);
}
可以看到参数又被交给另一个重载的defineClass方法处理,跳到这个方法看看
protected final Class<?> defineClass (
		final String className, 
		final byte[] classRep, 
		final int offset, 
		final int length, 
		ProtectionDomain protectionDomain) 
		throws java.lang.ClassFormatError 
{
	return defineClassInternal(className, classRep, offset, length, protectionDomain, false /* allowNullProtectionDomain */);
}
这个方法也是没啥用,参数又交给defineClassInternal方法处理,跳过去看看
final Class<?> defineClassInternal(
		final String className, 
		final byte[] classRep, 
		final int offset, 
		final int length, 
		ProtectionDomain protectionDomain,
		boolean allowNullProtectionDomain)
		throws java.lang.ClassFormatError 
{
	Certificate[] certs = null; 
	if (protectionDomain != null) {
		final CodeSource cs = protectionDomain.getCodeSource();
		if (cs != null) certs = cs.getCertificates();
	}
	if (className != null) {
		String packageName = checkClassName(className);
		if ((protectionDomain == null) && allowNullProtectionDomain) {
			/*
			 * Skip checkPackageSigners(), in this condition, the caller of this method is 
			 * java.lang.Access.defineClass() and invoked by trusted system code hence 
			 * there is no need to check its ProtectionDomain and associated code source certificates.
			 */
		} else {
			checkPackageSigners(packageName, className, certs);
		}
	}

	if (offset < 0 || length < 0 || offset > classRep.length || length > classRep.length - offset) {
		throw new ArrayIndexOutOfBoundsException();
	}

	if ((protectionDomain == null) && !allowNullProtectionDomain) {
		protectionDomain = getDefaultProtectionDomain();
	}
	
	final ProtectionDomain pd = protectionDomain;
	Class<?> answer = defineClassImpl(className, classRep, offset, length, pd);

	if (certs != null) {
		setSigners(answer, certs);
	}
	
	boolean isVerbose = isVerboseImpl();
	URL url = null;
	if (isVerbose) {
		if (pd != null) {
			CodeSource cs = pd.getCodeSource();
			if (cs != null) {
				url = cs.getLocation();
			}
		}
	}

	if (isVerbose) {
		String location = (url != null) ? url.toString() : "<unknown>"; //$NON-NLS-1$
		com.ibm.oti.vm.VM.dumpString("class load: " + answer.getName() + " from: " + location + "\n"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
	}
	return answer;
}

看似defineClassInternal方法代码挺多,其实并没有对参数做过多处理,主要是对参数的合法性判断,转化成合适的格式,最后返回一个Class对象answer,长叹一口气,终于返回值中不带调用的方法了,难道到这里就结束了?那ReflectTest.class是什么时候进入JVM的?迷茫


再来看看answer是怎么得到的,defineClassInternal方法中的这句:
Class<?> answer = defineClassImpl(className, classRep, offset, length, pd);

对比下原来自定义加载器MyClassLoader中地这句:
return defineClass(className , classByte , 0 , classByte.length);


发现了什么?!它竟然还在套娃,最初传入的传入的参数,几经轮转,几乎原封不动地又传入到方法defineClassImpl中,好吧,我们继续跳到defineClassImpl看看.

private final native Class<?> defineClassImpl(String className, byte [] classRep, int offset, int length, Object protectionDomain);




defineClassImpl方法还是一层套娃,但到这里就不能再往下点了,因为这里显示是native修饰的方法,是调用其它语言(C/C++)实现,在JDK库中是看不到的.


类加载器的C++实现


那从哪查看源码呢?在网上四处寻找方法,于是下载了openJDK15,里面有虚拟机实现的源码,虚拟机部分在目录的 jdk15-0dabbdfd97e6\src\hotspot 下,研究了一会,又找到了一个文件classLoader.cpp ,这个文件大概率就是加载器的最终实现,然后我打开了它,嘿嘿,你猜怎么着,我看不懂,因为C++还没学好,而且代码也太多,溜了溜了,如果有能力日后再研究.
在这里插入图片描述随便截取的一点点就已经头皮发麻了,这个研究下来估计得好一会了,自己有几斤几两还是清楚的,如果有什么问题欢迎评论吐槽和指点,如果有所研究希望也可以告诉我,感谢!

附上openJDK15下载地址
附上classLoader.cpp路径:jdk15-0dabbdfd97e6\src\hotspot\share\classfile\classLoader.cpp

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值