tomcat、jvm、mysql调优以及进程占用高的问题

本次文章是纪念某次失败后的的一次总结。

调优概述

调优一共分为以下几个方面:
架构调优:这个就没什么好说的,一般就是结构流程上的调整。一般涉及到架构上的调整都是会有很大的动作的。
Java代码调优:比如优化程序的流程。优化并发方法的代码、使用cache、阻塞改为非阻塞等等。
JVM调优:一般是作为最后的优化项了。比如根据自己程序的特点选择合适的gc算法,或者调整堆栈的参数等。
tomcat优化:优化tomcat的配置文件。调整为适合应用的参数。
SQL优化:SQL优化常常被提及。所以这部分的优化点和注意事项也要重点关注。
前端代码调优:由于前端不是很会。所以不懂。

其中:JVM调优经常定位Java代码调优。用JVM分析出的新生代和老年代的情况特征定位到哪部分的代码存在问题。比如存在较多的大对象导致频繁FGC,有很多的小对象一直回收不了导致频繁GC等。同样的sql的优化也是重中之重。基本就是面试必考点。

tomcat调优

tomcat介绍

首先上一张tomcat的架构图(百度的)。
在这里插入图片描述

这个应该很多人都了解过一点。它的结构图就对应了配置文件的结构。
下面就对照官网来具体的说一下结构。
http://tomcat.apache.org/tomcat-8.5-doc/architecture/overview.html

tomcat的架构一共分为以下6个方面:
Server层:对应server的标签。官网解释:In the Tomcat world, a Server represents the whole container. Tomcat provides a default implementation of the Server interface which is rarely customized by users.即在tomcat的世界中。一个server就代表了一个容器,一般都是使用默认的接口实现用户很少去改动它。

Service:对应service标签。官网解释:A Service is an intermediate component which lives inside a Server and ties one or more Connectors to exactly one Engine. The Service element is rarely customized by users, as the default implementation is simple and sufficient: Service interface.即在service中一个service就是一个中间组件。它位于server的内部。并将一个或多个Connector绑定到一个Engine上。它也很少被用户改动。使用默认的就足够了。

Engine层:对应engine标签。官网解释:An Engine represents request processing pipeline for a specific Service. As a Service may have multiple Connectors, the Engine receives and processes all requests from these connectors, handing the response back to the appropriate connector for transmission to the client. The Engine interface may be implemented to supply custom Engines, though this is uncommon.
Note that the Engine may be used for Tomcat server clustering via the jvmRoute parameter. Read the Clustering documentation for more information.即一个engine就代表为指定的service处理请求的服务管道。由于一个service可能有多个connector,Engine接受并处理所有来自connector的请求。并将处理的结果返回给适当的连接器,以便传输给客户端。engine接口可以自定义实现,虽然这并不常见。
注意:Engine 可以通过jvmRoute参数用于Tomcat服务器集群。

Connector层:对应connector标签。官方解释:A Connector handles communications with the client. There are multiple connectors available with Tomcat. These include the HTTP connector which is used for most HTTP traffic, especially when running Tomcat as a standalone server, and the AJP connector which implements the AJP protocol used when connecting Tomcat to a web server such as Apache HTTPD server. Creating a customized connector is a significant effort.即Connector处理与客户端的通信。tomcat提供了多个连接器。比如http connector就用于处理绝大多数的http流量。特别是当tomcat作为独立的服务器运行时更是如此。AJP连接器实现了AJP协议将Tomcat连接到web服务器(如apachehttpd服务器)。创建一个自定义的connector是一项非常重要的工作。

Host层:对应host标签。官网解释:A Host is an association of a network name, e.g. www.yourcompany.com, to the Tomcat server. An Engine may contain multiple hosts, and the Host element also supports network aliases such as yourcompany.com and abc.yourcompany.com. Users rarely create custom Hosts because the StandardHost implementation provides significant additional functionality.即host关联着网络名字。例如www.yourcompany.com网站,到Tomcat服务器。一个engine可以包含多个host,并且host元素还支持网络别名如yourcompany.com网站以及abc.yourcompany.com网站. 只是用户很少创建自定义主机,因为StandardHost实现提供了重要的附加功能。

