【问题标题】:Resolving policy of Java constant poolJava常量池的解析策略
【发布时间】:2021-02-26 10:58:43
【问题描述】:

Java SE Specifications 和“Inside Java Virtual Machine”等许多地方,他们都表示,当常量池条目的解析需要加载类型时,虚拟机使用加载引用类型的相同类加载器加载引用的类型。

但是,我发现这条规则并不总是成立:

package com.alaneuler.test;

import java.io.InputStream;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

public class LinkageMain {
    static class MyClassLoader extends ClassLoader {
        private Set<String> selfFirstClasses;
        private String name;

        public MyClassLoader(String name, ClassLoader parent, String... selfFirstNames) {
            super(parent);
            this.name = name;
            selfFirstClasses = new HashSet<>(Arrays.asList(selfFirstNames));
        }

        @Override
        public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
            if (selfFirstClasses.contains(name)) {
                String filename = name.substring(name.lastIndexOf(".") + 1) + ".class";
                try (InputStream is = getClass().getResourceAsStream(filename)) {
                    byte[] buf = new byte[is.available()];
                    int len = is.read(buf);
                    System.out.println(this.name + ": loading " + name);
                    return defineClass(name, buf, 0, len);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

            if (!name.startsWith("java.")) {
                System.out.println(this.name + ": super.loadClass(" + name + ")");
            }
            return super.loadClass(name, resolve);
        }

        @Override
        public String toString() {
            return name;
        }
    }

    public static class User {}
    public static class Login {
        public void login(User u) {
            System.out.println("--- login called with user loaded by " + u.getClass().getClassLoader()  + " ---");
        }
    }
    public static class Main {
        public static void process() {
            User u = new User();
            new Login().login(u);
        }
    }

    public static void main(String[] args) throws Exception {
        MyClassLoader baseCL = new MyClassLoader("Base", LinkageMain.class.getClassLoader(),
                "com.alaneuler.test.LinkageMain$User",
                "com.alaneuler.test.LinkageMain$Login");
        MyClassLoader specialCL = new MyClassLoader("specialCL", baseCL,
                "com.alaneuler.test.LinkageMain$User",
                "com.alaneuler.test.LinkageMain$Main");

        specialCL.loadClass("com.alaneuler.test.LinkageMain$Main").getMethod("process").invoke(null);
    }
}

我定义了一个自定义的ClassLoader,它在其构造函数中接收一个应该由它自己定义的类名列表。基于此,baseCL 被配置为加载类 LoginUserspecialCl 以加载 UserMain(我知道这种设置破坏了类加载器委托模型)。

运行这个例子会产生:

specialCL: loading com.alaneuler.test.LinkageMain$Main
specialCL: loading com.alaneuler.test.LinkageMain$User
specialCL: super.loadClass(com.alaneuler.test.LinkageMain$Login)
Base: loading com.alaneuler.test.LinkageMain$Login
--- login called with user loaded by specialCL ---

它说login方法被specialCl加载的对象u调用不是baseCL。这种现象与一开始所说的概念相反,真是奇怪,我不知道哪里出了问题。

此外,使用baseCl 加载类User 总是失败并显示LinkageErrorloader constraint violation: loader (instance of com/alaneuler/test/LinkageMain$MyClassLoader) previously initiated loading for a different type with name "com/alaneuler/test/LinkageMain$User"

我的问题是:

  1. login 的调用不应该立即引发错误吗?
  2. 为什么以下使用baseCL 加载User 失败并显示LinkageError

任何帮助将不胜感激!

感谢 Frank Kieviet 的 blog,因为这个例子是根据他对 LinkageError 的研究构建的。

【问题讨论】:

  • 你有什么问题?
  • 为什么login方法是用specialCl而不是baseCL加载的对象u调用的,我认为这个调用应该引发错误
  • 文档声明“要解析未解析的符号引用...” - MainspecialCL 加载;它使用 User,所以User 也由specialCL 加载(如文档所述);不需要baseCL 加载User(只是我的猜测){关于“这种现象与一开始就陈述的概念相反,这真的很奇怪,我不知道哪里出了问题”}

标签: java classloader linkageerror


【解决方案1】:

问题 1:“login 的调用不应该立即引发错误吗?”

不,因为只有在 anewarraycheckcastgetfieldgetstaticinstanceofinvokedynamicinvokeinterfaceinvokespecialinvokestaticinvokevirtualldcldc_wmultianewarraynew putfield,然后执行putstatic

  • 在调用方 (Main.process()) 解析完成并成功
  • 在被调用方(Login.login())没有进行解析,因为您没有调用任何User 类方法或访问该类的任何字段。
  • 方法调用 u.getClass() 不计算在内,因为它解析为 Object.getClass() 方法

问题 2:“为什么以下使用 baseCL 加载 User 失败并显示 LinkageError?

当 JVM 加载 Login 类时,它已经有一个名为 User 的类可用,由 specialCL 加载。它将对 (specialCL, "User") 存储在 Login 类的加载器约束中。

当 JVM 现在需要解析 Login 类中的 User 类时,它会调用 Login 类加载器来加载 User 类。但是,此加载会返回另一个 User 类实例(baseCL,“User”),然后是记录在加载程序约束中的实例(specialCL,“User”)。

有关加载约束的说明,请参阅Java SE Specification - Loading Constraints

【讨论】:

    猜你喜欢
    • 2015-11-22
    • 1970-01-01
    • 1970-01-01
    • 2011-06-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多