Tomcat8.0.11优化相关

Tomcat 8.0.X:

  要了解tomcat的优化,我们先看看Tomcat的官方定义:The Apache Tomcat® software is an open source implementation of the Java Servlet, JavaServer Pages, Java Expression Language and Java WebSocket technologies. The Java Servlet, JavaServer Pages, Java Expression Language and Java WebSocket specifications are developed under the Java Community Process.

  Apache Tomcat软件是一个开源的Java Servlet实现,JavaServer Pages,Java表达式语言和Java WebSocket技术。Java Servlet、JavaServer页面、Java表达式语言和Java WebSocket规范都是在Java Community Process下开发的。

Tomcat 系统架构:

  Tomcat 的缺省配置是不能稳定长期运行的,也就是不适合生产环境,它会死机,让你不断重新启动,甚至在午夜时分唤醒你。对于操作系统优化来说,是尽可能的增大可使用的内存容量、提高CPU 的频率,保证文件系统的读写速率等。经过压力测试验证,在并发连接很多的情况下,CPU 的处理能力越强,系统运行速度越快。

  从系统架构图再结合 conf/server.xml 中的标签配置来说,再结合tomcat的源码来看,每个组件都是对应Java中的一个类或者接口,先加载 server.xml 文件,解析文件中的标签组装成一个个的类,最后相互之间协同工作从而支撑起整个服务的运行,如果要对Tomcat本身进行优化的话,可以通过server.xml来改变相应组件的参数属性及行为方式来达到优化性能的目的,比如从架构图结合 server.xml我们可以得知其中比较重要的标签:Server,Services,Connector,Excutor,Engine,Host,Context等等。但是在官网中由如下介绍:

  在这3个组件的介绍中都提到了该元素很少由用户定制,所以这3个标签我们可以暂时不去过于的关注。Tomcat 的优化不像其它软件那样,简简单单的修改几个参数就可以了,由于他是由Java语言编写的,那么他也是运行在JVM上面的,它的优化主要有三方面,分为系统优化(机器本身的硬件性能)服务器的CPU、内存、硬盘等对性能有决定性的影响,硬件这块配置越高越好。,Tomcat 本身的优化,Java 虚拟机(JVM)调优。系统优化就不在介绍了,接下来就详细的介绍一下 Tomcat 本身与 JVM 优化,以 Tomcat 8 为例。

  从 Tomcat本身出发,在conf/web.xml文件中配置了默认的Servlet的支持以及一些静态资源的处理,还有资源压缩的支持,从代码的角度,只要是执行一段代码片段那么他一定会耗费一些时间,由于现在都采用nginx来管理静态资源,实现前后端分离开发,那么我们是否可以删除一些标签,让Tomcat本身尽可能少的去执行无用的代码也是可以提高相应的启动速度。

  先从 conf/web.xml文件出发,我们可以通过注释掉与我们项目无关的组件标签来使得Tomcat尽可能的少执行无用大代码块,再通过架构图结合源码 的执行逻辑是由外到内的标签解析顺序,我们可以先定位到的优化点则是Connector,由官网的介绍我们可以得知Connector(连接器)处理与客户机的通信。Tomcat提供了多个连接器。其中包括用于大多数HTTP通信的HTTP连接器,特别是在将Tomcat作为独立服务器运行时,以及实现将Tomcat连接到Apache HTTPD服务器等web服务器时使用的AJP协议的AJP连接器。创建定制的连接器是一项重大的工作。但是目前比较主流的是Nginx而不是Apache,我们可以根据自己的需求把 AJP协议相关的连接器注释掉,也能起到一定的效果。从外层标签到内层标签,一层层的来进行优化:

Connector标签:

  先来看一下在 server.xml 中这个标签的定义:

<Connector connectionTimeout="20000" port="8081" protocol="HTTP/1.1" redirectPort="8443"/>

  这里一个非常重要的优化的点是 protocol="HTTP/1.1",决定了Tomcat使用什么IO类型进行数据交互。我们知道每个标签对应到源码中都会有一个类来表示。在Tomcat 8.0.x的源码中支持4中IO类型。可以查看 AbstractEndpoint源码发现:

  在Connector 构造函数中会对该参数进行设置:

public Connector(String protocol) {
    setProtocol(protocol);
  .......
}

  在 setProtocol 方法中我们可以看到:

