tomcat error: This is very likely to create a memory leak problem solving

In the production environment, the tomcat memory setting is not good, and memory overflow is easy to occur. The reasons for the memory overflow are different, and the handling methods are also different.

There are three common situations:

1.OutOfMemoryError: Java heap space

2.OutOfMemoryError: PermGen space

3.OutOfMemoryError: unable to create new native thread.

For the first two cases, if there is no memory leak in the application itself, it can be solved by setting the tomcat jvm parameter. (-Xms -Xmx -XX:PermSize -XX:MaxPermSize)

The last one may need to adjust the operating system and tomcat jvm parameters at the same time to achieve the purpose.

 

The first: it is a heap overflow

Cause Analysis:

The setting of the JVM heap refers to the memory space that the JVM can allocate and use during the running of the java program. The JVM automatically sets the value of the Heap size when it starts up. The initial space (ie -Xms) is 1/64 of the physical memory, and the maximum space (-Xmx) is 1/4 of the physical memory. It can be set by using options such as -Xmn -Xms -Xmx provided by the JVM. The size of Heap size is the sum of Young Generation and Tenured Generation.

This exception message will be thrown in the JVM if 98% of the time is used for GC and the available Heap size is less than 2%.

The maximum Heap Size should not exceed 80% of the available physical memory. Generally, the -Xms and -Xmx options should be set to the same, and -Xmn is 1/4 of the -Xmx value.

If there is no memory leak, adjusting the -Xms -Xmx parameters can solve it.

-Xms: initial heap size

-Xmx: maximum heap size

But the size of the heap is affected by the following three aspects:

1. The data model (32-bit or 64-bit) of the relevant operating system is limited;

2. The available virtual memory limit of the system;

3. The system's available physical memory limit.

The size of the heap can be tested using the java -Xmx*M version command. If it is supported, the version number of jdk will appear. If it is not supported, an error will be reported.

-Xms -Xmx is generally better configured to be the same. For example: set JAVA_OPTS= -Xms1024m -Xmx1024m

Its initial space (ie -Xms) is 1/64 of the physical memory, and the maximum space (-Xmx) is 1/4 of the physical memory. You can use the options provided by the JVM such as -Xmn -Xms -Xmx.

 

For example, the parameter setting reference of java jvm in 1G memory environment is given below:

JAVA_OPTS="-server -Xms800m -Xmx800m  -XX:PermSize=64M 
      -XX:MaxNewSize=256m -XX:MaxPermSize=128m -Djava.awt.headless=true "

JAVA_OPTS="-server -Xms768m -Xmx768m -XX:PermSize=128m 
      -XX:MaxPermSize=256m -XX:NewSize=192m -XX:MaxNewSize=384m"

CATALINA_OPTS="-server -Xms768m -Xmx768m -XX:PermSize=128m -XX:MaxPermSize=256m
      -XX:NewSize=192m -XX:MaxNewSize=384m"

The server is 1G memory: 
JAVA_OPTS="-server -Xms800m -Xmx800m -XX:PermSize=64M -XX:MaxNewSize=256m 
      -XX:MaxPermSize=128m -Djava.awt.headless=true "

The server is 64-bit, 2G memory: 
JAVA_OPTS='-server -Xms1024m -Xmx1536m -XX:PermSize=128M -XX:MaxNewSize=256m 
     -XX:MaxPermSize=256m'

 

-------------------  Solution 1   ---------------------------- -

Premise: It is the way to execute startup.bat to start tomcat

Linux server:

catalina.sh in the /usr/local/apache-tomcat-5.5.23/bin directory

Add: JAVA_OPTS='-Xms512m -Xmx1024m'

或者 JAVA_OPTS="-server -Xms800m -Xmx800m -XX:MaxNewSize=256m"

or CATALINA_OPTS="-server -Xms256m -Xmx300m"

 

Windows server:

Add at the top of catalina.bat

set JAVA_OPTS=-Xms128m -Xmx350m

Or set CATALINA_OPTS=-Xmx300M -Xms256M