Context层:对应context标签。A Context represents a web application. A Host may contain multiple contexts, each with a unique path. The Context interface may be implemented to create custom Contexts, but this is rarely the case because the StandardContext provides significant additional functionality.即context表示web应用程序。一个host可以包含多个context,每个host都有一个唯一的路径。context接口可以用来自定义context,但这种情况很少发生,因为StandardContext提供了重要的附加功能。

其实从官方解释就能看出来。tomcat能优化的点除了server和service层都可以有点点优化(虽然并不是很多)。

tomcat优化点

上面有了整体架构后就可以对每一层开始优化了(除server和service层)。
connector层:上面说到了有http connector和AJP connector。在我工作中全是使用http connector的,没有遇到使用AJP的。所以避开AJP的,不过优化都是一样的。
说几个重要的参数:
protocol:是设置处理传入流量的协议。默认是http/1.1。意思是它可以自动的选择nio连接器还是apr连接器。如果不用自动选择,那么就使用以下方式指定选择:
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.
值得一提的是:NIO一般用于默认,而NIO2目前工作没看到人用过。
APR是需要另外装环境的,特别适合使用高并发的场景。
executor:执行器。一般都是使用连接池来设置连接数。针对executor标签有几个参数比较重要:
maxThreads:设置最大能够处理的线程数。它决定了最大并发数。。默认是200。注意,如果配置了一个executor标签,那么为这个属性设置的任何值都将被正确记录,但是它将被报告(例如通过JMX)为-1,以明确它没有被使用。
minSpareThreads:始终保持运行的最小线程数。这包括活动线程和空闲线程。默认是10。注意,如果配置了一个executor标签,那么为这个属性设置的任何值都将被正确记录,但是它将被报告(例如通过JMX)为-1,以明确它没有被使用。
其它参数也非常重要(如超时时间,队列长度(默认位100.不够就拒绝)、compression压缩策略等)。就不做一一的解释。

host层:仅仅看下host的标准实现。标准实现中有个非常重要的参数。unpackWARs:是否自动解压war包。其它参数一般都不怎么用。所以不说。

Context层:也仅仅看下context的标准实现。里面也有个unpackWARs。规定是里层的参数会覆盖外层的参数。除了这个还有一个很重要的参数:
reloadable:看下英文解释:

Set to true if you want Catalina to monitor classes in /WEB-INF/classes/ and /WEB-INF/lib for changes, and automatically reload the web application if a change is detected. This feature is very useful during application development, but it requires significant runtime overhead and is not recommended for use on deployed production applications. That’s why the default setting for this attribute is false. You can use the Manager web application, however, to trigger reloads of deployed applications on demand.翻译如下:如果希望Catalina监视/WEB-INF/classes/和/WEB-INF/lib中的类以进行更改,并在检测到更改时自动重新加载WEB应用程序,则设置为true。此特性在应用程序开发期间非常有用,但它需要大量的运行时开销,因此不建议在已部署的生产应用程序上使用。这就是该属性的默认设置为false的原因。但是,您可以使用managerweb应用程序按需触发已部署应用程序的重新加载。 需要大量的运行时开销。懂了吧。一般都会关闭。一般不要开启这个参数。

至于通用的,每一层存在的listener(监听器)、cluster(集群模式)、realm(权限管理)。一般不需要就不用开。

最后的web.xml也很简单。就是过滤器。过滤的规则越简单越少越来。欢迎页(welcome-file-list)不需要就直接干掉。session-config没有就不要等等。mime-mapping不需要就不用。而且web.xml会跟项目中的web.xml进行整合。

总结:tomcat的调优其实主要的就是connector那一层参数的调整。已经后面几层能少用一点资源就少用一点。以加快请求的处理或者更多的线程用于处理请求。

但是:除了tomcat结构上的调整就没有其它的了吗?tomcat使用Java开发的。所以是不是jvm的调整也可以优化tomcat呢?答案是肯定的。所以接下来就是jvm调优了。

