| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> Java知识库 -> [深度讲解]JVM调优 |Tomcat调优 -> 正文阅读 |
|
[Java知识库][深度讲解]JVM调优 |Tomcat调优 |
性能调优1、代码优化所谓代码优化是指对程序代码进行等价(指不改变程序的运行结果)变换。等价的含义是使得变换后的代码运行结果与变换前代码运行结果相同。优化的含义是最终生成的目标代码(运行时间更短、占用空间更小),时空效率优化。原则上,优化可以在编译的各个阶段进行,但最主要的一类是对中间代码进行优化,这类优化不依赖于具体的计算机。在不改变程序运行效果的前提下,对被编译的程序进行等价变换,使之能生成更加高效的目标代码。 1.0、编码规范避免随意使用静态变量 静态类、单例类、工厂类的构造函数应置为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、减少对变量的重复计算对方法的调用,即使方法中只有一句语句,也是有消耗的。所以例如下面的操作:
1.3、尽量采用懒加载的策略
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
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). 对于日志输出级别来说, 下面是我们应该记住的一些原则:
1.15、对资源的close()建议分开操作
2、Tomcat优化Tomcat是我们经常使用的 servlet容器之一,甚至很多线上产品都使用 Tomcat充当服务器。而且优化后的Tomcat性能提升显著。 2.1、Tomcat配置优化2.1.1 配置tomcat管理进入tomcat下的conf目录下的tomcat-users.xml
浏览器访问 点击“Server Status”,输入用户名、密码进行登录 name/psd
2.1.2 、禁用AJP连接在服务状态页面中可以看到,默认状态下会启用AJP服务,并且会占用8009端口。 AJP(Apache JServer Protocol) AJPv13协议是面向包的。WEB服务器和Servlet容器通过TCP连接来交互;为了节省SOCKET创建的昂贵代价,WEB服务器会尝试维护一个永久TCP连接到servlet容器,并且在多个请求和响应周期过程会重用连接。因为性能原因,使用二进制格式来传输可读性文本。WEB服务器通过TCP连接和SERVLET容器连接。为了减少进程生成socket的花费,WEB服务器和SERVLET容器之间尝试保持持久性的TCP连接,对多个请求/回复循环重用一个连接。一旦连接分配给一个特定的请求,在请求处理循环结束之前不会在分配。换句话说,在连接上,请求不是多元的。这个是连接两端的编码变得容易,虽然这导致在一时刻会有很多连接。一旦WEB服务器打开了一个到SERVLET容器的连接,连接处于下面的状态: Web客户访问Tomcat服务器上的JSP组件的两种方式 一般是使用Nginx+tomcat的架构,所以不会用AJP协议,把AJP连接器禁用。
**重启tomcat,查看效果。**看到AJP服务已经不存在了。 2.1.3 、执行器(线程池)在tomcat中每一个用户请求都是一个线程,所以可以使用线程池提高性能。 修改前 修改后
保存退出,重启tomcat 在页面中显示最大线程数为-1,这个是正常的,仅仅是显示的问题,实际使用的指定的值。 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包及其子包);是一个基于 APR模式: 简单理解就是,从操作系统级别解决异步IO问题,大幅度的提高服务器的处理合相应性能,也是Tomcat运行高并发应用的首选模式; apr 安装起来最困难,但是从操作系统级别来解决异步的IO问题,大幅度的提高性能. 推荐使用nio,不过,在tomcat8中有最新的nio2,速度更快,建议使用nio2. 设置nio2:
保存退出,重启tomcat 2.2、使用Apache JMeter进行测试(下载地址)JMeter是Apache组织的开放源代码项目,能做功能测试和性能测试。它能够对HTTP和FTP服务器进行压力和性能测试,也可以对任何数据库进行同样的测试(通过JDBC),还能以多种形式展现测试结果。跟LoadRunner的区别,JMeter是开源的,LR是商业软件,JMeter更灵活,LR更好用可以录制脚本。 2.2.1 、安装直接将下载好的zip压缩包进行解压即可。 进入bin目录,找到jmeter.bat文件,双机即可启动。 2.2.2 、设置语言为简体中文
2.2.3、创建首页的测试用例1、保存测试用例 2、添加线程组,使用线程模拟用户的并发
1000个线程,每个线程循环10次,也就是tomcat会接收到10000个请求。 3、添加http请求
4、添加请求监控 5、启动、进行测试 6、聚合报告 2.3、调整tomcat参数进行优化上面测试可以看出,tomcat在不做任何调整时,吞吐量为188次/秒 2.3.1、禁用AJP服务
2.3.2、设置线程池
测试结果: 吞吐量为220次/秒,性能有所提升。 2.3.3、最大线程数为1000,初始为200吞吐量为245次/秒,性能有所提升。
2.3.4、最大线程数为5000,初始为1000
吞吐量为204次/秒,性能有所下降。 可以看到,虽然最大线程已经设置到5000,但是实际测试效果并不理想,并且平均的响应时间也边长了,所以单纯靠提升线程数量是不能一直得到性能提升的 2.3.5、设置最大等待队列数默认情况下,请求发送到tomcat,如果tomcat正忙,那么该请求会一直等待。这样虽然可以保证每个请求都能请求到,但是请求时间就会边长。有些时候,我们也不一定要求请求一定等待,可以设置最大等待队列大小,如果超过就不等待了。这样虽然有些请求是失败的,但是请求时间会虽短。
**测试结果:**吞吐量为296次/秒有提升 2.3.6、设置nio2的运行模式
可以看到,平均响应时间有缩短,吞吐量为302次/秒有提升 3、调整JVM参数优化测试通过jvm参数进行优化,为了测试一致性,依然将最大线程数设置为1000,启用nio2运行模式。 3.1、设置并行垃圾回收器
测试结果与默认的JVM参数结果接近。 3.2、查看gc日志文件将gc.log文件上传到gceasy.io网站分析日志,查看gc中是否存在问题。 问题一:吞吐量表现还可以,但是gc时,线程的暂停时间稍有点长 问题二:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZrsaXGOp-1629686212992)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210823101742906.png)] 问题三:从GC原因的可以看出,年轻代大小设置不合理,导致了多次GC 3.3、调整年轻代大小将初始堆大小设置为128m,最大为1024m 初始年轻代大小64m,年轻代最大256m
从测试结果来看,吞吐量以及响应时间均有提升。 查看gc日志: 可以看到GC次数要明显减少,说明调整是有效的。 对tomcat性能优化就是需要不断的进行调整参数,然后测试结果,可能会调优也可能会调差,这时就需要借助于gc的可视化工具来看gc的情况。 |
|
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2025年1日历 | -2025/1/13 2:45:56- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |