目录
引言
在目前流行的互联网架构中,Tomcat在目前的网络编程中是举足轻重的,由于Tomcat的运行依赖于JVM,从虚拟机的角度把Tomcat的调整分为外部环境调优 JVM 和 Tomcat 自身调优两部分
安装和配置如JMX、Prometheus等监控工具,实时关注Tomcat运行状态和资源使用情况。
合理配置日志级别,避免在生产环境中生成过多不必要的日志影响性能。
负载均衡: 当部署了多个Tomcat实例后,可以考虑使用LVS、Nginx、HAProxy等负载均衡软件,在前端对各个实例进行流量分发,进一步提升整体系统的可用性和扩展性。
一、JVM相关理论
(一)JVM组成
1.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所用
2.JVM运行时数据区
JVM运行时数据区域由下面部分构成
2.1 方法区 Method Area 或 元空间 Metaspace(线程共享)
方法区是所有线程共享的内存空间
在JDK 1.8之前,这个区域被称为“永久代”(Permanent Generation),其中存储了类的元数据,如类信息、字段描述符、方法描述符、常量池等。但实例变量存放在堆内存中。
自从JDK 1.8开始,“永久代”被彻底移除,取而代之的是元空间,它不在Java堆中而是使用本地内存(Native Memory),存储类的元数据和其他运行时常量池的信息。
元空间大小可以通过参数 -XX:MaxMetaspaceSize 进行限制。
2.2 Java堆 heap (线程共享):堆在虚拟机启动时创建,存放创建的所有对象信息。如果对象无法申请到可用内存将抛出OOM异常.堆是靠GC垃圾回收器管理的,通过-Xmx -Xms 指定最大堆和最小堆空间大小
JVM管理的最大内存区域,所有线程共享。
所有对象实例和数组都在堆上分配内存。
堆被进一步划分为新生代(Young Generation)和老年代(Old Generation),新生代又可细分为Eden区、From Survivor区和To Survivor区,这是根据垃圾收集策略来区分的。
垃圾回收机制主要关注堆中的对象是否还被引用,未被引用的对象将在GC过程中被回收。
2.3 虚拟机栈 Java stack (线程私有):
每个线程同样有自己的虚拟机栈,也是线程私有的。
虚拟机栈中包含多个栈帧,每个栈帧对应一个方法调用,存储局部变量表、操作数栈、动态链接信息和方法出口等数据。
当线程执行一个方法时,就会创建一个新的栈帧并压入栈顶;当方法返回或抛出异常时,对应的栈帧会被弹出。
2.4 程序计数器 Program Counter Register (线程私有)
是一块较小的内存空间,每个线程都有一个独立的程序计数器。
它用于存储当前线程执行字节码指令的位置,是线程私有的。
在多线程环境中,程序计数器的存在可以确保线程切换后能恢复到正确的执行位置。
2.5 本地方法栈 Native Method stack (线程私有)
类似于虚拟机栈,但服务于JNI(Java Native Interface)调用的本地(非Java)方法。
本地方法栈也会有线程私有的栈帧,不过它们是用来支持本地方法调用的。
(二)垃圾回收
在堆内存中如果创建的对象不再使用,仍占用着内存,此时即为垃圾.需要及时进行垃圾回收,从而释放内存空间给其它对象使用
1.确定垃圾
Java虚拟机(JVM)在处理垃圾回收时采用了多种算法和策略
引用计数法
这是最基础的垃圾回收算法之一,通过为每个对象添加一个引用计数器,每当有一个地方引用它时,计数器加一;当引用失效时,计数器减一。当计数器为0时,说明该对象不再被任何对象引用,可以作为垃圾回收。
现代JVM并不采用引用计数法,因为它无法有效处理循环引用问题。
可达性分析算法(Reachability Analysis)
当前主流JVM如HotSpot使用的是可达性分析算法,也称为追踪式垃圾收集(Tracing Garbage Collection)。该算法以一系列被称为GC Roots的对象作为起点,通过从这些根节点开始向下搜索所有可达对象来确定哪些是活动对象。不可达的对象被认为是可回收的垃圾。
2.垃圾收集算法
基于可达性分析的结果,JVM有几种不同的垃圾收集算法来执行具体的垃圾清除:
标记-清除(Mark-Sweep):首先标记出所有需要回收的对象,然后进行清除操作。缺点是会产生内存碎片。
复制(Copying):将内存分为两块,每次只用其中一块,在垃圾回收时,将存活对象复制到另一块未使用的内存区域,然后清除原区域的所有内容。如新生代中的 Eden 区和两个 Survivor 区就是采用这种方式,常见于Minor GC中。
标记-整理(Mark-Compact):与标记-清除类似,但在清除不可达对象后,会将剩余的存活对象“压缩”在一起,从而解决内存碎片问题。老年代通常采用此方式或其变种。
分代收集(Generational Collection):根据对象生命周期的不同,将堆内存划分为新生代和老年代,分别采用不同的垃圾回收算法,比如新生代常使用复制算法,老年代使用标记-整理或标记-清除+压缩等。
在不同场景选择最合适的算法
二、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,内存管理效率下降 | -Xms4g |
-XX:NewSize | 设置初始新生代大小 | -XX:NewSize=128m |
-XX:MaxNewSize | 设置最大新生代内存空间 | -XX:MaxNewSize=256m |
-Xmnsize | 同时设置-XX:NewSize 和 -XX:MaxNewSize,代 | -Xmn1g |
-XX:NewRatio | 以比例方式设置新生代和老年代 | -XX:NewRatio=2new/old=1/2 |
-XX:SurvivorRatio | 以比例方式设置eden和survivor(S0或S1) | -XX:SurvivorRatio=6eden/survivor=6/1new/survivor=8/1 |
-Xss | 设置每个线程私有的栈空间大小,依据具体线程 | -Xss256k |
标准选项
输入java查看选项
[root@centos60 manager]#java
用法: java [-options] class [args...]
(执行类)
或 java [-options] -jar jarfile [args...]
(执行 jar 文件)
其中选项包括:
-d32 使用 32 位数据模型 (如果可用)
-d64 使用 64 位数据模型 (如果可用)
-server 选择 "server" VM
默认 VM 是 server,
因为您是在服务器类计算机上运行。
-cp <目录和 zip/jar 文件的类搜索路径>
-classpath <目录和 zip/jar 文件的类搜索路径>
用 : 分隔的目录, JAR 档案
和 ZIP 档案列表, 用于搜索类文件。
-D<名称>=<值>
设置系统属性
-verbose:[class|gc|jni]
启用详细输出
-version 输出产品版本并退出
-version:<值>
警告: 此功能已过时, 将在
未来发行版中删除。
需要指定的版本才能运行
-showversion 输出产品版本并继续
-jre-restrict-search | -no-jre-restrict-search
警告: 此功能已过时, 将在
未来发行版中删除。
在版本搜索中包括/排除用户专用 JRE
-? -help 输出此帮助消息
-X 输出非标准选项的帮助
-ea[:<packagename>...|:<classname>]
-enableassertions[:<packagename>...|:<classname>]
按指定的粒度启用断言
-da[:<packagename>...|:<classname>]
-disableassertions[:<packagename>...|:<classname>]
禁用具有指定粒度的断言
-esa | -enablesystemassertions
启用系统断言
-dsa | -disablesystemassertions
禁用系统断言
-agentlib:<libname>[=<选项>]
加载本机代理库 <libname>, 例如 -agentlib:hprof
另请参阅 -agentlib:jdwp=help 和 -agentlib:hprof=help
-agentpath:<pathname>[=<选项>]
按完整路径名加载本机代理库
-javaagent:<jarpath>[=<选项>]
加载 Java 编程语言代理, 请参阅 java.lang.instrument
-splash:<imagepath>
使用指定的图像显示启动屏幕
有关详细信息, 请参阅 http://www.oracle.com/technetwork/java/javase/documentation/index.html
非标准的稳定选项
输入java -XX查看
[root@centos60 manager]#java -X
-Xmixed 混合模式执行(默认)
-Xint 仅解释模式执行
-Xbootclasspath:<用 : 分隔的目录和 zip/jar 文件>
设置引导类和资源的搜索路径
-Xbootclasspath/a:<用 : 分隔的目录和 zip/jar 文件>
附加在引导类路径末尾
-Xbootclasspath/p:<用 : 分隔的目录和 zip/jar 文件>
置于引导类路径之前
-Xdiag 显示附加诊断消息
-Xnoclassgc 禁用类垃圾收集
-Xincgc 启用增量垃圾收集
-Xloggc:<file> 将 GC 状态记录在文件中(带时间戳)
-Xbatch 禁用后台编译
-Xms<size> 设置初始 Java 堆大小
-Xmx<size> 设置最大 Java 堆大小
-Xss<size> 设置 Java 线程堆栈大小
-Xprof 输出 cpu 分析数据
-Xfuture 启用最严格的检查,预计会成为将来的默认值
-Xrs 减少 Java/VM 对操作系统信号的使用(请参阅文档)
-Xcheck:jni 对 JNI 函数执行其他检查
-Xshare:off 不尝试使用共享类数据
-Xshare:auto 在可能的情况下使用共享类数据(默认)
-Xshare:on 要求使用共享类数据,否则将失败。
-XshowSettings 显示所有设置并继续
-XshowSettings:system
(仅限 Linux)显示系统或容器
配置并继续
-XshowSettings:all
显示所有设置并继续
-XshowSettings:vm 显示所有与 vm 相关的设置并继续
-XshowSettings:properties
显示所有属性设置并继续
-XshowSettings:locale
显示所有与区域设置相关的设置并继续-X 选项是非标准选项。如有更改,恕不另行通知
有不稳定选项的当前生效值:java -XX:+PrintFlagsFinal
查看所有不稳定选项的默认值:java -XX:+PrintFlagsInitial
(二)Tomcat的JVM参数设置
默认不指定,-Xmx大约使用了1/4的内存,当前本机内存指定约为1G。
在bin/catalina.sh中增加一行
-Xms 和 -Xmx 建议两个值调一样大小
-server: 指定使用服务器模式运行Java虚拟机,这种模式通常针对服务端应用进行了优化,提供更好的性能。
-Xms1024m: 设置Java堆的初始大小为1024MB。这意味着在JVM启动时就将分配1GB的内存作为堆空间的最小值。
-Xmx1024m: 设置Java堆的最大大小也为1024MB。这意味着堆内存的上限是1GB,在程序运行过程中,如果需要更多内存且不超过这个限制,JVM会动态增加堆内存;但如果超过这个值,且无法释放足够的空间,则可能会抛出 OutOfMemoryError 错误。
-XX:NewSize=300m: 设置新生代(Young Generation)的初始大小为300MB。新生代是Java堆中的一部分,主要存储新创建的对象和生命周期较短的对象。
-XX:MaxNewSize=400m: 设置新生代的最大大小为400MB。这意味着新生代的内存占用可以在300MB至400MB之间自动调整。
可以通过状态页来查看修改之后的状态
#这段信息描述的是Java虚拟机(JVM)的内存使用情况,具体来说是针对垃圾回收器
#为Parallel Scavenge(PS)的HotSpot JVM。
#每个部分分别表示不同的内存区域:
PS Eden Space
'这是年轻代(Young Generation)的一部分,主要用于存储新创建的对象'
PS Old Gen
'这是老年代(Old Generation),存放长期存活或晋升过来的对象'
PS Survivor Space
'同样是年轻代的一部分,用于保存在年轻代中经历过一次GC但尚未被回收的对象'
Code Cache
'非堆内存区域,用于存储JIT编译后的机器码'
Compressed Class Space
'另一个非堆内存区域,当类空间过大时,经过压缩的类会被存放到此区域以节省内存'
Metaspace
'从Java 8开始取代了永久代(PermGen),用于存储类元数据信息。这里显示的最大容量为-0.00 MB,
这意味着Metaspace的大小可以动态调整,不受固定上限限制'
这些设置共同决定了JVM在处理Java应用程序时如何管理和分配内存资源,以满足程序的运行需求,并尽可能地提高系统的稳定性和性能。不过,请注意,这些设置应当根据实际的应用场景、硬件资源和负载情况来合理配置。
Tomcat默认安装下的缺省配置并不适合生产环境,它可能会频繁出现假死现象需要重启,只有通过不断压测优化才能让它最高效率稳定的运行。优化主要包括三方面,分别为操作系统优化(内核参数优化),Tomcat配置文件参数优化,Java虚拟机(JVM)调优。
#Tomcat 配置文件参数优化##
常用的优化相关参数如下:
【redirectPort】如果某连接器支持的协议是HTTP,当接收客户端发来的HTTPS 443 请求时,则转发至此属性定义的 8443 端口。【maxThreads】Tomcat使用线程来处理接收的每个请求,这个值表示Tomcat可创建的最大的线程数,即支持的最大并发连接数,默认值是 200。
【minSpareThreads】最小空闲线程数,Tomcat 启动时的初始化的线程数,表示即使没有人使用也开这么多空线程等待,默认值是 10。
【maxSpareThreads】最大备用线程数,一旦创建的线程超过这个值,Tomcat就会关闭不再需要的socket线程。默认值是-1(无限制)。一般不需要指定。
【processorCache】进程缓冲器,可以提升并发请求。默认值是200,如果不做限制的话可以设置为-1,一般采用maxThreads的值或者-1。
【URIEncoding】指定 Tomcat 容器的 URL 编码格式,网站一般采用UTF-8作为默认编码。
【connnectionTimeout】网络连接超时,单位:毫秒,设置为 0 表示永不超时,这样设置有隐患的。通常默认 20000 毫秒就可以。
【enableLookups】是否反查域名,以返回远程主机的主机名,取值为:true 或 false,如果设置为 false,则直接返回 IP 地址,为了提高处理能力,应设置为 false。
【disableUploadTimeout】上传时是否使用超时机制。应设置为 true。
【connectionUploadTimeout】上传超时时间,毕竟文件上传可能需要消耗更多的时间,这个根据你自己的业务需要自己调,以使Servlet有较长的时间来完成它的执行,需要与上一个参数一起配合使用才会生效。
【acceptCount】指定当所有可以使用的处理请求的线程数都被使用时,可传入连接请求的最大队列长度,超过这个数的请求将不予处理,默认为 100 个。
【maxKeepAliveRequests】指定一个长连接的最大请求数。默认长连接是打开的,设置为1时,代表关闭长连接;为-1时,代表请求数无限制
【compression】是否对响应的数据进行GZIP压缩,off:表示禁止压缩;on:表示允许压缩(文本将被压缩)、force:表示所有情况下都进行压缩,默认值为 off,压缩数据后可以有效的减少页面的大小,一般可以减小 1/3 左右,节省带宽。
【compressionMinSize】表示压缩响应的最小值,只有当响应报文大小大于这个值的时候才会对报文进行压缩,如果开启了压缩功能,默认值就是 2048。
【compressableMimeType】压缩类型,指定对哪些类型的文件进行数据压缩。
【noCompressionUserAgents="gozilla, traviata"】对于以下的浏览器,不启用压缩
#如果已经进行了动静分离处理,静态页面和图片等数据就不需做 Tomcat 处理,也就不要在 Tomcat 中配置压缩了。以上是一些常用的配置参数,还有好多其它的参数设置,还可以继续深入的优化,HTTP Connector 与 AJP Connector 的参数属性值,可以参考官方文档的详细说明进行学习。
vim /usr/local/tomcat/conf/server.xml
......
<Connector port="8080" protocol="HTTP/11.1"
connectionTimeout="20000"
redirectPort="8443"
--71行--插入
minSpareThreads="50"
enableLookups="false"
disableUploadTimeout="true"
acceptCount="300"
maxThreads="500"
processorCache="500"
URIEncoding="UTF-8"
maxKeepAliveRequests="100"
compression="on"
compressionMinSize="2048"
compressableMimeType="text/html,text/xml,text/javascript,text/css,text/plain,image/gif,image /jpg,image/png"/>
三、TOMCAT多实例
安装好JDK与TOMCAT服务后进行配置
(一)建立两个服务文件
(二)建立system管理文件
(三)修改监听端口号
<Server port="8006" shutdown="SHUTDOWN">
#22行,修改Server prot,默认为8005 -> 修改为8006
<Connector port="9527" protocol="HTTP/1.1"
#69行,修改Connector port,HTTP/1.1 默认为8080 -> 修改为8081
<Connector port="8010" protocol="AJP/1.3" redirectPort="8443" />
#116行,修改Connector port AJP/1.3,默认为8009 -> 修改为8010
修改完毕后,启动第二个tomcat服务:systemctl start tomcat1
使用客户端去访问tomcat服务器监听的不同端口,可以看到,都提供了应用服务