0%

最近又碰到的 oom 的问题,一直在尝试定位中,由于现实使用的 G1 的垃圾回收器。所以今天打算线上的排查历程和方案查询出来。

jvm 常用参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
-Xmx1024m 最大堆内存
-Xms1024m 最小堆内存
-Xss256k 设置栈的大小。栈都是每个线程独有一个,所有一般都是几百k的大小。
-XX:MetaspaceSize=128m 元空间的大小
-XX:MaxMetaspaceSize=256m 最大元空间大小
-XX:MaxGCPauseMillis=200 设置每次年轻代垃圾回收的最长时间,如果无法满足此时间,JVM会自动调整年轻代大小,以满足此值
-XX:+UseG1GC 使用 G1 垃圾回收器
-XX:-OmitStackTraceInFastThrow 当一些异常在代码里某个特定位置被抛出很多次的话,HotSpot Server Compiler(C2)会用fast throw来优化这个抛出异常的地方。
-XX:MinHeapFreeRatio=30
-XX:MaxHeapFreeRatio=50
-XX:MaxDirectMemorySize=100M 直接内存大小
-XX:+PrintGCDetails 打印 GC 详细信息
-XX:+DisableExplicitGC 禁止显示GC

几个命令

在排查的过程中用到的下面几个命令

1
2
3
4
jmap
pmap 命令
perf 命令
内存 RSS、VSZ的区别

出现 OOM 的问题,一般情况下来说,都是堆上面内存分配的太多,且无法回收,导致 JVM 的内存溢出。

  1. jps 查看 java 运行时的 pid
  2. jmap -heap pid 看下堆上各个区的占用的内存大小
  3. jmap -histo pid 可以查看对应的类型的大小,或者使用 dump 成一个文件进行分析

    在对堆上的类型对象进行分析的时候,发现堆上的内存大小和回收的基本正常,实际使用的内存是大于堆上的内存, 这个时候我就开始怀疑是堆外内存的泄露的问题。

  4. 使用 pmap -x pid | sort -n -k3 指令,看下占用内存的地址空间和大小

  5. 前面的工具如果再无法定位问题的话,就只能使用 perf 命令,基本就两条语句
  6. perf record -g -p pid 开启监控栈函数调用。运行一段时间后 Ctrl+C 结束,会生成一个文件 perf.data。
  7. 执行perf report -i perf.data查看报告。

    根据查看的内容,定位到 zip 的内容,但是内容还是不大,也 review zip 相关代码,进行本地测试,均为发生 OOM 的现象。

  8. 前面的几个办法都无法定位问题,只能使用最笨的办法,打印直接内存的大小。具体的代码如下:

         public BufferPoolMXBean getDirectBufferPoolMBean(){
             return ManagementFactory.getPlatformMXBeans(BufferPoolMXBean.class)
                     .stream()
                     .filter(e -> e.getName().equals("direct"))
                     .findFirst()
                     .orElseThrow(null);
         }
    
         public JavaNioAccess.BufferPool getNioBufferPool(){
             return SharedSecrets.getJavaNioAccess().getDirectBufferPool();
         }
    
         /**
             * -XX:MaxDirectMemorySize=60M
             */
         @Test
         public void testGetMaxDirectMemory(){
             ByteBuffer.allocateDirect(25*1024*1024);
             System.out.println(Runtime.getRuntime().maxMemory() / 1024.0 / 1024.0);
             System.out.println(VM.maxDirectMemory() / 1024.0 / 1024.0);
             System.out.println(getDirectBufferPoolMBean().getTotalCapacity() / 1024.0 / 1024.0);
             System.out.println(getNioBufferPool().getTotalCapacity() / 1024.0 / 1024.0);
         }
    

G1 回收器的特点

阅读全文 »

为什么需要主从复制

  1. 分别读写数据库的时候,把读和写分开,能够有效的提高数据库的负载
  2. 保证数据的高可用,一旦有一台数据库服务器宕机,不会对数据产生太大的影响
  3. 可以横向扩展,实现数据库的水平扩容

主从同步的原理

主从复制的根本原理是从 master 服务器上面的数据,通过一定的方式同步到 slave 服务器上面。基本过程如下图:

