In-depth exploration of ClassLoader in Java Virtual Machine (Extras)

This article is 深入Java虚拟机(四)based on some ideas, from another aspect to study the problem of class loading.

Interface-oriented programming

An interface for obtaining the balance of the wallet is defined in the project engineering catalog, and as an ordinary person, it will return to 50 oceans for you to eat, drink and play.

public interface Pocket {
    
    
    int getBalance();
}
public class NormalUser implements Pocket{
    
    
    @Override
    public int getBalance() {
    
    
        return 50;
    }
}

I compiled another version on the desktop NormalUserand added a quota to my wallet.

public class NormalUser implements Pocket{
    
    
    @Override
    public int getBalance() {
    
    
        return 50000;
    }
}

Now I want to improve the quality of life, so I did the following:

Customize a class loader in the project:

public class LocalClassLoader extends ClassLoader {
    
    
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
    
    
        String path = "/Users/lijie/Desktop/NormalUser.class";
        try {
    
    
            FileInputStream ins = new FileInputStream(path);
            int size = ins.available();
            byte[] clazzBytes = new byte[size];
            if (ins.read(clazzBytes) > 0) {
    
    
                return defineClass("hua.lee.classloader.NormalUser", clazzBytes, 0, size);
            } else {
    
    
                throw new ClassNotFoundException();
            }
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }
        throw new ClassNotFoundException();
    }
}

LocalClassLoaderThe writing is relatively rigid, but the idea of ​​making money is there. . . .

LocalClassLoaderRewrite the findClassmethod is not overridden loadClass, so 双亲委托机制also effective. For the types that cannot be found in the findClasssystem, I will come here, and in the method, I will NormalUserload the desktop .

Let's look at the test code:

public class ClassLoaderTest {
    
    
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
    
    
        Class<?> clazz = Class.forName("hua.lee.classloader.NormalUser");
        System.out.println("系统自带 ClassLoader=" + clazz.getClassLoader());
        Pocket pocket = (Pocket) clazz.newInstance();
        System.out.println("系统自带:"+pocket.getBalance());

        //没有重写 loadClass,维持双亲委派机制
        LocalClassLoader lcl = new LocalClassLoader();
        //我们加个后缀名,这样双亲委托查找失败了,就调用LocalClassLoader.findClass 方法加载
        //其实 findClass 方法还是加载 NormalUser类,只是换了个位置和方式:
        //defineClass("hua.lee.classloader.NormalUser", clazzBytes, 0, size);
        Class<?> clazzOut02 = lcl.loadClass("hua.lee.classloader.NormalUser_Temp");

        //双亲委托机制先检查当前类加载器中有没有这个类的 Class 实例,没有的话再搜寻父类,
        //查找到 hua.lee.classloader.NormalUser 的 Class 实例,就返回该对象
        Class<?> clazzOut01 = lcl.loadClass("hua.lee.classloader.NormalUser");


        System.out.println("clazzOut01 自定义 ClassLoader=" + clazzOut01.getClassLoader());
        pocket = (Pocket) clazzOut01.newInstance();
        System.out.println("clazzOut01 自定义加载器获取余额:"+pocket.getBalance());

        System.out.println("clazzOut02 自定义 ClassLoader=" + clazzOut02.getClassLoader());
        pocket = (Pocket) clazzOut02.newInstance();
        System.out.println("clazzOut02 自定义加载器获取余额:"+pocket.getBalance());

        System.out.println(clazz==clazzOut01);
        System.out.println(clazz==clazzOut02);
        System.out.println(clazzOut01==clazzOut02);
    }

The console output is like this:

系统自带 ClassLoader=sun.misc.Launcher$AppClassLoader@18b4aac2
系统自带:50
clazzOut01 自定义 ClassLoader=hua.lee.classloader.LocalClassLoader@60e53b93
clazzOut01 自定义加载器获取余额:50000
clazzOut02 自定义 ClassLoader=hua.lee.classloader.LocalClassLoader@60e53b93
clazzOut02 自定义加载器获取余额:50000
false   //clazz==clazzOut01
false   //clazz==clazzOut02
true    //clazzOut01==clazzOut02

I feel that I have found a way to get rich. I 加载约束didn't see it in the virtual machine .

Abstract-oriented programming

The above road to wealth seems to be very smooth, so let's try the following way:

public abstract class Pocket {
    
    
    abstract int getBalance();
}
public class NormalUser extends Pocket{
    
    
    @Override
    public int getBalance() {
    
    
        return 50;
    }
}

Similarly, I compiled a rich version on the desktop:

public class NormalUser extends Pocket{
    
    
    @Override
    public int getBalance() {
    
    
        return 50000;
    }
}

LocalClassLoaderAnd the test code has not changed, but the printed result is indeed like this:

系统自带 ClassLoader=sun.misc.Launcher$AppClassLoader@18b4aac2
系统自带:50
clazzOut01 自定义 ClassLoader=hua.lee.classloader.LocalClassLoader@60e53b93
Exception in thread "main" java.lang.AbstractMethodError
	at hua.lee.classloader.ClassLoaderTest.main(ClassLoaderTest.java:25)

Is there any difference between the virtual machine's analysis 抽象类实现and 接口实现the time ?

Immature speculation

