9. 总结tomcat优化方法

9. 总结tomcat优化方法

 

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

 

9.1 JVM组成

[root@t1 ~]#java -version

java version "1.9.0_271"

Java(TM) SE Runtime Environment (build 1.9.0_271-b09)

Java HotSpot(TM) 64-Bit Server VM (build 25.271-b09, mixed mode)

 

 

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所用

 

java在运行时有时候会依赖一些第三方的库,而这些库不一定是用java实现的,有可能是用其它语言开发,如:C,C++库等用这些第三方语言实现的库,想要被java所使用,就要用到本地方法接口加载这些第三方语言写好的库。

 

所谓本地方法,就是使用native关健字修饰的方法,比如:Thread.sleep方法. 简单的说是Java实现的方法,例如操作系统的C编写的库提供的本地方法,Java调用这些本地方法接口执行。但是要注意,本地方法应该避免直接编程使用,因为Java可能跨平台使用,如果用了Windows API,换到了Linux平台部署就有了问题。

 

 

 

JVM运行时数据区域由下面部分构成:

 

Method Area 方法区(线程共享):所有线程共享的内存空间,存放已加载的类信息(构造方法,接口定义),常量(final),静态变量(static), 运行时常量池等。但实例变量存放在堆内存中. JDK8开始此空间由永久代改名为元空间

 

heap (线程共享):虚拟机启动时创建,存放创建的所有对象信息。如果对象无法申请到可用内存将抛出OOM异常堆是靠GC垃圾回收器管理的通过-Xmx -Xms 指定最大堆和最小堆空间大小

 

Java stack Java(线程私有):每个线程会分配一个栈,存放java8大基本数据类型,对象引用,实例的本地变量,方法参数和返回值等,基于FILO()First In Last Out,每个方法为一个栈帧

 

Program Counter Register PC寄存器(线程私有):就是一个指针,指向方法区中的方法字节码,每一个线程用于记录当前线程正在执行的字节码指令地址。由执行引擎读取下一条指令.因为线程需要切换,当一个线程被切换回来需要执行的时候,知道执行到哪里了

Native Method stack 本地方法栈(线程私有):为本地方法执行构建的内存空间,存放本地方法执行时的局部变量、操作数等。

 

 

 

9.1.2 虚拟机

目前Oracle官方使用的是HotSpot, 它最早由一家名为"Longview Technologies"公司设计,使用了很多优秀的设计理念和出色的性能,1997年该公司被SUN公司收购。后来随着JDK一起发布了HotSpot VM。目前HotSpot是最主要的VM

安卓程序需要运行在JVM上,而安卓平台使用了Google自研的Java虚拟机——Dalvid,适合于内存、处理器能力有限系统。

 

 

 

 

 

9.2 GC (Garbage Collection) 垃圾收集器

 

在堆内存中如果创建的对象不再使用,仍占用着内存,此时即为垃圾.需要即使进行垃圾回收,从而释放内存空间给其它对象使用

 

其实不同的开发语言都有垃圾回收问题,C,C++需要程序员人为回收,造成开发难度大,容易出错等问题,但执行效率高,JAVAPython中不需要程序员进行人为的回收垃圾,而由JVM或相关程序自动回收垃圾,减轻程序员的开发难度,但可能会造成执行效率低下。

 

堆内存里面经常创建、销毁对象,内存也是被使用、被释放。如果不妥善处理,一个使用频繁的进程,可能会出现虽然有足够的内存容量,但是无法分配出可用内存空间,因为没有连续成片的内存了,内存全是碎片化的空间。

 

所以需要有合适的垃圾回收机制,确保正常释放不再使用的内存空间,还需要保证内存空间尽可能的保持一定的连续。对于垃圾回收,需要解决三个问题。

1)哪些是垃圾要回收

2)怎么回收垃圾

3)什么时候回收垃圾

 

 

9.2.1 Garbage 垃圾确定方法

 

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

 

根搜索(可达)算法 Root Searching

 

9.2.2 垃圾回收基本算法

9.2.2.1 标记-清除 Mark-Sweep

分垃圾标记阶段和内存释放阶段。标记阶段,找到所有可访问对象打个标记。清理阶段,遍历整个堆,对未标记对象(即不再使用的对象)清理。

 

 

 

 

标记-清除最大的问题会造成内存碎片,但是效率很高,不浪费空间

 

 

9.2.2.2 标记-压缩Mark-Compact

 

