当前位置: 首页 > news >正文

Tomcat 性能优化终极指南

Apache Tomcat是一个广泛使用的开源Java Servlet容器,它为运行Java应用提供了强大的支持。然而,随着用户数量的增加和业务逻辑复杂度的上升,Tomcat默认配置可能无法满足高负载的需求。本文将探讨一些常见的Tomcat性能优化技巧,帮助您提高应用的响应速度和稳定性。

Tomcat 性能优化

在目前流行的互联网架构中,Tomcat 在目前的网络编程中是举足轻重的,由于Tomcat的运行依赖于
JVM,从虚拟机的角度把Tomcat的调整分为外部环境调优 JVM 和 Tomcat 自身调优两部分。

JVM 组成

[root@ubuntu2404 ~]#java -version
java version "21.0.7" 2025-04-15 LTS
Java(TM) SE Runtime Environment (build 21.0.7+8-LTS-245)
Java HotSpot(TM) 64-Bit Server VM (build 21.0.7+8-LTS-245, mixed mode, sharing)[root@ubuntu2404 ~]#java -version
openjdk version "1.8.0_462"
OpenJDK Runtime Environment (build 1.8.0_462-8u462-ga~us1-0ubuntu2~24.04.2-b08)
OpenJDK 64-Bit Server VM (build 25.462-b08, mixed mode)

JVM 组成

在这里插入图片描述
JVM 组成部分

  • 类加载子系统: 使用Java语言编写.java Source Code文件,通过javac编译成.class Byte Code文
    件。class loader类加载器将所需所有类加载到内存,必要时将类实例化成实例
  • 运行时数据区: 最消耗内存的空间,需要优化
  • 执行引擎: 包括JIT (JustInTimeCompiler)即时编译器, GC垃圾回收器
  • 本地方法接口: 将本地方法栈通过JNI(Java Native Interface)调用Native Method Libraries, 比
    如:C,C++库等,扩展Java功能,融合不同的编程语言为Java所用。

JVM运行时数据区域由下面部分构成:
Method Area (线程共享):方法区是所有线程共享的内存空间,存放已加载的类信息(构造方法,接
口定义),常量(final),静态变量(static), 运行时常量池等。但实例变量存放在堆内存中. 从JDK8开始此
空间由永久代改名为元空间 metaspace。
heap (线程共享):堆在虚拟机启动时创建,存放创建的所有对象信息。如果对象无法申请到可用内
存将抛出OOM异常.堆是靠GC垃圾回收器管理的,通过-Xmx -Xms 指定最大堆和最小堆空间大小。
Java stack (线程私有):Java栈是每个线程会分配一个栈,存放java中8大基本数据类型,对象引用,实例的本地变量,方法参数和返回值等,基于FILO()(First In Last Out),每个方法为一个栈帧。
Program Counter Register (线程私有):PC寄存器就是一个指针,指向方法区中的方法字节码,每
一个线程用于记录当前线程正在执行的字节码指令地址。由执行引擎读取下一条指令.因为线程需要
切换,当一个线程被切换回来需要执行的时候,知道执行到哪里了。
Native Method stack (线程私有):本地方法栈为本地方法执行构建的内存空间,存放本地方法
执行时的局部变量、操作数等。
所谓本地方法,使用native 关健字修饰的方法,比如:Thread.sleep方法. 简单的说是非Java实现的方
法,例如操作系统的C编写的库提供的本地方法,Java调用这些本地方法接口执行。但是要注意,
本地方法应该避免直接编程使用,因为Java可能跨平台使用,如果用了Windows API,换到了
Linux平台部署就有了问题

虚拟机

目前Oracle官方使用的是HotSpot, 它最早由一家名为"Longview Technologies"公司设计,使用了很
多优秀的设计理念和出色的性能,1997年该公司被SUN公司收购。后来随着JDK一起发布了HotSpot
VM。目前HotSpot是最主要的 JVM。
安卓程序需要运行在JVM上,而安卓平台使用了Google自研的Java虚拟机——Dalvid,适合于内存、处理器能力有限系统。

GC (Garbage Collection) 垃圾收集器

