【问题标题】:LinkageError from loading in classes that have a parent-child relation加载具有父子关系的类时出现 LinkageError
【发布时间】:2018-03-12 02:33:11
【问题描述】:

我按照http://www.jacoco.org/jacoco/trunk/doc/examples/java/CoreTutorial.java 的 CoreTutorial 示例课程了解如何将 Jacoco 合并到项目中。

但是,我遇到了与所使用的 MemoryClassLoader 类相关的 java.lang.LinkageError 问题。

/**
 * A class loader that loads classes from in-memory data.
 */
public static class MemoryClassLoader extends ClassLoader {

    private final Map<String, byte[]> definitions = new HashMap<String, byte[]>();

    /**
     * Add a in-memory representation of a class.
     * 
     * @param name
     *            name of the class
     * @param bytes
     *            class definition
     */
    public void addDefinition(final String name, final byte[] bytes) {
        definitions.put(name, bytes);
    }

    @Override
    protected Class<?> loadClass(final String name, final boolean resolve)
            throws ClassNotFoundException {
        final byte[] bytes = definitions.get(name);
        if (bytes != null) {
            return defineClass(name, bytes, 0, bytes.length);
        }
        return super.loadClass(name, resolve);
    }

}

具体来说,我有两个类,MyClass 和 MySubClass,其中 MySubClass 扩展了 MyClass。我对这两个类进行了检测,以便我可以获得与每个类相关的覆盖率信息。我发现如果我首先检测 MySubClass,MemoryClassLoader 将使用 MySubClass 的检测字节调用 defineClass。但是,这样做时,作为父类的 MyClass 也被父类加载器加载。因此,当我下一次检测并加载 MyClass 时,我收到 java.lang.LinkageError,它声称已经有一个由 ClassLoader 加载的 MyClass 定义。这里的问题是加载的 MyClass 的初始版本没有被检测。

如果我以相反的顺序加载类,则不会出现此问题。

我在这里尝试了一些不同的东西:https://github.com/huangwaylon/randoop/blob/bloodhound/src/main/java/randoop/main/MemoryClassLoader.java 通过在递归调用 loadClass 时立即尝试检测父类。但是,由于我尚未弄清楚的原因,这并不完全有效。我观察到,如果我使用这种错误的方法,这两个类的覆盖率信息都不会改变。

总体而言,我的问题是,如何在父子关系中相互关联的类中检测和加载,以使行为不受顺序影响?我可以替换类的定义吗?另一个问题似乎是 super.load,它是 MemoryClassLoader 的父 ClassLoader,我无法控制。

【问题讨论】:

    标签: java jvm classloader jacoco instrumentation


    【解决方案1】:

    首先,您应该覆盖findClass 而不是loadClass。具有特定名称的类只能在加载器中定义一次的限制确实总是适用,并且您可能总是会遇到多次请求同一个类的情况(在任何不平凡的情况下)。

    ClassLoader 继承的loadClass 实现已经处理了这个问题并返回现有定义(如果有的话)。由于它还首先查询父加载器,因此您应该注意指定正确的父加载器,如果加载器应该定义名称也被其他类加载器使用的类。使用loadClass 的默认实现,您的findClass 方法可以保持简单:

    public static class MemoryClassLoader extends ClassLoader {
        private final Map<String, byte[]> definitions = new HashMap<>();
    
        public void addDefinition(final String name, final byte[] bytes) {
            definitions.put(name, bytes);
        }
    
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            final byte[] bytes = definitions.get(name);
            if (bytes != null) {
                return defineClass(name, bytes, 0, bytes.length);
            }
            return super.findClass(name);
        }
    }
    

    确保所有预期的类都被检测,可以通过两种方式完成。

    1. 按需检测它们,即让findClass 方法触发请求类的检测。

    2. 在第一次调用loadClass之前,检测所有预期类的字节码并将结果放入加载器。

      final MemoryClassLoader memoryClassLoader = new MemoryClassLoader();
      memoryClassLoader.addDefinition("MyClass", instrumentedMyClass);
      memoryClassLoader.addDefinition("MySubClass", instrumentedMySubClass);
      final Class<?> myClass = memoryClassLoader.loadClass("MyClass");
      

      在这里,addDefinition 调用的顺序无关紧要,无论您先调用 loadClass("MyClass") 还是 loadClass("MySubClass") 也无关紧要。这甚至适用于循环依赖。

    【讨论】:

    • 感谢您提供的信息和建议!在调用loadClass之前,我首先检测了所有预期类的字节码并将它们放入加载器。
    猜你喜欢
    • 2022-10-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-12-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多