分垃圾标记阶段和内存整理阶段。标记阶段,找到所有可访问对象打个标记。内存清理阶段时,整理时将对象向内存一端移动,整理后存活对象连续的集中在内存一端。

 

 

标记-压缩算法好处是整理后内存空间连续分配,有大段的连续内存可分配,没有内存碎片。

缺点是内存整理过程有消耗,效率相对低下

 

 

9.2.2.3 复制 Copying

先将可用内存分为大小相同两块区域AB,每次只用其中一块,比如A。当A用完后,则将A中存活的对象复制到B。复制到B的时候连续的使用内存,最后将A一次性清除干净。

缺点是比较浪费内存,只能使用原来一半内存,因为内存对半划分了,复制过程毕竟也是有代价。

好处是没有碎片,复制过程中保证对象使用连续空间

 

 

 

 

9.2.2.4 多种算法总结

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

效率: 复制算法>标记清除算法> 标记压缩算法

内存整齐度: 复制算法=标记压缩算法> 标记清除算法

内存利用率: 标记压缩算法=标记清除算法>复制算法

STW

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

 

9.2.3 分代堆内存GC策略

上述垃圾回收算法都有优缺点,能不能对不同数据进行区分管理,不同分区对数据实施不同回收策略,分而治之。

 

9.2.3.1 堆内存分代

 

 

heap内存空间分为三个不同类别: 年轻代、老年代、持久代。

年轻代Young 分为eden幸存区fromS0)、to(S1) )

伊甸园区eden: 只有一个,刚刚创建的对象

幸存(存活)区Servivor Space:有2个幸存区,一个是from区,一个是to区。大小相等、地位 相同、可互换。

from 指的是本次复制数据的源区

to 指的是本次复制数据的目标区

老年代TenuredOld Generation, 长时间存活的对象

永久代:JDK1.7之前使用, 即Method Area方法区,保存JVM自身的类和方法,存储JAVA运行时的环境信息, JDK1.8 改名为 MetaSpace,此空间不存在垃圾回收,关闭JVM会释放此区域内存,此空间物理上不属于 heap内存,但逻辑上存在于heap内存 .

永久代必须指定大小限制,字符串常量JDK1.7存放在永久代,1.8后存放在heap中

MetaSpace 可以设置,也可不设置,无上限 .

规律: 一般情况99%的对象都是临时对象.

 

范例: 查看JVM内存分配情况

[root@centos8 ~]#java -classpath . Heap

max=243269632字节 232.0MB

total=16252928字节 15.5MB

[root@centos8 ~]#java -XX:+PrintGCDetails Heap

max=243269632字节 232.0MB

total=16252928字节 15.5MB

Heap

 def new generation   total 4928K, used 530K [0x00000000f1000000,

0x00000000f1550000, 0x00000000f6000000)

 eden space 4416K,  12% used [0x00000000f1000000, 0x00000000f1084a60,

0x00000000f1450000)

 from space 512K,   0% used [0x00000000f1450000, 0x00000000f1450000,

0x00000000f14d0000)

 to   space 512K,   0% used [0x00000000f14d0000, 0x00000000f14d0000,

0x00000000f1550000)

 tenured generation   total 10944K, used 0K [0x00000000f6000000,

0x00000000f6ab0000, 0x0000000100000000)

   the space 10944K,   0% used [0x00000000f6000000, 0x00000000f6000000,

0x00000000f6000200, 0x00000000f6ab0000)

 Metaspace       used 2525K, capacity 4486K, committed 4864K, reserved 1056768K

 class space   used 269K, capacity 386K, committed 512K, reserved 1048576K

  

[root@centos8 ~]#echo "scale=2;(4928+10944)/1024" |bc

15.50

#说明年轻代+老年代占用了所有heap空间, Metaspace实际不占heap空间,逻辑上存在于Heap

#默认JVM试图分配最大内存的总内存的1/4,初始化默认总内存为总内存的1/64

 

 

 

 

9.2.3.2 年轻代回收 Minor GC

 

1. 起始时,所有新建对象(特大对象直接进入老年代)都出生在eden,当eden满了,启动GC。这个称为Young GC 或者 Minor GC

2. 先标记eden存活对象,然后将存活对象复制到s0(假设本次是s0,也可以是s1,它们可以调换),eden剩余所有空间都清空。GC完成。

3. 继续新建对象,当eden再次满了,启动GC

4. 先同时标记edens0中存活对象,然后将存活对象复制到s1。将edens0清空,此次GC完成

