类加载和初始化

本文介绍了Java类的加载过程,包括加载器的作用、类的加载、连接、初始化的步骤,详细阐述了类加载器的层次结构,如Bootstrap、Extension和System ClassLoader,并解释了类的验证、准备和解析阶段。此外,还探讨了类的初始化机制和加载机制,特别是父类委托和缓存机制。最后,通过自定义类加载器的示例,展示了如何实现特定的加载行为。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

简介

调用java命令运行某个程序时,该命令会启动一个java 虚拟机进程。程序使用某个类时,如果该类没有被加载到内存中,则系统会通过加载、连接、初始化3个步骤对该类进行初始化。类加载指的是将类的class 文件读入内存,并创建一个java.lang.Class 对象,程序使用任何类时系统都会建立一个java.lang.Class对象。

加载器

类加载由类加载器完成,类加载器由JVM 提供,JVM 提供的类加载器通常称为系统类加载器,开发者可以继承ClassLoader 创建自己的类加载器。
JVM启动时会形成3个类加载器组成的加载器层次结构:
1.Bootstrap ClassLoader: 根类加载器
2.Extension ClassLoader: 扩展类加载器
3.System ClassLoader:系统类加载器

Bootstrap ClassLoader 是引导加载器,负责加载java 核心类,使用C++ 实现的,它不是java.lang.ClassLoader 的子类;

Extension ClassLoader 称为扩展类加载器,负责加载JRE 的扩展目录(JAVA_HOME/jre/lib/ext) 中的jar 包类;

System ClassLoader 称为系统类加载器,负责在JVM 启动时记载来自java 命令的 -classpath 选项,或CLASSPATH 环境变量指定加载的jar 包和类路径。程序可通过ClassLoader 的静态方法getSystemClassLoader() 获取系统类加载器。

系统为所有加载的类生成一个java.lang.Class 实例,一旦一个类被加载进JVM,同一个类就不会被再次加载。JVM 中使用类加载器和全限定类名作为唯一标识。

类的连接

类被加载生成对于的Class 对象后,会进入连接阶段:
1.验证: 验证阶段用于校验被加载的类是否有正确的内部结构
2.准备: 为类变量分配内存,设置默认初始值
3.解析: 把类的2进制数据中的符号引用替换成直接引用

类的初始化

JVM 初始化一个类需要经历如下步骤:
1.如果这个类没有被加载、连接,则程序先加载、连接该类;
2.如果该类的直接父类没有被初始化,则先初始化直接父类;
3.如果该类中有初始化语句则先执行初始化语句

JVM 初始化父类初始化也遵循1~3 的流程,即先初始化继承结构最顶层,再依次初始化间接父类、直接父类、本类。

加载机制

一、父类委托:
先让parent (父) 类加载器加载该class ,父类加载器无法加载该类时才使用子类加载器加载。

二、缓存机制:
缓存机制保障所有加载过的Class 都被缓存,程序使用某个类时,先从缓存区查找是否缓存该类,没被缓存则加载该类。
获取类加载器层次结构:

package com.sxt.test;
 
public class Test {
	 
	public static void main(String[] args) {
		
		ClassLoader loader = ClassLoader.getSystemClassLoader();
		System.out.println(loader);
		System.out.println(loader.getParent());
		System.out.println(loader.getParent().getParent());
	}
}

效果如下:
在这里插入图片描述

JVM 的根类加载器不是java实现的,而且在程序中无需访问根类加载器,故访问扩展类加载器的父类加载器是为null。

自定义类加载器

java.lang.ClassLoader 核心分析

/**
     * Loads the class with the specified <a href="#name">binary name</a>.  The
     * default implementation of this method searches for classes in the
     * following order:
     *
     * <ol>
     *
     *   <li><p> Invoke {@link #findLoadedClass(String)} to check if the class
     *   has already been loaded.  </p></li>
     *
     *   <li><p> Invoke the {@link #loadClass(String) <tt>loadClass</tt>} method
     *   on the parent class loader.  If the parent is <tt>null</tt> the class
     *   loader built-in to the virtual machine is used, instead.  </p></li>
     *
     *   <li><p> Invoke the {@link #findClass(String)} method to find the
     *   class.  </p></li>
     *
     * </ol>
     *
     * <p> If the class was found using the above steps, and the
     * <tt>resolve</tt> flag is true, this method will then invoke the {@link
     * #resolveClass(Class)} method on the resulting <tt>Class</tt> object.
     *
     * <p> Subclasses of <tt>ClassLoader</tt> are encouraged to override {@link
     * #findClass(String)}, rather than this method.  </p>
     *
     * <p> Unless overridden, this method synchronizes on the result of
     * {@link #getClassLoadingLock <tt>getClassLoadingLock</tt>} method
     * during the entire class loading process.
     *
     * @param  name
     *         The <a href="#name">binary name</a> of the class
     *
     * @param  resolve
     *         If <tt>true</tt> then resolve the class
     *
     * @return  The resulting <tt>Class</tt> object
     *
     * @throws  ClassNotFoundException
     *          If the class could not be found
     */
    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

loadClass(String name, boolean resolve) 方法被调用后将会调用
1.findLoadedClass(String) 从缓存中查找是否缓存该类,有则返回该类
2.loadClass(String) 调用父类加载器加载该类,加载成功则返回该类
3.findClass(String) 调用子类加载器查找该类,成功则返回

根据jdk 源码分析可知,在自定义加载器时 推荐重写findClass(String) 方法实现加载器自定义。

如下为简单类加载器的实现:

package com.sxt.test;

import java.nio.file.Files;
import java.nio.file.Paths;
import java.io.*;
import java.lang.reflect.*;


public class Test {
	 
	public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
		
		 Class clz = new MyClassLoader().loadClass("/Users/vincent/Desktop/test/Test");
		 
		 //通过反射获取该Test类的main 方法
		 Method tmain = clz.getMethod("main", String[].class);
		 tmain.invoke(null,new Object[] {new String[] {"自定义类加载器"}});
	}
}

 class MyClassLoader extends ClassLoader {
	 
	 
	 //从输入流中加载该类
	 //使用方法:	Class clz = new MyClassLoader().loadClass("/Users/vincent/Desktop/test/Test");
	 //路径/Users/vincent/Desktop/test 下有Test.class 文件,且该文件是没有包名
	 @Override
	 public Class<?> findClass(String name){
		 ByteArrayOutputStream bos = new ByteArrayOutputStream();
		 try {
			InputStream is = Files.newInputStream(Paths.get(name+".class"));
			int len = 0;
			byte[] buf = new byte[1024];
			while ((len=is.read(buf)) > 0) {
				bos.write(buf,0, len);
			}
			is.close();
			byte[] b = bos.toByteArray();
			
			String[] parts = name.split("/");
			Class<?> clz = defineClass(parts[parts.length-1],b, 0, b.length);
			return clz;
			
		} catch (IOException e) {
			e.printStackTrace();
		}
		 
		 return null;
	 }
 }

/Users/vincent/Desktop/test 路径下的Test.java 文件实现如下:

import java.io.*;
import java.util.*;

public class Test{
	volatile boolean flag = true;
    public static void main(String[] args){
        System.out.println("Test class");
        System.out.println(Arrays.toString(args));
    }
}

javac Test.java 命令编译该文件生成Test.class 文件

运行com.sxt.test.Test 文件效果如下:
在这里插入图片描述
自定义类加载器成功。

总结

自定义类加载器可以实现如下功能
1.对编译后的class 文件加密,防止反编译 *.class 文件
2.根据需求动态加载类

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值