在堆内存中如果创建的对象不再使用,仍占用着内存,此时即为垃圾.需要即使进行垃圾回收,从而释放内存空间给其它对象使用。
其实不同的开发语言都有垃圾回收问题,C,C++需要程序员人为回收,造成开发难度大,容易出错等问题,但执行效率高,而JAVA和Python中不需要程序员进行人为的回收垃圾,而由JVM或相关程序自动回收垃圾,减轻程序员的开发难度,但可能会造成执行效率低下。
堆内存里面经常创建、销毁对象,内存也是被使用、被释放。如果不妥善处理,一个使用频繁的进程,可能会出现虽然有足够的内存容量,但是无法分配出可用内存空间,因为没有连续成片的内存了,内存全是碎片化的空间。
所以需要有合适的垃圾回收机制,确保正常释放不再使用的内存空间,还需要保证内存空间尽可能的保持一定的连续。
对于垃圾回收,需要解决三个问题

  • 哪些是垃圾要回收
  • 怎么回收垃圾
  • 什么时候回收垃圾

Garbage 垃圾确定方法

  • 引用计数: 每一个堆内对象上都与一个私有引用计数器,记录着被引用的次数,引用计数清零,该对象所占用堆内存就可以被回收。循环引用的对象都无法将引用计数归零,就无法清除。Python中即使用此种方式

  • 根搜索(可达)算法 Root Searching
    在这里插入图片描述

垃圾回收基本算法

标记-清除 Mark-Sweep

分垃圾标记阶段和内存释放两个阶段

  • 标记阶段,找到所有可访问对象打个标记。清理阶段,遍历整个堆
  • 对未标记对象(即不再使用的对象)逐一进行清理
    在这里插入图片描述
    在这里插入图片描述
    特点
    优点:算法简单
    缺点:标记-清除最大的问题会造成内存碎片,但是不浪费空间,效率较高(如果对象较多时,逐一删除效率也会受到影响)

标记-压缩 (压实)Mark-Compact

分垃圾标记阶段和内存整理两个阶段

  • 标记阶段,找到所有可访问对象打个标记。
  • 内存清理阶段时,整理时将对象向内存一端移动,整理后存活对象连续的集中在内存一端。
    在这里插入图片描述
    特点
    标记-压缩算法好处是整理后内存空间连续分配,有大段的连续内存可分配,没有内存碎片
    缺点是内存整理过程有消耗,效率相对低下

复制 Copying

在这里插入图片描述
先将可用内存分为大小相同两块区域A和B,每次只用其中一块,比如A。当A用完后,则将A中存活的对象复制到B。复制到B的时候连续的使用内存,最后将A一次性清除干净。
特点
好处是没有碎片,复制过程中保证对象使用连续空间,且一次性清除所有垃圾,所以即使对象很多,收回效率也很高
缺点是比较浪费内存,只能使用原来一半内存,因为内存对半划分了,复制过程毕竟也是有代价。

多种算法总结
没有最好的算法,在不同场景选择最合适的算法

  • 效率: 复制算法>标记清除算法> 标记压缩算法
  • 内存整齐度: 复制算法=标记压缩算法> 标记清除算法
  • 内存利用率: 标记压缩算法=标记清除算法>复制算法

STW
对于大多数垃圾回收算法而言,GC线程工作时,停止所有工作的线程,称为Stop The World。GC 完成时,恢复其他工作线程运行。这也是JVM运行中最头疼的问题。

分代堆内存GC策略

上述垃圾回收算法都有优缺点,能不能对不同数据进行区分管理,不同分区对数据实施不同回收策略,分而治之。
堆内存分代
将heap内存空间分为三个不同类别: 年轻代、老年代、持久代。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
Heap堆内存分为

  • 年轻代Young:Young Generation

    • 伊甸园区eden: 只有一个,刚刚创建的对象。
    • 幸存(存活)区Servivor Space:有2个幸存区,一个是from区,一个是to区。大小相等、地位
      相同、可互换。
      • from 指的是本次复制数据的源区
      • to 指的是本次复制数据的目标区
  • 老年代Tenured:Old Generation, 长时间存活的对象

默认空间大小比例:

默认JVM试图分配最大内存的总内存的1/4,初始化默认总内存为总内存的1/64,年青代中heap的1/3,老年代占2/3
在这里插入图片描述
永久代:JDK1.7之前使用, 即Method Area方法区,保存JVM自身的类和方法,存储JAVA运行时的环境信息, JDK1.8后 改名为 MetaSpace,此空间不存在垃圾回收,关闭JVM会释放此区域内存,此空间物理上不属于heap内存,但逻辑上存在于heap内存

  • 永久代必须指定大小限制,字符串常量JDK1.7存放在永久代,1.8后存放在heap中
  • MetaSpace 可以设置,也可不设置,无上限

规律: 一般情况99%的对象都是临时对象
示例: 在tomcat 状态页可以看到以下的内存分代
在这里插入图片描述
查看JVM内存分配情况

