[深度讲解]JVM调优\Tomcat调优

性能调优

1、代码优化

所谓代码优化是指对程序代码进行等价(指不改变程序的运行结果)变换。等价的含义是使得变换后的代码运行结果与变换前代码运行结果相同。优化的含义是最终生成的目标代码(运行时间更短、占用空间更小),时空效率优化。原则上,优化可以在编译的各个阶段进行,但最主要的一类是对中间代码进行优化,这类优化不依赖于具体的计算机。在不改变程序运行效果的前提下,对被编译的程序进行等价变换,使之能生成更加高效的目标代码。

1.0、编码规范

避免随意使用静态变量
当某个对象被定义为static变量所引用,那么gc通常是不会回收这个对象所占有的堆内存,此时静态变量的生命周期与A类相同,如果A类不被卸载,该静态对象会常驻内存,直到程序终止。

静态类、单例类、工厂类的构造函数应置为private
静态类、单例类、工厂类,这种类将构造函数置为private之后,保证了这些类不会产生实例对象。

慎用异常

异常对性能不利,抛出异常首先要创建一个新的对象,只要有异常被抛出,Java虚拟机就必须调整调用堆栈。

公用集合类中的数据要及时remove掉

如果一个集合类是公用的,不是方法里面的属性,那么这个集合里面的元素是不会自动释放的,因为始终有引用指向它们,这个公用集合可能会不断增大,系统有内存泄露的隐患。

使用同步代码块替代同步方法

同步方法锁的范围比较大,而同步代码块范围相对要小,一般同步的范围越大,性能就越差,范围越小越好,这样性能更好。

把一个基本数据类型转为字符串

基本数据类型.toString()是最快的方式、String.valueOf(数据)次之、数据+”"最慢

public方法应避免有过多的形参

Java讲求一切都是对象,太多的形参和面向对象的编程思想并不契合,参数太多势必导致方法调用的出错概率增加。

1.1、关于局部变量的使用

调用方法时传递的参数以及在调用的过程中创建的临时变量都会保存在栈中速度较快。而其他变量,如静态变量、实例变量等都在堆中创建,速度较慢。另外,栈中创建的变量,随着方法的运行结束,这些内容就没了,不需要额外的垃圾回收。

Java把内存划分成两种:一种是栈内存,一种是堆内存。

栈(stack)与堆(heap)都是Java用来在Ram中存放数据的地方。与C++不同,Java自动管理栈和堆,程序员不能直接地设置栈或堆。栈的优势是,存取速度比堆要快,仅次于直接位于CPU中的寄存器。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。另外,栈数据可以共享。堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态分配内存,存取速度较慢。查看字节码揭示了堆栈变量效率更高的原因。jvm 是一种基于堆栈的虚拟机,因此优化了对堆栈数据的存取和处理。所有局部变量都存储在一个局部变量表中,在 java 操作数堆栈中进行处理,并可被高效地存取。存取 static 变量和实例变量成本更高,因为 jvm 必须使用代价更高的操作码,并从常数存储池中存取他们。(常数存储池保存一个类型所使用的所有类型、字段和方法的符号引用。)通常,在第一次从常数存储池中访问 static 变量或实例变量以后,jvm 将动态更改字节码以使用效率更高的操作码。尽管有这种优化,堆栈变量的存取仍然更快。所以便通过存取堆栈变量而不是实例变量或 static 变量使操作更高效

1.2、减少对变量的重复计算

对方法的调用,即使方法中只有一句语句,也是有消耗的。所以例如下面的操作:

for (int i = 0; i < list.size(); i++) {...}
//java建议替换为:
int length = list.size();
for (int i = 0, i < length; i++) {...}
//因为for循环中,每次都要去计算数组长度,这样在list.size()很大的时候,就会减少了很多的消耗

1.3、尽量采用懒加载的策略

String str = "懒加载的策略"; 
if (i == 1){ 
    list.add(str);
}
//建议替换成
if (i == 1){ 
    String str = "懒加载的策略"; 
	list.add(str);
}

1.4、异常不用来控制程序流程