public void setProtocol(String protocol) {
        //当开起了APR库的支持
	if (AprLifecycleListener.isAprAvailable()) {
	    if ("HTTP/1.1".equals(protocol)) {//默认使用APR方式
	        setProtocolHandlerClassName
	            ("org.apache.coyote.http11.Http11AprProtocol");
	    } else if ("AJP/1.3".equals(protocol)) {
	        setProtocolHandlerClassName
	            ("org.apache.coyote.ajp.AjpAprProtocol");
	    } else if (protocol != null) {
	        setProtocolHandlerClassName(protocol);
	    } else {
	        setProtocolHandlerClassName
	            ("org.apache.coyote.http11.Http11AprProtocol");
	    }
	} else {//否则默认使用NIO来实现。
	    if ("HTTP/1.1".equals(protocol)) {
	        setProtocolHandlerClassName
	            ("org.apache.coyote.http11.Http11NioProtocol");
	    } else if ("AJP/1.3".equals(protocol)) {
	        setProtocolHandlerClassName
	            ("org.apache.coyote.ajp.AjpNioProtocol");
	    } else if (protocol != null) {
	        setProtocolHandlerClassName(protocol);
	    }
	}

}

  我们可以很清晰的发现这个参数的值的重要性,在官网中也有介绍,我们可以修改成下列的指定值来指定我们需要的IO,当我们使用APR方式的时候需要安装相关APR支持的支持插件:

org.apache.coyote.http11.Http11Protocol - blocking Java connector
org.apache.coyote.http11.Http11NioProtocol - non blocking Java NIO connector
org.apache.coyote.http11.Http11Nio2Protocol - non blocking Java NIO2 connector
org.apache.coyote.http11.Http11AprProtocol - the APR/native connector.

  然后我们通过 Jmeter 测试工具进行压测,当我们进行 BIO 与 NIO 进行压测会发现,当并发量相对来说比较小的时候,两者并没有很明显的差别,甚至BIO还有略微的优势,当并发量加大的时候,NIO会表现出很明显的优势,由于BIO是阻塞的,当并发量达到一定量的时候,可以通过工具看到会创建200个线程对请求进行处理(如果没有配置线程池会使用默认的线程池),而其他的请求则进入了等待队列,而NIO在同样的并发量下并没有出现明显的阻塞,而且对于两者的吞吐量,以及平均响应时间的比较发现,NIO的吞吐量比BIO会高,而且响应时间会较低。当并发量只需增高,我们可以修改成APR的方式去调用系统的IO来执行,可以支撑更大的并发场景。

  查看架构图可以发现这里还涉及了一个 Executor 的配置。这里面一些关键的参数还是很影响Tomcat的性能的,可以在Connector标签中配置  executor 指定线程池:

  1. acceptCount:达到最大连接数之后,等待队列中还能放多少连接,超过即拒绝,配置太大也没有意义。
  2. maxConnections达到这个值之后,将继续接受连接,但是不处理,能继续接受多少根据acceptCount的值。
  3. maxThreads:最大工作线程数,也就是用来处理request请求的,默认是200,如果自己配了executor,并且和Connector有关联了,则之前默认的200就会被忽略,取决于CPU的配置。监控中就可以看到所有的工作线程是什么状态,通过监控就能知道开启多少个线程合适。
  4. minSpareThreads最小空闲线程数。

  可以实践一下,Connector配合自定义的线程池:

<Connector executor="tomcatThreadPool" port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />
<Executor name="tomcatThreadPool" namePrefix="catalina-exec-" maxThreads="150" minSpareThreads="4"/>

  这里我们还需要注意的一点是 enableLookups 这个属性把他设置成false ,如果为true,则可以通过调用request.getRemoteHost()进行DNS查询来得到远程客户端的实际主机名,若为false则不进行DNS查询,而是返回其ip地址。

  如果这里没有将 tomcat 与 Apache 服务器进行整合,可以删掉 AJP的Connector。

 Host标签:

<Host appBase="webapps" autoDeploy="true" name="localhost" unpackWARs="true">

  在host标签中需要注意的一个点是 autoDeploy="true",Tomcat运行时,要用一个线程拿出来进行检查,生产环境之下一定要改成false,他是开启了一个线程对appBase目录进行监控,如果改变了相关文件会进行自动发布。

Context标签:

  reloadable:如果这个属性设为true,tomcat服务器在运行状态下会监视在WEB-INF/classes和WEB-INF/lib目录下class文件的改动,如果监测到有class文件被更新的,服务器会自动重新加载Web应用。在开发阶段将reloadable属性设为true,有助于调试servlet和其它的class文件,但这样用加重服务器运行负荷,建议在Web应用的发存阶段将reloadable设为false。

