为什么我的URLClassLoader不能从另一个线程工作?

时间:2016-03-14 18:13:26

标签: java multithreading jar classloader

我有一个java项目,它使用URLClassLoader在运行时从另一个jar文件加载类,就像一个插件系统。 让我给你一个问题的简化版本:让我们说在我的main方法中我将创建ClassLoader,将它作为父类加载器传递给它getClass().getClassLoader()并从jar中加载我的插件类。 在main方法中,我创建了类的实例inst,然后将其传递给新线程。这个新线程调用inst.getObject(),这是我定义的方法。

现在,getObject()通过Builder在jar中创建另一个类new的实例 - 假设URLClassLoader现在将加载此类,并且它是当前的定义类加载器类。这里,如果从线程调用方法,则NoClassDefFoundError抛出Builder,但是从main方法调用时不会抛出Exception in thread "Thread-0" java.lang.NoClassDefFoundError: testapp/testplugin/Builder at testapp.testplugin.Plugin.getObject(Plugin.java:88) at testapp.mainapp.TestInit$1.run(TestInit.java:90) Caused by: java.lang.ClassNotFoundException: testapp.testplugin.Builder at java.net.URLClassLoader.findClass(URLClassLoader.java:381) at java.lang.ClassLoader.loadClass(ClassLoader.java:424) at java.net.FactoryURLClassLoader.loadClass(URLClassLoader.java:814) at java.lang.ClassLoader.loadClass(ClassLoader.java:357) ... 7 more

System.out.println(getClass().getClassLoader().toString())

当我将getObject()置于package testapp.testplugin; // Pluggable defines the getObject() method, common interface for all plugins public class Plugin implements Pluggable{ Builder build; public Plugin() { // set some fields } @Override public Object getObject() { // lazy initialisation for "build" if (build == null) build = new Builder(); ///// !NoClassDefFoundError! ///// // make Builder assemble an object and return it return build.buildObject(); } } 内时,无论是从main还是从线程调用方法,输出都完全相同。

关于为什么会发生这种情况的任何想法?以下是一些示例代码:

插件(在plugin.jar中)

package testapp.mainapp;

public class TestInit {

  public static void main(String[] args) throws Exception {
      // create URLClassLoader
      URLClassLoader clazzLoader = URLClassLoader.newInstance(new URL[]{new URL("testplugin.jar"},
          getClass().getClassLoader());
      // load plugin class
      Class<?> clazz = Class.forName("testapp.testplugin.Plugin", true, clazzLoader);
      Class<? extends Pluggable> subClazz = clazz.asSubclass(Pluggable.class);
      // instantiate plugin class using constructor (to avoid Class.newInstance())
      Constructor<? extends Pluggable> constr = subClazz.getConstructor();
      final Pluggable plugin = constr.newInstance();
      // create new thread and run getObject()
      Thread t = new Thread(){
           @Override
           public void run() {
                // something more sophisticated in the real application, but this is enough to reproduce the error
                System.out.println(plugin.getObject());
           }
      };
      t.start();
  }

}

主应用程序(在runnable app.jar中)

Builder

我目前的解决方法是在加载插件类后立即强制加载public class Plugin { static { try { Class.forName("testapp.testplugin.Builder"); } catch (ClassNotFoundException e) { e.printStackTrace(); } } [...] } 类:

sqlcmd

1 个答案:

答案 0 :(得分:1)

在你自己的评论中已经有了答案(非常感谢你,顺便说一下),但是很难找到它,所以我在这里引用它:

  

好。解决了它。我在主应用程序中关闭了ClassLoader,因为我将它放在try语句中的AutoClosable包装类中。改变了这个,现在它起作用了。 - RenWal 3月15日20:49

如果您决定在执行加载的类后关闭类加载器,我建议使用java.lang.ThreadGroup。方法和所有派生线程结束:

    ...
    Class mainClass = customClassLoader.findClass(name);
    ThreadGroup threadGroup = new ThreadGroup("Custom thread group");
    Thread thread = new Thread(threadGroup, new Runnable() {
        @Override
        public void run() {
            try {
                mainClass.getMethod("main", ...).invoke(...);
            } catch (Throwable e) {
                // exception handling
            }
        }
    });
    thread.start();
    while (threadGroup.activeCount() > 0) {
        Thread.sleep(100);
    }
    customClassLoader.close();

在线程上下文中创建的所有线程和线程组将直接或间接属于threadGroup。因此,我们可以等到活动线程数变为零。

<强> UPD 即可。当然,如果例如,那将无法保存。调用ExecutorService,其任务需要加载类,或者注册一个监听器,因此代码将脱离线程组。因此,在一般情况下,关闭类加载器只能在JVM出口上安全。