jvm调优

jvm调优有以下3个指标:

  1. 高吞吐量:重要指标之一,是指不考虑垃圾收集引起的停顿时间或内存消耗,垃圾收集器能支撑应用达到的最高性能指标。
  2. 低时延:其度量标准是缩短由于垃圾收集引起的停顿时间或者完全消除因垃圾收集所引起的停顿,避免应用运行时发生抖动。
  3. 内存占用:垃圾收集器流畅运行所需要 的内存数量。

所有的jvm调优一定都是围绕以上3点做的优化!谨记!

性能调优原则:

  1. MinorGC原则:每次MiniorGC都要尽可能多的收集垃圾对象。以减少应用程序发生FGC的频率。
  2. GC内存最大化原则:处理吞吐量和延时问题的时候。垃圾处理器能使用的内存越大,垃圾收集的效果就越好。应用程序也会越来越流畅。
  3. GC调优3选2原则: 在性能属性里面,吞吐量、延迟、内存占用,我们只能选择其中两个进行调优,不可三者兼得。

在说GC收集器之前。要先理解一些概念:
https://www.cnblogs.com/Cubemen/p/10905130.html
请仔细读下那篇博客。这里简单介绍:

判断对象是否存活的办法:
1.引用计数法:在对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1,引用失效时就减1.任何时刻计数器为0的对象就是不可能再被使用的。但是在JVM中会很难解决对象之间相互循环引用的问题,所以一直没有被引用。现在基本已经弃用了。
2.可达性分析:使用一系列的GC Roots的对象(包括:虚拟机栈中引用的对象,方法区中类静态属性引用的对象,方法区中常量引用的对象,本地方法栈中JNI引用的对象)作为起点,从节点开始向下搜索,当没有被GCRoots链接到的对象就可以回收。一般现在都用这个算法。

垃圾回收算法:

  1. 标记清除算法:就是使用GC Roots出发,扫描两次。第一次标记出所有已存活的对象。第二次扫描整个JVM,把没有引用的对象清理掉。
    优点:清理比较干净。 缺点:缺点太多了,列出最重要的两点。
    1.STW时间太长了。要扫描2次。一次标记存活对象,一次再全局扫。耗时长。
    2.容易产生内存碎片。清理完之后没有进行整理导致内存碎片会越来越多。后面如果有大对象出来就很可能找不到合适的存放点。
  2. 复制算法:就是把内存一分为二。一个地方用于存放对象。另一个地方备用。当达到了清理垃圾的情况时。那么就直接从GC
    Roots出发标记并复制到另一块干净的区域中,然后清空存对象的区域。这样就解决了上面的缺点。
    优点:一次扫描,STW很短,非常的高效,且没有内存碎片。 缺点:会浪费一半的资源空间。
  3. 标记整理算法:标记整理算是对标记清除的一种优化。就是从GC roots出发。标记存活的对象使其向一端移动。整理完后直接清除剩下的空间。
    优点:没有内存碎片。 缺点:移动对象的过程非常耗时,所以整个清理也会很长。开销也很大。

上述只是简单的提一下。具体可以百度了解。

垃圾回收算法有了。那么就有具体实现算法的垃圾回收器了对不对。
百度一下,就会有很多的收集器:所以可以自行百度理解。比如下面的链接:
https://blog.csdn.net/xiaomingdetianxia/article/details/77446762

当然不是说这个的。我就说一下CMS和G1。因为现在我就用这两个多一点。
我一直觉得有一个较好的比喻。就是堆比喻成一个现实生活中的仓库。垃圾的算法比喻成设计方案。垃圾回收器比喻成实际的仓库管理员(不止一个),剩下的自己脑补。

CMS垃圾收集器

首先来说说CMS,如果英文水平较好或者有一定了解的直接看这个。比我写的好得多。
https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/cms.html#concurrent_mark_sweep_cms_collector

