文章目录
- Tomcat 优化
- 1. Tomcat 配置文件参数优化
- 2. Java 虚拟机(JVM)调优
- 3. 常见错误说明
- 3.1 `java.lang.OutOfMemoryError: Java heap space`——JVM Heap(堆)溢出
- 3.2 `java.lang.OutOfMemoryError: PermGen space`——PermGen space 溢出
- 3.3 `java.lang.StackOverflowError`——栈溢出
Tomcat 优化
Tomcat 默认安装下的配置并不适合生产环境,它可能会频繁出现假死现象,需要频繁重启。只有通过不断的压力测试和优化,才能让它在高效稳定的状态下运行。优化主要包括三个方面:操作系统优化(内核参数优化)、Tomcat 配置文件参数优化以及 Java 虚拟机(JVM)调优。其中,最难理解的就是 JVM 调优。
1. Tomcat 配置文件参数优化
- maxThreads:Tomcat 使用线程来处理每个请求,这个值表示 Tomcat 可以创建的最大线程数,默认值是 200。
- minSpareThreads:最小空闲线程数,Tomcat 启动时的初始化线程数,即使没有请求也会开启这些空闲线程等待,默认值是 10。
- maxSpareThreads:最大备用线程数,一旦创建的线程超过这个值,Tomcat 就会关闭不再需要的 socket 线程。默认值是 -1(无限制),通常不需要指定。
- URIEncoding:指定 Tomcat 容器的 URL 编码格式。Tomcat 的语言编码格式配置比其它 Web 服务器软件稍复杂,需要分别指定。
- connnectionTimeout:网络连接超时,单位为毫秒。设置为 0 表示永不超时,这样设置有隐患。通常设置为 20000 毫秒比较合适。
- enableLookups:是否反查域名,以返回远程主机的主机名。可以设置为
true
或false
。如果设置为false
,则直接返回 IP 地址。为了提高处理能力,建议设置为false
。 - disableUploadTimeout:上传时是否使用超时机制,建议设置为
true
。 - connectionUploadTimeout:上传超时时间。由于文件上传可能需要消耗较多时间,可以根据业务需要自行调整,以确保 Servlet 有足够的时间完成执行。此参数需与
disableUploadTimeout
配合使用。 - acceptCount:指定当所有可用线程都被使用时,可以传入的连接请求的最大队列长度,超过这个数量的请求将不予处理。默认为 100 个。
- compression:是否对响应的数据进行 GZIP 压缩。
off
表示禁止压缩,on
表示允许压缩(文本将被压缩),force
表示所有情况下都进行压缩。默认值为off
。压缩数据可以有效减少页面大小,一般可以减少 1/3 左右,从而节省带宽。 - compressionMinSize:表示压缩响应的最小值,只有当响应报文大小大于这个值时才会进行压缩。如果开启了压缩功能,默认值是 2048。
- compressableMimeType:压缩类型,指定对哪些类型的文件进行数据压缩。
- noCompressionUserAgents=“gozilla, traviata”:对于以下的浏览器,不启用压缩。
如果已经对代码进行了动静分离,静态页面和图片等数据就不需要 Tomcat 处理了,因此不需要在 Tomcat 中配置压缩。由于这里只使用一台 Tomcat 服务器,且压力测试针对的是 Tomcat 首页(包含图片和静态资源文件),因此启用了压缩。
以上是一些常用的配置参数,还有更多其他的参数可以进一步优化。关于 HTTP Connector 和 AJP Connector 的详细参数,可以参考 Tomcat 官方文档。
2. Java 虚拟机(JVM)调优
Tomcat 启动时的优化参数,实际上就是 JVM 的优化。Tomcat 是 Java 程序,运行在 JVM 之上,因为它的启动其实就是一个 Java 命令行。我们需要对这个 Java 命令行进行调优。不论是 YGC 还是 Full GC,GC 都会导致程序运行中断。正确选择不同的 GC 策略并调整 JVM 和 GC 的参数,可以极大减少由于 GC 工作导致的程序运行中断,从而适当提高 Java 程序的工作效率。调整 GC 是一个极为复杂的过程,因为各个程序具有不同的特点,且运行在不同配置的机器上(如 CPU 数量、内存大小不同),所以使用的 GC 种类也会不同。下面是 JVM 参数的详细说明:
-server:一定要作为第一个参数,只要 Tomcat 是运行在生产环境中,这个参数必须加上,否则后面的参数不会生效。-Xms:表示 Java 初始化堆的大小,-Xms 与 -Xmx 设成一样的值,避免 JVM 反复重新申请内存,导致性能大起大落。默认值为物理内存的 1/64,默认(MinHeapFreeRatio 参数可以调整)空余堆内存小于 40% 时,JVM 就会增大堆直到 -Xmx 的最大限制。-Xmx:表示最大 Java 堆大小。当应用程序需要的内存超出堆的最大值时,虚拟机会提示内存溢出并导致应用服务崩溃。因此,一般建议堆的最大值设置为物理内存的最大值的 50%。-XX:NewSize:设置新生代内存大小。-XX:MaxNewSize:设置最大新生代内存大小。-XX:PermSize:设置持久代内存大小。-XX:MaxPermSize:设置最大值持久代内存大小,永久代不属于堆内存,堆内存只包含新生代和老年代。-XX:+AggressiveOpts:作用如其名(aggressive),启用这个参数后,每当 JDK 版本升级时,JVM 都会使用最新加入的优化技术(如果有的话)。-XX:+UseBiasedLocking:启用一个优化的线程锁。在 appserver 中,每个 http 请求就是一个线程,有些请求短,有些请求长,会出现请求排队的现象,甚至还会出现线程阻塞。这个优化的线程锁使得 appserver 内的线程处理自动进行最优调配。-XX:+DisableExplicitGC:禁止显示调用 “System.gc()”。每次操作结束时手动调用 System.gc(),会导致系统响应时间严重降低。像 Xms 和 Xmx 中一样,调用 GC 会导致系统 JVM 的性能大起大落。-XX:+UseParNewGC:对新生代采用多线程并行回收,加快回收速度。需要注意的是,在最新 JVM 版本中,当使用 -XX:+UseConcMarkSweepGC 时,-XX:+UseParNewGC 会自动开启。因此,如果年轻代的并行 GC 不想开启,可以通过设置 -XX:-UseParNewGC 来关闭。-XX:MaxTenuringThreshold:设置垃圾最大年龄。如果设置为 0,则新生代对象不经过 Survivor 区,直接进入老年代。对于老年代比较多的应用(需要大量常驻内存的应用),可以提高效率。如果将此值设置为较大值,则新生代对象会在 Survivor 区进行多次复制,增加对象在新生代的存活时间,减少 Full GC 的频率,从而提高服务稳定性。该参数只有在串行 GC 时有效,此值是通过 jprofiler 监控后得到的理想值,不能直接照抄。-XX:+CMSParallelRemarkEnabled:在使用 UseParNewGC 的情况下,尽量减少 mark 的时间。-XX:+UseCMSCompactAtFullCollection:在使用 concurrent gc 的情况下,防止 memory fragmention,对 live object 进行整理,使 memory 碎片减少。-XX:LargePageSizeInBytes:指定 Java heap 的分页页面大小。内存页的大小不应设置过大,否则会影响 Perm 的大小。-XX:+UseFastAccessorMethods:使用 get,set 方法转成本地代码,优化原始类型的快速访问。-XX:+UseCMSInitiatingOccupancyOnly:仅在 old generation 使用完初始化比例后,才启动 concurrent collector。-Duser.timezone=Asia/Shanghai:设置用户所在时区。-Djava.awt.headless=true:这个参数一般放在最后使用。有时我们在 J2EE 工程中使用一些图表工具(如 jfreechart)用于在 Web 网页中输出 GIF/JPG 流时,在 Windows 环境下通常不会有问题,但在 Linux/Unix 环境下,可能会出现 exception,导致图片无法显示。加上这个参数可以避免这种情况。-Xmn:设置新生代内存大小。注意:此处的大小是 eden + 2 survivor space。与 jmap-heap 中显示的 New gen 是不同的。整个堆大小 = 新生代大小 + 老年代大小 + 永久代大小。在保持堆大小不变的情况下,增大新生代后,将会减小老年代大小。此值对系统性能影响较大,Sun 官方推荐配置为整个堆的 3/8。-XX:CMSInitiatingOccupancyFraction:当堆满时,并行收集器会开始进行垃圾收集。例如,当没有足够空间容纳新分配或提升的对象时,对于 CMS 收集器,长时间等待是不可取的,因为并发垃圾收集期间应用会持续运行并分配对象。因此,为了在应用程序使用完内存之前完成垃圾收集周期,CMS 收集器要比并行收集器更早启动。由于不同应用的对象分配模式不同,JVM 会收集并分析运行时数据,以决定何时启动 CMS 垃圾收集周期。此参数设置需根据 Xmn 值进行关联调整,以避免垃圾回收时出现 promotion failed。-XX:+CMSIncrementalMode:该标志将开启 CMS 收集器的增量模式。增量模式会经常暂停 CMS 过程,完全让应用程序线程运行。因此,收集器将花更长时间完成收集周期。除非通过测试发现正常 CMS 周期对应用程序线程干扰太大,否则不建议使用增量模式。由于现代服务器有足够处理器来支持并发垃圾收集,所以很少发生此种情况,主要用于但 CPU 资源紧张的情况。-XX:NewRatio:设置年轻代(包括 Eden 和两个 Survivor 区)与年老代的比值(不包括持久代)。-XX:NewRatio=4 表示年轻代与年老代比值为 1:4,年轻代占整个堆栈的 1/5。Xms=Xmx 并且设置了 Xmn 的情况下,该参数可不进行设置。-XX:SurvivorRatio:设置 Eden 区与 Survivor 区的大小比值。设置为 8,表示 2 个 Survivor 区与 1 个 Eden 区的比值为 2:8,即 1 个 Survivor 区占整个年轻代大小的 1/10。-XX:+UseSerialGC:设置串行收集器。-XX:+UseParallelGC:设置并行收集器。此配置仅对年轻代有效,即年轻代使用并行收集,而年老代仍使用串行收集。-XX:+UseParallelOldGC:设置年老代垃圾收集方式为并行收集。JDK6.0 之后支持对年老代并行收集。-XX:ConcGCThreads:早期 JVM 版本中此参数名为 -XX:ParallelCMSThreads,定义并发 CMS 过程中使用的线程数。更多线程数会加快并发 CMS 过程,但也会带来额外的同步开销。应通过测试确定增加 CMS 线程数是否能提高性能。如果未设置此参数,JVM 会根据 -XX:ParallelGCThreads 的值自动计算并发 CMS 线程数。-XX:ParallelGCThreads:设置并行收集器的线程数,即同时进行垃圾收集的线程数。建议此值配置与 CPU 数目相等。-XX:OldSize:设置 JVM 启动时分配的老年代内存大小,类似于 -XX:NewSize 设置的新生代内存初始大小。
3. 常见错误说明
3.1 java.lang.OutOfMemoryError: Java heap space
——JVM Heap(堆)溢出
JVM 在启动时会自动设置 JVM Heap 的大小。其初始空间(即 -Xms
)是物理内存的 1/64,最大空间(-Xmx
)不可超过物理内存。可以利用 JVM 提供的 -Xmn
、-Xms
、-Xmx
等选项来设置 Heap 的大小。Heap 的大小是 Young Generation 和 Tenured Generation 之和。如果在 JVM 中 98% 的时间用于 GC 且可用的 Heap size 不足 2% 时,会抛出此异常信息。
解决方法:手动设置 JVM Heap(堆)的大小。
3.2 java.lang.OutOfMemoryError: PermGen space
——PermGen space 溢出
PermGen space 的全称是 Permanent Generation space,是指内存的永久保存区域。内存溢出的原因是这块内存主要被 JVM 存放 Class 和 Meta 信息。当 Class 被加载时,其信息被存入 PermGen space 区域,而 PermGen space 与存放 Instance 的 Heap 区域不同,Sun 的 GC 不会在主程序运行期间对 PermGen space 进行清理。所以,如果应用程序会加载大量 CLASS,很可能会出现 PermGen space 溢出。
解决方法:手动设置 MaxPermSize 大小。
3.3 java.lang.StackOverflowError
——栈溢出
JVM 使用栈式虚拟机,函数调用过程会体现在堆栈和退栈中。如果调用构造函数的层数太多,可能会导致栈区溢出。通常栈区远小于堆区,因为函数调用过程不会超过上千层,即便每个函数调用需要 1K 空间(大约相当于在一个 C 函数内声明 256 个 int 类型变量),栈区通常需要的空间也不过 1MB。栈大小一般在 1-2MB 之间。递归调用的层次不宜过多,否则容易溢出。
过上千层,即便每个函数调用需要 1K 空间(大约相当于在一个 C 函数内声明 256 个 int 类型变量),栈区通常需要的空间也不过 1MB。栈大小一般在 1-2MB 之间。递归调用的层次不宜过多,否则容易溢出。
解决方法:修改程序。