异常不要用来做流程控制,条件控制。说明:异常设计的初衷是解决程序运行中各种意外的情况,且异常的处理效率比条件判断方式低很多。异常对性能不利。抛出异常首先要创建一个新的对象,Throwable接口的构造函数调用名为fifillInStackTrace()的本地同步方 法,fifillInStackTrace()方法检查堆栈,收集调用跟踪信息。只要有异常被抛出,Java虚拟机就必须调整调用堆栈,因为在处理过程中创建 了一个新的对象。异常只能用于错误处理,不应该用来控制程序流程。

1.5、不将数组声明为public static final

因为数组是可变对象,所以最终约束要求数组对象本身只分配一次,但不保证数组元素的值。由于数组是public的,因此恶意程序可以更改存储在数组中的值。因此,在大多数情况下,声明为public final static的数组是一个错误。而且这毫无意义,这样只是定义了引用为static fifinal,数组的内容还是可以随意改变的,将数组声明为public更是一个安全漏洞,这意味着这个数组可以被外部类所改变。

1.6、不创建一些不使用的对象,不导入一些不使用的类

如果代码中出现"The value of the local variable i is not used"、“The import java.util is neverused”。在写到目前这个对象创建的语句时,没有对这个对象进行操作,编译器会认为创建的这个对象没有操作是浪费内存的行为,所以就给出警告,要删除这些无用的内容。

1.7、程序运行过程中避免使用反射

反射是一个强大的工具,它使得我们可以编写更为动态的软件,通过反射,一个应用程序可以通过添加一些“在应用程序部署时还不存在的”新组件,来完成新功能的升级,这是反射最大的作用。 反射也是一把双刃剑,它在带来便利的同时,也引入了复杂性,从而使得发生问题的概率也大大增加。当我们使用反射时,绕过了C#类型安全,Invoke方法接收的参数和返回值的类型都是System.Object,我们必须确保在运行时使用的是正确的类型。因此,虽然反射使得构建动态程序变得容易了,但是程序出现问题的可能性也变更多了,我们不应该过度使用反射。 由于反射带来了弱类型问题,这样在如何动态的创建对象方面,我们可以寻找其他解决方案,我们可以使用接口来在运行时指定对象的类型。只有当调用目标不能清晰的使用接口来表达时,我们才应该使用反射。反射是Java提供给用户一个很强大的功能,功能强大往往意味着效率不高。不建议在程序运行过程中使用尤其是频繁使用反射机制,特别是 Method的invoke方法。如果确实有必要,一种建议性的做法是将那些需要通过反射加载的类在项目启动的时候通过反射实例化出一个对象并放入内存.

1.8、使用数据库连接池和线程池

两个池都是用于重用对象的,前者可以避免频繁地打开和关闭连接,后者可以避免频繁地创建和销毁线程。当客户端请求的数据量比较大的时候,使用线程池可以节约大量的系统资源,使得更多的CPU时间和内存可以高效地利用起来。而数据库连接池的使用则将大大提高程序运行效率,同时,我们可以通过其自身的管理机制来监视数据库连接的数量、使用情况等。

1.9、容器初始化时尽可能指定长度

如:在初始化集合时,如果已知集合的数量,那么一定要在初始化时设置集合的容量大小,这样就可以有效的提高集合的性能,但需要注意的是 HashMap 的实际存储量是“元素个数*负载因子”,而负载因子默认是 0.75,因此在设置大小时,要使用“(存储元素个数/负载因子)+1”的公式计算出正确的值再进行设置。

1.10、ArrayList随机遍历快,LinkedList添加删除快

LinkedList 的 for 循环性能较差,而 ArrayList 的 for 循环性能较好。 这是因为 LinkedList 基于链表实现的,在使用 for 循环的时候,每一次 for 循环都会去遍历半个 List,所以严重影响了遍历的效率;ArrayList 则是基于数组实现的,并且实现了 RandomAccess 接口标志,意味着 ArrayList 可以实现快速随机访问,所以 for 循环效率非常高。 LinkedList 的迭代循环遍历和 ArrayList 的迭代循环遍历性能相当,也不会太差,所以在遍历 LinkedList 时,我们要尽量避免使用 for 循环遍历。

1.11、使用Entry遍历Map

//错误方式
Map<String,String> map = new HashMap<>(); 
for (Map.Entry<String,String> entry : map.entrySet()) {
	String key = entry.getKey(); 
	String value = entry.getValue(); 
}
//正确方式
Map<String,String> map = new HashMap<>(); 
for (String key : map.keySet()) {
	String value = map.get(key);
}