首先值得一提的是ParNew+CMS这个组合就是使用CMS垃圾收集器。也就是常常百度到的JVM调优模块。

CMS把JVM划分成多个区域。
1.新生代:是用于存放对象刚出来或者不久的时候的地方。新生代使用ParNew垃圾收集器。使用的复制算法,但并不是1:1的,而是默认8:1:1,占8份的成为Eden区对象刚出来的时候就在这里,剩下的2份称为survivor区。大小是一样的,人为的划分成from区和to区,当第一次Eden区达到了MinorGC的条件时,就会执行一次复制算法,此时会把所有的存活对象方法from区,然后清理掉Eden区。后面再次清理就使用复制算法把Eden区和from区存活的对象存放到to区,然后清理掉Eden区和from区,并且把from区变成to区。to区变成from区(其实百度一个图解过程就很容易理解,文字不好想象)。有的对象这from区和to区折腾了15次还没回收掉的话就会进入老年代。
问题一:为什么是15次?
这个跟对象头有关。https://blog.csdn.net/qq_37822914/article/details/107189753,这上面对象头明确就说了分代年龄占4位,所以最多16次,第一次是Eden到from区。这个参数是可以调整的。参数为:-XX:MaxTenuringThreshold调整。
问题二:如果Eden区和from区的存活对象很多,多到to区存不下了怎么办?
JVM存在一个内存分配担保机制。就是当在新生代无法分配内存的时候,把新生代的对象转移到老生代,然后把新对象放入腾空的新生代。参数为-XX:HandlePromotionFailure=true。
问题三:如果我想把很大的对象直接放到老年代。应该怎么做?
有个参数:-XX:PretenureSizeThreshold。对象如果大于或等于此值,会直接分配到老年代里。

2.老年代。
简单的说一下回收过程。
1、InitialMarking(初始化标记,整个过程STW):
标记GC Roots可达的老年代对象;
遍历新生代对象,标记可达的老年代对象;
2、Marking(并发标记)
该阶段GC线程和应用线程并发执行,遍历InitialMarking阶段标记出来的存活对象,然后继续递归标记这些对象可达的对象。
因为该阶段并发执行的,在运行期间可能发生新生代的对象晋升到老年代、或者是直接在老年代分配对象、或者更新老年代对象的引用关系等等,对于这些对象,都是需要进行重新标记的,否则有些对象就会被遗漏,发生漏标的情况。
3、Precleaning(预清理)
通过参数CMSPrecleaningEnabled选择关闭该阶段,默认启用,主要做两件事情:

  • 处理新生代已经发现的引用,比如在并发阶段,在Eden区中分配了一个A对象,A对象引用了一个老年代对象B(这个B之前没有被标记),在这个阶段就会标记对象B为活跃对象。
  • 在并发标记阶段,如果老年代中有对象内部引用发生变化,会把所在的Card标记为Dirty(其实这里并非使用CardTable,而是一个类似的数据结构,叫ModUnionTalble),通过扫描这些Table,重新标记那些在并发标记阶段引用被更新的对象(晋升到老年代的对象、原本就在老年代的对象)

4、AbortablePreclean(可中断的预清理)
该阶段发生的前提是,新生代Eden区的内存使用量大于参数CMSScheduleRemarkEdenSizeThreshold 默认是2M,如果新生代的对象太少,就没有必要执行该阶段,直接执行重新标记阶段。

5、FinalMarking(并发标记,STW过程)
该阶段并发执行,在之前的并行阶段(GC线程和应用线程同时执行,好比你妈在打扫房间,你还在扔纸屑),可能产生新的引用关系如下:

  • 老年代的新对象被GC Roots引用
  • 老年代的未标记对象被新生代对象引用
  • 老年代已标记的对象增加新引用指向老年代其它对象
  • 新生代对象指向老年代引用被删除
  • 也许还有其它情况…

6、并发清除(CMS-concurrent-sweep),与用户线程同时运行;

7、并发重置状态等待下次CMS的触发(CMS-concurrent-reset),与用户线程同时运行。