jdk21
[root@ubuntu2404 ~]#vim Heap.java
public class Heap {public static void main(String[] args){//返回JVM试图使用的最大内存,字节单位long max = Runtime.getRuntime().maxMemory();//返回JVM初始化总内存long total = Runtime.getRuntime().totalMemory();System.out.println("max="+max+"字节\t"+(max/(double)1024/1024)+"MB");System.out.println("total="+total+"字节\t"+(total/(double)1024/1024)+"MB");}
}
[root@ubuntu2404 ~]#javac Heap.java 
[root@ubuntu2404 ~]#java -classpath . Heap 
max=1015021568字节	968.0MB
total=67108864字节	64.0MB
[root@ubuntu2404 ~]#java -XX:+PrintGCDetails Heap
[0.000s][warning][gc] -XX:+PrintGCDetails is deprecated. Will use -Xlog:gc* instead.
[0.013s][info   ][gc,init] CardTable entry size: 512
[0.013s][info   ][gc     ] Using G1
[0.015s][info   ][gc,init] Version: 21.0.7+8-LTS-245 (release)
[0.015s][info   ][gc,init] CPUs: 128 total, 2 available
[0.015s][info   ][gc,init] Memory: 3868M
[0.015s][info   ][gc,init] Large Page Support: Disabled
[0.015s][info   ][gc,init] NUMA Support: Disabled
[0.015s][info   ][gc,init] Compressed Oops: Enabled (32-bit)
[0.015s][info   ][gc,init] Heap Region Size: 1M
[0.015s][info   ][gc,init] Heap Min Capacity: 8M
[0.015s][info   ][gc,init] Heap Initial Capacity: 62M
[0.015s][info   ][gc,init] Heap Max Capacity: 968M
[0.015s][info   ][gc,init] Pre-touch: Disabled
[0.015s][info   ][gc,init] Parallel Workers: 2
[0.016s][info   ][gc,init] Concurrent Workers: 1
[0.016s][info   ][gc,init] Concurrent Refinement Workers: 2
[0.016s][info   ][gc,init] Periodic GC: Disabled
[0.024s][info   ][gc,metaspace] CDS archive(s) mapped at: [0x00007444ab000000-0x00007444abcab000-0x00007444abcab000), size 13283328, SharedBaseAddress: 0x00007444ab000000, ArchiveRelocationMode: 1.
[0.024s][info   ][gc,metaspace] Compressed class space mapped at: 0x00007444ac000000-0x00007444ec000000, reserved size: 1073741824
[0.024s][info   ][gc,metaspace] Narrow klass base: 0x00007444ab000000, Narrow klass shift: 0, Narrow klass range: 0x100000000
max=1015021568字节	968.0MB
total=67108864字节	64.0MB
[0.052s][info   ][gc,heap,exit] Heap
[0.052s][info   ][gc,heap,exit]  garbage-first heap   total 65536K, used 2062K [0x00000000c3800000, 0x0000000100000000)
[0.052s][info   ][gc,heap,exit]   region size 1024K, 1 young (1024K), 0 survivors (0K)
[0.052s][info   ][gc,heap,exit]  Metaspace       used 231K, committed 448K, reserved 1114112K
[0.052s][info   ][gc,heap,exit]   class space    used 12K, committed 128K, reserved 1048576Kjdk8
[root@ubuntu2404 ~]#java -version
openjdk version "1.8.0_462"
OpenJDK Runtime Environment (build 1.8.0_462-8u462-ga~us1-0ubuntu2~24.04.2-b08)
OpenJDK 64-Bit Server VM (build 25.462-b08, mixed mode)[root@ubuntu2404 ~]#java -classpath . Heap 
max=449314816字节	428.5MB
total=32505856字节	31.0MB[root@ubuntu2404 ~]#java  -XX:+PrintGCDetails Heap
max=449314816字节	428.5MB
total=32505856字节	31.0MB
HeapPSYoungGen      total 9728K, used 870K [0x00000000f5f80000, 0x00000000f6a00000, 0x0000000100000000)eden space 8704K, 10% used [0x00000000f5f80000,0x00000000f6059bc8,0x00000000f6800000)from space 1024K, 0% used [0x00000000f6900000,0x00000000f6900000,0x00000000f6a00000)to   space 1024K, 0% used [0x00000000f6800000,0x00000000f6800000,0x00000000f6900000)ParOldGen       total 22016K, used 0K [0x00000000e1e00000, 0x00000000e3380000, 0x00000000f5f80000)object space 22016K, 0% used [0x00000000e1e00000,0x00000000e1e00000,0x00000000e3380000)Metaspace       used 2858K, capacity 4486K, committed 4864K, reserved 1056768Kclass space    used 299K, capacity 386K, committed 512K, reserved 1048576K[root@ubuntu2404 ~]#echo  "scale=2;(9728+22016)/1024" |bc
31.00#说明年轻代+老年代占用了所有heap空间, Metaspace实际不占heap空间,逻辑上存在于Heap

年轻代回收 Minor GC