The first 面向接口mode should avoid loading constraints, or the virtual machine is designed to support interfaces like this.

Now that it is prompted AbstractMethodError, I will change the test code, no longer use abstract classes Pocket, directly point to NormalUser:

public class ClassLoaderTest {
    
    
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
    
    

        Class<?> clazz = Class.forName("hua.lee.classloader.NormalUser");
        System.out.println("系统自带 ClassLoader=" + clazz.getClassLoader());
                    //请注意此处⬇️
        NormalUser pocket = (NormalUser) clazz.newInstance();
                    //请注意此处⬆️
        System.out.println("系统自带:"+pocket.getBalance());

        //没有重写 loadClass,维持双亲委派机制
        LocalClassLoader lcl = new LocalClassLoader();
        //我们加个后缀名,这样双亲委托查找失败了,就调用LocalClassLoader.findClass 方法加载
        //其实 findClass 方法还是加载 NormalUser类,只是换了个位置和方式:
        //defineClass("hua.lee.classloader.NormalUser", clazzBytes, 0, size);
        Class<?> clazzOut02 = lcl.loadClass("hua.lee.classloader.NormalUser_Temp");

        //双亲委托机制会自动搜寻父类,
        //查找到 hua.lee.classloader.NormalUser 的 Class 实例,就返回该对象
        Class<?> clazzOut01 = lcl.loadClass("hua.lee.classloader.NormalUser");


        System.out.println("clazzOut01 自定义 ClassLoader=" + clazzOut01.getClassLoader());
        pocket = (NormalUser) clazzOut01.newInstance();
        System.out.println("clazzOut01 自定义加载器获取余额:"+pocket.getBalance());

        System.out.println("clazzOut02 自定义 ClassLoader=" + clazzOut02.getClassLoader());
        pocket = (NormalUser) clazzOut02.newInstance();
        System.out.println("clazzOut02 自定义加载器获取余额:"+pocket.getBalance());

        System.out.println(clazz==clazzOut01);
        System.out.println(clazz==clazzOut02);
        System.out.println(clazzOut01==clazzOut02);
    }
}

The surprise came, and the output became as follows:

系统自带 ClassLoader=sun.misc.Launcher$AppClassLoader@18b4aac2
系统自带:50
clazzOut01 自定义 ClassLoader=hua.lee.classloader.LocalClassLoader@60e53b93
Exception in thread "main" java.lang.ClassCastException: hua.lee.classloader.NormalUser cannot be cast to hua.lee.classloader.NormalUser
	at hua.lee.classloader.ClassLoaderTest.main(ClassLoaderTest.java:24)

This anomaly indicates a problem that we custom loaded in is hua.lee.classloader.NormalUsernot what the virtual machine wants hua.lee.classloader.NormalUser.

The first question: How NormalUserdoes the virtual machine know that these two are different?

It 定义类装载器has something to do with, if the type with the same fully qualified name is not the same 定义类装载器, the virtual machine will consider it as not the same type. (According to the loading constraints, an error should be reported)

What is the definition of class loader? It is the ClassLoader that actually loads the class file

The second question: Why can the interface run like this?

That's because 面向接口实现the version does not explicitly name the type NormalUser. Type conversions are Pocketperformed according to the type. The virtual machine automatically finds the implementation of the interface type. As long as it is Pocketrealized.

If you change it pocket = (Pocket) clazzOut01.newInstance();to pocket = (NormalUser) clazzOut01.newInstance();, 面向接口the version will also be reported at this time ClassCastException.

For the virtual machine, in the implementation class of the interface, it does not care about the source of your implementation class (it doesn't matter which loader loads it), just conform to the interface definition.

But when you specify type conversion, it has to do type checking.

The third question: Why can't abstract classes work?

First YY java.lang.AbstractMethodError:

When it Pocketis an abstract class, according to the definition of the abstract class, there must be a specific implementation class to execute.
For pocket = (Pocket) clazzOut01.newInstance();this sentence, Pocketthe forced type conversion is successful (because the parsing Pokettype needs to execute 双亲委派logic, after parsing, it is found that everyone is the same Poket), but when looking for the implementation class, I found that the clazzOut01.newInstance()corresponding type of this object NormalUseris not what I want, virtual The machine thinks that there is no corresponding implementation, so when the execution arrives pocket.getBalance(), the virtual machine thinks that the program should be purely Pocketan abstract method of execution , so it reports an error java.lang.AbstractMethodError.

If the above is understood, then it java.lang.ClassCastExceptionis easy to understand:

For pocket = (NormalUser) clazzOut01.newInstance();this sentence, the virtual machine clazzOut01.newInstance()forced the object to be converted to NormalUser, and here is the answer to the first question: clazzOut01the representative NormalUserand the forced conversion is NormalUsernot the same type, the virtual machine directly reports an error

Conclusion

Why should there be a conclusion to this?

Because 不成熟的推测it is really the result of my own YY, I can't guarantee that it is true and effective. If there are mistakes, please feel free to enlighten me.

The next article will be continued Java虚拟机(五)垃圾收集(I always feel that I have to write about garbage classification, the kind that pigs can’t eat)

Guess you like

Origin blog.csdn.net/lijie2664989/article/details/107070141
Recommended