为什么在多次函数调用后我的代码内存泄漏?ClassLoader 是个问题吗?

huangapple 未分类评论47阅读模式
英文:

Why my code leaking memory after many function calls? Is ClassLoader a problem?

问题

我有一个名为getAllTests()的方法,它加载一个文件夹中的外部JAR文件,然后使用反射搜索带有@Test注解的方法。我使用以下代码:

  • 使用URLClassLoader类来加载JAR文件;
  • 使用Class currentClass = child.loadClass(classname);来加载类;
  • 使用Method method = currentClass.getMethods()[i];来获取方法;
  • 使用Annotation annTest = method.getAnnotation(Test.class);来获取注解。

我的代码如下:

public static void getAllTests() throws IllegalArgumentException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, IOException, LinkageError, ClassNotFoundException {
    TestLoaderApplication.testClassObjMap.clear(); // 这是一个HashMap类
    LoadLibrary loadLibrary = new LoadLibrary(); // 用于搜索文件夹中的所有.jar文件
    List<JarFile> jarList = loadLibrary.getListJar(pathJars).stream().map(f -> {
        try {
            return new JarFile(f);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    })
    .collect(Collectors.toList());

    for (JarFile j : jarList) {
        URLClassLoader child = new URLClassLoader(
            new URL[] { new File(j.getName()).toURI().toURL() },
            ServiceUtil.class.getClassLoader()
        );

        for (Enumeration<JarEntry> entries = j.entries(); entries.hasMoreElements(); ) {
            JarEntry entry = entries.nextElement();
            String file = entry.getName();
            if (file.endsWith(".class")) {
                String classname = file.replaceAll("/", ".")
                        .substring(0, file.lastIndexOf("."));
                try {
                    Class currentClass = child.loadClass(classname);
                    List<String> testMethods = new ArrayList<>();
                    for (int i = 0; i < currentClass.getMethods().length; i++) {
                        Method method = currentClass.getMethods()[i];
                        Annotation annTest = method.getAnnotation(Test.class);
                        Annotation annTestFactory = method.getAnnotation(TestFactory.class);
                        if (annTest != null || annTestFactory != null) {
                            testMethods.add(method.getName());
                        }
                    }
                    if (testMethods.size() >= 1) {
                        testClassObjMap.put(j.getName().substring(j.getName().lastIndexOf("\\") + 1), classname, testMethods);
                        TestLoaderApplication.testClassObjMap.put(j.getName().substring(j.getName().lastIndexOf("/") + 1), classname, testMethods);
                        LOGGER.info(String.format("%s %s %s", j.toString(), classname, testMethods));
                    }
                } catch (NoClassDefFoundError e) {
                    LOGGER.warn("WARNING: failed NoClassDefFoundError " + classname + " from " + file);
                } catch (Throwable e) {
                    LOGGER.warn("WARNING: failed to instantiate " + classname + " from " + file);
                }
            }
        }
        j.close();
        child.close();
        child = null;
    }
    System.gc();
    LOGGER.info("Test Loader Console Back-End: Fine Reflection Scan");
}

在这个函数的末尾,我关闭了JarFile(j)classLoader(child),并将child设置为null,然后调用了垃圾回收器。

但是,如果我多次调用这个方法,最终Java Web应用程序的内存使用率会达到约100%。

这个方法位于一个Spring Boot项目中,我使用Tomcat作为Web服务器。

你能否推荐一些在使用类加载器方面的优化方法,以及这是否是查找Java中带有@Test注解的方法的正确方式?谢谢。

英文:

i have a mehod getAllTests() that load external jar in a folder and with Reflection search method with Annotation Test (@Test).
I use:

URLClassLoader class for load Jar File;

Class currentClass=child.loadClass(classname); for load class;

Method method = currentClass.getMethods()[i];

Annotation annTest = method.getAnnotation(Test.class);

for get Method and get Annotation.

My code is this:

public static void getAllTests() throws IllegalArgumentException,  NoSuchMethodException, InvocationTargetException, IllegalAccessException, IOException,LinkageError, ClassNotFoundException {
        TestLoaderApplication.testClassObjMap.clear(); &lt;--is a HashMap class
        LoadLibrary loadLibrary=new LoadLibrary();&lt;--used for search all file .jar in folder
        List&lt;JarFile&gt; jarList= loadLibrary.getListJar(pathJars).stream().map(f -&gt; {
            try {
                return new JarFile(f);
            } catch (IOException e) {
                e.printStackTrace();
            }
            return null;
        })
                .collect(Collectors.toList());

        for (JarFile j : jarList) {
            URLClassLoader child = new URLClassLoader(
                    new URL[] {new File(j.getName()).toURI().toURL()},
                    ServiceUtil.class.getClassLoader()
            );

            for (Enumeration&lt;JarEntry&gt; entries = j.entries(); entries.hasMoreElements(); ) {
                JarEntry entry = entries.nextElement();
                String file = entry.getName();
                if (file.endsWith(&quot;.class&quot;)) {
                    String classname = file.replaceAll(&quot;/&quot;, &quot;.&quot;)
                            .substring(0, file.lastIndexOf(&quot;.&quot;));
                    try {
                        
                    	Class currentClass=child.loadClass(classname);
                        List&lt;String&gt; testMethods = new ArrayList&lt;&gt;();
                        for (int i = 0; i &lt; currentClass.getMethods().length; i++) {
                            Method method = currentClass.getMethods()[i];
                            Annotation annTest = method.getAnnotation(Test.class);
                            Annotation annTestFactory = method.getAnnotation(TestFactory.class);
                            if (annTest != null || annTestFactory != null) {
                                testMethods.add(method.getName());
                            }
                        }//fine for metodi
                        if (testMethods.size() &gt;=1) {
                            testClassObjMap.put(j.getName().substring(j.getName().lastIndexOf(&quot;\\&quot;)+1),classname,testMethods);
                            TestLoaderApplication.testClassObjMap.put(j.getName().substring(j.getName().lastIndexOf(&quot;/&quot;)+1),classname,testMethods);
                            LOGGER.info(String.format(&quot;%s %s %s&quot;,j.toString(),classname,testMethods));
                        }
                    }catch (NoClassDefFoundError e) {
                        LOGGER.warn(&quot;WARNING: failed NoClassDefFoundError &quot; + classname + &quot; from &quot; + file);
                    }
                    catch (Throwable e) {
                        LOGGER.warn(&quot;WARNING: failed to instantiate &quot; + classname + &quot; from &quot; + file);
                    }

                }//if .class

            }//chiudo jarentry for
            j.close();
            child.close();
            child=null;
        }//chiudo jarfile for
        System.gc();
        LOGGER.info(&quot;Test Loader Console Back-End:\tFine Reflection Scan&quot;);

    }

At end of this function i close both JarFile(j) and classLoader(child) and set child=null and agter call garbage collector.
But if i call this method many times at end i have about 100% memory used by java webapps.

This method is in a spring-boot project and i use Tomcat for webserver.

Can you recommend some optimization in the use of the classLoader and if this is the correct way to look for methods with annotation @Test in Java?
Thanks

huangapple
  • 本文由 发表于 2020年5月3日 21:19:10
  • 转载请务必保留本文链接:https://java.coder-hub.com/61575088.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定