主从复制

  1. 主服务器在修改的数据的时候,会产生一个 bin log
  2. 从服务器上面启动一个 I/O thread,通过配置好的用户名和密码, 连接到主服务器上面请求读取二进制日志,然后把读取到的二进制日志写到本地的一个Realy log(中继日志)里面。
  3. 从服务器上面同时开启一个 SQL thread 定时检查 Realy log(这个文件也是二进制的),如果发现有更新立即把更新的内容在本机的数据库上面执行一遍。

上面的 3 个过程是 MySQL 主从同步的大概流程,其中 binlogrelay log 的读写都是顺序 IO,性能很高。

Relay log转换成数据的过程是一个比较耗时的过程,一般出现了数据延迟的时候,基本都是这里的问题。

搭建主从同步

阅读全文 »

什么是事务日志?

事务要保证 ACID 的完整性必须依靠事务日志做跟踪:

  1. 每一个操作在真正写入数据数据库之前,先写入到日志文件中

  2. 如要删数据会先在日志文件中将此行标记为删除,但是数据库中的数据文件并没有发生变化。

  3. 只有在(包含多个 sql 语句)整个事务提交后,再把整个事务中的 sql 语句批量同步到磁盘上的数据库文件。

  4. 在事务引擎上的每一次写操作都需要执行两遍如下过程:

    • 先写入日志文件中

      写入日志文件中的仅仅是操作过程,而不是操作数据本身,所以速度比写数据库文件速度要快很多。

    • 然后再写入数据库文件中

      写入数据库文件的操作是重做事务日志中已提交的事务操作的记录

事务日志

事务的日志主要分为三类:redo log,undo logbinlog

日志组

在写日志的时候,单个日志如果过大,对于读写和同步都会产生影响,所以在日志变大的时候,需要对日志进行一个分组。

日志提高事务的效率和安全性保证

阅读全文 »

  1. 下载包

    1
    https://repo.huaweicloud.com/mysql/Downloads/MySQL-8.0/mysql-8.0.20-linux-glibc2.12-x86_64.tar.xz
  2. 解压

    1
    2
    3
    4
    $ tar -xvf mysql-8.0.20-linux-glibc2.12-x86_64.tar.xz 
    # 移动到local/mysql目录下
    $ sudo mv mysql-8.0.20-linux-glibc2.12-x86_64 /usr/local/mysql

  3. 创建 data 目录

    1
    mkdir data
  4. 创建 MySQL 用户 并升级权限

    1
    2
    3
    useradd mysql
    chown -R mysql:mysql /usr/local/mysql
    chmod -R 755 /usr/local/mysql
  5. 初始化

    1
    ./mysqld --initialize --user=mysql --datadir=/usr/local/mysql/data --basedir=/usr/local/mysql

    image-20200823152508875

    注意上面的红框是个初始的登录密码。

  6. 启动并修改密码

    1
    2
    cd support-files
    ./mysql.server start
  7. 连接修改密码

    在连接 MySQL 的时候出现依赖错误。

    1
    ./mysql: error while loading shared libraries: libtinfo.so.5: cannot open shared object file: No such file or directory

    image-20200823152726962

    这里是少了一个依赖导致的,可以使用 ldd mysql 来查看 MySQL 对应的依赖:

    image-20200823153059784

    从上图中可以看到,其中libtinfog.so.5依赖没有。

    这个文件一般在 /etc/lib64/,如果没有需要重现下载,或者拷贝一个。

    image-20200823153649214

    我本地是有的 6.0 ,直接创建一个同步链接就可以了:

    1
    sudo ln -s /usr/lib64/libtinfo.so.6.1 /usr/lib64/libtinfo.so.5
  1. 开始连接

    1
    ./mysql -uroot -p
  2. 修改密码和允许远程连接

    1
    2
    3
    4
    5
    6
    # 修改密码
    mysql>ALTER USER USER() IDENTIFIED BY 'yourpass';
    mysql>flush privileges
    mysql>use mysql;
    msyql>update user set user.Host='%' where user.User='root';
    mysql>flush privileges;
  3. 修改完还是无法连接

    测试是否是防火墙拦截了,先停止防火墙试试

    1
    2
    3
    4
    5
    # 火墙的状态
    $ firewall-cmd --state
    running
    $ systemctl stop firewalld

  4. 停止火墙后能够正常连接,说明是 MySQL 端口没有在防火墙中信任

    1
    2
    firewall-cmd --add-port=3306/tcp --permanent 开放某一个端口号
    systemctl restart firewalld 启动防火墙
阅读全文 »

