提交 24f210ca 编写于 作者: S shuang.kou

[feat] update 手把手教你定位常见Java性能问题

上级 f10a5c9c
......@@ -222,13 +222,12 @@ Github用户如果访问速度缓慢的话,可以转移到[码云](https://git
#### Spring/SpringBoot
1. [Spring 学习与面试(待重构)](docs/system-design/framework/spring/Spring.md)
2. **[Spring 常见问题总结](docs/system-design/framework/spring/SpringInterviewQuestions.md)**
3. **[Spring/Spring常用注解总结!安排!](./docs/system-design/framework/spring/spring-annotations.md)**
4. **[SpringBoot 指南/常见面试题总结](https://github.com/Snailclimb/springboot-guide)**
5. [Spring中 Bean 的作用域与生命周期](docs/system-design/framework/spring/SpringBean.md)
6. [SpringMVC 工作原理详解](docs/system-design/framework/spring/SpringMVC-Principle.md)
7. [Spring中都用到了那些设计模式?](docs/system-design/framework/spring/Spring-Design-Patterns.md)
1. **[Spring 常见问题总结](docs/system-design/framework/spring/SpringInterviewQuestions.md)**
2. **[Spring/Spring常用注解总结!安排!](./docs/system-design/framework/spring/spring-annotations.md)**
3. **[SpringBoot 指南/常见面试题总结](https://github.com/Snailclimb/springboot-guide)**
4. [Spring中 Bean 的作用域与生命周期](docs/system-design/framework/spring/SpringBean.md)
5. [SpringMVC 工作原理详解](docs/system-design/framework/spring/SpringMVC-Principle.md)
6. [Spring中都用到了那些设计模式?](docs/system-design/framework/spring/Spring-Design-Patterns.md)
#### MyBatis
......
......@@ -33,8 +33,6 @@ vmstat是一个指定周期和采集次数的虚拟内存检测工具,可以
- wa: IO等待时间百分比
- id: 空闲时间百分比
**pidstat命令**
pidstat 是 Sysstat 中的一个组件,也是一款功能强大的性能监测工具,`top``vmstat` 两个命令都是监测进程的内存、CPU 以及 I/O 使用情况,而 pidstat 命令可以检测到线程级别的。`pidstat`命令线程切换字段说明如下:
......@@ -95,7 +93,13 @@ jmap也是JDK工具命令,他可以查看堆内存的初始化信息以及堆
**mat内存工具**
MAT(Memory Analyzer Tool)工具是eclipse的一个插件(MAT也可以单独使用),它分析大内存的dump文件时,可以非常直观的看到各个对象在堆空间中所占用的内存大小、类实例数量、对象引用关系、利用OQL对象查询,以及可以很方便的找出对象GC Roots的相关信息。[下载地址可以点击这里](https://www.eclipse.org/mat/downloads.php)
MAT(Memory Analyzer Tool)工具是eclipse的一个插件(MAT也可以单独使用),它分析大内存的dump文件时,可以非常直观的看到各个对象在堆空间中所占用的内存大小、类实例数量、对象引用关系、利用OQL对象查询,以及可以很方便的找出对象GC Roots的相关信息。
**idea中也有这么一个插件,就是JProfiler**
相关阅读:
1. 《性能诊断利器 JProfiler 快速入门和最佳实践》:[https://segmentfault.com/a/1190000017795841](https://segmentfault.com/a/1190000017795841)
## 模拟环境准备
......@@ -106,7 +110,7 @@ MAT(Memory Analyzer Tool)工具是eclipse的一个插件(MAT也可以单独使
模拟CPU占满还是比较简单,直接写一个死循环计算消耗CPU即可。
````java
/**
/**
* 模拟CPU占满
*/
@GetMapping("/cpu/loop")
......@@ -127,15 +131,15 @@ MAT(Memory Analyzer Tool)工具是eclipse的一个插件(MAT也可以单独使
请求接口地址测试`curl localhost:8080/cpu/loop`,发现CPU立马飙升到100%
![JshPzQ.png](https://s1.ax1x.com/2020/04/25/JshPzQ.png)
![](./images/performance-tuning/java-performance1.png)
通过执行`top -Hp 32805` 查看Java线程情况
[![JsheoV.png](https://s1.ax1x.com/2020/04/25/JsheoV.png)](https://imgchr.com/i/JsheoV)
![](./images/performance-tuning/java-performance2.png)
执行 `printf '%x' 32826` 获取16进制的线程id,用于`dump`信息查询,结果为 `803a`。最后我们执行`jstack 32805 |grep -A 20 803a `来查看下详细的`dump`信息。
![JshFMj.png](https://s1.ax1x.com/2020/04/25/JshFMj.png)
![](./images/performance-tuning/java-performance3.png)
这里`dump`信息直接定位出了问题方法以及代码行,这就定位出了CPU占满的问题。
......@@ -168,23 +172,23 @@ java.lang.OutOfMemoryError: Java heap space
我们用`jstat -gc pid` 命令来看看程序的GC情况。
[![Jshkss.png](https://s1.ax1x.com/2020/04/25/Jshkss.png)](https://imgchr.com/i/Jshkss)
![](./images/performance-tuning/java-performance4.png)
很明显,内存溢出了,堆内存经过45次 Full Gc 之后都没释放出可用内存,这说明当前堆内存中的对象都是存活的,有GC Roots引用,无法回收。那是什么原因导致内存溢出呢?是不是我只要加大内存就行了呢?如果是普通的内存溢出也许扩大内存就行了,但是如果是内存泄漏的话,扩大的内存不一会就会被占满,所以我们还需要确定是不是内存泄漏。我们之前保存了堆 Dump 文件,这个时候借助我们的MAT工具来分析下。导入工具选择`Leak Suspects Report`,工具直接就会给你列出问题报告。
![JshALn.png](https://s1.ax1x.com/2020/04/25/JshALn.png)
![](./images/performance-tuning/java-performance5.png)
这里已经列出了可疑的4个内存泄漏问题,我们点击其中一个查看详情。
[![JshCRg.png](https://s1.ax1x.com/2020/04/25/JshCRg.png)](https://imgchr.com/i/JshCRg)
![](./images/performance-tuning/java-performance6.png)
这里已经指出了内存被线程占用了接近50M的内存,占用的对象就是ThreadLocal。如果想详细的通过手动去分析的话,可以点击`Histogram`,查看最大的对象占用是谁,然后再分析它的引用关系,即可确定是谁导致的内存溢出。
![JshZd0.png](https://s1.ax1x.com/2020/04/25/JshZd0.png)
![](./images/performance-tuning/java-performance7.png)
上图发现占用内存最大的对象是一个Byte数组,我们看看它到底被那个GC Root引用导致没有被回收。按照上图红框操作指引,结果如下图:
![JshniT.png](https://s1.ax1x.com/2020/04/25/JshniT.png)
![](./images/performance-tuning/java-performance8.png)
我们发现Byte数组是被线程对象引用的,图中也标明,Byte数组对像的GC Root是线程,所以它是不会被回收的,展开详细信息查看,我们发现最终的内存占用对象是被ThreadLocal对象占据了。这也和MAT工具自动帮我们分析的结果一致。
......
## 什么是事务?
**事务是逻辑上的一组操作,要么都执行,要么都不执行。**
具体对应到我们日常开发过程中是这样的:**我们系统的每个业务方法可能包括了多个原子性的数据库操作,并且原子性的数据库操作是有依赖的,它们要不都执行,要不就都不执行。**
事务最经典也经常被拿出来说例子就是转账了。假如小明要给小红转账1000元,这个转账会涉及到两个关键操作就是:将小明的余额减少1000元,将小红的余额增加1000元。万一在这两个操作之间突然出现错误比如银行系统崩溃,导致小明余额减少而小红的余额没有增加,这样就不对了。事务就是保证这两个关键操作要么都成功,要么都要失败。
```java
public class OrdersService {
private AccountDao accountDao;
public void setOrdersDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, readOnly = false, timeout = -1)
public void accountMoney() {
//小红账户多1000
accountDao.addMoney(1000,xiaohong);
//模拟突然出现的异常,比如银行中可能为突然停电等等
//如果没有配置事务管理的话会造成,小红账户多了1000而小明账户没有少钱
int i = 10 / 0;
//小王账户少1000
accountDao.reduceMoney(1000,xiaoming);
}
}
```
## 事物的特性(ACID)了解么?
![](https://imgkr.cn-bj.ufileos.com/bda7231b-ab05-4e23-95ee-89ac90ac7fcf.png)
- **原子性:** 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;
- **一致性:** 执行事务前后,数据保持一致;
- **隔离性:** 并发访问数据库时,一个用户的事物不被其他事物所干扰,各并发事务之间数据库是独立的;
- **持久性:** 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。
## Spring对事务的支持
### Spring支持两种方式的事务管理
- **编程式事务管理:** 通过Transaction Template手动管理事务,实际应用中很少使用,
- **置声明式事务:** 推荐使用(代码侵入性最小),实际是通过AOP实现(基于` @Transactional` 的全注解方式使用最多,后文主要介绍这个注解的使用)。
### Spring事务管理接口介绍
Spring 框架中,事务管理相关最重要的3个接口如下:
- **`PlatformTransactionManager`**: (平台)事务管理器,Spring事务策略的核心。
- **`TransactionDefinition`**: 事务定义信息(事务隔离级别、传播行为、超时、只读、回滚规则)
- **`TransactionStatus`**: 事务运行状态
#### PlatformTransactionManager
**Spring并不直接管理事务,而是提供了多种事务管理器** 。Spring事务管理器的接口是: **org.springframework.transaction.PlatformTransactionManager** ,通过这个接口,Spring为各个平台如JDBC、Hibernate等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了。
`PlatformTransactionManager`接口中定义了三个方法:
```java
package org.springframework.transaction;
import org.springframework.lang.Nullable;
public interface PlatformTransactionManager {
//获得事务
TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;
//提交事务
void commit(TransactionStatus var1) throws TransactionException;
//回滚事务
void rollback(TransactionStatus var1) throws TransactionException;
}
```
#### TransactionDefinition
事务管理器接口 **`PlatformTransactionManager`** 通过 **`getTransaction(TransactionDefinition definition)`** 方法来得到一个事务,这个方法里面的参数是 **`TransactionDefinition`**类 ,这个类就定义了一些基本的事务属性。
**那么什么是事务属性呢?**
事务属性可以理解成事务的一些基本配置,描述了事务策略如何应用到方法上。事务属性包含了5个方面。
![](https://imgkr.cn-bj.ufileos.com/a616b84d-9eea-4ad1-b4fc-461ff05e951d.png)
`TransactionDefinition` 接口中定义了5个方法以及一些表示事务属性的常量比如隔离级别、传播行为等等。
```java
package org.springframework.transaction;
import org.springframework.lang.Nullable;
public interface TransactionDefinition {
int PROPAGATION_REQUIRED = 0;
int PROPAGATION_SUPPORTS = 1;
int PROPAGATION_MANDATORY = 2;
int PROPAGATION_REQUIRES_NEW = 3;
int PROPAGATION_NOT_SUPPORTED = 4;
int PROPAGATION_NEVER = 5;
int PROPAGATION_NESTED = 6;
int ISOLATION_DEFAULT = -1;
int ISOLATION_READ_UNCOMMITTED = 1;
int ISOLATION_READ_COMMITTED = 2;
int ISOLATION_REPEATABLE_READ = 4;
int ISOLATION_SERIALIZABLE = 8;
int TIMEOUT_DEFAULT = -1;
// 返回事务的传播行为,默认值为 REQUIRED。
int getPropagationBehavior();
//返回事务的隔离级别,默认值是 DEFAULT
int getIsolationLevel();
// 返回事务的超时时间,默认值为-1。如果超过该时间限制但事务还没有完成,则自动回滚事务。
int getTimeout();
// 返回是否为只读事务,默认值为 false
boolean isReadOnly();
@Nullable
String getName();
}
```
#### TransactionStatus
`TransactionStatus`接口用来记录事务的状态 该接口定义了一组方法,用来获取或判断事务的相应状态信息.
`PlatformTransactionManager.getTransaction(…) `方法返回一个 `TransactionStatus` 对象。返回的 `TransactionStatus` 对象可能代表一个新的或已经存在的事务(如果在当前调用堆栈有一个符合条件的事务)。
**TransactionStatus接口接口内容如下:**
```java
public interface TransactionStatus{
boolean isNewTransaction(); // 是否是新的事物
boolean hasSavepoint(); // 是否有恢复点
void setRollbackOnly(); // 设置为只回滚
boolean isRollbackOnly(); // 是否为只回滚
boolean isCompleted; // 是否已完成
}
```
### 事务属性详解
#### 事务传播行为
**事务传播行为是为了解决业务层方法之间互相调用的事务问题**
当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。
举个例子!
我们在 A 类的`aMethod()`方法中调用了 B 类的 `bMethod()` 方法。这个时候就涉及到业务层方法之间互相调用的事务问题。**我们的 `bMethod() `如果发生异常需要回滚,怎么样才能让 `aMethod()`也跟着回滚呢?**
```java
Class A {
@Transactional(propagation=propagation.xxx)
public void aMethod {
//do something
B b = new B();
b.bMethod();
}
}
Class B {
@Transactional(propagation=propagation.xxx)
public void bMethod {
//do something
}
}
```
`TransactionDefinition`定义中包括了如下几个表示传播行为的常量:
```java
public interface TransactionDefinition {
int PROPAGATION_REQUIRED = 0;
int PROPAGATION_SUPPORTS = 1;
int PROPAGATION_MANDATORY = 2;
int PROPAGATION_REQUIRES_NEW = 3;
int PROPAGATION_NOT_SUPPORTED = 4;
int PROPAGATION_NEVER = 5;
int PROPAGATION_NESTED = 6;
......
}
```
不过如此,为了方便使用,Spring会相应地定义了一个枚举类:`Propagation`
```java
package org.springframework.transaction.annotation;
import org.springframework.transaction.TransactionDefinition;
public enum Propagation {
REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),
SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),
MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),
REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),
NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),
NEVER(TransactionDefinition.PROPAGATION_NEVER),
NESTED(TransactionDefinition.PROPAGATION_NESTED);
private final int value;
Propagation(int value) {
this.value = value;
}
public int value() {
return this.value;
}
}
```
**正确的事务传播类型的可能值如下**
**1.`TransactionDefinition.PROPAGATION_REQUIRED`**
使用的最多的一个事务传播行为,我们平时经常使用的`@Transactional`注解默认使用就是这个事务传播行为。如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。也就是说:
1. 如果外部方法没有开启事务的话,`Propagation.REQUIRED`修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。
2. 如果外部方法开启事务并且被`Propagation.REQUIRED`的话,所有`Propagation.REQUIRED`修饰的内部方法和外部方法均属于同一事务 ,只要一个方法回滚,整个事务均回滚。
举个例子:如果我们上面的`aMethod()``bMethod()`使用的都是`PROPAGATION_REQUIRED`传播行为的话,两者使用的就是同一个事务,只要其中一个方法回滚,整个事务均回滚。
```java
Class A {
@Transactional(propagation=propagation.PROPAGATION_REQUIRED)
public void aMethod {
//do something
B b = new B();
b.bMethod();
}
}
Class B {
@Transactional(propagation=propagation.PROPAGATION_REQUIRED)
public void bMethod {
//do something
}
}
```
**`2.TransactionDefinition.PROPAGATION_REQUIRES_NEW`**
创建一个新的事务,如果当前存在事务,则把当前事务挂起。也就是说不管外部方法是否开启事务,`Propagation.REQUIRES_NEW`修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。
举个例子:如果我们上面的`bMethod()`使用`PROPAGATION_REQUIRES_NEW`事务传播行为修饰,`aMethod`还是用`PROPAGATION_REQUIRED`修饰的话。如果`aMethod()`发生异常回滚,`bMethod()`不会跟着回滚,因为 `bMethod()`开启了独立的事务。但是,如果 `bMethod()`抛出了未被捕获的异常并且这个异常满足事务回滚规则的话,`aMethod()`同样也会回滚,因为这个异常被 `aMethod()`的事务管理机制检测到了。
```java
Class A {
@Transactional(propagation=propagation.PROPAGATION_REQUIRED)
public void aMethod {
//do something
B b = new B();
b.bMethod();
}
}
Class B {
@Transactional(propagation=propagation.REQUIRES_NEW)
public void bMethod {
//do something
}
}
```
**4.`TransactionDefinition.PROPAGATION_NESTED`**:
如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于`TransactionDefinition.PROPAGATION_REQUIRED`。也就是说:
1. 在外部方法未开启事务的情况下`Propagation.NESTED``Propagation.REQUIRED`作用相同,修饰的内部方法都会新开启自己的事务,且开启的事务相互独立,互不干扰。
2. 如果外部方法开启事务的话,`Propagation.NESTED`修饰的内部方法属于外部事务的子事务,外部主事务回滚的话,子事务也会回滚,而内部子事务可以单独回滚而不影响外部主事务和其他子事务。
这里还是简单举个例子:
如果 `aMethod()` 回滚的话,`bMethod()``bMethod2()`都要回滚,而`bMethod()`回滚的话,并不会造成 `aMethod()``bMethod()`回滚。
```java
Class A {
@Transactional(propagation=propagation.PROPAGATION_REQUIRED)
public void aMethod {
//do something
B b = new B();
b.bMethod();
b.bMethod2();
}
}
Class B {
@Transactional(propagation=propagation.PROPAGATION_NESTED)
public void bMethod {
//do something
}
@Transactional(propagation=propagation.PROPAGATION_NESTED)
public void bMethod2 {
//do something
}
}
```
**5.`TransactionDefinition.PROPAGATION_MANDATORY`**
如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)
**若是错误的配置以下三种事务传播行为,事务将不会发生回滚,这里不对照案例讲解了,使用的很少。**
- **`TransactionDefinition.PROPAGATION_SUPPORTS`**: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
- **`TransactionDefinition.PROPAGATION_NOT_SUPPORTED`**: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。
- **`TransactionDefinition.PROPAGATION_NEVER`**: 以非事务方式运行,如果当前存在事务,则抛出异常。
#### 事务超时属性
所谓事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 TransactionDefinition 中以 int 的值来表示超时时间,其单位是秒。
#### 事务只读属性
事务的只读属性是指,对事务性资源进行只读操作或者是读写操作。所谓事务性资源就是指那些被事务管理的资源,比如数据源、 JMS 资源,以及自定义的事务性资源等等。如果确定只对事务性资源进行只读操作,那么我们可以将事务标志为只读的,以提高事务处理的性能。在 TransactionDefinition 中以 boolean 类型来表示该事务是否只读。
#### 事务回滚规则
这些规则定义了哪些异常会导致事务回滚而哪些不会。默认情况下,事务只有遇到运行期异常时才会回滚,而在遇到检查型异常时不会回滚(这一行为与EJB的回滚行为是一致的)。 但是你可以声明事务在遇到特定的检查型异常时像遇到运行期异常那样回滚。同样,你还可以声明事务遇到特定的异常不回滚,即使这些异常是运行期异常。
### @Transactional注解使用详解
@Transactional注解就代表支持事务管理,@Transactional 注解可以被应用于接口定义和接口方法、类定义和类的 public 方法上。如果这个注解在类上,那么表示该注解对于所有该类中的public方法都生效;如果注解出现在方法上,则代表该注解仅对该方法有效,会覆盖先前从类层次继承下来的注解。
## Reference
1. 可能是最漂亮的Spring事务管理详解:https://juejin.im/post/5b00c52ef265da0b95276091
2. Spring 事务管理机制概述 : [https://blog.csdn.net/justloveyou_/article/details/73733278https://blog.csdn.net/justloveyou_/article/details/73733278](https://blog.csdn.net/justloveyou_/article/details/73733278https://blog.csdn.net/justloveyou_/article/details/73733278)
3. [总结]Spring事务管理中@Transactional的参数:[http://www.mobabel.net/spring事务管理中transactional的参数/](http://www.mobabel.net/spring事务管理中transactional的参数/)
4. 透彻的掌握 Spring 中@transactional 的使用: [https://www.ibm.com/developerworks/cn/java/j-master-spring-transactional-use/index.html](https://www.ibm.com/developerworks/cn/java/j-master-spring-transactional-use/index.html)
5. Spring事务的传播特性:[https://github.com/love-somnus/Spring/wiki/Spring事务的传播特性](https://github.com/love-somnus/Spring/wiki/Spring事务的传播特性)
6. [Spring事务传播行为详解](https://segmentfault.com/a/1190000013341344)[https://segmentfault.com/a/1190000013341344](https://segmentfault.com/a/1190000013341344)
7.
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册