【问题标题】:Java Spring application has memory leak. system non heap memory increases constantlyJava Spring 应用程序存在内存泄漏。系统非堆内存不断增加
【发布时间】:2017-02-23 17:11:22
【问题描述】:

我已使用 yourkit profiler 监控我的 Web 应用程序。保留最大大小的主要对象是 SessionFactoryImpl、webappclassloader 和 CGlib 对象 Shows。 * spring crone 调度器会导致内存泄漏吗? 我试过的解决方案

1) 我试图杀死线程,但它们仍然活着。

2) 关闭所有连接。

3) 将 null 分配给我在代码中使用的所有变量和对象。

4) 我也申请了服务器端

-Xms128m -Xmx256m -XX:MaxPermSize=512m -XX:+UseConcMarkSweepGC -XX:+CMSClassUnloadingEnabled -XX:+HeapDumpOnOutOfMemoryError -XX:MaxHeapFreeRatio=70 -XX:ReservedCodeCacheSize=32m -XX:+UseCodeCacheFlushing -XX:-OmitStackTraceInFastThrow

5) 我在 web.xml 中添加了防泄漏库

<listener>
    <listener-class>se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventorListener
        </listener-class>
  </listener>
  <context-param>
    <param-name>ClassLoaderLeakPreventor.stopThreads</param-name>
    <param-value>true</param-value>
  </context-param>
  <context-param>
    <param-name>ClassLoaderLeakPreventor.stopTimerThreads</param-name>
    <param-value>true</param-value>
  </context-param>
  <context-param>
    <param-name>ClassLoaderLeakPreventor.executeShutdownHooks</param-name>
    <param-value>true</param-value>
  </context-param>
  <context-param>
    <param-name>ClassLoaderLeakPreventor.threadWaitMs</param-name>
    <param-value>5000</param-value>
  </context-param>
  <context-param>
    <param-name>ClassLoaderLeakPreventor.shutdownHookWaitMs</param-name>
    <param-value>10000</param-value>
  </context-param>
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/root-context.xml</param-value>
  </context-param> 

6) 我还添加了 ContextFinalizer 类

 package com.thl.test;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.Driver;
import java.sql.DriverManager;
import java.util.Enumeration;
import java.util.Set;

import javax.servlet.ServletContextEvent;

import org.apache.commons.logging.LogFactory;
import org.springframework.beans.CachedIntrospectionResults;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.Proxy;
import org.springframework.context.ApplicationContext;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.web.util.IntrospectorCleanupListener;

import com.mysql.jdbc.AbandonedConnectionCleanupThread;

import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor;

public class ContextFinalizer extends IntrospectorCleanupListener {

    private ClassLoader loader = null;

    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("Calling>>>>>>>>>>>>>>>>>>>>>>>.?");
        /* Introspector.flushCaches(); */
        ClassLoader cl1 = Thread.currentThread().getContextClassLoader();
        CachedIntrospectionResults.clearClassLoader(cl1);
        LogFactory.releaseAll();
        ClassLoaderLeakPreventor.gc();
        try {
            AbandonedConnectionCleanupThread.shutdown();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        /*Enhancer.registerCallbacks(enhanced, null);*/
        // cleanUp();
    }

