ps: 大多是死知识,记来面试时回顾的,可以直接看《深入理解Java虚拟机》
相关知识
- Java类是什么时候去加载的?
- 首次使用new关键字实例化一个对象时;
- 访问某个静态成员变量或静态方法时;
- 使用反射调用类的方法时;
- 使用Class.forName()方法加载类时。
- Java类是如何被查找的?
首先查找自己的类加载器(自定义的加载器),如果没有找到,就向上查找父加载器(系统默认的加载器),直到找到为止,如果找不到就抛出ClassNotFoundException异常。这样做的好处是可以确保一个类只被加载一次,避免了重复加载的问题。
- 什么是双亲委托机制?
当ClassLoader去加载类时,会优先去父类加载器查找是否已经加载过这个类了,每一个类加载器都是如此,这样最终都会去启动器类加载器中查找,当父类无法完成这个类加载时,才会交给自身去加载。这样避免了同一个类重复加载,混用的问题。
对于任意一个类,都必须由加载它的类加载器和类本身来确定它在Java虚拟机中的唯一性,即即使是同一个类,由不同的类加载器加载,那他俩也是不同的
- ClassLoader有哪些?分别是做什么的?
- Bootstrap ClassLoader:它是用原生代码实现的最顶层的类加载器,负责加载Java的核心类库,如java.lang.、java.net.等;
- Extensions ClassLoader:它继承自Bootstrap ClassLoader,它负责加载Java的扩展类库,如sun.misc.*等;
- System ClassLoader:它是最常用的类加载器,负责加载应用程序classpath目录下的类库;
- User Defined ClassLoader:它是用户自定义的类加载器,可以用来加载自定义的类库。
- 类的加载过程?
加载:在类加载器中查找类的字节码,并把它们加载到内存中。
1.1. 通过一个类的全限定名来获取定义此类的二进制字节流。(可以从任意地方获取,zip,本地存储,网络,运行时生成等等)
1.2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
1.3. 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入
口。链接:将加载的字节码校验,合并到JVM的运行时数据区,并为之分配内存空间。
2.1. 文件格式验证:魔数,版本号,常量池等等
2.2. 元数据验证:是否有父类,父类是否被final修饰,是否实现了抽象类或接口的全部方法等等
2.3. 字节码验证:对Class文件中的Code属性进行校验分析,确保代码是安全的。
2.4. 符号引用验证:对常量池中的各种符号引用进行验证,即引用的类/字段等是否缺少或禁止访问准备:
3.1. 为静态变量在方法区中分配内存,赋默认值(int=0等),实际值在类初始化时分配在Java堆中
3.2. 当变量被修饰为常量时,那在编译阶段Javac会为变量生成ConstantValue属性,到准备阶段就会根据ConstantValue属性为变量赋值解析:将常量池内的符号引用替换为直接引用
初始化:为类变量分配内存并设置初始值,执行类构造器。
类的初始化顺序?
- 静态代码块和静态变量按代码中顺序初始化
- 成员变量和初始化块按代码中顺序初始化
- 执行父类构造函数,执行构造函数
自定义ClassLoader
- TestClass
public class TestClass {
public TestClass() {
System.out.println("test hhhhh");
}
public void printTest(){
System.out.println("aaaaaaaaaaaaaaaaaaaaaa");
}
}
- 使用javac编译成class文件
javac TestClass.java
- MyClassLoader
public class MyClassLoader extends ClassLoader{
private String path;
public MyClassLoader(String path) {
this.path = path;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
String classPath = path + name + ".class";
InputStream is = null;
ByteArrayOutputStream os = null;
try {
is = new FileInputStream(new File(classPath));
os = new ByteArrayOutputStream();
int temp = 0;
while ((temp = is.read()) != -1){
os.write(temp);
}
byte[] bytes = os.toByteArray();
return defineClass(name, bytes, 0, bytes.length);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (is != null) is.close();
if (os != null) os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return super.findClass(name);
}
}
- main
public class HelloWord {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
MyClassLoader myClassLoader = new MyClassLoader("D:\\project\\java\\demo1\\src\\main\\java\\");
try {
Class clazz = myClassLoader.findClass("TestClass");
System.out.println(clazz);
Object testClazz = clazz.getConstructor().newInstance();
System.out.println(testClazz);
Method method = clazz.getMethod("printTest",null);
method.invoke(testClazz,null);
} catch (Exception e) {
System.out.println(e.getLocalizedMessage());
e.printStackTrace();
}
}
}).start();
}
}
《深入理解Java虚拟机》
ChatGPT回答
自定义ClassLoader的实现 - 腾讯云开发者社区-腾讯云 (tencent.com)
Gradle 插件 + ASM 实战 - JVM 虚拟机加载 Class 原理 - 掘金 (juejin.cn)