5. 继续新建对象,当eden满了,启动GC

6. 先标记edens1中存活对象,然后将存活对象复制到s0。将edens1清空,此次GC完成

 

以后就重复上面的步骤。

 

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

 

9.2.3.3 老年代回收 Major GC

进入老年代的数据较少,所以老年代区被占满的速度较慢,所以垃圾回收也不频繁。

如果老年代也满了,会触发老年代GC,称为Old GC或者 Major GC

由于老年代对象一般来说存活次数较长,所以采用标记-压缩算法。

当老年代满时,会触发 Full GC,即对所有""的内存进行垃圾回收

Minor GC比较频繁,Major GC较少。但一般Major GC时,由于老年代对象也可以引用新生代对象,所以先进行一次Minor GC,然后在Major GC会提高效率。可以认为回收老年代的时候完成了一次Full GC。所以可以认为 Major GC = Full GC

 

 

9.2.3.4 GC 触发条件

 

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

 

Full GC 触发条件:

老年代满了

System.gc()手动调用。不推荐

 

年轻代:

存活时长低

适合复制算法

 

老年代:

区域大,存活时长高

适合标记清除和标记压缩算法

 

 

9.2.4 java 内存调整相关参数

9.2.4.1 JVM 内存常用相关参数

 

 

选项分类

-选项名称 此为标准选项,所有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=2  new/old=1/2

 

-XX:SurvivorRatio 以比例方式设置edensurvivor(S0S1)

例:

-XX:SurvivorRatio=6

eden/survivor=6/1

new/survivor=8/1

 

-Xss 设置每个线程私有的栈空间大小,依据具体线程大小和数量 例:-Xss256k

 

范例: 查看java的选项帮助

 

#查看java命令标准选项

[root@centos ~]#java

 

#查看java的非标准选项

[root@centos8 ~]#java -X

 

 

#查看所有不稳定选项的当前生效值

[root@centos8 ~]#java -XX:+PrintFlagsFinal

 

#查看所有不稳定选项的默认值

[root@centos8 ~]#java -XX:+PrintFlagsInitial

 

#查看当前命令行的使用的选项设置

[root@centos8 ~]#java -XX:+PrintCommandLineFlags

 

 

#指定内存空间

[root@centos8 ~]#java -Xms1024m -Xmx1024m -XX:+PrintGCDetails Heap

 

范例: 查看OOM

[root@centos8 ~]#cat HeapOom2.java

[root@centos8 ~]#javac HeapOom2.java

 

 

 

9.2.4.3 TomcatJVM参数设置

 

默认不指定,-Xmx大约使用了1/4的内存,当前本机内存指定约为1G。 在bin/catalina.sh中增加一行

 

vim /usr/local/tomcat/bin/catalina.sh

......

# OS specific support. $var _must_ be set to either true or false.

#添加下面一行

JAVA_OPTS="-server -Xms128m -Xmx512m -XX:NewSize=48m -XX:MaxNewSize=200m"

                                                            

cygwin=false

darwin=false

........

 

 

-serverVM运行在server模式,为在服务器端最大化程序运行速度而优化

-clientVM运行在Client模式,为客户端环境减少启动时间而优化

 

重启 Tomcat 观察

[root@tomcat ~]#ps aux|grep tomcat

tomcat    22194  5.1 15.1 2497008 123480 ?     Sl   13:31   0:03

 

 

 

 

9.2.5 垃圾收集方式

按工作模式不同:指的是GC线程和工作线程是否一起运行

独占垃圾回收器:只有GC在工作,STW 一直进行到回收完毕,工作线程才能继续执行

并发垃圾回收器:让GC线程垃圾回收某些阶段可以和工作线程一起进行,:标记阶段并行,回收阶段仍然串行

按回收线程数:指的是GC线程是否串行或并行执行

串行垃圾回收器:一个GC线程完成回收工作

并行垃圾回收器:多个GC线程同时一起完成回收工作,充分利用CPU资源

 

 

9.2.6 调整策略

JVM调整策略应用极广

WEB领域中Tomcat

在大数据领域Hadoop生态各组件

在消息中间件领域的Kafka

在搜索引擎领域的ElasticSearchSolr

 

注意: 在不同领域和场景对JVM需要不同的调整策略

减少 STW 时长,串行变并行

减少 GC 次数,要分配合适的内存大小

 

一般情况下,我们大概可以使用以下原则:

客户端或较小程序,内存使用量不大,可以使用串行回收