  1. 起始时,所有新建对象(特大对象直接进入老年代)都出生在eden,当eden满了,启动GC。这个称为Young GC 或者 Minor GC
  2. 标记eden存活对象,然后将存活对象复制到s0(假设本次是s0,也可以是s1,它们可以调换),eden剩余所有空间都清空。GC完成
  3. 继续新建对象,当eden再次满了,启动GC
  4. 先同时标记eden和s0中存活对象,然后将存活对象复制到s1。将eden和s0清空,此次GC完成
  5. 继续新建对象,当eden满了,启动GC
  6. 标记eden和s1中存活对象,然后将存活对象复制到s0。将eden和s1清空,此次GC完成

以后就重复上面的步骤。
通常场景下,大多数对象都不会存活很久,而且创建活动非常多,新生代就需要频繁垃圾回收。
但是,如果一个对象一直存活,它最后就在from、to来回复制,如果from区中对象复制次数达到阈值
(默认15次,CMS为6次,可通过java的选项 -XX:MaxTenuringThreshold=N 指定),就直接复制到老年代。

老年代回收 Major GC

进入老年代的数据较少,所以老年代区被占满的速度较慢,所以垃圾回收也不频繁。
如果老年代也满了,会触发老年代GC,称为Old GC或者 Major GC。
由于老年代对象一般来说存活次数较长,所以较常采用标记-压缩算法。
当老年代满时,会触发 Full GC,即对所有"代"的内存进行垃圾回收
Minor GC比较频繁,Major GC较少。但一般Major GC时,由于老年代对象也可以引用新生代对象,所以先进行一次Minor GC,然后在Major GC会提高效率。可以认为回收老年代的时候完成了一次Full
GC。
所以可以认为 MajorGC = FullGC

GC 触发条件

在这里插入图片描述

Minor GC 触发条件:当eden区满了触发
Full GC 触发条件

  • 老年代满了
  • System.gc()手动调用。不推荐

年轻代:

  • 存活时长低
  • 适合复制算法

老年代:

  • 区域大,存活时长高
  • 适合标记压缩算法

Minor GC 可能会引起短暂的STW暂停。当进行 Minor GC 时,为了确保安全性,JVM 需要在某些特定的点上暂停所有应用程序的线程,以便更新一些关键的数据结构。这些暂停通常是非常短暂的,通常在毫秒级别,并且很少对应用程序的性能产生显著影响。

Major GC的暂停时间通常会比Minor GC的暂停时间更长,因为老年代的容量通常比年轻代大得多。这意味着在收集和整理大量内存时,需要更多的时间来完成垃圾收集操作。

尽管Major GC会引起较长的STW暂停,但JVM通常会尽量优化垃圾收集器的性能,以减少这些暂停对应用程序的影响。例如,通过使用并行或并发垃圾收集算法,可以减少STW时间,并允许一部分垃圾收集工作与应用程序的线程并发执行。

Java 内存调整相关参数

JVM 内存常用相关参数

Java 命令行参考文档:
https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html
在这里插入图片描述
帮助:man java
选项分类