1.12、不手动调用System.gc();

至少在6600的VM上,gc是会真的执行的,而不是"建议"执行.有人说这样至少可以防止OOME(OutOfMemoryException).其实经过实际测试,VM总是会在内存还剩10%~20%左右的时候调用自动GC,所以不会存在垃圾越堆越多.不过如果在内存资源紧缺的时候,加载大的资源还是会出现OOME:即使这个时候调用GC也无济于事.只能从根本上避免在内存到达峰值的时候加载大的资源.

1.13、String尽量少用正则表达式

正则表达式虽然功能强大,但是其效率较低,除非是有需要,否则尽可能少用。replace() 不支持正则 replaceAll() 支持正则如果仅仅是字符的替换建议使用replace()。

1.14、日志的输出要注意级别

相对于System.out来说, 日志框架有两个最大的优点就是可以指定输出类别(category)和级别(level). 对于日志输出级别来说, 下面是我们应该记住的一些原则:

  • **ERROR:**系统发生了严重的错误, 必须马上进行处理, 否则系统将无法继续运行. 比如, NPE, 数据库不可用等.

  • **WARN:**系统能继续运行, 但是必须引起关注. 对于存在的问题一般可以分为两类: 一种系统存在明显的问题(比如, 数据不可用), 另一种就是系统存在潜在的问题, 需要引起注意或者给出一些建议(比如, 系统运行在安全模式或者访问当前系统的账号存在安全隐患). 总之就是系统仍然可用, 但是最好进行检查和调整.

  • **INFO:**重要的业务逻辑处理完成. 在理想情况下, INFO的日志信息要能让高级用户和系统管理员理解, 并从日志信息中能知道系统当前的运行状态. 比如对于一个机票预订系统来说, 当一个用户完成一个机票预订操作之后, 提醒应该给出"谁预订了从A到B的机票". 另一个需要输出INFO信息的地方就是一个系统操作引起系统的状态发生了重大变化(比如数据库更新, 过多的系统请求).

  • **DEBUG:**主要给开发人员看, 下面会进一步谈到.

  • TRACE: 系统详细信息, 主要给开发人员用, 一般来说, 如果是线上系统的话, 可以认为是临时输出, 而且随时可以通过开关将其关闭. 有时候我们很难将DEBUG和TRACE区分开, 一般情况下, 如果是一个已经开发测试完成的系统, 再往系统中添加日志输出, 那么应该设为TRACE级别.

1.15、对资源的close()建议分开操作