对于服务端大型计算,可以使用并行回收

大型WEB应用,用户端不愿意等待,尽量少的STW,可以使用并发回收

 

 

 

 

 

 

 

 

 

 

 

9.2.7 垃圾回收器

 

9.2.7.1.1 按分代设置不同垃圾回收器

 

新生代

新生代串行收集器Serial:单线程、独占式串行,采用复制算法,简单高效但会造成STW

 

新生代并行回收收集器PS(Parallel Scavenge):多线程并行、独占式,会产生STW, 使用复制算法关注调整吞吐量,此收集器关注点是达到一个可控制的吞吐量

 

吞吐量 = 运行用户代码时间/(运行用户代码时间+垃圾收集时间),比如虚拟机总共运行100分钟,其中垃圾回收花掉1分钟,那吞吐量就是99%

 

高吞吐量可以高效率利用CPU时间,尽快完成运算任务,主要适合在后台运算而不需要太多交互的任务。

除此之外,Parallel Scavenge 收集器具有自适应调节策略,它可以将内存管理的调优任务交给虚拟机去完成。自适应调节策略也是Parallel ScavengeParNew 收集器的一个重要区别。

 

ParNew不同,PS不可以和老年代的CMS组合

 

新生代并行收集器ParNew:就是Serial 收集器的多线程版,将单线程的串行收集器变成了多线程并行、独占式,使用复制算法,相当于PS的改进版

 

经常和CMS配合使用,关注点是尽可能地缩短垃圾收集时用户线程的停顿时间,适合需要与用户交互的程序,良好的响应速度能提升用户体验

 

老年代:

老年代串行收集器Serial Old:Serial Old是Serial收集器的老年代版本,单线程、独占式串行,回收算法使用标记压缩

老年代并行回收收集器Parallel Old:多线程、独占式并行,回收算法使用标记压缩,关注调整吞吐量

Parallel Old收集器是Parallel Scavenge 收集器的老年代版本,这个收集器是JDK1.6之后才开始提 供,从HotSpot虚拟机的垃圾收集器的图中也可以看出,Parallel Scavenge 收集器无法与CMS收集 器配合工作,因为一个是为了吞吐量,一个是为了客户体验(也就是暂停时间的缩短)

CMS (Concurrent Mark Sweep并发标记清除算法) 收集器

在某些阶段尽量使用和工作线程一起运行,减少STW时长(200ms以内), 提升响应速度,是互联 网服务端BS系统上较佳的回收算法

分为4个阶段:初始标记、并发标记、重新标记、并发清除,在初始标记、重新标记时需要

STW

初始标记:此过程需要STWStop The Word),只标记一下GC Roots能直接关联到的对 象,速度很快。

并发标记:就是GC Roots进行扫描可达链的过程,为了找出哪些对象需要收集。这个过程远 远慢于初始标记,但它是和用户线程一起运行的,不会出现STW,所有用户并不会感受到。

重新标记:为了修正在并发标记期间,用户线程产生的垃圾,这个过程会比初始标记时间稍微 长一点,但是也很快,和初始标记一样会产生STW

并发清理:在重新标记之后,对现有的垃圾进行清理,和并发标记一样也是和用户线程一起运 行的,耗时较长(和初始标记比的话),不会出现STW。 由于整个过程中,耗时最长的并发标记和并发清理都是与用户线程一起执行的,所以总体上来 说,CMS收集器的内存回收过程是与用户线程一起并发执行的。

 

 

 

9.2.7.2 垃圾收集器设置

优化调整Java 相关参数的目标: 尽量减少FullGC和STW

通过以下选项可以单独指定新生代、老年代的垃圾收集器

-server 指定为Server模式,也是默认值,一般使用此工作模式

-XX:+UseSerialGC

运行在Client模式下,新生代是Serial, 老年代使用SerialOld

-XX:+UseParNewGC

新生代使用ParNew,老年代使用SerialOld

-XX:+UseParallelGC

运行于server模式下,新生代使用Serial Scavenge, 老年代使用SerialOld

-XX:+UseParallelOldGC

新生代使用Paralell Scavenge, 老年代使用Paralell Old

-XX:ParallelGCThreads=N,在关注吞吐量的场景使用此选项增加并行线程数

-XX:+UseConcMarkSweepGC

新生代使用ParNew, 老年代优先使用CMS,备选方式为Serial Old 响应时间要短,停顿短使用这个垃圾收集器