上篇博客说了 MVCC 解决了 MySQL 在可重复的隔离情况下幻读的问题,这篇博客主要探讨下,在修改的时候,如何解决幻读的问题。

MySQL 在控制并发的时候,同样采用了锁的机制。从读写上面分,有读写和写锁,从结构上分,有行锁和表锁.行锁又分为行锁、间隙锁和 Next Key

读锁和写锁

读锁 :共享锁 ,S 锁

写锁:排它锁 ,X 锁

select :不加锁,加锁后,也可以使用 select 查询数据

怎么加锁

select ...lock in share mode 加读锁

阅读全文 »

数据库的事务一共有四个特性:

  1. 原子性:代表事务是一个动作,要么同时成功,要么同时失败
  2. 一致性:事务开始和结束数据完整性没有发生破坏
  3. 隔离性:两个事务动作相互独立,不受干扰
  4. 持久性:事务完成后,能够保存到数据库。

那 MySQL 是如何保证这个四个特性的呢?

为了弄明白这几个特性,我们需要先看下事务的隔离级别。

事务隔离级别

事务隔离级别分为 4 种,分别如下:

事务隔离级别 脏读 不可重复读 幻读
读未提交(read-uncommitted)
读已提交(read-committed)
可重复读(repeatable-read)
串行化(serializable)

上面的四种隔离级别,是通用的规则,在每一种不同的数据库中有不同的实现。

例如 MySQL 默认的事务隔离级别是 可重复读,但不会产生幻读的问题

阅读全文 »

5489. 两球之间的磁力

在代号为 C-137 的地球上,Rick 发现如果他将两个球放在他新发明的篮子里,它们之间会形成特殊形式的磁力。Rick 有 n 个空的篮子,第 i 个篮子的位置在 position[i] ,Morty 想把 m 个球放到这些篮子里,使得任意两球间 最小磁力 最大。

已知两个球如果分别位于 xy ,那么它们之间的磁力为 |x - y|

给你一个整数数组 position 和一个整数 m ,请你返回最大化的最小磁力。

示例 1:

img

1
2
3
输入:position = [1,2,3,4,7], m = 3
输出:3
解释:将 3 个球分别放入位于 1,4 和 7 的三个篮子,两球间的磁力分别为 [3, 3, 6]。最小磁力为 3 。我们没办法让最小磁力大于 3 。

示例 2:

1
2
3
输入:position = [5,4,3,2,1,1000000000], m = 2
输出:999999999
解释:我们使用位于 1 和 1000000000 的篮子时最小磁力最大。
阅读全文 »

5471. 和为目标值的最大数目不重叠非空子数组数目

给你一个数组 nums 和一个整数 target

请你返回 非空不重叠 子数组的最大数目,且每个子数组中数字和都为 target

示例 1:

1
2
3
输入:nums = [1,1,1,1,1], target = 2
输出:2
解释:总共有 2 个不重叠子数组(加粗数字表示) [1,1,1,1,1] ,它们的和为目标值 2 。

示例 2:

1
2
3
4
输入:nums = [-1,3,5,1,4,2,-9], target = 6
输出:2
解释:总共有 3 个子数组和为 6 。
([5,1], [4,2], [3,5,1,4,2,-9]) 但只有前 2 个是不重叠的。

示例 3:

阅读全文 »

为什么又要折腾?

  1. 原来的博客用的是 Jekyll 搭建的,jekyll 是基于 ruby 开发的。现在的 ruby 维护太少了,我碰到的问题,一直没有很好的解决方案

  2. 老版本不支持 golang 语言的高亮,即使换了 highlightjs 的版本也不行,后续很多博客都无法更新

  3. 觊觎 next 主题有一段时间了,也想换成 netx 主题。

  4. 耗费了半天时间,踩了不少坑。还有很多优化,没有时间弄,后续再不上

以后博客这东西 还是少折腾。

阅读全文 »

模拟JVM指令的运行

模拟JVM的运行,需要有两个个方面的知识准备

  1. JVM的结构
  2. 字节码的含义
JVM的结构

对于JVM的结构,在很多地方都有描述,此处不再赘述,具体结构如下:

JVM

方法区

重点说下方法区,方法区有两个实现

  1. 永久代(1.7以前),永久代分配到堆上
  2. 元空间(1.8),元空间在直接内存上面(分配的快,回收的慢,元空间加载后不会被回收)
阅读全文 »