(The difference is that one directly sets the jvm memory, and the other sets the tomcat memory. CATALINA_OPTS seems to be used indiscriminately with JAVA_OPTS)

 

Basic parameter description:

-client,-server

These two parameters are used to set which operating mode the virtual machine uses. It must be used as the first parameter. The client mode starts faster, but the runtime performance and memory management efficiency are not as good as the server mode, which is usually used for client applications. Conversely, the server mode starts slower than the client, but can achieve higher performance.

在windows上,缺省的虚拟机类型为client模式,如果要使用server模式,就需要在启动虚拟机时加-server参数,以获得更高性能,对服务器端应用,推荐采用server模式,尤其是多个CPU的系统。在Linux,Solaris上缺省采用server模式。

此外,在多cup下,建议用server模式

-Xms<size>

设置虚拟机可用内存堆的初始大小,缺省单位为字节,该大小为1024的整数倍并且要大于1MB,可用k(K)或m(M)为单位来设置较大的内存数。初始堆大小为2MB。加“m”说明是MB,否则就是KB了。

例如:-Xms6400K,-Xms256M

-Xmx<size>

设置虚拟机的最大可用大小,缺省单位为字节。该值必须为1024整数倍,并且要大于2MB。可用k(K)或m(M)为单位来设置较大的内存数。缺省堆最大值为64MB。

例如:-Xmx81920K,-Xmx80M

当应用程序申请了大内存运行时虚拟机抛出java.lang.OutOfMemoryError: Java heap space错误,就需要使用-Xmx设置较大的可用内存堆。

PermSize/MaxPermSize:定义Perm段的尺寸,即永久保存区域的大小,PermSize为JVM启动时初始化Perm的内存大小;MaxPermSize为最大可占用的Perm内存大小。在用户生产环境上一般将这两个值设为相同,以减少运行期间系统在内存申请上所花的开销。

如果用startup.bat启动tomcat,OK设置生效.够成功的分配200M内存.

 

-------------------  解决方案2  ------------------------

前提:是执行startup.bat启动tomcat的方式,手动设置Heap size

Windows服务器:

修改TOMCAT_HOME/bin/catalina.bat,在“echo "Using CATALINA_BASE: $CATALINA_BASE"”上面加入以下行:

 

set JAVA_OPTS=%JAVA_OPTS% -server -Xms800m -Xmx800m -XX:MaxNewSize=256m 

注:JAVA_OPTS是保留先前设置。
Linux服务器:

 

修改TOMCAT_HOME/bin/catalina.sh

在“echo "Using CATALINA_BASE: $CATALINA_BASE"”上面加入以下行:

 

JAVA_OPTS="$JAVA_OPTS -server -Xms800m -Xmx800m -XX:MaxNewSize=256m"

注:$JAVA_OPTS是保留先前设置。
 

 

-------------------  解决方案3  -----------------------------

前提:是执行windows的系统服务启动tomcat的方式

但是如果不是执行startup.bat启动tomcat而是利用windows的系统服务启动tomcat服务,上面的设置就不生效了,

就是说set JAVA_OPTS=-Xms128m -Xmx350m 没起作用.上面分配200M内存就OOM了..

 

windows服务执行的是bin\tomcat.exe.他读取注册表中的值,而不是catalina.bat的设置。

解决办法:

修改注册表HKEY_LOCAL_MACHINE\SOFTWARE\Apache Software Foundation\Tomcat Service Manager\Tomcat5\Parameters\JavaOptions

原值为

-Dcatalina.home="C:\ApacheGroup\Tomcat 5.0"

-Djava.endorsed.dirs="C:\ApacheGroup\Tomcat 5.0\common\endorsed"

-Xrs

加入 -Xms300m -Xmx350m

重起tomcat服务,设置生效

 

-------------------  解决方案4  -----------------------------

前提:是执行windows的系统服务启动tomcat的方式

在安装tomcat时若有勾选"NT Service(NT/2000/XP only)",则安装完成后在安装目录的"bin"目录里会有一个tomcat.exe的档案