JVM优化:

  为什么会有JVM这块的优化?因为tomcat是java语言写的,那么对于jvm这块的优化在tomcat中就是适用的。比如修改一些参数,调整内存大小,选择合适的垃圾回收算法等等。

  现在有个问题,修改JVM参数在哪里修改会对tomcat生效?还是在bin文件夹之下,有一个catalina.sh,找到JAVA_OPTS即可,当然不建议对此文件进行直接修改,一般是在外面新建一个文件,然后引入进来,我们就不这样做了,直接修改bin/catalina.sh 文件。

运行时数据区和内存结构:

(1)程序计数器The pc Register,JVM支持多线程同时执行,每一个线程都有自己的pc register,线程正在执行的方法叫做当前方法。如果是java代码,pc register中存放的就是当前正在执行的指令的地址,如果是c代码,则为空。

(2)Java虚拟机栈Java Virtual Machine Stacks,Java虚拟机栈是线程私有的,它的生命周期和线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直到执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。

(3)堆Heap,Java堆是Java虚拟机所管理的内存中最大的一块。对是被所有线程共享的一块内存区域,在虚拟机启动时创建。次内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。Java对可以处于物理上不连续的内存空间中,只要逻辑上市连续的即可。

(4)方法区Method Area,方法区和Java堆一样,是各个线程共享的内存区域,也是在虚拟机启动时创建。它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做Non-Heap(非堆),目的是与Java堆区分开来。jdk1.8中就是metaspace,jdk1.6或者1.7中就是perm space。运行时常量池Runtime Constant Pool是方法区的一部分,Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池,用于存放编译时期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。

(5)本地方法栈Native Method Stacks,本地方法栈和虚拟机栈锁发挥的作用是非常相似的,它们之间的区别不过是虚拟机栈执行Java方法服务,而本地方法栈则为虚拟机使用到的native方法服务。

内存结构:

  上面对运行时数据区描述了很多,其实重点存储数据的是堆和方法区(非堆),所以我们内存结构的设计也是着重从这两方面展开的。简单的描述一下JVM内存结构。一块是非堆区,一块是堆区。堆区分为两大块,一个是Old区,一个是Young区。Young区分为两大块,一个是Survival区(S0+S1),一块是Eden区。 Eden:S0:S1=8:1:1,S0和S1一样大,也可以叫From和To。在同一个时间点上,S0和S1只能有一个区有数据,另外一个是空的。

垃圾回收算法:

  Java是做自动内存管理的,自动垃圾回收。如何确定一个对象是否是垃圾,从而确定是否需要回收?

(1)引用计数:对于某个对象而言,只要应用程序中持有该对象的引用,就说明该对象不是垃圾,如果一个对象没有任何指针对其引用,它就是垃圾。弊端 :AB相互持有引用,导致永远不能被回收。

(2)枚举根节点做可达性分析。能作为根节点的 :类加载器、Thread、虚拟机栈的本地变量表、static成员、常量引用、本地方法栈的变量等。

  常用的垃圾回收算法:能够确定一个对象是垃圾之后,怎么回收?得要有对应的算法

(1)标记清除:先标记所有需要回收的对象,然后统一回收。缺点 :效率不高,标记和清除两个过程的效率都不高,容易产生碎片,碎片太多会导致提前GC。

(2)复制:将内存按容量划分为大小相等的两块(S0和S1),每次只使用其中一块。当这块使用完了,就讲还存活的对象复制到另一块上,然后再把已经使用过的内存空间一次性清除掉【Young区此采用的是复制算法】优缺点 :实现简单,运行高效,但是空间利用率低。

(3)标记整理:标记需要回收的对象,然后让所有存活的对象移动到另外一端,直接清理掉端边界意外的内存。

  JVM中采用的是分代垃圾回收,换句话说,堆中的Old区和Young区采用的垃圾回收算法是不一样的。

(1)Young区:复制算法

(2)Old区:标记清除或标记整理:对象在被分配之后,可能声明周期比较短,Young区复制效率比较高。Old区对象存活时间比较长,复制来复制去没必要,不如做个标记。

对象分配方式:

  对象优先分配在Eden区,大对象直接进入老年代,多大的对象称为大对象?可以通过JVM参数指定 -XX:PretenureSizeThreshold,长期存活对象进入老年代。

垃圾收集器:

  垃圾收集器从大类上分为三大类,串行收集器,并行收集器,并发收集器。

