在具有父子关系的类中加载LinkageError

时间:2018-03-12 02:33:12

标签: java jvm classloader jacoco instrumentation

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

但是,我面临的java.lang.LinkageError问题与使用的MemoryClassLoader类有关。

/**
 * 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,我无法控制。

1 个答案:

答案 0 :(得分:1)

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

loadClass继承的ClassLoader实现已经处理了这个并返回现有的定义(如果有的话)。因为它还首先查询父加载器,所以如果加载器应该定义名称也被其他类加载器使用的类,则应该注意指定正确的父加载器。使用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")也无关紧要。这甚至适用于循环依赖。