本文我们来分析分析应用服务器的内存泄露的问题,看看Tomcat是如何应对这个问题的;
首先,来看看内存泄露这个词,内存对于java程序来说,即指JVM内存,而我们知道JVM的内存泄露是有很多种情况的;
一种情况,class泄露是perm区的内存,此种场景就是当应用服务器的类特别多的时候,perm区的容量也是由限度的,会撑爆;
另外一种就是,java对象太多,新生代,老年代等对象存储区太多,full GC都已经收拾不了这种残局;
上述的两种情况,最终都会导致OOM溢出;
而作为Tomcat来讲,如何避免这种溢出,就是这个 Jre Memory Leak Prevention Listener 的能力了;
对于第一种class的情况,Jre Memory Leak Prevention Listener将class的加载优先放在系统类加载器这一级中,来解决这个问题;
回过头来,我们先看看Tomcat的class层级结构就可以明白:
作为每一个应用的WebappClassLoader,如果要加载同样的类库和class,相当于每一份ClassLoader中都得加载一遍,而无论是哪来的ClassLoader,只要再同一个JVm下,都会占用perm区的资源;
如果每个应用中的加载的类库版本不同还好说,或者加载的库千差万别也好说,那么每一个WebappClassLoader都加载一遍这些Class没有其他的办法;
而一般如log4j,sax-dom的xml解析,jdbc驱动等等这些非常常用的类库,很多应用都一样;
因此,我们一般在开发应用的时候,技术经理总会说,有没有什么通用的库和lib包,都要放到common目录下面就是这个道理;
Jre Memory Leak Prevention Listener和上述的思路类似,其主要会将一些常用的class资源泄露的库优先加载;
如下面的一段代码:
classToInitialize属性是以,分隔的一串字符串的配置,实际的意思就是预先加载的一系列的类;
你可以在Jre Memory Leak Prevention Listener中进行配置,然后优先在系统ClassLoader中进行加载;
说到这里,Jre Memory Leak Prevention Listener的整体流程是先保存原来的线程上下文的classloader,然后直接进行切换,最后再切换回来:
对于第二种情况,也就是内存泄露是不是因为class的问题,而是因为java对象太多造成的;
其实也可以借鉴class的处理办法,即很多库,特别是java的库中,JDBC驱动库这种通用库中,例如数据驱动的初始化,如果在应用类加载器中进行初始化,这些都会产生n多个对象,我们不妨将其优先在系统类加载器中加载,这样如果该对象在查找的时候,直接通过双亲委派查找到了系统ClassLoader加载的类,对于类中引用的静态对象资源直接进行初始化,这样应用中的查找到该类的时候,相当于是已经初始化过的类引用的静态资源,这样大大减少了降低java对象的作用,使其只有一份;
如DriverManager的启动:
如果是应用类加载器进行加载,不同应用中可能将其注册的驱动实现类加载到自己的应用类加载器中,并进行初始化,类的增多暂且不说,这一系列的操作会搞出很多对象出来;
而通过这种“预热”的机制,一下子就把所有的驱动进行初始化了,而在应用中无非也是这么调用API,所以这样就相当于减少了n多个对象的创建;
类似的机制还有很多,Jre Memory Leak Prevention Listener还可以减少一些线程创建,其思路也是也是这种将每一个应用的线程变成只有一份线程;
我们来看看配置:
JRE Memory Leak Prevention Listener - org.apache.catalina.core.JreMemoryLeakPreventionListener
The JRE Memory Leak Prevention Listener provides work-arounds for known places where the Java Runtime environment uses the context class loader to load a singleton as this will cause a memory leak if a web application class loader happens to be the context class loader at the time. The work-around is to initialise these singletons when this listener starts as Tomcat‘s common class loader is the context class loader at that time. It also provides work-arounds for known issues that can result in locked JAR files.
This listener must only be nested within Server elements.
需要注意,Jre Memory Leak Prevention Listener 只能配置在<Server>中;
The following additional attributes are supported by the JRE Memory Leak Prevention Listener:
Attribute | Description |
---|---|
appContextProtection |
Enables protection so that calls to sun.awt.AppContext.getAppContext() triggered by a web application do not result in a memory leak. Note that enabling this protection will trigger a requirement for a graphical environment unless Java is started in head-less mode. The default is false . This protection is disabled if running on Java 8 onwards since the leak has been fixed for Java 8 onwards.
web应用多次执行sun.awt.AppContext.getAppContext()会造成泄露,这里需要进行一下保护,代码为: |
AWTThreadProtection |
Enables protection so that calls to java.awt.Toolkit.getDefaultToolkit() triggered by a web application do not result in a memory leak. Defaults to false because an AWT thread is launched. This protection is disabled if running on Java 9 onwards since the leak has been fixed for Java 9 onwards.
web应用多次执行java.awt.Toolkit.getDefaultToolkit() 会造成内存泄露,保护代码为: |
classesToInitialize |
List of comma-separated fully qualified class names to load and initialize during the startup of this Listener. This allows to pre-load classes that are known to provoke classloader leaks if they are loaded during a request processing. Non-JRE classes may be referenced, like oracle.jdbc.driver.OracleTimeoutThreadPerVM . The default value is empty, but specific JRE classes are loaded by other leak protection features managed by other attributes of this Listener.
预先通过系统类加载器加载的类,以逗号分隔,这样类就不会有多份了,相当于减少perm区的占用; 代码见上; |
driverManagerProtection |
The first use of java.sql.DriverManager will trigger the loading of JDBC Driver in the current class loader. The web application level memory leak protection can take care of this in most cases but triggering the loading here has fewer side-effects. The default is true .
数据库驱动的初始化先搞一轮,驱动的实现就不会每一个应用一份,减少对象创建; 代码见上; |
gcDaemonProtection |
Enables protection so that calls to sun.misc.GC.requestLatency(long) triggered by a web application do not result in a memory leak. Use of RMI is likely to trigger a call to this method. A side effect of enabling this protection is the creation of a thread named "GC Daemon". The protection uses reflection to access internal Sun classes and may generate errors on startup on non-Sun JVMs. The default is true . This protection is disabled if running on Java 9 onwards since the leak has been fixed for Java 9 onwards.
作为SUN的JDK,在进行GC请求的时候,会调用 而调用完这个方法之后,SUN的实现会启动一个daemon线程; 试想一下,如果每一个应用调用这段代码的话,即很容易产生内存泄露; |
ldapPoolProtection |
Enables protection so that the PoolCleaner thread started by com.sun.jndi.ldap.LdapPoolManager does not result in a memory leak. The thread is started the first time the LdapPoolManager class is used if the system property com.sun.jndi.ldap.connect.pool.timeout is set to a value greater than 0. Without this protection, if a web application uses this class the PoolCleaner thread will be configured with the thread‘s context class loader set to the web application class loader which in turn will trigger a memory leak on reload. Defaults to true . This protection is disabled if running on Java 9 onwards since the leak has been fixed for Java 9 onwards.
Ldap的池中的PoolCleaner 线程如果每一个应用的应用classLoader加载极容易出现内存泄露; |
securityLoginConfigurationProtection |
Enables protection so that usage of the javax.security.auth.login.Configuration class by a web application does not provoke a memory leak. The first access of this class will trigger the initializer that will retain a static reference to the context class loader. The protection loads the class with the system class loader to ensure that the static initializer is not triggered by a web application. Defaults to true . This protection is disabled if running on Java 8 onwards since the leak has been fixed for Java 8 onwards.
让系统类加载器就把 |
securityPolicyProtection |
Enables protection so that usage of the deprecated javax.security.auth.Policy class by a web application does not result in a memory leak. The first access of this class will trigger the static initializer that will retain a static reference to the context class loader. The protection calls the getPolicy() method of this class to ensure that the static initializer is not triggered by a web application. Defaults to true .
Note: The underlying leak has been fixed in Java 7 update 51 onwards and Java 8 onwards. This protection is therefor disabled if running on Java 8 onwards.
|
tokenPollerProtection |
Enables protection so that any token poller thread initialized by sun.security.pkcs11.SunPKCS11.initToken() does not result in a memory leak. The thread is started depending on various conditions as part of the initialization of the Java Cryptography Architecture. Without the protection this can happen during Webapp deployment when the MessageDigest for generating session IDs is initialized. As a result the thread has the Webapp class loader as its thread context class loader. Enabling the protection initializes JCA early during Tomcat startup. Defaults to true . This protection is disabled if running on Java 9 onwards since the leak has been fixed for Java 9 onwards.
减少Java Cryptography Architecture的线程,不创建那么多个tokenPollerProtection线程; |
urlCacheProtection |
Enables protection so that reading resources from JAR files using java.net.URLConnection s does not result in the JAR file being locked. Note that enabling this protection disables caching by default for all resources obtained via java.net.URLConnection s. Caching may be re-enabled on a case by case basis as required. Defaults to true .
在读取jar文件的时候,极容易造成lock文件,而实质原因是jarConnection中缓存默认设置没有禁用; 解决办法就是找一个根本不存在的jar链接,然后设置就OK了: |
xmlParsingProtection |
Enables protection so that parsing XML files within a web application does not result in a memory leak. Note that memory profilers may not display the GC root associated with this leak making it particularly hard to diagnose. Defaults to true . This protection is disabled if running on Java 9 onwards since the leak has been fixed for Java 9 onwards.
尽可能的多初始化dom实现的内容,减少对象; |
总结一下,其实Jre Memory Leak Prevention Listener主要做的就是让多份内存变成1份内存,方法有很多,线程减少,提高classloader加载的层级,提前预热,甚至我们通过Jre Memory Leak Prevention Listener的代码来发现,其还能解锁jar文件。。。
看似貌似是很神奇的,但可以看到,这里面大多数与实际的具体实现框架有关,如SUN.xxx,和特定的JDK版本的实现缺陷有关,很多属性在JAVA 9中都被新版本JDK所修复了,因此该配置也没有意义;
而值得注意的一点是,对于gcDaemonProtection 这个属性,在特定版本的JDK下,配置还会造成频繁GC,如邮件:
http://mail-archives.apache.org/mod_mbox/tomcat-users/201008.mbox/%[email protected]%3E
因此,对这些属性需要逐个去分析和研究一下,本文由于篇幅的限制并没有逐个去研究,这个空间就留给各位读者了;
原文地址:https://www.cnblogs.com/yuantongaaaaaa/p/10313326.html