串行收集器Serial:Serial、Serial Old:一个线程跑,停止,启动垃圾回收线程,回收完成,继续执行刚才暂停的线程。适用于内存比较小的嵌入式设备中。

并行收集器Parallel:Parallel Scavenge、Parallel Old,吞吐量优先,多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态,适合科学计算、后台处理等弱交互场景。

并发收集器Concurrent:CMS、G1,停顿时间优先,用户线程和垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),垃圾收集线程在执行的时候不会停顿用户程序的运行。适合于对相应时间有要求的场景,比如Web。

吞吐量和停顿时间解释:

  吞吐量:花在垃圾收集的时间和花在应用程序时间的占比。

  停顿时间:垃圾收集器做垃圾回收终端应用执行的时间。

  小结: 评价一个垃圾回收器的好坏,其实调优的时候就是在观察者两个变量

开启垃圾收集器:XX参数中的+-代表启用或者禁用该选项

(1)串行: -XX:+UseSerialGC -XX:+UseSerialOldGC 新老生代
(2)并行(吞吐量优先):
        -XX:+UseParallelGC
        -XX:+UseParallelOldGC
(3)并发收集器(响应时间优先)
        CMS: -XX:+UseConcMarkSweepGC
        G1: -XX:+UseG1GC    

  我们可以通过命令查看当前使用的垃圾回收器,可以根据自己的需求进行优化:

[root@pretty ~]# jinfo -flag UseParallelGC 6925
-XX:+UseParallelGC --->发现使用了ParallelGC
[root@pretty ~]# jinfo -flag UseG1GC 6925
-XX:-UseG1GC --->发现没有使用G1GC

  那么我们到底怎么查看GC的执行呢,我该怎么由我的肉眼去看到当前设置的GC算法及垃圾收集器的好坏呢?要想分析,得把GC日志打印出来才行,可以在tomcat中catalina.sh JAVA_OPTS配置相关参数。我们可以设置GC日志输出:

XX:+PrintGCDetails(:打印日志详情信息) -XX:+PrintGCTimeStamps(输出GC的时间戳(以基准时间的形式)) -XX:+PrintGCDateStamps -Xloggc:$CATALINA_HOME/logs/gc.log

  然后重启tomcat,在需要的时候可以将log下载下来看看内容,这样直接看日志有点费劲,对于高手当然是不费劲的,这里推荐一款GC日志分析软件,在线:http://gceasy.io,登陆这个网站,将log上传就会得到他对我们GC日志分析的结果,以及相应一些可行性优化建议。

内存溢出和优化:

  通常会优化以下参数,一般将 Xms 与 Xmx设置成一样的数值,这也是前辈们得出的最佳实践:

-Xms 等价于-XX:InitialHeapSize -- 初始化堆区大小
-Xmx 等价于-XX:MaxHeapSize -- 堆区最大大小
-Xss 等价于-XX:ThreadStackSize -- 栈大小

  另外一点,我们需要知道当前的参数是多少才能进行优化不是吗?接下去介绍几个查看参数的命令:

jinfo 查看已经运行的jvm里面的参数值
jinfo -flag MaxHeapSize PID 查看最大内存
jinfo -flag UseG1GC PID 查看垃圾回收器
jinfo -flags PID 查看曾经赋过值的一些参数

  内存不够用主要分为两个方面:堆和非堆。所以这时候就要去手动设置堆或者非堆的大小,然后程序中不停使用相对应的区域,等待内存溢出。关键是内存溢出之后,怎么得到溢出信息进行分析,有两种做法:

参数设置自动:

-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=./

jmap手动:

查看当前进程id PID
jmap -dump:format=b,file=heap.hprof PID
jmap -heap PID 打印出堆内存相关的信息

  当内存信息打印出来之后,发现看不懂,怎么办呢?得要有工具帮助我们看这块的信息,比如MAT。内存大小的设置会影响GC的使用频率,想得到最大的优化效果需要进行不断地调试,取出一个最优的选项。

其他优化:

  • Connector:配置压缩属性compression="500",文件大于500bytes才会压缩
  • 数据库优化:减少对数据库访问等待的时间,可以从数据库的层面进行优化,或者加缓存等等各种方案。
  • 开启浏览器缓存,nginx静态资源部署

  所有的优化都是相辅相成的,一点一滴的去提升服务器的性能,但是有一句话不得不说,再牛逼的服务器性能要是遇到傻逼开发写的代码,那也无济于事。

转载于:https://www.cnblogs.com/wuzhenzhao/p/10355560.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值