JVM调优2-远程监控

监控远程JVM

VisualJVM不仅是可以监控本地jvm进程,还可以监控远程的jvm进程,需要借助于JMX技术实现。

什么是JMX

JMX(Java Management Extensions,即Java管理扩展)是一个为应用程序、设备、系统等植入管理功能的框架。JMX可以跨越一系列异构操作系统平台、系统体系结构和网络传输协议,灵活地开发无缝集成的系统、网络和服务管理应用。

监控Tomcat

想要监控远程的tomcat,就需要在远程的tomcat进行对JMX配置,方法如下:

#在tomcat的bin目录下,修改catalina.sh,添加如下的参数 
JAVA_OPTS="-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=9999 
-Dcom.sun.management.jmxremote.authenticate=false 
-Dcom.sun.management.jmxremote.ssl=false" 
#这几个参数的意思是: 
#-Dcom.sun.management.jmxremote :允许使用JMX远程管理 
#-Dcom.sun.management.jmxremote.port=9999 :JMX远程连接端口 
#-Dcom.sun.management.jmxremote.authenticate=false :不进行身份认证,任何用户都可以连接 
#-Dcom.sun.management.jmxremote.ssl=false :不使用ssl

使用VisualJVM远程连接Tomcat

添加主机

在一个主机下可能会有很多的jvm需要监控,所以接下来要在该主机上添加需要监控的jvm:

连接成功。使用方法和前面就一样了,就可以和监控本地jvm进程一样,监控远程的tomcat进程。

3.6 可视化GC日志分析工具

GC日志输出参数

前面通过-XX:+PrintGCDetails可以对GC日志进行打印,我们就可以在控制台查看,这样虽然可以查看GC的信息,但是并不直观,可以借助于第三方的GC日志分析工具进行查看。

在日志打印输出涉及到的参数如下:

-XX:+PrintGC 输出GC日志 

-XX:+PrintGCDetails 输出GC的详细日志 

-XX:+PrintGCTimeStamps 输出GC的时间戳(以基准时间的形式) 

-XX:+PrintGCDateStamps 输出GC的时间戳(以日期的形式,如 2013-05-04T21:53:59.234+0800) 

-XX:+PrintHeapAtGC 在进行GC的前后打印出堆的信息 

-Xloggc:../logs/gc.log 日志文件的输出路径 

1.2.3.4.5.6.7.8.9.10.11.

测试:

-XX:+UseG1GC -XX:MaxGCPauseMillis=100 -Xmx256m -XX:+PrintGCDetails 
-XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC
-Xloggc:F://test//gc.log 

运行后就可以在E盘下生成gc.log文件。

使用GC Easy

它是一款在线的可视化工具,易用、功能强大,网站: http://gceasy.io/

3.7 调优实战

先部署一个web项目(自行准备)

压测

下面我们通过jmeter进行压力测试,先测得在初始状态下的并发量等信息,然后我们在对jvm做调优处理,再与初始状态测得的数据进行比较,看调好了还是调坏了。

首先需要对jmeter本身的参数调整,jmeter默认的的内存大小只有1g,如果并发数到达300以上时,将无法
正常执行,会抛出内存溢出等异常,所以需要对内存大小做出调整。
修改jmeter.bat文件:
set HEAP=-Xms1g -Xmx4g -XX:MaxMetaspaceSize=512m
在该文件中可以看到,jmeter默认使用的垃圾收集器是G1.
Defaults to '-XX:+UseG1GC -XX:MaxGCPauseMillis=100 -XX:G1ReservePercent=20'

添加gc相关参数

#内存设置较小是为了更频繁的gc,方便观察效果,实际要比此设置的更大 JAVA_OPTS="-XX:+UseParallelGC -XX:+UseParallelOldGC -Xms64m -Xmx128m - XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC - Xloggc:../logs/gc.log -Dcom.sun.management.jmxremote - Dcom.sun.management.jmxremote.port=9999 - Dcom.sun.management.jmxremote.authenticate=false - Dcom.sun.management.jmxremote.ssl=false"

1。重启tomcat

创建测试用例进行压测

调优方向主要从以下几个方面:

  • 调整内存
  • 更换垃圾收集器

对于JVM的调优,给出大家几条建议

  • 生产环境的JVM一定要进行参数设定,不能全部默认上生产。
  • 对于参数的设定,不能拍脑袋,需要通过实际并发情况或压力测试得出结论。
  • 对于内存中对象临时存在居多的情况,将年轻代调大一些。如果是G1或ZGC,不需要设定。
  • 仔细分析gceasy给出的报告,从中分析原因,找出问题。
  • 对于低延迟的应用建议使用G1或ZGC垃圾收集器。
  • 不要将焦点全部聚焦jvm参数上,影响性能的因素有很多,比如:操作系统、tomcat本身的参数等。

