代码之家  ›  专栏  ›  技术社区  ›  samitgaur

使用URLClassLoader重新加载jar时出现问题

  •  17
  • samitgaur  · 技术社区  · 14 年前

    我写了一些示例代码,但遇到了NullPointerException。首先让我给你们看代码:

    package test.misc;
    
    import java.io.File;
    import java.net.URL;
    import java.net.URLClassLoader;
    
    import plugin.misc.IPlugin;
    
    public class TestJarLoading {
    
        public static void main(String[] args) {
    
            IPlugin plugin = null;
    
            while(true) {
                try {
                    File file = new File("C:\\plugins\\test.jar");
                    String classToLoad = "jartest.TestPlugin";
                    URL jarUrl = new URL("jar", "","file:" + file.getAbsolutePath()+"!/");
                    URLClassLoader cl = new URLClassLoader(new URL[] {jarUrl}, TestJarLoading.class.getClassLoader());
                    Class loadedClass = cl.loadClass(classToLoad);
                    plugin = (IPlugin) loadedClass.newInstance();
                    plugin.doProc();
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    try {
                        Thread.sleep(30000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    

    IPlugin是一个简单的接口,只有一个方法doProc:

    public interface IPlugin {
        void doProc();
    }
    

    现在,我将jartest.TestPlugin类打包到一个名为test.jar的jar中,并将其放在C:\plugins下并运行以下代码。第一次迭代运行平稳,类加载没有问题。

    java.lang.NullPointerException
    at java.io.FilterInputStream.close(FilterInputStream.java:155)
    at sun.net.www.protocol.jar.JarURLConnection$JarURLInputStream.close(JarURLConnection.java:90)
    at sun.misc.Resource.getBytes(Resource.java:137)
    at java.net.URLClassLoader.defineClass(URLClassLoader.java:256)
    at java.net.URLClassLoader.access$000(URLClassLoader.java:56)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:195)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:188)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:307)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:252)
    at test.misc.TestJarLoading.main(TestJarLoading.java:22)
    

    我在网上搜索了一下,摸了摸脑袋,但对于为什么会抛出这个异常和那个异常,我真的没有得出任何结论——只是有时候,不总是。

    如果你需要更多的信息,请告诉我。感谢您的光临!

    5 回复  |  直到 14 年前
        1
  •  11
  •   Ryan Fernandes    14 年前

    这种行为与 bug
    记录了2种解决方法 here

        2
  •  16
  •   samitgaur    12 年前

    正如Ryan指出的,有一个 bug 在JVM中,影响Windows平台。 URLClassLoader 在打开jar文件以加载类之后,不会关闭它们,从而有效地锁定jar文件。无法删除或替换jar文件。

    解决方法很简单:在读取打开的jar文件后关闭它们。然而,为了获得打开的jar文件的句柄,我们需要使用反射,因为我们需要遍历的属性不是公共的。所以我们沿着这条路走

    URLClassLoader -> URLClassPath ucp -> ArrayList<Loader> loaders
    JarLoader -> JarFile jar -> jar.close()
    

    关闭打开的jar文件的代码可以添加到扩展URLClassLoader的类中的close()方法中:

    public class MyURLClassLoader extends URLClassLoader {
    
    public PluginClassLoader(URL[] urls, ClassLoader parent) {
        super(urls, parent);
    }
    
        /**
         * Closes all open jar files
         */
        public void close() {
            try {
                Class clazz = java.net.URLClassLoader.class;
                Field ucp = clazz.getDeclaredField("ucp");
                ucp.setAccessible(true);
                Object sunMiscURLClassPath = ucp.get(this);
                Field loaders = sunMiscURLClassPath.getClass().getDeclaredField("loaders");
                loaders.setAccessible(true);
                Object collection = loaders.get(sunMiscURLClassPath);
                for (Object sunMiscURLClassPathJarLoader : ((Collection) collection).toArray()) {
                    try {
                        Field loader = sunMiscURLClassPathJarLoader.getClass().getDeclaredField("jar");
                        loader.setAccessible(true);
                        Object jarFile = loader.get(sunMiscURLClassPathJarLoader);
                        ((JarFile) jarFile).close();
                    } catch (Throwable t) {
                        // if we got this far, this is probably not a JAR loader so skip it
                    }
                }
            } catch (Throwable t) {
                // probably not a SUN VM
            }
            return;
        }
    }
    

    (此代码取自Ryan发布的第二个链接。此代码也发布在错误报告页上。)

    然而,有一个陷阱: 为了使这段代码能够工作并能够获得打开的jar文件的句柄来关闭它们,URLClassLoader实现用来从文件中加载类的加载程序必须是 JarLoader source code 属于 URLClassPath (方法 getLoader(URL url)

    URL jarUrl = new URL("file:" + file.getAbsolutePath());
    

    整个类加载代码应该如下所示:

    void loadAndInstantiate() {
        MyURLClassLoader cl = null;
        try {
            File file = new File("C:\\jars\\sample.jar");
            String classToLoad = "com.abc.ClassToLoad";
            URL jarUrl = new URL("file:" + file.getAbsolutePath());
            cl = new MyURLClassLoader(new URL[] {jarUrl}, getClass().getClassLoader());
            Class loadedClass = cl.loadClass(classToLoad);
            Object o = loadedClass.getConstructor().newInstance();
        } finally {
            if(cl != null)
                cl.close();
        } 
    }
    

    更新: JRE 7引入了 close() 类中的方法 URLClassLoader 可能解决了这个问题。我还没核实。

        3
  •  3
  •   Nicolas Filotto    8 年前

    close() 中的方法 URLClassLoader 但是,如果您直接或间接地调用类型的方法,完全释放jar文件是不够的 ClassLoader#getResource(String) ClassLoader#getResourceAsStream(String) ClassLoader#getResources(String) JarFile 实例会自动存储到的缓存中 JarFileFactory 如果我们直接或间接调用前面的方法之一,并且即使我们调用 java.net.URLClassLoader#close()

    因此,在这种特殊情况下,即使使用Java1.8.0\u74,仍然需要一个hack,下面是我的hack https://github.com/essobedo/application-manager/blob/master/src/main/java/com/github/essobedo/appma/core/util/Classpath.java#L83 我在这里用的 https://github.com/essobedo/application-manager/blob/master/src/main/java/com/github/essobedo/appma/core/DefaultApplicationManager.java#L388 . 即使使用了这种方法,我仍然必须显式地调用GC来完全释放jar文件,正如您在这里看到的那样 https://github.com/essobedo/application-manager/blob/master/src/main/java/com/github/essobedo/appma/core/DefaultApplicationManager.java#L419

        4
  •  1
  •   venergiac    9 年前

    这是 成功的java 7 URLClassLoader 对我来说很好

    我的重装机

    class MyReloaderMain {
    
    ...
    
    //assuming ___BASE_DIRECTORY__/lib for jar and ___BASE_DIRECTORY__/conf for configuration
    String dirBase = ___BASE_DIRECTORY__;
    
    File file = new File(dirBase, "lib");
    String[] jars = file.list();
    URL[] jarUrls = new URL[jars.length + 1];
    int i = 0;
    for (String jar : jars) {
        File fileJar = new File(file, jar);
        jarUrls[i++] = fileJar.toURI().toURL();
        System.out.println(fileJar);
    }
    jarUrls[i] = new File(dirBase, "conf").toURI().toURL();
    
    URLClassLoader classLoader = new URLClassLoader(jarUrls, MyReloaderMain.class.getClassLoader());
    
    // this is required to load file (such as spring/context.xml) into the jar
    Thread.currentThread().setContextClassLoader(classLoader);
    
    Class classToLoad = Class.forName("my.app.Main", true, classLoader);
    
    instance = classToLoad.newInstance();
    
    Method method = classToLoad.getDeclaredMethod("start", args.getClass());
    Object result = method.invoke(instance, args);
    
    ...
    }
    

    关闭并重新启动ClassReloader

    classLoader.close();
    

    然后你可以用新版本重新启动应用程序。

    不要将jar包含到基类加载器中” MyReloaderMain.class.getClassLoader() MyReloaderMain 换句话说,开发两个项目,两个罐子一个 MyReloaderMain公司 “而另一个对于你的实际应用没有依赖性,否则你就无法理解我是谁在加载什么。

        5
  •  0
  •   dalvarezmartinez1    8 年前

    jdk1.8.0_2 Windows . 虽然@Nicolas'的回答有帮助,但我击中了一个 ClassNotFound 对于 sun.net.www.protocol.jar.JarFileFactory

    java -jar.... 目前看来一切正常。

        6
  •  0
  •   Richard Kotal    4 年前
    1. 原则上,已经加载的类不能用同一个类加载器重新加载。
    2. 对于新的加载,需要创建一个新的类加载器,从而加载类。
    3. URLClassLoader 有一个问题,那就是jar文件保持打开状态。
    4. 如果有多个类由不同的 如果在运行时更改jar文件,通常会出现以下错误: java.util.zip.ZipException: ZipFile invalid LOC header (bad signature) . 错误可能不同。
    5. 为了不发生上述错误,有必要使用 close URLClassLoader 正在使用给定的jar文件。但这是一个实际导致整个应用程序重新启动的解决方案。

    更好的解决方案是修改 URLClassLoader 以便jar文件的内容被加载到RAM缓存中。这不再影响其他人 URLClassloader 从同一jar文件中读取数据的。然后可以在应用程序运行时自由更改jar文件。例如,您可以使用 URLClassLoader in-memory URLClassLoader