gitbook/高楼的性能工程实战课/docs/372274.md
2022-09-03 22:05:03 +08:00

406 lines
26 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 22 | 支付订单信息如何高效解决for循环产生的内存溢出
你好,我是高楼。
今天我们来优化支付订单接口。通过这个接口我们来看看怎么高效解决for循环产生的内存溢出问题。
对于JVM内存溢出或泄露来说通常性能人员都能定位到一个应用hang住了。但是要想进一步判断出应用hang住的原因并没有那么容易做到。因为内存大时做堆Dump比较费时更重要的一点是要想把堆里面的对象和栈关联起来是需要足够的经验和时间的。这也是其中的难点之一。
这节课我就带你来看看怎么解决这个难点。
不过,在此之前,我们会先处理一个熟悉的问题,就是数据库表加索引。很显然,我们在测试这个接口时又遇到它了。虽然我在[第16讲](https://time.geekbang.org/column/article/367285)中给你重点讲过这个问题但是这一次的每秒全表扫描比之前要高得多。通过这次的讲解我希望你能明白只要存在全表扫描CPU消耗很快就会达到100%。同时也希望你能借此看清楚全表扫描对CPU消耗的影响。
## 场景运行数据
首先,我们来运行一下场景:
![](https://static001.geekbang.org/resource/image/02/e1/02f5facd8d0196ae07c223b8dc1e4ae1.png)
这是一个典型的TPS太低、响应时间不断上升的性能瓶颈对于这种瓶颈的分析逻辑我在前面的课程里已经写过很多次了相信你已经掌握。下面我们来看一下具体的问题是什么。
## 架构图
![](https://static001.geekbang.org/resource/image/72/8f/72b7b04b1e2323d4fae9fcc30cb3be8f.png)
这个接口的链路比较简单User - Gateway - Order - MySQL我们大概记在脑子里就好。
## 第一阶段
在这里我就不拆分时间了,我们直接来看全局监控。因为第一阶段的问题相对来说比较简单,只是性能瓶颈的表现形式和之前不太一样。
### 全局监控分析
全局监控的数据如下:
![](https://static001.geekbang.org/resource/image/0a/3a/0a2471dca622df4a813e8c06d31e5b3a.png)
看到这张图你是不是有一种终于见到典型性能瓶颈的感觉CPU使用率这么高那还不简单打栈看代码呀
不过我们得先查一下是什么东西导致k8s-worker-1的CPU使用率这么高的。这个worker上运行的服务如下
```
[root@k8s-master-2 ~]# kubectl get pods -o wide | grep worker-1
mysql-min-6685c9ff76-4m5xr 1/1 Running 0 4d23h 10.100.230.14 k8s-worker-1 <none> <none>
skywalking-es-init-ls7j5 0/1 Completed 0 4d11h 10.100.230.18 k8s-worker-1 <none> <none>
[root@k8s-master-2 ~]#
```
可以看到有两个服务在这个worker上跑着一个是初始化容器另一个是MySQL。初始化容器已经是完成的状态那CPU使用率高肯定是因为MySQL了。因此我们就进到容器中执行下top看看资源消耗到什么程度。
![](https://static001.geekbang.org/resource/image/a4/5f/a420ef3454ee13f4f37834eyy0e5045f.png)
什么情况CPU使用率这么高吗既然这样我们就得来查一下MySQL的全局监控了。
在这里,**查看全局监控其实是分析MySQL的必要步骤**。如果直接查看trx表中间其实是有些跳跃的因为查看MySQL的全局监控数据才是承上启下的一步。我现在把这个过程写全一些以免你产生困惑。
于是我们执行一个mysqlreport命令看看mysql的全局监控数据是怎样的。这里我截取了其中的一些重要信息
```
__ Questions ___________________________________________________________
Total 307.93M 912.0/s
+Unknown 201.91M 598.0/s %Total: 65.57
DMS 43.20M 128.0/s 14.03
Com_ 32.90M 97.5/s 10.69
QC Hits 29.91M 88.6/s 9.71
COM_QUIT 389 0.0/s 0.00
Slow 20 ms 273.66k 0.8/s 0.09 %DMS: 0.63 Log:
DMS 43.20M 128.0/s 14.03
SELECT 32.39M 95.9/s 10.52 74.98
INSERT 10.64M 31.5/s 3.46 24.63
UPDATE 170.15k 0.5/s 0.06 0.39
REPLACE 0 0/s 0.00 0.00
DELETE 0 0/s 0.00 0.00
Com_ 32.90M 97.5/s 10.69
set_option 21.98M 65.1/s 7.14
commit 10.70M 31.7/s 3.48
admin_comma 137.68k 0.4/s 0.04
__ SELECT and Sort _____________________________________________________
Scan 20.04M 59.4/s %SELECT: 61.88
Range 0 0/s 0.00
Full join 32 0.0/s 0.00
Range check 0 0/s 0.00
Full rng join 0 0/s 0.00
Sort scan 120 0.0/s
Sort range 2.41k 0.0/s
Sort mrg pass 0 0/s
```
你看DMS中的select占比比较大。其实如果只是select的占比比较大的话倒不是什么大事关键是在下面的数据中还有一个Scan全表扫描而全表扫描是典型的性能问题点。
看到这里我想你应该非常清楚我接下来的套路了吧就是找SQL看执行计划然后确定优化方案。如果你不太清楚可以再看一下[第](https://time.geekbang.org/column/article/367285)[16](https://time.geekbang.org/column/article/367285)[讲](https://time.geekbang.org/column/article/367285)或[第](https://time.geekbang.org/column/article/370723)[20](https://time.geekbang.org/column/article/370723)[讲](https://time.geekbang.org/column/article/370723),其中都有描述。
### 定向监控分析
于是我们现在进入到了定向监控分析阶段。通过查看innodb\_trx表我们看到了SQL中执行慢的语句它的执行计划如下
![](https://static001.geekbang.org/resource/image/82/4c/82c5c3ba11f32184972ef2ce32127f4c.png)
这是很典型的全表扫描,虽然数据量并不大,但是我们也要添加索引。添加索引的语句如下:
![](https://static001.geekbang.org/resource/image/e5/c7/e510e1a94a6d59c11204d062da9ed4c7.png)
这里你要注意一下在创建索引的时候如果数据量太大创建索引可能会卡住很长时间这要取决于机器单CPU的能力。
### 优化效果
添加索引之后,我们直接来看一下优化效果:
![](https://static001.geekbang.org/resource/image/5d/1a/5d245afc0a324280d9d93c0102a0f81a.png)
你看TPS要上千了
其实对于SQL的优化如果我们只是加一个索引那就算是非常简单的步骤了并且效果也会非常好TPS增加上千倍、甚至上万倍都有可能。可是如果优化涉及到了业务逻辑那就麻烦一些了。
如果你觉得这节课只是为了给你讲一个加索引的案例,那你就有些单纯了。下面,我们来看一个复杂的问题。
## 第二阶段
在我接着执行压力的时候,看到了一个非常有意思的情况,我们来一起折腾折腾!
### 场景运行数据
![](https://static001.geekbang.org/resource/image/29/a4/291ee16344ffbdd94cd0ae1yy62a11a4.png)
你看这张图在压力持续大概6分钟之后TPS不稳定也就算了居然还掉下来了你掉下来也就算了居然还断开了你断开了也就算了响应时间居然也不增加
这可怎么分析呢?想要拆分时间都没有一个适合的理由呀!
这时候,就得用上哥的性能分析决策树了。我们把相关的全局监控计数器都看一看,一层层查下去,还好这个接口的逻辑链路也不怎么长。
### 全局监控分析
按照高老师的习惯,我们首先来看全局监控:
![](https://static001.geekbang.org/resource/image/62/78/62412b8c7159df58cbdcce7b196yya78.png)
从这张图上我们什么也没看出来。所以我们接着查性能分析决策树一层层地看下去。当看到Order的GC健康状态时我们看到了下面这些数据
```
[root@svc-mall-order-568bd9b79-twhcw /]# jstat -gcutil 1 1s
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
100.00 0.00 100.00 100.00 95.06 92.82 1182 34.966 495 3279.704 3314.670
100.00 0.00 100.00 100.00 95.06 92.82 1182 34.966 495 3279.704 3314.670
100.00 0.00 100.00 100.00 95.06 92.82 1182 34.966 495 3279.704 3314.670
100.00 0.00 100.00 100.00 95.06 92.82 1182 34.966 495 3279.704 3314.670
90.88 0.00 100.00 100.00 95.08 92.82 1182 34.966 495 3286.621 3321.58
100.00 0.00 100.00 100.00 95.08 92.82 1182 34.966 496 3286.621 3321.586
100.00 0.00 100.00 100.00 95.08 92.82 1182 34.966 496 3286.621 3321.586
100.00 0.00 100.00 100.00 95.08 92.82 1182 34.966 496 3286.621 3321.586
100.00 0.00 100.00 100.00 95.08 92.82 1182 34.966 496 3286.621 3321.586
100.00 0.00 100.00 100.00 95.08 92.82 1182 34.966 496 3286.621 3321.586
100.00 0.00 100.00 100.00 95.08 92.82 1182 34.966 496 3286.621 3321.586
...........................
```
有没有感到绝望内存泄漏了年轻代和年老代的内存都用到100%了即便是FullGC之后内存也没回收下来可怜呀。
### 定向监控分析
既然是内存被用完了那我们自然要查一下是什么样的对象把内存用完了。所以我们进到容器里面执行jmap -histo 1|more看一眼
```
num #instances #bytes class name
----------------------------------------------
1: 49727866 1397691896 [
2: 12426103 795269200 [[
3: 12426038 397633216 com.mysql.cj.protocol.a.result.ByteArrayRo
4: 2002596 384498432 com.dunshan.mall.order.domain.OmsOrderDetai
5: 12426082 198817312 com.mysql.cj.protocol.a.MysqlTextValueDecode
6: 2070085 182840264 [Ljava.lang.Object
7: 6008660 144207840 java.lang.Lon
8: 2207452 132116320 [
9: 4072895 97749480 java.util.ArrayLis
10: 2002690 80107600 org.apache.ibatis.cache.CacheKe
11: 2039613 65267616 java.util.HashMap$Nod
12: 2197616 52742784 java.lang.Strin
13: 14736 23246672 [Ljava.util.HashMap$Node
14: 36862 3243856 java.lang.reflect.Metho
15: 97195 3110240 java.util.concurrent.ConcurrentHashMap$Nod
16: 62224 2986752 java.util.HashMa
17: 19238 2452264 [
18: 21482 2360328 java.lang.Clas
19: 26958 1078320 java.util.LinkedHashMap$Entr
...........................
```
从中我们似乎找到了问题点。你看这里面有一个MySQL的result占的内存还挺大同时在它的下面我们也看到了OmsOrderDetail类这个类是用来在数据库中查询订单的详细信息的。
从逻辑上来讲我们看订单的详细信息实际上是想查询数据库中的信息进而把查询出来的数据放到应用的内存中。所以MySQL的result查的数据越多就会导致应用的JVM内存消耗越大。
你也许会想接下来是不是直接去看OmsOrderDetail的代码就可以了你可以去看但是我们这个案例并没有那么直接。因为我们已经知道代码了逻辑也梳理清楚了所以再去查看代码其实也看不出什么问题来。
那为什么JVM内存消耗会高呢这里我们就要查一下线程在做什么动作了
```
-- top
[root@k8s-worker-3 ~]# docker exec -it 66d3639cf4a8 /bin/bash
[root@svc-mall-order-568bd9b79-twhcw /]# top
top - 16:10:50 up 11 days, 2:37, 0 users, load average: 3.92, 4.77, 3.35
Tasks: 4 total, 1 running, 3 sleeping, 0 stopped, 0 zombie
%Cpu0 : 46.7 us, 8.6 sy, 0.0 ni, 43.6 id, 0.0 wa, 0.0 hi, 0.7 si, 0.3 st
%Cpu1 : 23.3 us, 9.2 sy, 0.0 ni, 66.1 id, 0.0 wa, 0.0 hi, 1.0 si, 0.3 st
%Cpu2 : 50.0 us, 7.2 sy, 0.3 ni, 41.4 id, 0.0 wa, 0.0 hi, 0.7 si, 0.3 st
%Cpu3 : 46.4 us, 8.5 sy, 0.0 ni, 43.7 id, 0.0 wa, 0.0 hi, 1.0 si, 0.3 st
%Cpu4 : 50.5 us, 8.0 sy, 0.0 ni, 41.5 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu5 : 50.2 us, 3.1 sy, 0.0 ni, 46.1 id, 0.0 wa, 0.0 hi, 0.7 si, 0.0 st
KiB Mem : 16265992 total, 171760 free, 9077080 used, 7017152 buff/cache
KiB Swap: 0 total, 0 free, 0 used. 6676508 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1 root 20 0 8788300 4.2g 13860 S 127.9 27.1 115:17.15 java
575 root 20 0 11828 1776 1328 S 0.0 0.0 0:00.01 sh
789 root 20 0 11964 1980 1484 S 0.0 0.0 0:00.02 bash
802 root 20 0 56232 2012 1432 R 0.0 0.0 0:00.05 to
-- top -Hp 1
top - 16:11:39 up 11 days, 2:38, 0 users, load average: 8.87, 6.09, 3.87
Threads: 85 total, 1 running, 84 sleeping, 0 stopped, 0 zombie
%Cpu0 : 55.6 us, 7.1 sy, 0.0 ni, 36.6 id, 0.0 wa, 0.0 hi, 0.3 si, 0.3 st
%Cpu1 : 41.3 us, 3.8 sy, 0.0 ni, 54.9 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu2 : 30.4 us, 9.9 sy, 0.0 ni, 59.4 id, 0.0 wa, 0.0 hi, 0.3 si, 0.0 st
%Cpu3 : 60.3 us, 6.7 sy, 0.0 ni, 32.7 id, 0.0 wa, 0.0 hi, 0.3 si, 0.0 st
%Cpu4 : 21.2 us, 9.2 sy, 0.0 ni, 69.6 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu5 : 45.6 us, 10.1 sy, 0.3 ni, 43.6 id, 0.0 wa, 0.0 hi, 0.0 si, 0.3 st
KiB Mem : 16265992 total, 197656 free, 9071444 used, 6996892 buff/cache
KiB Swap: 0 total, 0 free, 0 used. 6681848 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
7 root 20 0 8788300 4.2g 13836 R 96.0 27.1 70:13.42 VM Thread
26 root 20 0 8788300 4.2g 13836 S 0.7 27.1 0:05.70 VM Periodic Tas
```
执行了上面两个命令之后你有没有注意到只有一个线程在消耗CPU根据我们前面查看的GC状态这个线程应该在忙着做FullGC。我们打印栈信息来确认一下果然是它
```
"VM Thread" os_prio=0 tid=0x00007fb18c0f4800 nid=0x7 runnable
```
到这里我们下一步该怎么做就非常清晰了那就是打印一个堆Dump来看看对象在内存中的消耗比例。
所以我们现在执行下面这个命令来生成堆Dump。
```
jmap -dump:format=b,file=文件名 pid
```
然后我们再把生成的堆Dump下载下来用MAT打开。
在我打开堆文件的过程中出现了一个小插曲这也是你需要留意的地方那就是如果堆Dump的内存太大的话我们打开堆Dump就会报这样的错
![](https://static001.geekbang.org/resource/image/be/85/be3dc22bf50e7cdab4c3ea474a050d85.png)
这个时候我们就要到MemoryAnalyzer.ini文件中把JVM的最大值参数-Xmx给调大。-Xmx的默认值是1G至于要调到多大就要看你想打开多大的文件了。如果-Xmx调整得不够大还会出现下面这样的错误
![](https://static001.geekbang.org/resource/image/da/0e/dac4692b9da8b5680131aa4ea16b470e.png)
在我们费了九牛二虎之力,终于打开堆文件后,看到这样的信息:
![](https://static001.geekbang.org/resource/image/1d/7f/1d6964cyy56e5934b8657234ea2cf47f.png)
有一段内存居然消耗了2.6G这个可疑的内存点在上图中也出现了用MAT或jmap来看对象的内存消耗是两种不同的手段你可以自己选择。只是在MAT上我们可以看到可疑的内存消耗点的提醒而在jmap中是不会给出这样的提醒的需要我们自己判断我们点进去看一眼看看可疑的内存点里具体的对象是什么
![](https://static001.geekbang.org/resource/image/02/bc/02cb9099ec17332ab42e2564c2a106bc.png)
你看确实是SQL返回的数据量比较大在上图的列表中居然有1千多万条记录。我们再把相应的栈展开看看
![](https://static001.geekbang.org/resource/image/4e/94/4e2dea48f6969a4a6f3919bddd4c3594.png)
看到OmsOrderDetail这个类了没你可能会想是支付订单信息这个接口有问题但是证明还不足我们要确定OmsOrderDetail是不是在这个接口中调用或生成的才能判断出是不是这个接口的问题。
由于当前使用的接口是paySuccess我们看一下paySuccess的调用逻辑发现paySuccess有一个getDetail函数。看到这个“getDetail”还有其中这个“Detail”字符你是不是感觉和OmsOrderDetail可以对应上那我们就来查看一下getDetail对应的代码看看它和OmsOrderDetail之间是什么关系
![](https://static001.geekbang.org/resource/image/51/4a/5133a446f6e72549a40daace912f794a.png)
不难发现getDetail是一个OmsOrderDetail类。这么看来我们的接口确实用到了OmsOrderDetail类你是不是有一种抓住元凶的兴奋感别着急让人无奈的事情总是会出现的下面我们看一下这段代码对应的SQL语句是一个什么样的逻辑。
通过查看代码可以看到这个接口中有两个update和一个select这三个语句分别是
```
<update id="updateByPrimaryKeySelective" parameterType="com.dunshan.mall.model.OmsOrder">
update oms_orde
........................
where id = #{id,jdbcType=BIGINT
</update
<update id="updateByPrimaryKeySelective" parameterType="com.dunshan.mall.model.PmsSkuStock">
update pms_sku_stoc
........................
where id = #{id,jdbcType=BIGINT
</update
<select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap"
select
<include refid="Base_Column_List" /
from pms_sku_stoc
where id = #{id,jdbcType=BIGINT
</select
```
在我查了这三个SQL语句对应的SQL、表和索引之后发现它们都是精准查找并且索引也在第一阶段的分析中创建完了按理说不会出现大的数据量。可是我们在前面的确看到OmsOrderDetail产生了巨大的数据量这是怎么一回事
为了搞清楚这个问题我们查查还有谁调用了OmsOrderDetail
![](https://static001.geekbang.org/resource/image/74/44/745a6dcedd341c57eededccfb5804344.png)
我们找啊找终于看到了两个for循环其实在前面的代码段中也可以看出来。我点进去一看发现它们是定时任务。这个时候问题产生的逻辑就变得清晰了由于存在for循环接口调用时就会循环执行某段代码这就成了JVM内存不断增加的一种可能性。
你还记得吗?在[上](https://time.geekbang.org/column/article/370976)[节课](https://time.geekbang.org/column/article/370976)中我们其实也定位到了“for循环对应的SQL执行慢”这个问题但是由于压力持续的时间不够长内存没有被耗尽所以内存被消耗光的问题并没有体现出来。而当场景执行的时间变长时就出现了TPS断断续续的奇怪现象。
### 优化效果
到这里我们的优化方案其实非常清楚了就是做定时任务时不要一下子查那么多数据。因此我在这里加了一个limit限定一次就查500行。如果处理不过来的话我们可以直接写一个单独的服务进行多线程处理。
修改之后我们再次看看Order服务的JVM内存消耗
```
[root@svc-mall-order-f7b6d6f7-xl2kp /]# jstat -gcutil 1 1s
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
0.00 5.44 100.00 96.03 95.03 93.17 43710 1044.757 161 83.635 1128.39
0.00 3.18 82.83 96.21 95.03 93.17 43713 1044.797 161 83.635 1128.43
2.09 0.00 4.54 96.21 95.03 93.17 43718 1044.850 161 83.635 1128.48
1.99 0.00 44.92 96.21 95.03 93.17 43722 1044.891 161 83.635 1128.52
0.00 2.24 1.51 96.22 95.03 93.17 43727 1044.936 161 83.635 1128.57
2.23 0.00 0.00 96.22 95.03 93.17 43732 1044.987 161 83.635 1128.62
40.97 0.00 76.46 96.22 95.03 93.17 43736 1045.051 161 83.635 1128.68
0.00 41.76 47.74 98.81 95.03 93.17 43741 1045.136 161 83.635 1128.77
45.59 0.00 77.61 98.81 95.03 93.17 43746 1045.210 161 83.635 1128.84
0.00 0.00 51.01 52.55 95.03 93.17 43749 1045.270 162 84.021 1129.29
52.34 0.00 60.57 53.23 95.03 93.17 43754 1045.353 162 84.021 1129.37
0.00 51.85 0.00 56.32 95.03 93.17 43759 1045.450 162 84.021 1129.47
0.00 57.97 98.59 58.79 95.03 93.17 43764 1045.526 162 84.021 1129.54
42.02 0.00 83.01 60.83 95.03 93.17 43768 1045.602 162 84.021 1129.62
0.00 42.51 72.69 60.83 95.03 93.17 43773 1045.668 162 84.021 1129.68
28.95 0.00 67.94 61.52 95.03 93.17 43778 1045.735 162 84.021 1129.75
0.00 4.50 32.29 62.74 95.03 93.17 43783 1045.788 162 84.021 1129.80
62.60 0.00 27.04 62.80 95.03 93.17 43788 1045.866 162 84.021 1129.88
0.00 45.52 0.00 65.14 95.03 93.17 43793 1045.950 162 84.021 1129.97
0.00 47.10 71.13 65.14 95.03 93.17 43797 1046.015 162 84.021 1130.03
49.36 0.00 33.30 65.14 95.03 93.17 43802 1046.080 162 84.021 1130.10
5.92 0.00 35.47 67.33 95.03 93.17 43806 1046.132 162 84.021 1130.15
0.00 50.15 65.90 67.37 95.03 93.17 43811 1046.209 162 84.021 1130.23
46.75 0.00 10.39 69.71 95.03 93.17 43816 1046.305 162 84.021 1130.32
47.27 0.00 54.91 69.71 95.03 93.17 43820 1046.364 162 84.021 1130.38
0.00 45.69 46.99 69.71 95.03 93.17 43825 1046.430 162 84.021 1130.45
3.25 0.00 13.65 71.93 95.03 93.17 43830 1046.488 162 84.021 1130.50
0.00 38.00 46.98 71.94 95.03 93.17 43835 1046.551 162 84.021 1130.57
43.74 0.00 37.69 74.44 95.03 93.17 43840 1046.634 162 84.021 1130.65
0.00 42.88 15.64 74.44 95.03 93.17 43845 1046.702 162 84.021 1130.72
0.00 44.13 12.90 74.44 95.03 93.17 43849 1046.756 162 84.021 1130.77
0.00 16.42 33.96 75.79 95.03 93.17 43853 1046.813 162 84.021 1130.83
4.25 0.00 20.10 76.45 95.03 93.17 43858 1046.863 162 84.021 1130.88
0.00 3.22 0.00 76.46 95.03 93.17 43863 1046.914 162 84.021 1130.93
```
从GC的状态来看内存现在可以回收得正常一些了。这里我要说明一下上面的JVM数据不是在场景一开始的时候抓取的而是在场景执行了很久之后才抓取的。因为我们从上面的数据看到JVM内存已经可以正常回收了所以上面的数据是在确定没有内存溢出的前提下得到的有效数据。
不过这里还是有问题的不知道你有没有发现那就是YGC过快。你通过YGC这一列就能看到我一秒打印一次一秒就有四五次的YGC平均每次YGC时间大概在1020毫秒。虽然这个问题还没有对TPS造成明显的影响但是也在危险的边缘了。不过也正因为它现在对TPS还没有造成明显的影响所以我在这里先不处理YGC快的问题了。
我们再来重新执行一遍场景,得到的场景执行结果如下:
![](https://static001.geekbang.org/resource/image/73/36/730851a50364bde422aef5f69c29ce36.png)
你看TPS是不是有改善而且不会再出现第二阶段刚开始的那种情况了因为当时是压到了6分钟之后就开始出现问题了而在这里我们压了30多分钟仍然没有出现掉下来的情况。
可以欢呼了,对不对?对的!我们不用再等翻车了。
## 总结
在这节课中,我们在分两个阶段描述了两个问题。
第一个问题比较简单是全表扫描没加索引的问题我们在前面也有过描述。之所以在这节课中又说一下是因为SQL把CPU全都吃光了而这种状态才是全表扫描在性能中比较常见的问题。
第二个问题和内存溢出有关对于Java的应用来说我们要把内存的溢出找出来就必须理清楚代码的逻辑知道哪个变量在哪里定义谁在取值谁在使用还有层级关系也要划分清楚。现在的开发工具已经非常友好了可以告诉我们代码的前后调用关系。
在这个例子中难点其实不在于怎么找到这个内存溢出点而是在于找到溢出的原因。我们只要看下HeapDump就可以知道溢出点在哪里而引用这个类的是谁可能会有很多地方我们需要一个个去查看。
这个例子的特殊之处在于我们的接口本身就使用了pms\_sku\_stock表和OmsOrderDetail类但是从语句上来看这个接口不会导致内存溢出。所以我们才需要找出是谁使用了这个pms\_sku\_stock表和OmsOrderDetail类并且会产生溢出。这个转折才是关键。
希望你能在这节课中受益。
## 课后作业
最后,我给你留两道题,请你思考一下:
1. 你能否快速找到需要创建索引的SQL
2. 在内存不断增长时,如何快速定位出哪个对象导致的?
记得在留言区和我讨论、交流你的想法,每一次思考都会让你更进一步。
如果你读完这篇文章有所收获,也欢迎你分享给你的朋友,共同学习进步。我们下一讲再见!