  • -选项名称 此为标准选项,所有HotSpot都支持
  • -X选项名称 此为稳定的非标准选项
  • -XX:选项名称 非标准的不稳定选项,下一个版本可能会取消
参数			说明										举例
-Xms		设置应用程序初始使用的堆内存大小(年轻代+老年代)		-Xms2g
-Xmx		设置应用程序能获得的最大堆内存早期JVM不建议超过32G,内存管理效率下降  -Xmx4g
-XX:NewSize	设置初始新生代大小									-XX:NewSize=128m
-XX:MaxNewSize	设置最大新生代内存空间 							-XX:MaxNewSize=256m
-Xmn			同时设置-XX:NewSize 和-XX:MaxNewSize,代替两者 -Xmn1g
-XX:NewRatio    以比例方式设置新生代和老年代           -XX:NewRatio=2即:new:old=1:2
-XX:SurvivorRatio 以比例方式设置eden和survivor(S0或S1) -XX:SurvivorRatio=6即:Eden:S0:S1=6:1:1
-Xss		设置每个线程私有的栈空间大小,依据具体线程大小和数量		-Xss256k

查看java的选项帮助

[root@ubuntu2404 ~]#java
Usage: java [options] <mainclass> [args...](to execute a class)or  java [options] -jar <jarfile> [args...](to execute a jar file)or  java [options] -m <module>[/<mainclass>] [args...]java [options] --module <module>[/<mainclass>] [args...](to execute the main class in a module)or  java [options] <sourcefile> [args](to execute a single source-file program)Arguments following the main class, source file, -jar <jarfile>,-m or --module <module>/<mainclass> are passed as the arguments tomain class.where options include:-cp <class search path of directories and zip/jar files>-classpath <class search path of directories and zip/jar files>--class-path <class search path of directories and zip/jar files>A : separated list of directories, JAR archives,and ZIP archives to search for class files.-p <module path>--module-path <module path>...A : separated list of elements, each element is a file pathto a module or a directory containing modules. Each module is eithera modular JAR or an exploded-module directory.--upgrade-module-path <module path>...A : separated list of elements, each element is a file pathto a module or a directory containing modules to replaceupgradeable modules in the runtime image. Each module is eithera modular JAR or an exploded-module directory.--add-modules <module name>[,<module name>...]root modules to resolve in addition to the initial module.<module name> can also be ALL-DEFAULT, ALL-SYSTEM,ALL-MODULE-PATH.--enable-native-access <module name>[,<module name>...]modules that are permitted to perform restricted native operations.<module name> can also be ALL-UNNAMED.--list-moduleslist observable modules and exit-d <module name>--describe-module <module name>describe a module and exit--dry-run     create VM and load main class but do not execute main method.The --dry-run option may be useful for validating thecommand-line options such as the module system configuration.--validate-modulesvalidate all modules and exitThe --validate-modules option may be useful for findingconflicts and other errors with modules on the module path.-D<name>=<value>set a system property-verbose:[class|module|gc|jni]enable verbose output for the given subsystem-version      print product version to the error stream and exit--version     print product version to the output stream and exit-showversion  print product version to the error stream and continue--show-versionprint product version to the output stream and continue--show-module-resolutionshow module resolution output during startup-? -h -helpprint this help message to the error stream--help        print this help message to the output stream-X            print help on extra options to the error stream--help-extra  print 
http://www.dtcms.com/a/340976.html

相关文章:

  • 从零开始学AI——13
  • 吴恩达 Machine Learning(Class 3)
  • MySQL 8.x的性能优化文档整理
  • JavaScript 性能优化实战(易懂版)
  • InfluxDB 查询性能优化实战(一)
  • 【PSINS工具箱】平面上的组合导航,观测量为位置、速度、航向角。附完整的MATLAB代码
  • sqli-labs通关笔记-第58关 GET字符型报错注入(单引号闭合 限制5次探测机会)
  • 六大缓存(Caching)策略揭秘:延迟与复杂性的完美平衡
  • git-git submodule和git subtree的使用方式
  • 大规模IP轮换对网站的影响(服务器压力、风控)
  • CISP-PTE之路--05文
  • 企业微信2025年发布会新功能解读:企业微信AI——2025年企业协作的「最优解」是如何炼成的?
  • 跨境电商独立站搭建多少钱?响应式设计 + 全球 CDN 加速服务
  • IBMS系统集成平台具备哪些管理优势?核心价值体现在哪里?
  • HTTP/1.1 与 HTTP/2 全面对比:性能革命的深度解析
  • 工控PID控制器学习总结
  • [element-plus] el-tree 拖拽到其他地方,不拖拽到树上
  • 怎么确定mongodb是不是链接上了?
  • 疏老师-python训练营-day51复习日+退款开始
  • AP数学课程AB和BC怎么选?AP数学课程培训机构推荐哪家?
  • Git 新手完全指南(一):从零开始掌握版本控制
  • .gitignore 文件 记录
  • git报错解决:ssh: connect to host github.com port 22: Connection refused
  • 阶跃星辰 StepFun 入驻 GitCode 平台,带来工业级 AI 体验
  • macos 多个版本的jdk
  • 版本软件下载电脑适配说明
  • 【数据类型】
  • UE5 PCG 笔记(二) Difference 节点
  • 从天线到芯片封装,CST如何赋能高频设计全流程
  • MySQL程序和选项文件配置