CMS是一种标记清除算法。所以它存在标记清除算法的缺点。下面再说一遍缺点。

  • 1、可能会产生Concurrent Mode Failure:如果CMS收集器无法在使用期限生成填充之前完成回收不可访问的对象,或者如果无法满足使用期限生成中的可用空间块的分配,则应用程序将暂停,并在停止所有应用程序线程的情况下完成收集。无法同时完成一个收集被称为并发模式故障Concurrent Mode Failure。一旦产生了Concurrent Mode Failure问题,系统会直接使用Serial Old垃圾回收器取代CMS垃圾回收器,从头开始进行GC Roots追踪对象,并清理垃圾,这样会导致整个垃圾回收的时间变得更长。它的解决办法就是根据系统的需求,合理设置“-XX:CMSInitiatingOccupancyFraction”的值,如果过大,则会产生Concurrent Mode Failure问题,如果设置的过小,则会导致老年代更加频繁的垃圾回收。
  • 2、GC时间过长导致OutOfMemoryError:如果在垃圾收集中花费了太多时间,CMS收集器将抛出OutOfMemoryError:如果超过98%的总时间花在垃圾收集上,而不到2%的堆被恢复,则抛出OutOfMemoryError。此功能旨在防止应用程序长时间运行,同时由于堆太小而几乎没有进展。如果需要,可以通过在命令行中添加选项-XX:-usegoverheadlimit来禁用此功能。
  • 3、产生浮动垃圾:由于应用程序线程和垃圾回收器线程在主要收集期间并发运行,因此垃圾回收器线程跟踪的对象可能随后在收集过程结束时变得无法访问。这种尚未回收的无法访问的对象称为浮动垃圾。浮动垃圾的数量取决于并发收集循环的持续时间和应用程序的引用更新(也称为突变)的频率。
  • 4、空间碎片:CMS的标记-清理算法会在并发清理的阶段产生大量的内存碎片,如果不整理的话,则会有大量不连续的内存空间存在,无法放入一些进入老年代的大对象,导致老年代频繁垃圾回收。所以CMS存在一个默认的参数
    “-XX:+UseCMSCompactAtFullCollection”,意思是在Full
    GC之后再次STW,停止工作线程,整理内存空间,将存活的对象移到一边。还要一个参数是“-XX:+CMSFullGCsBeforeCompaction”,表示在进行多少次Full
    GC之后进行内存碎片整理,默认为0,即每次Full GC之后都进行内存碎片整理。

总结:
CMS虽然使用并发的方式降低了STW的时间,但是还需要配合一些CMS的参数才能完全发挥出CMS的优势,否则甚至会降低垃圾回收的效率。因此只有掌握了CMS的原理和参数的调试,才能让系统运行的更加流畅。

G1(Garbage-First)垃圾收集器

官网解释:
G1介绍:
https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/g1_gc.html#garbage_first_garbage_collection
G1调试:
https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/g1_gc_tuning.html#g1_gc_tuning

G1相对于前面的垃圾收集是一种重大升级(面向服务器的):
1、堆划分不同。前面的垃圾收集器把内存从物理和逻辑上分为了新生代和老年代,界限分明。而G1从物理上把内存分为一个个的region。每个region可能是新生代也可能是老年代,此外还专门划分了一个巨型对象区域:新建对象大小超过 Region 大小一半时,直接在新的一个或多个连续 Region 中分配,并标记为H。
2、使用的算法不同。从region与region之间看就是使用了复制算法。从整个堆来看就是使用了标记整理算法。相比于CMS吞吐量和延时性能都有极大的提升。
3、垃圾收集方式有所不同。存在年轻代的收集和混合型的收集:年轻代不多说,混合收集就是不单单收集年轻代也收集部分的老年代(注意是部分。主要对垃圾回收的耗时时间进行控制)。同样使用复制算法。
4、可预测的停顿。在每个region计算出存活的对象。从而标记该region的一个优先级。根据优先级选择region。而不是整代内存来回收,其它GC每次回收都会回收整个内存(Eden, Old), 回收内存所需的时间取决于内存的大小,以及实际垃圾的多少,所以垃圾回收时间是不可控的;而G1每次并不会回收整代内存,到底回收多少内存就看用户配置的暂停时间,配置的时间短就少回收点相反就多回收点。

