从 JVM 常见异常来看 Tomcat 中内存的设置

jvm内存设置
tomcat内存优化
tomcat内存设置

#1

##一、Tomcat和JDK的关系

1、Tomcat本身不能直接在计算机上运行,需要依赖于硬件基础之上的操作系统和一个java 虚拟机。

2、JAVA程序启动时JVM都会分配一个初始内存和最大内存给这个应用程序。这个初始内存和最大内存在一定程度都会影响程序的性能。比如说在应用程序用到最大内存的时候,JVM是要先去做垃圾回收的动作,释放被占用的一些内存。所以想调整Tomcat的启动时初始内存和最大内存就需要向JVM声明, 一般的JAVA程序都可以通过-Xms -Xmx来调整应用程序的初始内存和最大内存,这两个值的大小一般根据需要进行设置。

3、为什么一般把-Xms和-Xmx设置成一样大?

初始化堆的大小是虚拟机在启动时向系统申请的内存的大小。一般而言,这个参数不重要。但是有的应用程序在大负载的情况下会急剧地占用更多的内存,此时这个参数就是显得非常重要,如果虚拟机启动时设置使用的内存比较小而在这种情况下有许多对象要进行初始化,虚拟机就必须重复地增加内存来满足使用。由于这种原因,我们一般把-Xms和-Xmx设为一样大,而堆的最大值受限于系统使用的物理内存。一般访问量较大的应用程序会使用许多生命周期较长的对象,内存使用有可能迅速地增长。当应用程序需要的内存超出堆的最大值时虚拟机就会提示内存溢出,并且导致应用服务崩溃。因此一般建议堆的最大值设置为可用内存的最大值的80%。

Tomcat默认可以使用的内存为128MB,在较大型的应用项目中,这点内存是不够的,会造成内存溢出的异常。

##二、常见的Java内存溢出有以下三种
注意:这些内存溢出情况适用于JDK 1.5-JDK1.7,由于JDK1.8对于JVM内存区域重新进行了划分,因此设置会有些变化。大家可以参考:Java 8: 从永久代(PermGen)到元空间(Metaspace)Java 8新特性探究(9):跟OOM:Permgen说再见吧

1、java.lang.OutOfMemoryError: Java heap space ----JVM Heap(堆)溢出

JVM在启动的时候会自动设置JVM Heap的值,其初始空间(即-Xms)是物理内存的1/64,最大空间(-Xmx)不可超过物理内存。

堆内存可以利用JVM提供的-Xmn -Xms -Xmx等选项进行设置。Heap的大小是Young Generation 和Tenured Generaion 之和。

在JVM中如果98%的时间是用于GC,且可用的Heap size 不足2%的时候将抛出此异常信息。

解决方法:手动设置JVM Heap(堆)的大小。

2、 java.lang.OutOfMemoryError: PermGen space ---- PermGen space溢出。

PermGen space的全称是Permanent Generation space,是指内存的永久保存区域。

为什么会内存溢出,这是由于这块内存主要是被JVM存放Class和Meta信息的,Class在被Load的时候被放入PermGen space区域,它和存放Instance的Heap区域不同。sun的GC不会在主程序运行期对PermGen space进行清理,所以如果你的APP会载入很多CLASS的话,就很可能出现PermGen space溢出。

解决方法:手动设置MaxPermSize大小。

3、java.lang.StackOverflowError ---- 栈溢出

JVM依然是采用栈式的虚拟机,这个和C和Pascal都是一样的。函数的调用过程都体现在堆栈和退栈上了。调用构造函数的 “层”太多了,以致于把栈区溢出了。

通常来讲,一般栈区远远小于堆区的,因为函数调用过程往往不会多于上千层,而即便每个函数调用需要 1K的空间(这个大约相当于在一个C函数内声明了256个int类型的变量),那么栈区也不过是需要1MB的空间。通常栈的大小是1-2MB的。通常递归即使递归的层次不会过多,也很容易溢出。

解决方法:修改程序。

##三、怎样修改Tomcat的初始化内存

有如下修改方法:

1、Windows下,在文件/bin/catalina.bat,Unix下,在文件/bin/catalina.sh的前面,增加如下设置:

Windows下:

set JAVA_OPTS=-server -Xms2048m -Xmx2048m -Xss512k -XX:PermSize=512M -XX:MaxNewSize=512m -XX:MaxPermSize=780m

Linux下:注意没有set,但是有引号

JAVA_OPTS="-Xms256m -Xmx512m -Xss1024K -XX:PermSize=128m -XX:MaxPermSize=256m"

参数说明:

-server:一定要作为第一个参数,在多个CPU时性能佳

JAVA_OPTS=’-Xms【初始化堆内存大小】 -Xmx【可以使用的最大堆内存】’

需要把这个两个参数值调大。例如:

JAVA_OPTS=’-Xms256m -Xmx512m’

表示初始化内存为256MB,可以使用的最大内存为512MB。必须要有m(表示兆,如果没有,则是KB)

2、环境变量中设变量名:JAVA_OPTS 变量值:-Xms512m -Xmx512m