    @SuppressWarnings("deprecation")
    public void contextDestroyed(ServletContextEvent sce) {
        /*
         * Thread t = Thread.currentThread();
         * Runtime.getRuntime().addShutdownHook(t);
         */
        System.out.println("Good Bye>>>>>>>>>>>>>>>>>>>>>.?");
        cleanUp();
        ClassLoaderLeakPreventor.gc();
        java.beans.Introspector.flushCaches();
        java.security.Security.removeProvider(null);
        ClassLoader cl1 = Thread.currentThread().getContextClassLoader();
        CachedIntrospectionResults.clearClassLoader(cl1);
        LogFactory.releaseAll();
        org.apache.log4j.LogManager.shutdown();

        Enumeration<Driver> drivers = DriverManager.getDrivers();
        Driver d = null;

        ClassLoader cl = Thread.currentThread().getContextClassLoader();

        while (drivers.hasMoreElements()) {
            try {
                d = drivers.nextElement();
                if (d.getClass().getClassLoader() == cl) {
                    DriverManager.deregisterDriver(d);
                } else {
                    DriverManager.deregisterDriver(d);
                }
            } catch (Exception ex) {
                // LOGGER.warn(String.format("Error deregistering driver %s",
                // d), ex);
            }
        }

        /*
         * if (ConnectionImpl.class.getClassLoader() ==
         * getClass().getClassLoader()) { Field f = null; try { f =
         * ConnectionImpl.class.getDeclaredField("cancelTimer");
         * f.setAccessible(true); Timer timer = (Timer) f.get(null);
         * timer.cancel(); }catch(Exception e) {
         * 
         * }finally { f = null; } }
         */

        try {
            com.mysql.jdbc.AbandonedConnectionCleanupThread.shutdown();

        } catch (InterruptedException e) {
        } finally {
            try {

                /* org.apache.commons.pool.impl.GenericObjectPool. */

                com.mysql.jdbc.AbandonedConnectionCleanupThread.shutdown();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }

        Set<Thread> threadSet = Thread.getAllStackTraces().keySet();
        Thread[] threadArray = threadSet.toArray(new Thread[threadSet.size()]);
        for (Thread t : threadArray) {
            /*
             * if (t.isInterrupted()) { break; }
             */

            if (t.getName().contains("Abandoned connection cleanup thread")) {
                synchronized (t) {
                    // don't complain, it works
                    if (t.isAlive()) {
                        System.out.println("Alive True");
                        if (t.isDaemon()) {
                            System.out.println("isDaemon True");
                            t.stop();
                        } else {
                            System.out.println("isDaemon False");
                            t.stop();
                        }
                    } else {
                        System.out.println("Alive Flase");
                        t.stop();
                    }
                    // new Timer(true);
                }
            } else if (t.getName().contains("http-nio-8081-exec-1")) {
                System.out.println("http-nio-8081-exec-1>>>>>>>>>>>");
            } else {
                System.out.println("Else If Block");
                synchronized (t) {
                    t.setDaemon(true);
                    t.suspend();
                }
            }
        }
        java.beans.Introspector.flushCaches();

    }

    public void onApplicationEvent(ContextRefreshedEvent arg0) {
        System.out.println("--------------- Context Refreshed -----------------");
        System.out.println("::::::::::::::::::::::::  Calling   :::::::::::::::::::::::::::::");

        ApplicationContext context = arg0.getApplicationContext();
        System.out.println(context.getDisplayName());
    }

    private void cleanUp() {
        Thread[] threads = getThreads();
        for (Thread thread : threads) {
            if (thread != null) {
                System.out.println("Inside IFF");
                cleanContextClassLoader(thread);
                cleanOrb(thread);
                cleanThreadLocal(thread);

            }

        }
    }

    private Thread[] getThreads() {
        ThreadGroup rootGroup = Thread.currentThread().getThreadGroup();
        ThreadGroup parentGroup;
        if (rootGroup.getParent() != null) {
            parentGroup = rootGroup.getParent();
            if (parentGroup != null) {
                rootGroup = parentGroup;
            }
        }
        Thread[] threads = new Thread[rootGroup.activeCount()];
        while (rootGroup.enumerate(threads, true) == threads.length) {
            threads = new Thread[threads.length * 2];
        }
        return threads;
    }

    private boolean loaderRemovable(ClassLoader cl) {
        if (cl == null) {
            return false;
        }
        Object isDoneCalled = getObject(cl, "doneCalled");
        String clName = cl.getClass().getName();
        loader = Thread.currentThread().getContextClassLoader();
        String ldr = null;
        loader = loader.getParent();
        if (loader != null) {
            // loader.getParent();
            ldr = loader.getClass().getName();
        }

        if (clName != null && ldr != null && isDoneCalled != null) {
            if (clName.equalsIgnoreCase(ldr) && isDoneCalled instanceof Boolean && (Boolean) isDoneCalled) {
                return true;
            }
        }

        return loader == cl;
    }

    private Field getField(Class clazz, String fieldName) {
        Field f = null;
        try {
            f = clazz.getDeclaredField(fieldName);
        } catch (NoSuchFieldException ex) {

        } catch (SecurityException ex) {
        }

        if (f == null) {
            Class parent = clazz.getSuperclass();
            if (parent != null) {
                f = getField(parent, fieldName);
            }
        }
        if (f != null) {
            f.setAccessible(true);
        }
        return f;
    }

    private Object getObject(Object instance, String fieldName) {
        Class clazz = instance.getClass();
        Field f = getField(clazz, fieldName);
        if (f != null) {
            try {
                return f.get(instance);
            } catch (IllegalArgumentException | IllegalAccessException ex) {
            }
        }
        return null;
    }

    private void cleanContextClassLoader(Thread thread) {
        if (loaderRemovable(thread.getContextClassLoader())) {
            thread.setContextClassLoader(null);
        }
    }

    private void cleanOrb(Thread thread) {
        Object currentWork = getObject(thread, "currentWork");
        if (currentWork != null) {
            Object orb = getObject(currentWork, "orb");
            if (orb != null) {
                Object transportManager = getObject(orb, "transportManager");
                if (transportManager != null) {
                    Thread selector = (Thread) getObject(transportManager, "selector");
                    if (selector != null && loaderRemovable(selector.getContextClassLoader())) {
                        selector.setContextClassLoader(null);
                    }
                }
            }
        }
    }

    private void removeThreadLocal(Object entry, Object threadLocals, Thread thread) {
        ThreadLocal threadLocal = (ThreadLocal) getObject(entry, "referent");
        if (threadLocal != null) {
            Class clazz = null;
            try {
                clazz = Class.forName("java.lang.ThreadLocal$ThreadLocalMap");
            } catch (ClassNotFoundException ex) {
            }
            if (clazz != null) {
                Method removeMethod = null;
                Method[] methods = clazz.getDeclaredMethods();
                if (methods != null) {
                    for (Method method : methods) {
                        if (method.getName().equals("remove")) {
                            removeMethod = method;
                            removeMethod.setAccessible(true);
                            break;
                        }
                    }
                }
                if (removeMethod != null) {
                    try {
                        removeMethod.invoke(threadLocals, threadLocal);
                    } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
                    }
                }

            }

        }
    }