先把tomcat的服务停掉,在命令列模式下(运行里输入CMD),将目录切换到tomcat的bin目录,用下面的命令把服务移除:

tomcat -uninstall "Apache Tomcat 4.1"

 

接下来,写个批处理:

 

set SERVICENAME=Apache Tomcat 4.1

set CATALINA_HOME=E:\Tomcat 4.1.24

set CLASSPATH=D:\j2sdk1.4.1_01\lib

set JAVACLASSPATH=%CLASSPATH%

set JAVACLASSPATH=%JAVACLASSPATH%;?TALINA_HOME%\bin\bootstrap.jar

set JAVACLASSPATH=%JAVACLASSPATH%;?TALINA_HOME%\common\lib\servlet.jar

set JAVACLASSPATH=%JAVACLASSPATH%;%JAVA_HOME%\lib\tools.jar

tomcat.exe -install "%SERVICENAME%" "%JAVA_HOME%\jre\bin\server\jvm.dll" -Djava.class.path="%JAVACLASSPATH%" -Dcatalina.home="?TALINA_HOME%" -Xms512m -Xmx768m -start org.apache.catalina.startup.Bootstrap -params start -stop org.apache.catalina.startup.Bootstrap -params stop -out "?TALINA_HOME%\logs\stdout.log" -err "?TALINA_HOME%\logs\stderr.log"

注意,从 tomcat.exe -install开始的是最后一行!不要手工回车换行把这一行分成了好几段。保存后在命令行下执行这个bat文件,注意执行的时候将“服务”窗口关闭。

 

第二种:永久保存区域溢出

原因分析:

PermGen space的全称是Permanent Generation space,是指内存的永久保存区域,这块内存主要是被JVM存放Class和Meta信息的,Class在被Loader时就会被放到PermGen space中,它和存放类实例(Instance)的Heap区域不同,GC(Garbage Collection)不会在主程序运行期对PermGen space进行清理,所以如果你的应用中有很CLASS的话,就很可能出现PermGen space错误,这种错误常见在web服务器对JSP进行pre compile的时候。如果你的WEB APP下都用了大量的第三方jar, 其大小超过了jvm默认的大小(4M)那么就会产生此错误信息了。但目前的hibernate和spring项目中也很容易出现这样的问题。可能是由于这些框架会动态class,而且jvm的gc是不会清理PemGen space的,超过了jvm默认的大小(4M),导致内存溢出。

建议:将相同的第三方jar文件移置到tomcat/shared/lib目录下,这样可以达到减少jar 文档重复占用内存的目的。

这一个一般是加大-XX:PermSize -XX:MaxPermSize 来解决问题。

-XX:PermSize 永久保存区域初始大小

-XX:PermSize 永久保存区域初始最大值

这一般结合第一条使用,比如 set JAVA_OPTS= -Xms1024m -Xmx1024m -XX:PermSize=128M -XX:PermSize=256M

有一点需要注意:java -Xmx***M version 命令来测试的最大堆内存是 -Xmx与 -XX:PermSize的和比如系统支持最大的jvm堆大小事1.5G,那 -Xmx1024m -XX:PermSize=768M 是无法运行的。

 

-----------------  解决方案1  -------------------------

Linux服务器:

在catalina.sh的第一行增加:

JAVA_OPTS=

-Xms64m

-Xmx256m

-XX:PermSize=128M

-XX:MaxNewSize=256m

-XX:MaxPermSize=256m

或者

在“echo "Using CATALINA_BASE:   $CATALINA_BASE"”上面加入以下行:

JAVA_OPTS="-server -XX:PermSize=64M -XX:MaxPermSize=128m

 

Windows服务器:

在catalina.bat的第一行增加:

set JAVA_OPTS=-Xms64m -Xmx256m -XX:PermSize=128M -XX:MaxNewSize=256m -XX:MaxPermSize=256m

 

-----------------  解决方案2  ------------------------