-XX:CMSInitiatingOccupancyFraction=N,N为0-100整数表示达到老年代的大小的百分比多 少触发回收 默认68

-XX:+UseCMSCompactAtFullCollection 开启此值,在CMS收集后,进行内存碎片整理

-XX:CMSFullGCsBeforeCompaction=N 设定多少次CMS后,进行一次内存碎片整理

-XX:+CMSParallelRemarkEnabled 降低标记停顿

 

 

范例: 指定垃圾回收设置

#将参数加入到bin/catalina.sh中,重启观察Tomcat status。老年代已经使用CMS

[root@tomcat ~]#vim /usr/local/tomcat/bin/catalina.sh

......

# OS specific support. $var _must_ be set to either true or false.

JAVA_OPTS="-server -Xmx512m -Xms128m -XX:NewSize=48m -XX:MaxNewSize=200m -

XX:+UseConcMarkSweepGC -XX:+UseCMSCompactAtFullCollection -

XX:CMSFullGCsBeforeCompaction=5"                                                 

                                                          

cygwin=false

darwin=false

os400=false

.......

[root@tomcat ~]#systemctl restart tomcat

[root@tomcat ~]#ps aux |grep tomcat

tomcat     22093  2.2 15.2 3018616 148448 ?     Sl   17:51   0:06

 

 

 

 

开启垃圾回收输出统计信息,适用于调试环境的相关选项

-XX:+PrintGC 输出GC信息

-XX:+PrintGCDetails 输出GC详细信息

-XX:+PrintGCTimeStamps 与前两个组合使用,在信息上加上一个时间戳

-XX:+PrintHeapAtGC 生成GC前后椎栈的详细信息,日志会更大

注意: 以上适用调试环境,生产环境请移除这些参数,否则有非常多的日志输出

 

 

 

9.3 JVM相关工具

9.3.1 JVM 工具概述

$JAVA_HOME/bin

 

 

 

 

9.4 Tomcat 性能优化常用配置

9.4.1 内存空间优化

JAVA_OPTS="-server -Xms4g -Xmx4g -XX:NewSize= -XX:MaxNewSize= "

-server:服务器模式

-Xms:堆内存初始化大小

-Xmx:堆内存空间上限

-XX:NewSize=:新生代空间初始化大小

-XX:MaxNewSize=:新生代空间最大值

 

生产案例:

[root@centos8 ~]#vim /usr/local/tomcat/bin/catalina.sh

JAVA_OPTS="-server -Xms4g -Xmx4g -Xss512k -Xmn1g -

XX:CMSInitiatingOccupancyFraction=65 -XX:+AggressiveOpts -XX:+UseBiasedLocking -

XX:+DisableExplicitGC -XX:MaxTenuringThreshold=10 -XX:NewRatio=2 -

XX:PermSize=128m -XX:MaxPermSize=512m -XX:CMSFullGCsBeforeCompaction=5 -

XX:+ExplicitGCInvokesConcurrent -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -

XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -

XX:LargePageSizeInBytes=128m -XX:+UseFastAccessorMethods"

#一台tomcat服务器并发连接数不高,生产建议分配物理内存通常4G到8G较多,如果需要更多连接,一般会利用虚拟化技术实现多台tomcat

 

 

9.4.2 线程池调整

常用属性:

connectionTimeout :连接超时时长,单位ms

maxThreads:最大线程数,默认200

minSpareThreads:最小空闲线程数

maxSpareThreads:最大空闲线程数

acceptCount:当启动线程满了之后,等待队列的最大长度,默认100

URIEncodingURI 地址编码格式,建议使用 UTF-8

enableLookups:是否启用客户端主机名的DNS反向解析,缺省禁用,建议禁用,就使用客户端IP就行

compression:是否启用传输压缩机制,建议 "on"CPU和流量的平衡

compressionMinSize:启用压缩传输的数据流最小值,单位是字节

compressableMimeType:定义启用压缩功能的MIME类型text/html, text/xml, text/css,

text/javascript

 

9.4.3 Java压力测试工具

PerfMa 致力于打造一站式IT系统稳定性保障解决方案,专注于性能评测与调优、故障根因定位与解决,为企业提供一系列技术产品与专家服务,提升系统研发与运行质量。

[root@centos8 ~]#vim /usr/local/tomcat/conf/server.xml

......

<Connector port="8080" protocol="HTTP/1.1"  connectionTimeout="20000"

redirectPort="8443" />

......

 

posted @   惊起千层浪  阅读(241)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示