    private void cleanThreadLocal(Thread thread) {
        Object threadLocals = getObject(thread, "threadLocals");
        if (threadLocals != null) {
            Object table = getObject(threadLocals, "table");
            if (table != null) {
                int size = Array.getLength(table);
                for (int i = 0; i < size; i++) {
                    Object entry = Array.get(table, i);
                    if (entry != null) {
                        Field valueField = getField(entry.getClass(), "value");
                        if (valueField != null) {
                            try {
                                Object value = valueField.get(entry);
                                if (value != null && value instanceof ClassLoader
                                        && loaderRemovable((ClassLoader) value)) {
                                    removeThreadLocal(entry, threadLocals, thread);
                                }
                            } catch (IllegalArgumentException | IllegalAccessException ex) {

                            }

                        }
                    }

                }
            }
        }
    }

}

关于内存泄漏监视器的快照如下。

enter link description here

【问题讨论】:

  • 13:42:01 的截图显示,86% 的内存空间被不强可达的对象占用。这让我相信垃圾收集器无法继续收集垃圾并释放内存。
  • @ThomasKläger 对于 Garbage Colletion 强烈无法访问的对象我应该怎么做?
  • 垃圾收集器统计信息会很有帮助。您可以通过添加以下选项来打开它们:“-verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimeStamps”。
  • 我已经看到还有许多对象正在等待完成(即有一个 finalize() 方法,不再可访问但还没有调用他们的 finalize 方法)。具有finalize() 方法的对象也可以是出色的性能(和内存)猪
  • @ThomasKläger 感谢您的回复。我会试试你的建议。

标签: java spring memory-leaks


【解决方案1】:

there is an answer here

您所看到的只是症状,而不是泄漏的原因。 导致泄漏的原因是 ClassLoader 从未被收集,在 在这种情况下, CachedIntrospectionResults 影响不大 比较每个类及其静态所占用的内存 成员。如果你的 ClassLoader 没有被收集,你甚至可能有 未释放的外部资源,例如 JDBC 连接,如果您有 从静态成员引用到您的连接池(直接或 间接)。

任何 servlet 容器都会在 webapp 运行时释放 ClassLoader 卸载,但有很多东西(错误)可能会阻止 要收集的 ClassLoader。我个人最可能的原因 能够识别的是 java.lang.Introspector 中的缓存,一个 Driver 未在 DriverManager 或 ThreadLocal 变量中未注册 清除。但无论如何,除非我的补丁中有错误 提供,或者它以某种方式被破坏了(在这两种情况下我都怀疑 它),那么 CacheIntrospectionResults 中的代码将永远不会阻止 ClassLoader 被垃圾回收。

所以,很可能,这不是 JBoss 问题,也不是 Spring 问题,而是 您自己的代码或 DOM4J 等库中的问题。

对于 Introspector,解决方案(除了不使用它)是拥有一个 ContextListener 在上下文时调用 Introspector.flushCaches() 被摧毁。 Spring提供一个,它的 os.web.util.IntrospectorCleanupContextListener 或类似的东西。 此外,Introspector 中的泄漏只是 JDK 到 JDK 的问题 1.4.2,问题在1.5中得到纠正。

对于未注册的驱动程序(如果您的驱动程序类在 WEB-INF/lib),解决方法类似,需要有一个 ContextListener,当上下文被销毁时,你要求一个列表 从 DriverManager 中注册的驱动程序,然后删除任何 来自你的 webapp(你可以查看它的 ClassLoader)。

对于 ThreadLocal 的东西,你必须确保你放入的任何东西 在那里你最终将其删除。但是,如果第三方库这样做 (例如DOM4J)你不能修复它或修复它,那么它更多 解决起来很复杂。您可以查看我之前发给此的电子邮件 列出一个潜在的解决方法,但它有点像黑客,并且会 由于并发问题在生产中很危险

.

【讨论】:

    【解决方案2】:

    如果你认为你的spring代码有问题,请在jvisualvm上过滤你的包,然后你就可以看到你的实例内存了。

    在我看来,我认为您使用和配置了一些其他库不正确,这可能会给您的应用程序带来一些内存问题。看看jvisualvm上最相关的包,对你有帮助

    【讨论】:

    • 你能告诉我什么类型的图书馆吗? Cglib? 而且我在 jvisualvm 中检查过有一个 Timer-0 线程保持打开状态,并且等待该线程的计数不断增加。
    • 你需要先检查哪个包最消耗你的内存,它会帮助你找到错误的库。
    • 此外,您是否使用任何库来处理文件或数据库。我总是被这种情况卡住,我消耗了很多我无法控制的内存
    猜你喜欢
    • 2020-03-11
    • 1970-01-01
    • 2019-11-11
    • 2011-01-21
    • 2016-07-11
    • 2019-02-02
    • 1970-01-01
    • 2018-07-15
    • 1970-01-01
    相关资源
    最近更新 更多