垃圾收集的过程:
1、初始标记(Initial Mark):STW的。负责标记所有能被直接可达的根对象(原生栈对象、全局对象、JNI对象),根是对象图的起点,因此初始标记需要将Mutator线程(Java应用线程)暂停掉,也就是需要一个STW的时间段。
2、根region扫描(Root Region Scanning):负责扫描所有初始化标记的survivor regions中引用了的old的对象,并做标记。
3、并发标记(Concurrent marking):和应用线程并发执行。在整个堆中标记出存活的对象。
4、重新标记(Remark):STW的。修正标记的对象。并且使用复制算法将垃圾比较多的区域中存活的对象复制到干净的区域中。
5:清理:部分是STW的。计算各个区域的存活对象和GC回收比例,并进行排序(STW)。处理Soft,Weak,Phantom,Final,JNI Weak 等引用。然后清理堆分区。(并发的)。

优点:1、没有内存碎片。2、提高了吞吐量且进一步降低了停顿时间。

性能优化

参考这篇文章:https://blog.csdn.net/qq_35623773/article/details/87909624
就是上面提到的那几个方面。主要说JVM和java代码的优化,再简单说说mysql的优化。
一般假设网络很好。会存在2种情况。
1.该系统的内存占用很高。
首先内存高就要定位到堆中了。
要么就是堆中一直分配很多的无法回收大对象,要么就是内存不够大。
寻找办法:
1、首先要执行命令:top然后shift+m按内存排序。找到对应的pid。
2、执行jstat -gcutil pid ms。查看堆的大致情况以及走向趋势。如果FGC次数过多肯定就存在问题。
3、用jmap -dump:format=b,file=文件名 [pid]生成堆内存快照,分析具体的对象数目和占用内存大小,从而定位代码。分析是否存在内存泄漏等等。
4、如果代码不存在问题。那么就是GC收集器的问题了。用jinfo pid查看内存的分布,以及使用的垃圾回收器。用jstat -gcutil pid ms看GC的情况,还可以用-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/run/ppcollector/tomcat/logs/gc.log打印gc的信息,用工具查看或肉眼看。分析是否内存分配不合理。推断是否存在内存碎片。更换更好的垃圾收集器等。

2.该系统的CPU占用很高。
CPU占用高说明执行得很频繁。就需要定位到线程和栈得地方了。
寻找办法:
1、首先要执行命令:top然后shift+p按CPU排序。找到对应的pid。
2、执行top -Hp pid找到CPU占用高的线程号。
3、执行jstack -l pid > file。找到高CPU的线程号(这里打印的是16进制的线程号)。然后分析CPU占用高的原因。
附载:
高CPU:https://www.cnblogs.com/fengweiweicoder/p/10992043.html
高内存:https://blog.csdn.net/u011635492/article/details/80387816

mysql优化

以后如果有空闲就写下mysql的文章。
1、尽量全值匹配。
2、遵守最左前缀法则。
3、不在索引列上做任何的操作。可能会导致索引失效。
4、范围条件放最后。会导致范围查询之后的会失效。
5、覆盖索引尽量用。减少select *。非常重要!!!!!!!!!!!!!
6、不等于要慎用。会导致全表扫描。解决:使用覆盖索引。
7、null/not null有影响。可能导致索引失效。解决:使用覆盖索引。
8、like查询要当心。%放最右边。解决:使用覆盖索引。
9、字符类型加引号。字符串不加单引号会导致索引失效。
10、or改union效率高。
网上的口诀:
全值匹配我最爱,最左前缀要遵守;
带头大哥不能死,中间兄弟不能断;
索引列上少计算,范围之后全失效;
Like百分写最右,覆盖索引不写星;
不等空值还有or,索引失效要少用;
VAR引号不可丢,SQL高级也不难!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值