【JVM】JVM内存结构之——堆(堆内存泄漏问题/ 内存泄漏分类/ 内存溢出/ 内存溢出解决方案/ 排查堆内存泄漏问题/ CPU飙高问题)

目录

  • 1. 程序计数器
  • 2. 堆
  • 3. 堆内存泄漏问题
  • 4. 内存泄漏的分类
  • 5. 堆内存溢出问题
  • 6. 常见内存解决方案
    • 6.1 如何解决内存溢出
    • 6.2 如何分析堆内存情况
    • 6.3 如何分析GC回收多次,还是无法释放内存
  • 7. 如何排查堆内存泄漏问题
  • 8. 如何排查cpu飙高问题
    • 8.1 cpu发生飙高代码演示
    • 8.2 Windows情况下排查cpu飙高问题
    • 8.2 Linux情况下排查cpu飙高问题


1. 程序计数器

1.程序计数器(Program Counter Register)是一块较小的内存空间,可以看作是当前线程所执行字节码的行号指示器,指向下一个将要执行的指令代码,由执行引擎来读取下一条指令。更确切的说,一个线程的执行,是通过字节码解释器改变当前线程的计数器的值,来获取下一条需要执行的字节码指令,从而确保线程的正确执行。

2.为了确保线程切换后(上下文切换)能恢复到正确的执行位置,每个线程都有一个独立的程序计数器,各个线程的计数器互不影响,独立存储。也就是说程序计数器是线程私有的内存。
如果线程执行 Java 方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果执行的是 Native 方法,计数器值为Undefined。

3.程序计数器不会发生内存溢出(OutOfMemoryError即OOM)问题。
简单总结:程序计数器的作用:
【JVM】JVM内存结构之——堆(堆内存泄漏问题/ 内存泄漏分类/ 内存溢出/ 内存溢出解决方案/ 排查堆内存泄漏问题/ CPU飙高问题)_第1张图片

2. 堆

Java 堆是虚拟机所管理的内存中最⼤的⼀块,Java 堆是所有线程共享的⼀块内存区域,在虚拟机启动时创建。此内存区域的唯⼀⽬的就是存放对象实例,⼏乎所有的对象实例以及数组都在这⾥分配内存

  1. 通过new关键 ,创建的对象存放在堆中;
  2. 所有线程会共享到同一个堆内存;
  3. 在堆内存中是有垃圾回收机制的;

3. 堆内存泄漏问题

内存泄漏(Memory Leak)是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果

4. 内存泄漏的分类

1.常发性内存泄露
发生内存泄漏的代码会多次被执行到,每次被执行到的时候都会导致一块内存泄漏。

2.偶发性内存泄露
发生内存泄露的代码只有在某些特定环境或操作过程下才会发生。 常发性和偶发性是相对的。

3.一次性内存泄露
发生内存泄露的代码只会被执行一次,或者由于算法上的缺陷,导致总会有一块仅且一块内存发生泄漏。 比如,在类的构造函数中分配内存,在析构函数中却没有释放该内存,所以只会发生一次。

4.隐式内存泄露
程序在运行的过程中不断分配内存,直到在结束的时候才释放内存。 严格的说这里并没有发生内存泄漏,因为最终程序释放了所有申请的内存。但是对于一个服务器程序,需要运行几天,几周甚至几个月,不及时释放内存也可能导致最终耗尽系统的所有内存。所以,我们称这类内存泄漏为隐式内存泄漏。

发生的情况
长生命周期存活的对象,内部持有不适用对象的引用,导致不适用的垃圾对象无法回收。

5. 堆内存溢出问题

  1. 数据库表中 存放几千万条数据---- limit 0,100000
  2. 循环代码 一直new 新的对象存放在我们的集合中;
  3. Jar bug内存溢出
  4. 最大堆内存10mb

内存溢出(Out Of Memory)应用系统中存在无法回收的内存或使用的内存过多,最终使得程序运行要用到的内存大于能提供的内存。

发生的情况
1.内存中加载的数据量过于庞大,如一次从数据库取出过多数据;
2.集合类中有对对象的引用,使用完后未清空,使得JVM不能回收;
3.代码中存在死循环或循环产生过多重复的对象实体;
4.使用的第三方软件中的BUG;
5.启动参数内存值设定的过小

堆内存溢出解决办法:
1.修改JVM启动参数,直接增加内存。(-Xms,-Xmx参数 设置堆内存最大和最小值)
2.检查错误日志,查看“OutOfMemory”错误前是否有其它异常或错误。
3.使用java jconsole 或者 jvisualvm 阿里巴巴 Arthas 等。

6. 常见内存解决方案

6.1 如何解决内存溢出

设置java虚拟机内存 启动 最大内存是为8m

/**
 * 演示堆内存溢出 -Xmx8m
 *
 * @param args
 */
public static void main(String[] args) {
    List list = new ArrayList<>();
    while (true) {
        list.add(new UserEntity());
    }
}

【JVM】JVM内存结构之——堆(堆内存泄漏问题/ 内存泄漏分类/ 内存溢出/ 内存溢出解决方案/ 排查堆内存泄漏问题/ CPU飙高问题)_第2张图片