3、前两种方法针对的是bin目录下有catalina.bat的情况(比如 直接解压的Tomcat等),但是有些安装版的Tomcat下没有catalina.bat,这个时候可以采用如下方法,当然这个方法也是最通用的方法: 打开tomcatHome//bin//tomcat7w.exe,点击Java选项卡,然后将会发现其中有这么两项:Initial memory pool和Maximum memory pool,其中Initial memory pool这个就是初始化设置的内存的大小,Maximum memory pool这个是最大内存的大小,设置完了就按确定然后再重启TOMCAT你就会发现tomcat中jvm可用的内存改变了。

4、Eclipse中修改JVM内存

Eclipse->Window->Preferences->Server->Runtime Environments->选中Apache Tomcat v5.0->点击Edit按钮->在弹出对话框里点击JRE后面的Installed JREs按钮->在弹出对话框中选中tomcat使用的那个JRE->点击Edit按钮->在弹出对话框中,找到Default VM Arguments,并在输入框中输入:-Xms110M -Xmx110M:

然后我们在jvisualvm控制台可以看到:

其实除了进行常见的-Xms和-Xmx的设置外,还能对jvm参数进行如下的设置,下面接着说明jvm参数的常用配置。

##四、jvm参数说明

  • -server:一定要作为第一个参数,在多个CPU时性能佳。

  • -Xms:java Heap初始大小。 默认是物理内存的1/64。

  • -Xmx:java heap最大值。不可超过物理内存。

  • -XX:PermSize:设定内存的永久保存区初始大小,用visualvm.exe可见其缺省值为64M。

  • -XX:MaxPermSize:设定内存的永久保存区最大大小,用visualvm.exe可见其缺省值为64M。

  • -XX:SurvivorRatio=2:生还者池的大小,默认是2,如果垃圾回收变成了瓶颈,您可以尝试设置该值。

  • -XX:NewSize:新生成的池的初始大小。缺省值为2M。

  • -XX:MaxNewSize: 新生成的池的最大大小。缺省值为32M。

如果 JVM 的堆大小大于 1GB,则应该使用值:-XX:newSize=640m -XX:MaxNewSize=640m -XX:SurvivorRatio=16,或者将堆的总大小的 50% 到 60% 分配给新生成的池。调大新对象区,减少Full GC次数。

  • +XX:AggressiveHeap 会使得 Xms没有意义。这个参数让jvm忽略Xmx参数,疯狂地吃完一个G物理内存,再吃尽一个G的swap。

  • -Xss:每个线程的Stack大小。

  • -verbose:gc 显示垃圾收集信息

  • -Xloggc:gc.log 指定垃圾收集日志文件

  • -Xmn:young generation的heap大小,一般设置为Xmx的3、4分之一

  • -XX:+UseParNewGC :缩短minor收集的时间

  • -XX:+UseConcMarkSweepGC :缩短major收集的时间 此选项在Heap Size 比较大而且Major收集时间较长的情况下使用更合适。

  • -XX:UserParNewGC 可用来设置并行收集【多CPU】

  • -XX:ParallelGCThreads 可用来增加并行度【多CPU】

  • -XX:UseParallelGC 设置后可以使用并行垃圾收集器【多CPU】

##五、关于垃圾收集的一些经验

另外需要考虑的是Java提供的垃圾回收机制。虚拟机的堆大小决定了虚拟机花费在收集垃圾上的时间和频度。收集垃圾可以接受的速度与应用有关,应该通过分析实际的垃圾收集的时间和频率来调整。如果堆的大小很大,那么完全垃圾收集(Full GC)就会很慢, 但是频度会降低。如果你把堆的大小和内存的需要配置一致,完全收集(Full GC)就很快,但是会更加频繁。调整堆大小的的目的是最小化垃圾收集的时间,以在特定的时间内最大化处理客户的请求。在基准测试的时候,为保证最好的性能,要把堆的大小设大,保证垃圾收集不在整个基准测试的过程中出现。

如果系统花费很多的时间收集垃圾,请减小堆大小。一次完全的垃圾收集应该不超过 3-5 秒。如果垃圾收集成为瓶颈,那么需要指定代的大小,检查垃圾收集的详细输出,研究垃圾收集参数对性能的影响。一般说来,你应该使用物理内存的 80% 作为堆大小。当增加处理器时,记得增加内存,因为分配可以并行进行,而垃圾收集不是并行的。

一个要注意的地方:建议把内存的最高值跟最低值的差值缩小,不然会浪费很多内存的, 最低值加大 ,最高值可以随便设,但是要根据实际的物理内存 ,如果内存设置太大了,比如设置了512M最大内存,但如果没有512M可用内存,Tomcat就不能启动,还有可能存在内存被系统回收,终止进程的情况。

##六、Tomcat 7中怎样查看内存的使用情况

1、在$Tomcat_Home/conf/tomcat-users.xml下配置一个tomcat user,并使用http://localhost:8080/manager 访问。

<tomcat-users>
  <role rolename="tomcat"/>
  <role rolename="role1"/>
  <role rolename="manager-gui"/>
  <user username="tomcat" password="tomcat" roles="tomcat"/>
  <user username="both" password="tomcat" roles="tomcat,role1"/>
  <user username="role1" password="tomcat" roles="role1"/>
  <user username="swang6" password="swang6" roles="manager-gui"/>
</tomcat-users>

2、点击右方的Server Status,可以看到如下数据:


#2

非常不错的文章,追求卓越:+1:


#3

总结的很全,很细心!