/**能避免资源泄露,万一XXX.close()抛异常了,那么就进入了catch块中了,
YYY.close()不会执行,YYY这块资源就不会回收了,一直占用着,这样的代码一多,
是可能引起资源句柄泄露的。/
try{
    XXX.close(); 
    YYY.close();
}catch (Exception e){
	... 
}
// 建议改为
try{
	XXX.close();
}catch (Exception e){
	...
}
try{
	YYY.close();
}catch (Exception e){
	... 
}

2、Tomcat优化

Tomcat是我们经常使用的 servlet容器之一,甚至很多线上产品都使用 Tomcat充当服务器。而且优化后的Tomcat性能提升显著。

2.1、Tomcat配置优化

2.1.1 配置tomcat管理

进入tomcat下的conf目录下的tomcat-users.xml

在这里插入图片描述

#末尾写入如下内容: 
<role rolename="manager"/>
<role rolename="manager-gui"/> 
<role rolename="admin"/> 
<role rolename="admin-gui"/> 
<user username="name" password="psd" roles="admin-gui,admin,manager-gui,manager"/> 
#保存退出 

#如果是tomcat7,配置了tomcat用户就可以登录系统了,但是tomcat8中不行,还需要修改另一个配置文件,否则访 问不了,提示403
vi webapps/manager/META-INF/context.xml 
#将<Valve的内容注释掉                                                                                                                <Context antiResourceLocking="false" privileged="true" > 
<!-- <Valve className="org.apache.catalina.valves.RemoteAddrValve" allow="127\.\d+\.\d+\.\d+|::1|0:0:0:0:0:0:0:1" /> -->
<Manager sessionAttributeValueClassNameFilter="java\.lang\. (?:Boolean|Integer|Long|Number|String)|org\.apache\.catalina\.filters\.CsrfPreventionFi lter\$LruCache(?:\$1)?|java\.util\.(?:Linked)?HashMap"/> 
</Context> 
#保存退出即可

浏览器访问

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0zl38Nyr-1629680401706)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210820140753998.png)]

点击“Server Status”,输入用户名、密码进行登录 name/psd

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hdOXqQqG-1629680401708)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210820141916653.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ztiw51bJ-1629680401709)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210820140934125.png)]

2.1.2 、禁用AJP连接

在服务状态页面中可以看到,默认状态下会启用AJP服务,并且会占用8009端口。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VwSLypyj-1629680401710)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210820142425024.png)]

AJP(Apache JServer Protocol) AJPv13协议是面向包的。WEB服务器和Servlet容器通过TCP连接来交互;为了节省SOCKET创建的昂贵代价,WEB服务器会尝试维护一个永久TCP连接到servlet容器,并且在多个请求和响应周期过程会重用连接。因为性能原因,使用二进制格式来传输可读性文本。WEB服务器通过TCP连接和SERVLET容器连接。为了减少进程生成socket的花费,WEB服务器和SERVLET容器之间尝试保持持久性的TCP连接,对多个请求/回复循环重用一个连接。一旦连接分配给一个特定的请求,在请求处理循环结束之前不会在分配。换句话说,在连接上,请求不是多元的。这个是连接两端的编码变得容易,虽然这导致在一时刻会有很多连接。一旦WEB服务器打开了一个到SERVLET容器的连接,连接处于下面的状态:
◆ 空闲
这个连接上没有处理的请求。
◆ 已分派
连接正在处理特定的请求。一旦一个连接被分配给一个特定的请求,在连接上发送的基本请求信息是高度压缩的。在这点,SERVLET容器大概准备开始处理请求,当它处理的时候,它能发回下面的信息给WEB服务器:
◆ SEND_HEADERS
发送一组头到浏览器。
◆ SEND_BODY_CHUNK
   发送一块主体数据到浏览器。
◆ GET_BODY_CHUNK
从请求获得下一个数据如果还没有全部传输完,如果请求内容的包长度非常大或者长度不确定,这是非常必要的。例如上载文件。注意这和HTTP的块传输没有关联。
◆ END_RESPONSE
结束请求处理循环。

Web客户访问Tomcat服务器上的JSP组件的两种方式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VExQ0qra-1629680401711)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210820144056065.png)]

一般是使用Nginx+tomcat的架构,所以不会用AJP协议,把AJP连接器禁用。

修改conf下的server.xml文件,将AJP服务禁用掉即可。
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
<!-- Define an AJP 1.3 Connector on port 8009 -->
 <!--<Connector protocol="AJP/1.3"
               address="::1"
               port="8009"
               redirectPort="8443" />-->

**重启tomcat,查看效果。**看到AJP服务已经不存在了。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jSHXgFmy-1629680401712)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210820144419459.png)]

2.1.3 、执行器(线程池)

在tomcat中每一个用户请求都是一个线程,所以可以使用线程池提高性能。

修改前

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IOZ95mvL-1629680401713)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210820144638960.png)]

修改后

修改server.xml文件
<!--将注释打开-->
<Executor name="tomcatThreadPool" namePrefix="catalina-exec-" maxThreads="500" minSpareThreads="50" prestartminSpareThreads="true" maxQueueSize="100"/> 
<!-- 参数说明: maxThreads:最大并发数,默认设置 200,一般建议在 500 ~ 1000,
根据硬件设施和业务来判断 minSpareThreads:Tomcat 初始化时创建的线程数,默认设置 25 prestartminSpareThreads: 
在 Tomcat 初始化的时候就初始化 minSpareThreads 的参数值,
如果不等于 true,minSpareThreads 的值就没啥效果了 maxQueueSize,最大的等待队列数,超过则拒绝请求 --> 
<!--在Connector中设置executor属性指向上面的执行器-->
<Connector executor="tomcatThreadPool" port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SZy4XxuA-1629680401713)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210820145110117.png)]

保存退出,重启tomcat

在页面中显示最大线程数为-1,这个是正常的,仅仅是显示的问题,实际使用的指定的值。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Htb4rplM-1629680401714)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210820145244273.png)]

2.1.3 、3种运行模式

BIO模式: 阻塞式I/O操作,表示Tomcat使用的是传统Java I/O操作(即:java.io包及其子包);Tomcat 7以下版本默认情况下是以BIO模式运行的,由于每个请求的都要创建一个线程来处理,因此 线程的开销较大,不能处理高兵的场景,在三种模式中性能也最低效;bio 默认的模式,性能非常低下,没有经过任何优化处理和支持.

NIO模式: 是Java SE 1.4以后续版本提供的一种新的I/O操作方式(即:java.nio包及其子包);是一个基于 缓存区、并提供非阻塞I/O操作的Java API,它拥有比传统的I/O操作(BIO)更好的并发运行性能;

APR模式: 简单理解就是,从操作系统级别解决异步IO问题,大幅度的提高服务器的处理合相应性能,也是Tomcat运行高并发应用的首选模式; apr 安装起来最困难,但是从操作系统级别来解决异步的IO问题,大幅度的提高性能.

推荐使用nio,不过,在tomcat8中有最新的nio2,速度更快,建议使用nio2.

设置nio2:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-y0jckZS0-1629680401715)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210820145624800.png)]

修改server.xml文件
<Connector executor="tomcatThreadPool" port="8080" protocol="org.apache.coyote.http11.Http11Nio2Protocol" connectionTimeout="20000" redirectPort="8443" />

保存退出,重启tomcat

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V1UmUdTi-1629680401715)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210820150013249.png)]

2.2、使用Apache JMeter进行测试(下载地址)

JMeter是Apache组织的开放源代码项目,能做功能测试和性能测试。它能够对HTTP和FTP服务器进行压力和性能测试,也可以对任何数据库进行同样的测试(通过JDBC),还能以多种形式展现测试结果。跟LoadRunner的区别,JMeter是开源的,LR是商业软件,JMeter更灵活,LR更好用可以录制脚本。

2.2.1 、安装

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UuVqz2tc-1629680401716)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210820151245947.png)]

直接将下载好的zip压缩包进行解压即可。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uRdWyyZi-1629680401716)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210820151615025.png)]

进入bin目录,找到jmeter.bat文件,双机即可启动。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jSz6aEOl-1629680401716)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210820151630275.png)]

2.2.2 、设置语言为简体中文

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m5kgdnX5-1629680401717)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210820151938749.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OoQWzQmo-1629680401717)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210820151953174.png)]

2.2.3、创建首页的测试用例

1、保存测试用例

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EWM1PISz-1629680401718)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210820152159305.png)]

2、添加线程组,使用线程模拟用户的并发

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5AAE0jae-1629680401718)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210820155002538.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Hj9C3Sw6-1629680401719)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210820153035701.png)]

1000个线程,每个线程循环10次,也就是tomcat会接收到10000个请求。

3、添加http请求

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sYcnn9To-1629680401719)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210820154949356.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7wqzuEQr-1629680401719)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210820154223341.png)]

4、添加请求监控

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ot0kUVn0-1629680401720)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210820154443824.png)]

5、启动、进行测试

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d4Lilxud-1629680401720)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210820154938643.png)]

6、聚合报告

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ETGUqm3R-1629680401720)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210820164701544.png)]

2.3、调整tomcat参数进行优化

上面测试可以看出,tomcat在不做任何调整时,吞吐量为188次/秒

2.3.1、禁用AJP服务
修改conf下的server.xml文件,将AJP服务禁用掉即可。
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
<!-- Define an AJP 1.3 Connector on port 8009 -->
 <!--<Connector protocol="AJP/1.3"
               address="::1"
               port="8009"
               redirectPort="8443" />-->
2.3.2、设置线程池
<Executor name="tomcatThreadPool" namePrefix="catalina-exec-" maxThreads="500" minSpareThreads="50" prestartminSpareThreads="true"/>

<Connector executor="tomcatThreadPool" port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />

测试结果:

吞吐量为220次/秒,性能有所提升。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KQJ86eME-1629680401721)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210820165054567.png)]

2.3.3、最大线程数为1000,初始为200

吞吐量为245次/秒,性能有所提升。

<Executor name="tomcatThreadPool" namePrefix="catalina-exec-" maxThreads="1000" minSpareThreads="200" prestartminSpareThreads="true"/>

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OEuxgIk9-1629680401721)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210820170551372.png)]

2.3.4、最大线程数为5000,初始为1000
<Executor name="tomcatThreadPool" namePrefix="catalina-exec-" maxThreads="5000" minSpareThreads="1000" prestartminSpareThreads="true"/>

吞吐量为204次/秒,性能有所下降。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FtCIDwdH-1629680401721)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210820165906061.png)]

可以看到,虽然最大线程已经设置到5000,但是实际测试效果并不理想,并且平均的响应时间也边长了,所以单纯靠提升线程数量是不能一直得到性能提升的

2.3.5、设置最大等待队列数

默认情况下,请求发送到tomcat,如果tomcat正忙,那么该请求会一直等待。这样虽然可以保证每个请求都能请求到,但是请求时间就会边长。有些时候,我们也不一定要求请求一定等待,可以设置最大等待队列大小,如果超过就不等待了。这样虽然有些请求是失败的,但是请求时间会虽短。

<!--最大等待数为100--> 
<Executor name="tomcatThreadPool" namePrefix="catalina-exec-" maxThreads="1000" minSpareThreads="200" prestartminSpareThreads="true" maxQueueSize="100"/>

**测试结果:**吞吐量为296次/秒有提升

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-22AZ45ZU-1629680401722)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210820170604945.png)]

2.3.6、设置nio2的运行模式

<!-- 设置nio2 --> 
<Connector executor="tomcatThreadPool" port="8080" protocol="org.apache.coyote.http11.Http11Nio2Protocol" connectionTimeout="20000" redirectPort="8443" />

可以看到,平均响应时间有缩短,吞吐量为302次/秒有提升

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5GBkNWT1-1629680401722)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210820173325341.png)]

3、调整JVM参数优化

测试通过jvm参数进行优化,为了测试一致性,依然将最大线程数设置为1000,启用nio2运行模式。

3.1、设置并行垃圾回收器

  • window 在tomcat的bin下的catalina.bat文件中第一行增加配置

  • linux 在tomcat的bin下的catalina.sh文件中第一行增加配置

  • windows服务执行的是bin\tomcat.exe.他读取注册表中的值,而不是catalina.bat的设置.(修改注册表重起tomcat服务,设置生效)

#年轻代、老年代均使用并行收集器,初始堆内存64M,最大堆内存512M 
JAVA_OPTS="-XX:+UseParallelGC -XX:+UseParallelOldGC -Xms64m -Xmx512m - XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -Xloggc:../logs/gc.log

测试结果与默认的JVM参数结果接近。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-beiNAIJw-1629686212987)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210823093739458.png)]

3.2、查看gc日志文件

将gc.log文件上传到gceasy.io网站分析日志,查看gc中是否存在问题。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dAKGLgXz-1629686212989)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210823093932488.png)]

问题一:吞吐量表现还可以,但是gc时,线程的暂停时间稍有点长

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Uy1ApVRh-1629686212990)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210823094514419.png)]

问题二:

  • 年轻代的gc有329次,次数有点多,说明年轻代设置的大小不合适需要调整
  • FullGC有8次,说明堆内存的大小不合适,需要调整

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZrsaXGOp-1629686212992)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210823101742906.png)]

问题三:从GC原因的可以看出,年轻代大小设置不合理,导致了多次GC

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-srn1xpER-1629686212994)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210823102714670.png)]

3.3、调整年轻代大小

将初始堆大小设置为128m,最大为1024m

初始年轻代大小64m,年轻代最大256m

JAVA_OPTS="-XX:+UseParallelGC -XX:+UseParallelOldGC -Xms128m -Xmx1024m -XX:NewSize=64m -XX:MaxNewSize=256m -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -Xloggc:../logs/gc.log"

从测试结果来看,吞吐量以及响应时间均有提升。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LLx95jDm-1629686212994)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210823101229759.png)]

查看gc日志:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hgdLp13i-1629686212995)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210823101607136.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nY4RHrrI-1629686212996)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210823101629756.png)]

可以看到GC次数要明显减少,说明调整是有效的。

对tomcat性能优化就是需要不断的进行调整参数,然后测试结果,可能会调优也可能会调差,这时就需要借助于gc的可视化工具来看gc的情况。

  • 48
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 65
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 65
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值