深入理解JVM之加载自己的String类

编程入门 行业动态 更新时间:2024-10-28 21:25:49

前言

面试Java时,一个较为常见的问题是——“能否加载自己的String类?”

那到底能不能呢?本文就带你一探究竟。(想知道答案的小伙伴可以直接跳到文章末尾~)

预备知识

虚拟机类加载机制双亲委派模型定义自己的类加载器

以上内容是本文的预备知识,《深入理解Java虚拟机》这本书以及网上的很多文章都已经讲的很清楚了,还不清楚的读者可以自行查阅,本文就不再赘述。

加载自己的String类

首先,我们需要知道,双亲委派模型并不是一个强制性的约束条件,而只是Java设计者推荐给开发者的类加载器实现方式

双亲委派模型的实现位于ClassLoader类的loadClass方法中,源码如下:

protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException
{synchronized (getClassLoadingLock(name)) {// First, check if the class has already been loadedClass<?> 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 statsPerfCounter.getParentDelegationTime().addTime(t1 - t0);PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);PerfCounter.getFindClasses().increment();}}if (resolve) {resolveClass(c);}return c;}
}

通过源码可知,在loadClass的过程中,会首先尝试将要加载的类交给父类加载,若父类为空,则交给BootstrapClassLoader加载(这也就是双亲委派模型)。

所以,正常情况下,我们使用的String类会由BootstrapClassLoader加载,测试代码如下:

public class Test {public static void main(String[] args) {String s = "Hello world";System.out.println(s.getClass().getClassLoader()); // 输出 null,代表BootstrapClassLoader}
}

那么,我们想要加载自己的String类,就需要去破坏双亲委派原则!而双亲委派模型的实现位于loadClass方法中,所以,很显然,我们需要做的就是重写loadClass方法以绕过双亲委派原则!核心代码如下所示:

@Override
protected Class<?> loadClass(java.lang.String name, boolean resolve)throws ClassNotFoundException {if (name.equals("String")) { // 当要加载String类时,直接调用自己的findClass来加载,不进行双亲委派逻辑return findClass(name);} else { // 其他类的加载依然遵循双亲委派原则return super.loadClass(name, resolve);}
}

写出了上述代码,接下来就简单了,只需要再重写findClass方法读取自己的String类的字节码即可!代码如下所示:

@Override
protected Class<?> findClass(java.lang.String name) throws ClassNotFoundException {byte[] classData = getClassData(name);if (classData == null) {throw new ClassNotFoundException();} else {//直接生成class对象return defineClass(name, classData, 0, classData.length);}
}private byte[] getClassData(java.lang.String className) {// 读取类文件的字节java.lang.String path = classPath + className + ".class";try (InputStream inputStream = new FileInputStream(path);ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {int bufferSize = 4096;byte[] buffer = new byte[bufferSize];int read;// 读取类文件的字节码while ((read = inputStream.read(buffer)) != -1) {byteArrayOutputStream.write(buffer, 0, read);}return byteArrayOutputStream.toByteArray();} catch (IOException e) {e.printStackTrace();}return null;
}

到此,我们的加载逻辑就已经完成啦,接下来创建我们自己的String类以供测试:

public class String {public void sayHello() {System.out.println("Hello world!");}
}

将以上代码编译成String.class文件后放在代码中classPath指定的路径下即可。

测试代码如下:

public static void main(java.lang.String[] args) throws Exception {CustomStringClassTest customStringClassTest = new CustomStringClassTest("E:/Java_Code/");customStringClassTest.loadClass("String");String myString = new String();myString.sayHello(); // Hello world!
}

完整代码

CustomStringClassTest.java

import java.io.*;public class CustomStringClassTest extends ClassLoader {private java.lang.String classPath;public CustomStringClassTest(java.lang.String classPath) {this.classPath = classPath;}@Overrideprotected Class<?> loadClass(java.lang.String name, boolean resolve)throws ClassNotFoundException {if (name.equals("String")) { // 当要加载String类时,直接调用自己的findClass来加载,不进行双亲委派逻辑return findClass(name);} else { // 其他类的加载依然遵循双亲委派原则return super.loadClass(name, resolve);}}@Overrideprotected Class<?> findClass(java.lang.String name) throws ClassNotFoundException {byte[] classData = getClassData(name);if (classData == null) {throw new ClassNotFoundException();} else {//直接生成class对象return defineClass(name, classData, 0, classData.length);}}private byte[] getClassData(java.lang.String className) {// 读取类文件的字节java.lang.String path = classPath + className + ".class";try (InputStream inputStream = new FileInputStream(path);ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {int bufferSize = 4096;byte[] buffer = new byte[bufferSize];int read;// 读取类文件的字节码while ((read = inputStream.read(buffer)) != -1) {byteArrayOutputStream.write(buffer, 0, read);}return byteArrayOutputStream.toByteArray();} catch (IOException e) {e.printStackTrace();}return null;}public static void main(java.lang.String[] args) throws Exception {CustomStringClassTest customStringClassTest = new CustomStringClassTest("E:/Java_Code/");customStringClassTest.loadClass("String");String myString = new String();myString.sayHello(); // Hello world!}
}

String.java

public class String {public void sayHello() {System.out.println("Hello world!");}
}

几点说明

我们可以加载自己的String类,但无法加载自己的java.lang.String类。

虽然我们可以破坏双亲委派模型,但JVM规定了以java.开头的类必须由BootstrapClassLoader加载!所以我们无法加载自己的java.lang.String类

代码实现中为了区分自定义的String和java.lang.String,所有用到java.lang.String的地方均采用全限定名表示

正常情况下,我们编写自己的类加载器时,只需继承ClassLoader类并重写findClass方法即可(这也是推荐的做法),本文只是为了演示特殊情况才重写了loadClass方法和findClass方法。

更多推荐

自己的,加载,JVM,String

本文发布于:2023-05-27 16:18:21,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/299868.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:自己的   加载   JVM   String

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!