竹笋

首页 » 问答 » 常识 » 深度讲解JVM调优Tomcat调
TUhjnbcbe - 2023/1/13 21:19:00

性能调优

、代码优化

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

.0、编码规范

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

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

慎用异常

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

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

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

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

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

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

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

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

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

.、关于局部变量的使用

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

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

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

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

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

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

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

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

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

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

.5、不将数组声明为publicstaticfinal

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

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

如果代码中出现"Thevalueofthelocalvariableiisnotused"、“Theimportjava.utilisneverused”。在写到目前这个对象创建的语句时,没有对这个对象进行操作,编译器会认为创建的这个对象没有操作是浪费内存的行为,所以就给出警告,要删除这些无用的内容。

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

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

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

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

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

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

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

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

.、使用Entry遍历Map

//错误方式MapString,Stringmap=newHashMap();for(Map.EntryString,Stringentry:map.entrySet()){  Stringkey=entry.getKey();  Stringvalue=entry.getValue();}//正确方式MapString,Stringmap=newHashMap();for(Stringkey:map.keySet()){  Stringvalue=map.get(key);}

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

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

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

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

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

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

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

**WARN:**系统能继续运行,但是必须引起
   发送一块主体数据到浏览器。◆GET_BODY_CHUNK从请求获得下一个数据如果还没有全部传输完,如果请求内容的包长度非常大或者长度不确定,这是非常必要的。例如上载文件。注意这和HTTP的块传输没有关联。◆END_RESPONSE结束请求处理循环。

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

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

修改conf下的server.xml文件,将AJP服务禁用掉即可。Connectorport=""protocol="AJP/."dictPort="8"/!--DefineanAJP.Connectoronport--!--Connectorprotocol="AJP/."addss="::"port=""dictPort="8"/--

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

..、执行器(线程池)

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

修改前

修改后

修改server.xml文件!--将注释打开--Executorname="tomcatThadPool"namePfix="catalina-exec-"maxThads=""minSpaThads="50"pstartminSpaThads="true"maxQueueSize="00"/!--参数说明:maxThads:最大并发数,默认设置00,一般建议在~,根据硬件设施和业务来判断minSpaThads:Tomcat初始化时创建的线程数,默认设置5pstartminSpaThads:在Tomcat初始化的时候就初始化minSpaThads的参数值,如果不等于true,minSpaThads的值就没啥效果了maxQueueSize,最大的等待队列数,超过则拒绝请求--!--在Connector中设置executor属性指向上面的执行器--Connectorexecutor="tomcatThadPool"port=""protocol="HTTP/."connectionTimeout="0"dictPort="8"/

保存退出,重启tomcat

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

..、种运行模式

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

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

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

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

设置nio:

修改server.xml文件Connectorexecutor="tomcatThadPool"port=""protocol="org.apache.coyote.

1
查看完整版本: 深度讲解JVM调优Tomcat调