diff --git a/Database.md b/Database.md index ec95d21..2d9e940 100644 --- a/Database.md +++ b/Database.md @@ -2350,3 +2350,190 @@ mysql> show variables like 'slave_parallel%'; - 提高从库机器的配置,减少主库写binlog和从库读binlog的效率差 - 尽量采用短的链路,也就是主库和从库服务器的距离尽量要短,提升端口带宽,减少binlog传输的网络延时 - 实时性要求的业务读强制走主库,从库只做灾备,备份 + + + +# 常见问题 + +## 问题一:MySQL事务 + +**问题一:什么Mysql的事务?事务的四大特性?事务带来的什么问题?** + + + +在Mysql中事务的四大特性(简称为`ACID`)主要包含: + +- **原子性(Atomicity)**:是指事务的原子性操作,**对数据的修改要么全部执行成功,要么全部失败,实现事务的原子性**,是基于日志的Redo/Undo机制 +- **一致性**:是指**执行事务前后的状态要一致**,可以理解为数据一致性 +- **隔离性**:侧重指**事务之间相互隔离,不受影响**,这个与事务设置的隔离级别有密切的关系 +- **持久性**:则是指在**一个事务提交后,这个事务的状态会被持久化到数据库中,也就是事务提交,对数据的新增、更新将会持久化到数据库中** + +在我的理解中:**原子性、隔离性、持久性都是为了保障一致性而存在的,一致性也是最终的目的**。 + + + +Mysql中事务的隔离级别分为四大等级: + +- **读未提交(READ UNCOMMITTED)** +- **读提交 (READ COMMITTED)** +- **可重复读 (REPEATABLE READ)** +- **串行化 (SERIALIZABLE)** + +没有哪种隔离级别是完美的,只能根据自己的项目业务场景去评估选择最适合的隔离级别,大部分的公司一般选择Mysql默认的隔离级别:**可重复读**。隔离级别从:**读未提交-读提交-可重复读-串行化**,级别越来越高,隔离也就越来越严实,到最后的串行化,当出现读写锁冲突的时候,后面的事务只能等前面的事务完成后才能继续访问。 + +- **读未提交:读取到别的事务还没有提交的数据,从而产生了脏读** +- **读提交:读取别的事务已经提交的数据,从而产生不可重复读** +- **可重复读:事务开启过程中看到的数据和事务刚开始看到的数据是一样的,从而产生幻读,在Mysql的中通过MVCC多版本控制的一致性视图解决了不可重复读问题以及通过间隙锁解决了幻读问题** +- **串行化:对于同一行记录,若是读写锁发生冲突,后面访问的事务只能等前面的事务执行完才能继续访问** + + + +## MySQL存储引擎 + +**问题二:MySQL存储引擎的InnoDB和MyISAM有什么区别?** + +- InnoDB和MyISAM都是Mysql的存储引擎,现在MyISAM也逐渐被InnoDB给替代,主要因为InnoDB支持事务和行级锁,MyISAM不支持事务和行级锁,MyISAM最小锁单位是表级。因为MyISAM不支持行级锁,所以在并发处理能力上InnoDB会比MyISAM好 +- 数据的存储上:MyISAM的索引也是由B+树构成,但是树的叶子结点存的是行数据的地址,查找时需要找到叶子结点的地址,再根据叶子结点地址查找数据 +- 数据文件构成:MyISAM有三种存储文件分别是扩展名为:`.frm`(文件存储表定义)、`.MYD` (MYData数据文件)、`.MYI` (MYIndex索引文件)。而InnoDB的表只受限于操作系统文件的大小,一般是2GB +- 查询区别:对于读多写少的业务场景,MyISAM会更加适合,而对于update和insert比较多的场景InnoDB会比较适合。 +- coun(\*)区别:select count(\*) from table,MyISAM引擎会查询已经保存好的行数,这是不加where的条件下,而InnoDB需要全表扫描一遍,InnoDB并没有保存表的具体行数 +- 其它的区别:InnoDB支持外键,但是不支持全文索引,而MyISAM不支持外键,支持全文索引,InnoDB的主键的范围比MyISAM的大 + + + +## SQL查询流程 + +**问题三:SQL查询流程是什么?** + +- **1 连接** + +  1.1客户端发起一条Query请求,监听客户端的‘连接管理模块’接收请求 + +  1.2将请求转发到‘连接进/线程模块’ + +  1.3调用‘用户模块’来进行授权检查 + +  1.4通过检查后,‘连接进/线程模块’从‘线程连接池’中取出空闲的被缓存的连接线程和客户端请求对接,如果失败则创建一个新的连接请求 + +- **2 处理** + +  2.1先查询缓存,检查Query语句是否完全匹配,接着再检查是否具有权限,都成功则直接取数据返回 + +  2.2上一步有失败则转交给‘命令解析器’,经过词法分析,语法分析后生成解析树 + +  2.3接下来是预处理阶段,处理解析器无法解决的语义,检查权限等,生成新的解析树 + +  2.4再转交给对应的模块处理 + +  2.5如果是SELECT查询还会经由‘查询优化器’做大量的优化,生成执行计划 + +  2.6模块收到请求后,通过‘访问控制模块’检查所连接的用户是否有访问目标表和目标字段的权限 + +  2.7有则调用‘表管理模块’,先是查看table cache中是否存在,有则直接对应的表和获取锁,否则重新打开表文件 + +  2.8根据表的meta数据,获取表的存储引擎类型等信息,通过接口调用对应的存储引擎处理 + +  2.9上述过程中产生数据变化的时候,若打开日志功能,则会记录到相应二进制日志文件中 + +- **3 结果** + +  3.1Query请求完成后,将结果集返回给‘连接进/线程模块’ + +  3.2返回的也可以是相应的状态标识,如成功或失败等 + +  3.3‘连接进/线程模块’进行后续的清理工作,并继续等待请求或断开与客户端的连接 + +SQL查询流程图如下: + +**![SQL查询流程图](images/Database/SQL查询流程图.png)** + + + +## MySQL Log + +**问题四:redo log和binlog了解过吗?** + +- redo log + - `redo log`日志也叫做`WAL`技术(`Write- Ahead Logging`),他是一种**先写日志,并更新内存,最后再更新磁盘的技术**,为了就是减少sql执行期间的数据库io操作,并且更新磁盘往往是在Mysql比较闲的时候,这样就大大减轻了Mysql的压力 + - `redo log`是固定大小,是物理日志,属于InnoDB引擎的,并且写redo log是环状写日志的形式 + - `redo log`日志实现了即使在数据库出现异常宕机的时候,重启后之前的记录也不会丢失,这就是`crash-safe`能力 + +- binlog + +- `binlog`称为**归档日志**,是逻辑上的日志,它属于Mysql的Server层面的日志,记录着sql的原始逻辑,主要有两种模式:**一个是statement格式记录的是原始的sql,而row格式则是记录行内容**。 + + + +redo log和binlog记录的形式、内容不同,这两者日志都能通过自己记录的内容恢复数据。之所以这两个日志同时存在,是因为刚开始Mysql自带的引擎MyISAM就没有crash-safe功能的,并且在此之前Mysql还没有InnoDB引擎,Mysql自带的binlog日志只是用来归档日志的,所以InnoDB引擎也就通过自己redo log日志来实现crash-safe功能。 + + + +## MySQL索引种类 + +**问题五:你知道有哪些种类的索引?** + +索引从**数据结构**进行划分的分为:**B+树索引、hash索引、R-Tree索引、FULLTEXT索引**。 + +索引从**物理存储**的角度划分为:**聚族索引**和**非聚族索引**。 + +从**逻辑的角度**分为:**主键索引**、**普通索引、唯一索引、联合索引**以及**空间索引**。 + + + +## MySQL索引 + +**问题六:怎么查看索引是否生效?什么情况下索引会失效呢?** + +查看索引是否起作用可以使用explain关键字,查询后的语句中的key字段,若是使用了索引,该字段会展示索引的名字。 + +![MySQL索引-explain](images/Database/MySQL索引-explain.jpg) + +- `id`:查询的序列号 +- `select_type`:查询类型 +- `table`:查询表名 +- `type`:扫描方式,all表示全表扫描 +- `possible_keys`:可是使用到的索引 +- `key`:实际使用到的索引 +- `rows`:该sql扫面了多少行 +- `Extra`:sql语句额外的信息,比如排序方式 + + + +- **where条件查询中使用了or关键字**,有可能使用了索引进行查询也会导致索引失效,若是想使用or关键字,又不想索引失效,只能在or的所有列上都建立索引。 + +![MySQL索引-or使索引失效](images/Database/MySQL索引-or使索引失效.jpg) + +- **条件查询中使用like关键字,并且不符合最左前缀原则**,会导致索引失效。 + +![MySQL索引-like使索引失效](images/Database/MySQL索引-like使索引失效.png) + +- **条件查询的字段是字符串,而错误的使用where column = 123 数字类型**也会导致索引失效。 + +![MySQL索引-数据类型不匹配使索引失效](images/Database/MySQL索引-数据类型不匹配使索引失效.png) + +- 对于**联合索引查询不符合最左前缀原则**,也会导致索引失效,如下所示: + +```sql +alter table user add index union_index(name, age) // name左边的列, age 右边的列 +select * from user where name = 'lidu' // 会用到索引 +select * from user where age = 18 // 不会使用索引 +``` + +- 在**where条件查询的后面对字段进行null值判断**,会导致索引失效,解决方式为可以把null改为0或者-1这些特殊的值代替: + +```sql +SELECT id FROM table WHERE num is null +``` + +- 在**where子句中使用!= ,< >这样的符号**,也会导致索引失效。 + +```sql +SELECT id FROM table WHERE num != 0 +``` + +- **where条件子句中=的左边使用表达式操作或者函数操作**,也会导致索引失效。 + +```sql +SELECT id FROM user WHERE age / 2 = 1 +SELECT id FROM user WHERE SUBSTRING(name,1,2) = 'lidu' +``` \ No newline at end of file diff --git a/JVM.md b/JVM.md index 7efa6a6..a418b39 100644 --- a/JVM.md +++ b/JVM.md @@ -1373,6 +1373,475 @@ CMS垃圾收集器JVM参数最佳实践: - JNI 调用需要谨慎,不一定可以提升性能,反而可能造成 GC 问题 - 升级 JDK 版本到 14,避免 [JDK-8048556](https://bugs.openjdk.java.net/browse/JDK-8048556) 导致的重复 GC + + +# JVM性能调优 + +## 磁盘不足排查 + +其实,磁盘不足排查算是系统、程序层面的问题排查,并不算是JVM,但是另一方面考虑过来就是,系统磁盘的不足,也会导致JVM的运行异常,所以也把磁盘不足算进来了。并且排查磁盘不足,是比较简单,就是几个命令,然后就是逐层的排查,首先第一个命令就是**df -h**,查询磁盘的状态: + +![JVM-磁盘不足排查](images/JVM/JVM-磁盘不足排查.jpg) + +从上面的显示中其中第一行使用的2.8G最大,然后是挂载在 **/** 目录下,我们直接**cd /**。然后通过执行: + +```shell +du -sh * +``` + +查看各个文件的大小,找到其中最大的,或者说存储量级差不多的并且都非常大的文件,把那些没用的大文件删除就好。 + +![JVM-磁盘不足排查-du-sh](images/JVM/JVM-磁盘不足排查-du-sh.jpg) + +然后,就是直接cd到对应的目录也是执行:du -sh *,就这样一层一层的执行,找到对应的没用的,然后文件又比较大的,可以直接删除。 + + + +## CPU过高排查 + +然后就是排查CPU的飙高的原因,**CPU飙高的排查都是直接找到对应CPU占比最高的进程,然后找到CPU最高的线程**。 + +总结一下可能导致CPU标高的原因,可能是**一个GC线程频繁或者锁资源竞争频繁,线程数过多**等原因。 + +- **GC线程频繁** +- **锁竞争频繁(自旋)** + +其中GC线程频繁,有可能是**大对象(对象过多),内存泄漏**等原因导致内存紧张一直在执行GC,但是每次执行的GC回收的垃圾都非常少。 + +一般CPU紧张,都是线上实施排查,并且一般大厂都会有自己自研的监控平台,我们自己的监控平台,对于我们每台服务器的健康状况(健康分)、服务期内的应用(Mysql、Redis、Mq、Kafka、服务)都会进行实施的监控报警,所以一般都能都在出现问题前将问题解决掉。 + +在线上之间也提到过可以使用**top**、**jstack**命令排查CPU飙高的问题。这里有一段案例代码如下: + +```java +public class CPUSoaring { + public static void main(String[] args) { + + Thread thread1 = new Thread(new Runnable(){ + @Override + public void run() { + for (;;){ + System.out.println("I am children-thread1"); + } + } + },"children-thread1"); + + Thread thread2 = new Thread(new Runnable(){ + @Override + public void run() { + for (;;){ + System.out.println("I am children-thread2"); + } + } + },"children-thread2"); + + thread1.start(); + thread2.start(); + System.err.println("I am is main thread!!!!!!!!"); + } +} +``` + +(1)首先通过**top**命令可以查看到id为**3806**的进程所占的CPU最高: + +![CPU过高排查-top](images/JVM/CPU过高排查-top.jpg) + +(2)然后通过**top -Hp pid**命令,找到占用CPU最高的线程: + +![CPU过高排查-top-Hp-pid](images/JVM/CPU过高排查-top-Hp-pid.jpg) + +(3)接着通过:**printf '%x\n' tid**命令将线程的tid转换为十六进制:xid: + +![CPU过高排查-printf](images/JVM/CPU过高排查-printf.jpg) + +(4)最后通过:**jstack pid|grep xid -A 30**命令就是输出线程的堆栈信息,线程所在的位置: + +![CPU过高排查-jstack](images/JVM/CPU过高排查-jstack.jpg) + +(5)还可以通过**jstack -l pid > 文件名称.txt** 命令将线程堆栈信息输出到文件,线下查看。 + +这就是一个CPU飙高的排查过程,目的就是要**找到占用CPU最高的线程所在的位置**,然后就是**review**你的代码,定位到问题的所在。使用Arthas的工具排查也是一样的,首先要使用top命令找到占用CPU最高的Java进程,然后使用Arthas进入该进程内,**使用dashboard命令排查占用CPU最高的线程。**,最后通过**thread**命令线程的信息。 + + + +## OOM异常排查 + +OOM的异常排查也比较简单,首先服务上线的时候,要先设置这两个参数: + +```shell +-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=${目录} +``` + +指定项目出现OOM异常的时候自动导出堆转储文件,然后通过内存分析工具(**Visual VM**)来进行线下的分析。 + +首先我们来聊一聊,哪些原因会导致OOM异常,站在JVM的分区的角度: + +- **Java堆** +- **方法区** +- **虚拟机栈** +- **本地方法栈** +- **程序计数器** +- **直接内存** + +只有**程序计数器**区域不会出现OOM,在Java 8及以上的**元空间**(本地内存)都会出现OOM。 + +而站在程序代码的角度来看,总结了大概有以下几点原因会导致OOM异常: + +- **内存泄露** +- **对象过大、过多** +- **方法过长** +- **过度使用代理框架,生成大量的类信息** + +接下来我们屋来看看OOM的排查,出现OOM异常后dump出了堆转储文件,然后打开jdk自带的Visual VM工具,导入堆转储文件,首先我使用的OOM异常代码如下: + +```java +import java.util.ArrayList; +import java.util.List; + +class OOM { + + static class User{ + private String name; + private int age; + + public User(String name, int age){ + this.name = name; + this.age = age; + } + } + + public static void main(String[] args) throws InterruptedException { + List list = new ArrayList<>(); + for (int i = 0; i < Integer.MAX_VALUE; i++) { + Thread.sleep(1000); + User user = new User("zhangsan"+i,i); + list.add(user); + } + } + +} +``` + +代码很简单,就是往集合里面不断地add对象,带入堆转储文件后,在类和实例那栏就可以看到实例最多的类: + +![OOM异常排查-查看实例最多的类](images/JVM/OOM异常排查-查看实例最多的类.jpg) + +这样就找到导致OOM异常的类,还可以通过下面的方法查看导致OOM异常的线程堆栈信息,找到对应异常的代码段。 + +![OOM异常排查-查看异常代码](images/JVM/OOM异常排查-查看异常代码.jpg) + +![OOM异常排查-查看异常代码堆栈](images/JVM/OOM异常排查-查看异常代码堆栈.jpg) + +上面的方法是排查已经出现了OOM异常的方法,肯定是防线的最后一步,那么在此之前怎么防止出现OOM异常呢? + +一般大厂都会有自己的监控平台,能够实施的**监控测试环境、预览环境、线上实施的服务健康状况(CPU、内存)** 等信息,对于频繁GC,并且GC后内存的回收率很差的,就要引起我们的注意了。 + +因为一般方法的长度合理,95%以上的对象都是朝生夕死,在**Minor GC**后只剩少量的存活对象,所以在代码层面上应该避免**方法过长、大对象**的现象。 + +每次自己写完代码,自己检查后,都可以提交给比自己高级别的工程师**review**自己的代码,就能及时的发现代码的问题,基本上代码没问题,百分之九十以上的问题都能避免,这也是大厂注重代码质量,并且时刻**review**代码的习惯。 + + + +## 栈溢出 + +栈溢出异常的排查(包括**虚拟机栈、本地方法栈**)基本和OOM的一场排查是一样的,导出异常的堆栈信息,然后使用mat或者Visual VM工具进行线下分析,找到出现异常的代码或者方法。 + +当线程请求的栈深度大于虚拟机栈所允许的大小时,就会出现**StackOverflowError**异常,二从代码的角度来看,导致线程请求的深度过大的原因可能有:**方法栈中对象过大,或者过多,方法过长从而导致局部变量表过大,超过了-Xss参数的设置**。 + + + +## 死锁排查 + +死锁的案例演示的代码如下: + +```java +public class DeadLock { + + public static Object lock1 = new Object(); + public static Object lock2 = new Object(); + + public static void main(String[] args){ + Thread a = new Thread(new Lock1(),"DeadLock1"); + Thread b = new Thread(new Lock2(),"DeadLock2"); + a.start(); + b.start(); + } +} +class Lock1 implements Runnable{ + @Override + public void run(){ + try{ + while(true){ + synchronized(DeadLock.lock1){ + System.out.println("Waiting for lock2"); + Thread.sleep(3000); + synchronized(DeadLock.lock2){ + System.out.println("Lock1 acquired lock1 and lock2 "); + } + } + } + }catch(Exception e){ + e.printStackTrace(); + } + } +} +class Lock2 implements Runnable{ + @Override + public void run(){ + try{ + while(true){ + synchronized(DeadLock.lock2){ + System.out.println("Waiting for lock1"); + Thread.sleep(3000); + synchronized(DeadLock.lock1){ + System.out.println("Lock2 acquired lock1 and lock2"); + } + } + } + }catch(Exception e){ + e.printStackTrace(); + } + } +} +``` + +上面的代码非常的简单,就是两个类的实例作为锁资源,然后分别开启两个线程,不同顺序的对锁资源资源进行加锁,并且获取一个锁资源后,等待三秒,是为了让另一个线程有足够的时间获取另一个锁对象。 + +运行上面的代码后,就会陷入死锁的僵局: + +![死锁排查-死锁运行结果示例](images/JVM/死锁排查-死锁运行结果示例.jpg) + +对于死锁的排查,若是在测试环境或者本地,直接就可以使用Visual VM连接到该进程,如下界面就会自动检测到死锁的存在 + +![死锁排查-检测死锁](images/JVM/死锁排查-检测死锁.jpg) + +并且查看线程的堆栈信息。就能看到具体的死锁的线程: + +![死锁排查-查看线程堆栈信息](images/JVM/死锁排查-查看线程堆栈信息.jpg) + +线上的话可以上用Arthas也可以使用原始的命令进行排查,原始命令可以先使用**jps**查看具体的Java进程的ID,然后再通过**jstack ID**查看进程的线程堆栈信息,他也会自动给你提示有死锁的存在: + +![死锁排查-jstack查看线程堆栈](images/JVM/死锁排查-jstack查看线程堆栈.jpg) + +Arthas工具可以使用**thread**命令排查死锁,要关注的是**BLOCKED**状态的线程,如下图所示: + +![死锁排查-Arthas查看死锁](images/JVM/死锁排查-Arthas查看死锁.jpg) + +具体thread的详细参数可以参考如下图所示: + +![死锁排查-Thread详细参数](images/JVM/死锁排查-Thread详细参数.jpg) + + + +**如何避免死锁** + +上面我们聊了如何排查死锁,下面我们来聊一聊如何避免死锁的发生,从上面的案例中可以发现,死锁的发生两个线程同时都持有对方不释放的资源进入僵局。所以,在代码层面,要避免死锁的发生,主要可以从下面的四个方面进行入手: + +- **首先避免线程的对于资源的加锁顺序要保持一致** + +- **并且要避免同一个线程对多个资源进行资源的争抢** + +- **另外的话,对于已经获取到的锁资源,尽量设置失效时间,避免异常,没有释放锁资源,可以使用acquire() 方法加锁时可指定 timeout 参数** + +- **最后,就是使用第三方的工具检测死锁,预防线上死锁的发生** + +死锁的排查已经说完了,上面的基本就是问题的排查,也可以算是调优的一部分吧,但是对于JVM调优来说,重头戏应该是在**Java堆**,这部分的调优才是重中之重。 + + + +## 调优实战 + +上面说完了调优的目的和调优的指标,那么我们就来实战调优,首先准备我的案例代码,如下: + +```java +import java.util.ArrayList; +import java.util.List; + +class OOM { + + static class User{ + private String name; + private int age; + + public User(String name, int age){ + this.name = name; + this.age = age; + } + + } + + public static void main(String[] args) throws InterruptedException { + List list = new ArrayList<>(); + for (int i = 0; i < Integer.MAX_VALUE; i++) { + Tread.sleep(1000); + System.err.println(Thread.currentThread().getName()); + User user = new User("zhangsan"+i,i); + list.add(user); + } + } +} +``` + +案例代码很简单,就是不断的往一个集合里里面添加对象,首先初次我们启动的命令为: + +```shell +java -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+UseGCLogFileRotation -XX:+PrintHeapAtGC -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=50M -Xloggc:./logs/emps-gc-%t.log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./logs/emps-heap.dump OOM +``` + +就是纯粹的设置了一些GC的打印日志,然后通过Visual VM来看GC的显示如下: + +![调优实战-VisualVM查看GC显示](images/JVM/调优实战-VisualVM查看GC显示.jpg) + +可以看到一段时间后出现4次Minor GC,使用的时间是29.648ms,发生一次Full GC使用的时间是41.944ms。 + +Minor GC非常频繁,Full GC也是,在短时间内就发生了几次,观察输出的日志发现以及Visual VM的显示来看,都是因为内存没有设置,太小,导致Minor GC频繁。 + +因此,我们第二次适当的增大Java堆的大小,调优设置的参数为: + +```shell +java -Xmx2048m -Xms2048m -Xmn1024m -Xss256k -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+UseGCLogFileRotation -XX:+PrintHeapAtGC -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=50M -Xloggc:./logs/emps-gc-%t.log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./logs/emps-heap.dump OOM +``` + +观察一段时间后,结果如下图所示: + +![调优实战-一段时间后VisualVM查看GC显示](images/JVM/调优实战-一段时间后VisualVM查看GC显示.jpg) + +可以发现Minor GC次数明显下降,但是还是发生了Full GC,根据打印的日志来看,是因为元空间的内存不足,看了上面的Visual VM元空间的内存图,也是一样,基本都到顶了: + +![调优实战-元空间不足](images/JVM/调优实战-元空间不足.jpg) + +因此第三次对于元空间的区域设置大一些,并且将GC回收器换成是CMS的,设置的参数如下: + +```shell +java -Xmx2048m -Xms2048m -Xmn1024m -Xss256k -XX:MetaspaceSize=100m -XX:MaxMetaspaceSize=100m -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+UseGCLogFileRotation -XX:+PrintHeapAtGC -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=50M -Xloggc:./logs/emps-gc-%t.log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./logs/emps-heap.dump OOM +``` + +观察相同的时间后,Visual VM的显示图如下: + +![调优实战-元空间调后后VisualVM查看GC显示](images/JVM/调优实战-元空间调后后VisualVM查看GC显示.jpg) + +同样的时间,一次Minor GC和Full GC都没有发生,所以这样我觉得也算是已经调优了。 + +但是调优并不是一味的调大内存,是要在各个区域之间取得平衡,可以适当的调大内存,以及更换GC种类,举个例子,当把上面的案例代码的Thread.sleep(1000)给去掉。 + +然后再来看Visual VM的图,如下: + +![调优实战-去掉线程休眠VisualVM显示](images/JVM/调优实战-去掉线程休眠VisualVM显示.jpg) + +可以看到Minor GC也是非常频繁的,因为这段代码本身就是不断的增大内存,直到OOM异常,真正的实际并不会这样,可能当内存增大到一定两级后,就会在一段范围平衡。 + +当我们将上面的情况,再适当的增大内存,JVM参数如下: + +```shell +java -Xmx4048m -Xms4048m -Xmn2024m -XX:SurvivorRatio=7 -Xss256k -XX:MetaspaceSize=300m -XX:MaxMetaspaceSize=100m -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+UseGCLogFileRotation -XX:+PrintHeapAtGC -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=50M -Xloggc:./logs/emps-gc-%t.log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./logs/emps-heap.dump OOM +``` + +可以看到相同时间内,确实Minor GC减少了,但是时间增大了,因为复制算法,基本都是存活的,复制需要耗费大量的性能和时间: + +![调优实战-减少MinorGC后VisualVM显示](images/JVM/调优实战-减少MinorGC后VisualVM显示.jpg) + +所以,调优要有取舍,取得一个平衡点,性能、状态达到佳就OK了,并没最佳的状态,这就是调优的基本法则,而且调优也是一个细活,所谓慢工出细活,需要耗费大量的时间,慢慢调,不断的做对比。 + + + +## 调优参数 + +### 堆 + +- -Xms1024m 设置堆的初始大小 +- -Xmx1024m 设置堆的最大大小 +- -XX:NewSize=1024m 设置年轻代的初始大小 +- -XX:MaxNewSize=1024m 设置年轻代的最大值 +- -XX:SurvivorRatio=8 Eden和S区的比例 +- -XX:NewRatio=4 设置老年代和新生代的比例 +- -XX:MaxTenuringThreshold=10 设置晋升老年代的年龄条件 + + + +### 栈 + +- -Xss128k + + + +### 元空间 + +- -XX:MetasapceSize=200m 设置初始元空间大小 +- -XX:MaxMatespaceSize=200m 设置最大元空间大小 默认无限制 + + + +### 直接内存 + +- -XX:MaxDirectMemorySize 设置直接内存的容量,默认与堆最大值一样 + + + +### 日志 + +- -Xloggc:/opt/app/ard-user/ard-user-gc-%t.log 设置日志目录和日志名称 +- -XX:+UseGCLogFileRotation 开启滚动生成日志 +- -XX:NumberOfGCLogFiles=5 滚动GC日志文件数,默认0,不滚动 +- -XX:GCLogFileSize=20M GC文件滚动大小,需开 UseGCLogFileRotation +- -XX:+PrintGCDetails 开启记录GC日志详细信息(包括GC类型、各个操作使用的时间),并且在程序运行结束打印出JVM的内存占用情况 +- -XX:+ PrintGCDateStamps 记录系统的GC时间 +- -XX:+PrintGCCause 产生GC的原因(默认开启) + + + +### GC + +#### Serial垃圾收集器(新生代)开启 + +- -XX:+UseSerialGC 关闭: +- -XX:-UseSerialGC //新生代使用Serial 老年代则使用SerialOld + + + +#### Parallel Scavenge收集器(新生代)开启 + +- -XX:+UseParallelOldGC 关闭 +- -XX:-UseParallelOldGC 新生代使用功能Parallel Scavenge 老年代将会使用Parallel Old收集器 + + + +#### ParallelOl垃圾收集器(老年代)开启 + +- -XX:+UseParallelGC 关闭 +- -XX:-UseParallelGC 新生代使用功能Parallel Scavenge 老年代将会使用Parallel Old收集器 + + + +#### ParNew垃圾收集器(新生代)开启 + +- -XX:+UseParNewGC 关闭 +- -XX:-UseParNewGC //新生代使用功能ParNew 老年代则使用功能CMS + + + +#### CMS垃圾收集器(老年代)开启 + +- -XX:+UseConcMarkSweepGC 关闭 +- -XX:-UseConcMarkSweepGC +- -XX:MaxGCPauseMillis GC停顿时间,垃圾收集器会尝试用各种手段达到这个时间,比如减小年轻代 +- -XX:+UseCMSCompactAtFullCollection 用于在CMS收集器不得不进行FullGC时开启内存碎片的合并整理过程,由于这个内存整理必须移动存活对象,(在Shenandoah和ZGC出现前)是无法并发的 +- -XX:CMSFullGCsBefore-Compaction 多少次FullGC之后压缩一次,默认值为0,表示每次进入FullGC时都进行碎片整理) +- -XX:CMSInitiatingOccupancyFraction 当老年代使用达到该比例时会触发FullGC,默认是92 +- -XX:+UseCMSInitiatingOccupancyOnly 这个参数搭配上面那个用,表示是不是要一直使用上面的比例触发FullGC,如果设置则只会在第一次FullGC的时候使用-XX:CMSInitiatingOccupancyFraction的值,之后会进行自动调整 +- -XX:+CMSScavengeBeforeRemark 在FullGC前启动一次MinorGC,目的在于减少老年代对年轻代的引用,降低CMSGC的标记阶段时的开销,一般CMS的GC耗时80%都在标记阶段 +- -XX:+CMSParallellnitialMarkEnabled 默认情况下初始标记是单线程的,这个参数可以让他多线程执行,可以减少STW +- -XX:+CMSParallelRemarkEnabled 使用多线程进行重新标记,目的也是为了减少STW + + + +#### G1垃圾收集器开启 + +- -XX:+UseG1GC 关闭 +- -XX:-UseG1GC +- -XX:G1HeapRegionSize 设置每个Region的大小,取值范围为1MB~32MB +- -XX:MaxGCPauseMillis 设置垃圾收集器的停顿时间,默认值是200毫秒,通常把期望停顿时间设置为一两百毫秒或者两三百毫秒会是比较合理的 + + + # JDK Tools ## jps diff --git a/images/Database/MySQL索引-explain.jpg b/images/Database/MySQL索引-explain.jpg new file mode 100644 index 0000000..49eb86c Binary files /dev/null and b/images/Database/MySQL索引-explain.jpg differ diff --git a/images/Database/MySQL索引-like使索引失效.png b/images/Database/MySQL索引-like使索引失效.png new file mode 100644 index 0000000..752de5e Binary files /dev/null and b/images/Database/MySQL索引-like使索引失效.png differ diff --git a/images/Database/MySQL索引-or使索引失效.jpg b/images/Database/MySQL索引-or使索引失效.jpg new file mode 100644 index 0000000..e99a334 Binary files /dev/null and b/images/Database/MySQL索引-or使索引失效.jpg differ diff --git a/images/Database/MySQL索引-数据类型不匹配使索引失效.png b/images/Database/MySQL索引-数据类型不匹配使索引失效.png new file mode 100644 index 0000000..9970dc1 Binary files /dev/null and b/images/Database/MySQL索引-数据类型不匹配使索引失效.png differ diff --git a/images/Database/SQL查询流程图.png b/images/Database/SQL查询流程图.png new file mode 100644 index 0000000..5a157da Binary files /dev/null and b/images/Database/SQL查询流程图.png differ diff --git a/images/JVM/CPU过高排查-jstack.jpg b/images/JVM/CPU过高排查-jstack.jpg new file mode 100644 index 0000000..b07c101 Binary files /dev/null and b/images/JVM/CPU过高排查-jstack.jpg differ diff --git a/images/JVM/CPU过高排查-printf.jpg b/images/JVM/CPU过高排查-printf.jpg new file mode 100644 index 0000000..cf0311c Binary files /dev/null and b/images/JVM/CPU过高排查-printf.jpg differ diff --git a/images/JVM/CPU过高排查-top-Hp-pid.jpg b/images/JVM/CPU过高排查-top-Hp-pid.jpg new file mode 100644 index 0000000..786a4eb Binary files /dev/null and b/images/JVM/CPU过高排查-top-Hp-pid.jpg differ diff --git a/images/JVM/CPU过高排查-top.jpg b/images/JVM/CPU过高排查-top.jpg new file mode 100644 index 0000000..4a50542 Binary files /dev/null and b/images/JVM/CPU过高排查-top.jpg differ diff --git a/images/JVM/JVM-磁盘不足排查-du-sh.jpg b/images/JVM/JVM-磁盘不足排查-du-sh.jpg new file mode 100644 index 0000000..b16d6a2 Binary files /dev/null and b/images/JVM/JVM-磁盘不足排查-du-sh.jpg differ diff --git a/images/JVM/JVM-磁盘不足排查.jpg b/images/JVM/JVM-磁盘不足排查.jpg new file mode 100644 index 0000000..b87a8cb Binary files /dev/null and b/images/JVM/JVM-磁盘不足排查.jpg differ diff --git a/images/JVM/OOM异常排查-查看实例最多的类.jpg b/images/JVM/OOM异常排查-查看实例最多的类.jpg new file mode 100644 index 0000000..148ec22 Binary files /dev/null and b/images/JVM/OOM异常排查-查看实例最多的类.jpg differ diff --git a/images/JVM/OOM异常排查-查看异常代码.jpg b/images/JVM/OOM异常排查-查看异常代码.jpg new file mode 100644 index 0000000..9e2b0bd Binary files /dev/null and b/images/JVM/OOM异常排查-查看异常代码.jpg differ diff --git a/images/JVM/OOM异常排查-查看异常代码堆栈.jpg b/images/JVM/OOM异常排查-查看异常代码堆栈.jpg new file mode 100644 index 0000000..109fe41 Binary files /dev/null and b/images/JVM/OOM异常排查-查看异常代码堆栈.jpg differ diff --git a/images/JVM/死锁排查-Arthas查看死锁.jpg b/images/JVM/死锁排查-Arthas查看死锁.jpg new file mode 100644 index 0000000..2f3f768 Binary files /dev/null and b/images/JVM/死锁排查-Arthas查看死锁.jpg differ diff --git a/images/JVM/死锁排查-Thread详细参数.jpg b/images/JVM/死锁排查-Thread详细参数.jpg new file mode 100644 index 0000000..d28bb54 Binary files /dev/null and b/images/JVM/死锁排查-Thread详细参数.jpg differ diff --git a/images/JVM/死锁排查-jstack查看线程堆栈.jpg b/images/JVM/死锁排查-jstack查看线程堆栈.jpg new file mode 100644 index 0000000..4f07233 Binary files /dev/null and b/images/JVM/死锁排查-jstack查看线程堆栈.jpg differ diff --git a/images/JVM/死锁排查-查看线程堆栈信息.jpg b/images/JVM/死锁排查-查看线程堆栈信息.jpg new file mode 100644 index 0000000..1e70f75 Binary files /dev/null and b/images/JVM/死锁排查-查看线程堆栈信息.jpg differ diff --git a/images/JVM/死锁排查-检测死锁.jpg b/images/JVM/死锁排查-检测死锁.jpg new file mode 100644 index 0000000..b5d3e02 Binary files /dev/null and b/images/JVM/死锁排查-检测死锁.jpg differ diff --git a/images/JVM/死锁排查-死锁运行结果示例.jpg b/images/JVM/死锁排查-死锁运行结果示例.jpg new file mode 100644 index 0000000..e1bcfcb Binary files /dev/null and b/images/JVM/死锁排查-死锁运行结果示例.jpg differ diff --git a/images/JVM/调优实战-VisualVM查看GC显示.jpg b/images/JVM/调优实战-VisualVM查看GC显示.jpg new file mode 100644 index 0000000..c2fabc2 Binary files /dev/null and b/images/JVM/调优实战-VisualVM查看GC显示.jpg differ diff --git a/images/JVM/调优实战-一段时间后VisualVM查看GC显示.jpg b/images/JVM/调优实战-一段时间后VisualVM查看GC显示.jpg new file mode 100644 index 0000000..55b652a Binary files /dev/null and b/images/JVM/调优实战-一段时间后VisualVM查看GC显示.jpg differ diff --git a/images/JVM/调优实战-元空间不足.jpg b/images/JVM/调优实战-元空间不足.jpg new file mode 100644 index 0000000..9bcf68d Binary files /dev/null and b/images/JVM/调优实战-元空间不足.jpg differ diff --git a/images/JVM/调优实战-元空间调后后VisualVM查看GC显示.jpg b/images/JVM/调优实战-元空间调后后VisualVM查看GC显示.jpg new file mode 100644 index 0000000..7029757 Binary files /dev/null and b/images/JVM/调优实战-元空间调后后VisualVM查看GC显示.jpg differ diff --git a/images/JVM/调优实战-减少MinorGC后VisualVM显示.jpg b/images/JVM/调优实战-减少MinorGC后VisualVM显示.jpg new file mode 100644 index 0000000..0b8d1d8 Binary files /dev/null and b/images/JVM/调优实战-减少MinorGC后VisualVM显示.jpg differ diff --git a/images/JVM/调优实战-去掉线程休眠VisualVM显示.jpg b/images/JVM/调优实战-去掉线程休眠VisualVM显示.jpg new file mode 100644 index 0000000..65c4e97 Binary files /dev/null and b/images/JVM/调优实战-去掉线程休眠VisualVM显示.jpg differ