PerfMa

PerfMa提供了JVM参数分析、线程分析、堆内存分析功能,界面美观,功能强大,我们在做jvm调优时,可以作为一个辅助工具。官网: https://www.perfma.com/

04 Tomcat8优化

4.1 禁用AJP连接

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

什么是AJP呢?

AJP(Apache JServer Protocol) AJPv13协议是面向包的。WEB服务器和Servlet容器通过TCP连接来交互;为了节省SOCKET创建的昂贵代价,WEB服务器会尝试维护一个永久TCP连接到servlet容器,并且在多个请求和响应周期过程会重用连接。

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

修改conf下的server.xml文件,将AJP服务禁用掉即可。

<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
1.

4.2 执行器(线程池)

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

修改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" />

保存退出,重启tomcat,查看效果。

4.3 三种运行模式

tomcat的运行模式有3种:

\1. bio 默认的模式,性能非常低下,没有经过任何优化处理和支持.

\2. nio nio(new I/O),是Java SE 1.4及后续版本提供的一种新的I/O操作方式(即java.nio包及其子包)。Java nio是一个基于缓冲区、并能提供非阻塞I/O操作的Java API,因此nio也被看成是non-blocking I/O的缩写。它拥有比传统I/O操作(bio)更好的并发运行性能。

\3. apr 安装起来最困难,但是从操作系统级别来解决异步的IO问题,大幅度的提高性能.

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

设置nio2

<Connector executor="tomcatThreadPool" port="8080" protocol="org.apache.coyote.http11.Http11Nio2Protocol" connectionTimeout="20000" redirectPort="8443" />
1.

05 代码优化建议

(1)尽可能使用局部变量

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

(2)尽量减少对变量的重复计算

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

for (int i = 0; i < list.size(); i++) {...}

建议替换为:

int length = list.size(); for (int i = 0, i < length; i++) {...}

这样,在list.size()很大的时候,就减少了很多的消耗。

(3)尽量采用懒加载的策略,即在需要的时候才创建

String str = "aaa"; 
if (i == 1){ 
  list.add(str); 
}//建议替换成 
if (i == 1){ 
  String str = "aaa"; 
  list.add(str); 
}

(4)异常不应该用来控制流程

异常对性能不利。抛出异常首先要创建一个新的对象,Throwable接口的构造函数调用名为fifillInStackTrace()的本地同步方 法,fifillInStackTrace()方法检查堆栈,收集调用跟踪信息。只要有异常被抛出,Java虚拟机就必须调整调用堆栈,因为在处理过程中创建 了一个新的对象。异常只能用于错误处理,不应该用来控制程序流程。

(5)不要将数组声明为public static final

因为这毫无意义,这样只是定义了引用为static final,数组的内容还是可以随意改变的,将数组声明为public更是一个安全漏洞,这意味着这个数组可以被外部类所改变。

(6)不要创建一些不使用的对象,不要导入一些不使用的类

这毫无意义,如果代码中出现"The value of the local variable i is not used"、"The import java.util is never used",那么请删除这些无用的内容

(7)程序运行过程中避免使用反射

反射是Java提供给用户一个很强大的功能,功能强大往往意味着效率不高。不建议在程序运行过程中使用尤其是频繁使用反射机制,特别是 Method的invoke方法。

如果确实有必要,一种建议性的做法是将那些需要通过反射加载的类在项目启动的时候通过反射实例化出一个对象并放入内存。

(8)使用数据库连接池和线程池

这两个池都是用于重用对象的,前者可以避免频繁地打开和关闭连接,后者可以避免频繁地创建和销毁线程。

(9)容器初始化时尽可能指定长度

容器初始化时尽可能指定长度,如:new ArrayList<>(10); new HashMap<>(32); 避免容器长度不足时,扩容带来的性能损耗。

(10)ArrayList随机遍历快,LinkedList添加删除快

(11)使用Entry遍历map

(12)不要手动调用System().gc;

(13)String尽量少用正则表达式

正则表达式虽然功能强大,但是其效率较低,除非是有需要,否则尽可能少用。

replace() 不支持正则 replaceAll() 支持正则

如果仅仅是字符的替换建议使用replace()。

(14)日志的输出要注意级别

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

posted @ 2021-12-12 20:15  风光小磊  阅读(241)  评论(0编辑  收藏  举报