修改TOMCAT_HOME/bin/catalina.bat(Linux下为catalina.sh),在Java代码

“echo "Using CATALINA_BASE: $CATALINA_BASE"”上面加入以下行:  

set JAVA_OPTS=%JAVA_OPTS% -server -XX:PermSize=128M -XX:MaxPermSize=512m 

“echo "Using CATALINA_BASE: $CATALINA_BASE"”上面加入以下行:

set JAVA_OPTS=%JAVA_OPTS% -server -XX:PermSize=128M -XX:MaxPermSize=512m

catalina.sh下为:

 

JAVA_OPTS="$JAVA_OPTS -server -XX:PermSize=128M -XX:MaxPermSize=512m"

JAVA_OPTS="$JAVA_OPTS -server -XX:PermSize=128M -XX:MaxPermSize=512m"

 

第三种:无法创建新的线程。

这种现象比较少见,也比较奇怪,主要是和jvm与系统内存的比例有关。

这种怪事是因为JVM已经被系统分配了大量的内存(比如1.5G),并且它至少要占用可用内存的一半。有人发现,在线程个数很多的情况下,你分配给JVM的内存越多,那么,上述错误发生的可能性就越大。

原因分析

(从这个blog中了解到原因:http://hi.baidu.com/hexiong/blog/item/16dc9e518fb10c2542a75b3c.html):

每一个32位的进程最多可以使用2G的可用内存,因为另外2G被操作系统保留。这里假设使用1.5G给JVM,那么还余下500M可用内存。这500M内存中的一部分必须用于系统dll的加载,那么真正剩下的也许只有400M,现在关键的地方出现了:当你使用Java创建一个线程,在JVM的内存里也会创建一个Thread对象,但是同时也会在操作系统里创建一个真正的物理线程(参考JVM规范),操作系统会在余下的 400兆内存里创建这个物理线程,而不是在JVM的1500M的内存堆里创建。在jdk1.4里头,默认的栈大小是256KB,但是在jdk1.5里头,默认的栈大小为1M每线程,因此,在余下400M的可用内存里边我们最多也只能创建400个可用线程。

这样结论就出来了,要想创建更多的线程,你必须减少分配给JVM的最大内存。还有一种做法是让JVM宿主在你的JNI代码里边。

给出一个有关能够创建线程的最大个数的估算公式:

(MaxProcessMemory - JVMMemory - ReservedOsMemory) / (ThreadStackSize) = Number of threads

对于jdk1.5而言,假设操作系统保留120M内存:

1.5GB JVM: (2GB-1.5Gb-120MB)/(1MB) = ~380 threads

1.0GB JVM: (2GB-1.0Gb-120MB)/(1MB) = ~880 threads

在2000/XP/2003的boot.ini里头有一个启动选项,好像是:/PAE /3G ,可以让用户进程最大内存扩充至3G,这时操作系统只能占用最多1G的虚存。那样应该可以让JVM创建更多的线程。

因此这种情况需要结合操作系统进行相关调整。

因此:我们需要结合不同情况对tomcat内存分配进行不同的诊断才能从根本上解决问题。

检测当前JVM内存使用情况:

System.out.println("JVM MAX MEMORY: " + Runtime.getRuntime().maxMemory()/1024/1024+"M");

System.out.println("JVM IS USING MEMORY:" + Runtime.getRuntime().totalMemory()/1024/1024+"M");

System.out.println("JVM IS FREE MEMORY:" + Runtime.getRuntime().freeMemory()/1024/1024+"M");
 

这三个方法都是说JVM的内存使用情况而不是操作系统的内存;

maxMemory()这个方法返回的是java虚拟机(这个进程)能构从操作系统那里挖到的最大的内存,以字节为单位,如果在运行java程序的时候,没有添加-Xmx参数,那么就是64兆,也就是说maxMemory()返回的大约是64*1024*1024字节,这是java虚拟机默认情况下能从操作系统那里挖到的最大的内存。如果添加了-Xmx参数,将以这个参数后面的值为准,例如java -cp ClassPath -Xmx512m ClassName,那么最大内存就是512*1024*0124字节。

totalMemory()这个方法返回的是java虚拟机现在已经从操作系统那里挖过来的内存大小,也就是java虚拟机这个进程当时所占用的所有内存。如果在运行java的时候没有添加-Xms参数,那么,在java程序运行的过程的,内存总是慢慢的从操作系统那里挖的,基本上是用多少挖多少,直挖到maxMemory()为止,所以totalMemory()是慢慢增大的。如果用了-Xms参数,程序在启动的时候就会无条件的从操作系统中挖-Xms后面定义的内存数,然后在这些内存用的差不多的时候,再去挖。

freeMemory()是什么呢,刚才讲到如果在运行java的时候没有添加-Xms参数,那么,在java程序运行的过程的,内存总是慢慢的从操作系统那里挖的,基本上是用多少挖多少,但是java虚拟机100%的情况下是会稍微多挖一点的,这些挖过来而又没有用上的内存,实际上就是freeMemory(),所以freeMemory()的值一般情况下都是很小的,但是如果你在运行java程序的时候使用了-Xms,这个时候因为程序在启动的时候就会无条件的从操作系统中挖-Xms后面定义的内存数,这个时候,挖过来的内存可能大部分没用上,所以这个时候freeMemory()可能会有些

 

--------------------  解决方案  --------------------------

JVM堆大小的调整

Sun HotSpot 1.4.1使用分代收集器,它把堆分为三个主要的域:新域、旧域以及永久域。Jvm生成的所有新对象放在新域中。一旦对象经历了一定数量的垃圾收集循环后,便获得使用期并进入旧域。在永久域中jvm则存储class和method对象。就配置而言,永久域是一个独立域并且不认为是堆的一部分。

下面介绍如何控制这些域的大小。可使用-Xms和-Xmx 控制整个堆的原始大小或最大值。

下面的命令是把初始大小设置为128M:

java –Xms128m

–Xmx256m为控制新域的大小,可使用-XX:NewRatio设置新域在堆中所占的比例。

下面的命令把整个堆设置成128m,新域比率设置成3,即新域与旧域比例为1:3,新域为堆的1/4或32M:

java –Xms128m –Xmx128m

–XX:NewRatio =3可使用-XX:NewSize和-XX:MaxNewsize设置新域的初始值和最大值。

下面的命令把新域的初始值和最大值设置成64m:

java –Xms256m –Xmx256m –Xmn64m

永久域默认大小为4m。运行程序时,jvm会调整永久域的大小以满足需要。每次调整时,jvm会对堆进行一次完全的垃圾收集。

使用-XX:MaxPerSize标志来增加永久域搭大小。在WebLogic Server应用程序加载较多类时,经常需要增加永久域的最大值。当jvm加载类时,永久域中的对象急剧增加,从而使jvm不断调整永久域大小。为了避免调整,可使用-XX:PerSize标志设置初始值。

下面把永久域初始值设置成32m,最大值设置成64m。

java -Xms512m -Xmx512m -Xmn128m -XX:PermSize=32m -XX:MaxPermSize=64m

默认状态下,HotSpot在新域中使用复制收集器。该域一般分为三个部分。第一部分为Eden,用于生成新的对象。另两部分称为救助空间,当Eden充满时,收集器停止应用程序,把所有可到达对象复制到当前的from救助空间,一旦当前的from救助空间充满,收集器则把可到达对象复制到当前的to救助空间。From和to救助空间互换角色。维持活动的对象将在救助空间不断复制,直到它们获得使用期并转入旧域。使用-XX:SurvivorRatio可控制新域子空间的大小。

同NewRation一样,SurvivorRation规定某救助域与Eden空间的比值。比如,以下命令把新域设置成64m,Eden占32m,每个救助域各占16m:

java -Xms256m -Xmx256m -Xmn64m -XX:SurvivorRation =2

如前所述,默认状态下HotSpot对新域使用复制收集器,对旧域使用标记-清除-压缩收集器。在新域中使用复制收集器有很多意义,因为应用程序生成的大部分对象是短寿命的。理想状态下,所有过渡对象在移出Eden空间时将被收集。如果能够这样的话,并且移出Eden空间的对象是长寿命的,那么理论上可以立即把它们移进旧域,避免在救助空间反复复制。但是,应用程序不能适合这种理想状态,因为它们有一小部分中长寿命的对象。最好是保持这些中长寿命的对象并放在新域中,因为复制小部分的对象总比压缩旧域廉价。为控制新域中对象的复制,可用-XX:TargetSurvivorRatio控制救助空间的比例(该值是设置救助空间的使用比例。如救助空间位1M,该值50表示可用500K)。该值是一个百分比,默认值是50。当较大的堆栈使用较低的sruvivorratio时,应增加该值到80至90,以更好利用救助空间。用-XX:maxtenuring threshold可控制上限。

为放置所有的复制全部发生以及希望对象从eden扩展到旧域,可以把MaxTenuring Threshold设置成0。设置完成后,实际上就不再使用救助空间了,因此应把SurvivorRatio设成最大值以最大化Eden空间,设置如下:

java … -XX:MaxTenuringThreshold=0 –XX:SurvivorRatio=50000 …

垃圾回收描述:

垃圾回收分多级,0级为全部(Full)的垃圾回收,会回收OLD段中的垃圾;1级或以上为部分垃圾回收,只会回收Young中的垃圾,内存溢出通常发生于OLD段或Perm段垃圾回收后,仍然无内存空间容纳新的Java对象的情况。

当一个URL被访问时,内存申请过程如下:

A. JVM会试图为相关Java对象在Eden中初始化一块内存区域

B. 当Eden空间足够时,内存申请结束。否则到下一步

C. JVM试图释放在Eden中所有不活跃的对象(这属于1或更高级的垃圾回收);释放后若Eden空间仍然不足以放入新对象,则试图将部分Eden中活跃对象放入Survivor区/OLD区

D. Survivor区被用来作为Eden及OLD的中间交换区域,当OLD区空间足够时,Survivor区的对象会被移到Old区,否则会被保留在Survivor区

E. 当OLD区空间不够时,JVM会在OLD区进行完全的垃圾收集(0级)

F. 完全垃圾收集后,若Survivor及OLD区仍然无法存放从Eden复制过来的部分对象,导致JVM无法在Eden区为新对象创建内存区域,则出现”out of memory错误”

Java堆相关参数:

ms/mx:定义YOUNG+OLD段的总尺寸,ms为JVM启动时YOUNG+OLD的内存大小;mx为最大可占用的YOUNG+OLD内存大小。在用户生产环境上一般将这两个值设为相同,以减少运行期间系统在内存申请上所花的开销。

NewSize/MaxNewSize:定义YOUNG段的尺寸,NewSize为JVM启动时YOUNG的内存大小;MaxNewSize为最大可占用的YOUNG内存大小。在用户生产环境上一般将这两个值设为相同,以减少运行期间系统在内存申请上所花的开销。

PermSize/MaxPermSize:定义Perm段的尺寸,PermSize为JVM启动时Perm的内存大小;MaxPermSize为最大可占用的Perm内存大小。在用户生产环境上一般将这两个值设为相同,以减少运行期间系统在内存申请上所花的开销。

SurvivorRatio:设置Survivor空间和Eden空间的比例

例:

MEM_ARGS="-Xms512m -Xmx512m -XX:NewSize=256m -XX:MaxNewSize=256m -XX:PermSize=128m -XX:MaxPermSize=128m -XX:SurvivorRatio=6"

在上面的例子中:

YOUNG+OLD: 512M

YOUNG: 256M

Perm: 128M

Eden: YOUNG*6/(6+1+1)=192M

Survivor: YOUNG/(6+1+1)=32M

Java堆的总尺寸=YOUNG+OLD+Perm=640M

 

转:http://www.tuicool.com/articles/Fve6zmI

 

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326544971&siteId=291194637