6.2 如何分析堆内存情况

  1. Jps 查看当前系统中有哪些Java进程
    在配合Jmap工具 查看堆内存占用情况 jmap -heap 进程id
  2. 图形化界面 Jvisualvm 或者是 jconsole.exe

Linux windows 相同
相关代码:

public class Demo04 {
    /**
     * -XX:+PrintGCDetails  输出GC回收日志(后期我们会详细学习的)
     *
     * @param args
     * @throws InterruptedException
     */
    public static void main(String[] args) throws InterruptedException {
        System.out.println("1");
        Thread.sleep(1000 * 30);
        byte[] bytes = new byte[1024 * 1024 * 10];// 申请10mb内存
        System.out.println("2");
        Thread.sleep(1000 * 30);
        bytes = null;
        System.gc();
        System.out.println("3");
        Thread.sleep(1000 * 10000);
    }
}


6.3 如何分析GC回收多次,还是无法释放内存

相关代码:

public class Demo05 {
    public static void main(String[] args) throws InterruptedException {
        ArrayList<UserEntity> userEntities = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            userEntities.add(new UserEntity());
        }
        Thread.sleep(100000000);
    }
}


  1. 使用Jvisualvm 工具
    【JVM】JVM内存结构之——堆(堆内存泄漏问题/ 内存泄漏分类/ 内存溢出/ 内存溢出解决方案/ 排查堆内存泄漏问题/ CPU飙高问题)_第3张图片

  2. 点击 堆 dump
    【JVM】JVM内存结构之——堆(堆内存泄漏问题/ 内存泄漏分类/ 内存溢出/ 内存溢出解决方案/ 排查堆内存泄漏问题/ CPU飙高问题)_第4张图片

  3. 点击查找最大对象
    【JVM】JVM内存结构之——堆(堆内存泄漏问题/ 内存泄漏分类/ 内存溢出/ 内存溢出解决方案/ 排查堆内存泄漏问题/ CPU飙高问题)_第5张图片

7. 如何排查堆内存泄漏问题

内存泄漏发生的案例:
1.ThreadLocal内存泄漏问题
2.HashMap自定义key 避免内存泄漏问题
通过以上案例排查内存泄漏问题
排查思路:查找到 java虚拟机 哪些对象占用空间最大 前20个 列出分析

8. 如何排查cpu飙高问题

1.生产环境cpu飙高的产生的原因
2.Arthas工具如何排查Linux环境下cpu飙高的问题
3.jvisualvm工具如何排查Linux环境下cpu飙高的问题
4.生产环境下内存泄漏的产生的原因
5.Arthas工具如何排查Linux环境下内存泄漏的问题

cpu飙高问题:
1.死循环---- 直接引发cpu飙高问题
2.cas 操作导致循环形式 控制 一直运行 (乐观锁),需要控制 失败循环次数 10次
3.2018 阿里云服务器上安装Redis 开放6379端口,被黑客注入挖矿程序 阿里云服务器cpu使用瞬间飙高
到100%—阿里云服务器报警
4.不要直接循环追加字符串最好StringBuilder
5. 服务器攻击 注入挖矿程序|ddos攻击 接口执行的时间比较长 建议 最好改成mq异步实现。
Nginx或者网关入口 防御 接口限流、图形验证码 机器
模拟刷接口

  1. CAS 自旋 没有控制自旋次数; 乐观锁
  2. 死循环----cpu飙高的问题;控制循环的次数
  3. 阿里云Redis被注入挖矿程序; Redis端口不要能够被外网访问
  4. 服务器被DDOS工具 导致cpu飙高; 限流、ip黑名单、图形验证码防止机器模拟攻击
  5. String直接追加字符串

8.1 cpu发生飙高代码演示

public static void main(String[] args) {
    new Thread(() -> {
        while (true) {
            System.out.println("1111");
        }
    }, "demoThread").start();
}

public static void main(String[] args) {
    String s="a";
    for (int i=0;i<80000;i++){
        s+=i;
    }
}

阿里巴巴的java开发手册 使用到线程池建议配置线程池名称,方便在后期可以定位是那个业务相关的线程。

8.2 Windows情况下排查cpu飙高问题

  1. Win操作系统打开任务管理系统 查看到那个进程占用cpu比较高
    【JVM】JVM内存结构之——堆(堆内存泄漏问题/ 内存泄漏分类/ 内存溢出/ 内存溢出解决方案/ 排查堆内存泄漏问题/ CPU飙高问题)_第6张图片

8.2 Linux情况下排查cpu飙高问题

Linux操作系统查看进程占用cpu飙高 top -c
【JVM】JVM内存结构之——堆(堆内存泄漏问题/ 内存泄漏分类/ 内存溢出/ 内存溢出解决方案/ 排查堆内存泄漏问题/ CPU飙高问题)_第7张图片
使用arthas(阿尔萨斯) 排查cpu飙高的问题
1.下载阿尔萨斯
curl -O https://arthas.aliyun.com/arthas-boot.jar
2.java -jar arthas-boot.jar
3.选择Test04 进程 输入1
4.thread -n 3

你可能感兴趣的