内存问题(二)——内存问题模拟

发布时间:2023年12月21日

一、堆栈溢出错误模拟-StackOverflowError

1.1 代码

/**
 * 堆栈溢出错误模拟
 *
 * @author sunzhiqiang
 * @date 2021/12/01 19:19:36
 */
public class _1_StackOverflowErrorMock {
    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            try {
                stackOverflowError();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }).start();
        new Thread(() -> {
            int num = 0;
            while (true) {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(num);
                num++;
            }
        }).start();
        new CountDownLatch(1).await();
    }

    private static void stackOverflowError() {
        try {
            TimeUnit.NANOSECONDS.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        stackOverflowError();
    }
}

1.2 分析

在这里插入图片描述

最终打印:在这里插入图片描述
在这里插入图片描述

线程1代码抛出异常了,线程2还会运行:
——这里的栈溢出,只是一个栈帧装不下;

代码地址(3_memory):
msb-debug

二、堆内存泄漏模拟,mat分析

Java笔记:MAT使用详解
一文深度讲解JVM 内存分析工具 MAT及实践(建议收藏)

2.1 模拟代码

/**
 * 内存泄漏模拟
 * jmap -dump:format=b,file=D:/workspace/mashibing/msb-debug/dump.hprof pid
 *
 * @author sunzhiqiang
 * @date 2021/11/26 21:56:42
 */
@Data
public class MemoryLeakMock {
    private List<Object> list = new ArrayList<>();

    public static void main(String[] args) throws InterruptedException {
        MemoryLeakMock memoryLeakMock = new MemoryLeakMock();
        long num = 1 * 1024 * 1024;
        while (num > 0) {
            memoryLeakMock.getList().add(new byte[1024]);
            num--;
        }
        new CountDownLatch(1).await();
    }
}

2.2 分析

在这里插入图片描述
t线程1代码抛出异常了,线程2还会运行;

2.3 导出dump文件

jmap -dump:format=b,file=E:/Work_Space/Mca_Space/msb-debug/dump.hprof pid

2.4 mat分析dump文件

2.4.1 Leak Suspects分析

疑惑:生成的dump.hprof文件,与课上的不一样;
课上的:
在这里插入图片描述

我的:没有提示MemoryLeakMock类的这条;
在这里插入图片描述
在这里插入图片描述
猜测可能跟jvm启动参数有关。

除了mat分析工具,还有jprofile工具,只不过是收费的。

三、元数据空间内存溢出-MataspaceOOMTest

3.1 代码

/**
 * JVM参数
 * -XX:MetaspaceSize=12m -XX:MaxMetaspaceSize=12m
 * <p>
 * java 8及之后的版本使用Metaspace来替代永久代,存放了以下信息:
 * <p>
 * 虚拟机加载的类信息
 * 即时编译后的代码
 * <p>
 * 模拟Metaspace空间溢出
 **/
public class _3_MataspaceOOMTest {
    public static void main(String[] args) {
        new Thread(() -> {
            try {
                while (true) {
                    Enhancer enhancer = new Enhancer();
                    enhancer.setSuperclass(OOMTest.class);
                    enhancer.setUseCache(false);
                    enhancer.setCallback((MethodInterceptor) (o, method, objects, methodProxy) -> methodProxy.invoke(o, args));
                    enhancer.create();
                }

            } catch (Throwable throwable) {
                throwable.printStackTrace();
            }
        }).start();


        new Thread(() -> {
            int num = 0;
            while (true) {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(num);
                num++;
            }
        }).start();


    }

    static class OOMTest {
    }

}

3.2 分析

window系统未成功复现此异常,不知具体什么原因。

四、不能创建线程异常-java.lang.OutOfMemoryError:unable to create new native thread

4.1 代码

/**
 * 高并发请求服务器时,经常出现如下异常:java.lang.OutOfMemoryError:unable to create new native thread
 * native thread异常与对应的平台有关
 * <p>
 * 导致原因:
 * 1 应用创建了太多线程了,一个应用进程创建多个线程,超过系统承载极限
 * 2 服务器并不允许应用程序创建这么多线程,linux系统默认允许单个进程可以创建的线程数是1024个,
 * 应用创建超过这个数量,就会报java.lang.OutOfmemoryError:unable to create new native thread
 * <p>
 * 解决办法:
 * 1.想办法降低你应用程序创建线程的数量,分析应用是否真的需要创建这么多线程,如果不是,改代码将线程数量降到最低
 * 2.对于有的应用,确实需要创建很多线程,远超过linux系统的默认1024个线程的限制,可以通过修改linux服务器配置,扩大linux默认限制
 **/
public class _4_UnableCreateNewThreadDemo {
    public static void main(String[] args) {
        for (int i = 0; ; i++) {
            System.out.println("********i=" + i);
            new Thread(() -> {
                try {
                    TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }, "" + i).start();
        }

    }
}

五、java.lang.OutOfMemoryError:Direct buffer memory

5.1 代码

/**
 * 〈一句话功能简述〉<br>
 * 配置参数:
 * -Xms10m -Xmx10m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m
 * <p>
 * 故障现象
 * Exception in thread "main" java.lang.OutOfMemoryError:Direct buffer memory
 * <p>
 * 导致原因:
 * 写NIO程序经典使用ByteBuffer来读取或者写入数据,这是一种基于通道(Channel)与缓冲区(Buffer)的 I/O方式,
 * 可以使用native函数库直接分配堆外内存,然后通过一个存储在Java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。
 * 这样能在一些场景种显著提高性能,因为避免了在Java堆和Native堆种来回复制数据。
 * <p>
 * ByteBuffer.allocate(capability) 第一种方式是分配JVM堆内存,
 * ByteBuffer.allocateDirect(capability)第一种方式时分配OS本地内存,由于不需要内存拷贝所以速度相对较快。
 * <p>
 * 但如果不断分配本地内存,对内存很少使用,那么JVM就不需要执行GC,DirectByteBuffer对象们就不会被回收。
 * 这时候对内存重组,但本地内存可能已经使用光了,再次尝试分配本地内存就会出OutOfMemoryError,那程序就直接崩溃了。
 **/

public class _5_DirectBufferMemoryDemo {
    public static void main(String[] args) {
        System.out.println("配置的maxDirectMemory:" + (sun.misc.VM.maxDirectMemory() / (double) 1024 / 1024) + "MB");
        ByteBuffer bb = ByteBuffer.allocateDirect(6 * 1024 * 1024);
    }

}

六、GC overhead limit exceeded

6.1 代码

/**
 * 〈一句话功能简述〉<br>
 * <p>
 * JVM参数配置演示
 * -Xms20m -Xmx20m -XX:+PrintGCDetails
 * <p>
 * GC 回收实践过程时会抛出OutOfMemoryError。过长的定义是,超过98%的时间用来做GC并且回收了不到2%的堆内存
 * 连续多次GC都只会收到了不到2%的极端情况下才会抛出。假如不抛出GC overhead limit错误会发生什么情况呢?
 * <p>
 * 那就是GC清理的这么点内存很快会再次填满,迫使GC再次执行,这样就形成了恶性循环,
 * CPU使用率一直100%,而GC却没有任何成果
 **/

public class _6_GCOverheadDemo {
    public static void main(String[] args) {
        int i = 0;
        List<String> list = new ArrayList<>();
        try {
            while (true) {
                list.add(String.valueOf(++i).intern());
            }
        } catch (Throwable e) {
            System.out.println("*******i:" + i);
            e.printStackTrace();
            throw e;
        }
    }
}

参考:OutOfMemoryError系列(2): GC overhead limit exceeded
java.lang.OutOfMemoryError: GC overhead limit exceeded问题分析及解决

七、其他

  • 慢sql为什么影响应用?
    ——占用连接数,导致资源无法释放。
    慢sql的累计,最先会导致数据库服务器cpu飙高。一般数据库cpu利用率都不会超过20%。如果超过20%,一般来说,肯定是有慢sql了,监控慢sql,最方便的就是配一个慢sql日志,每天查一下;

  • 一般情况下,内存溢出,只是异常,不会使jvm进程死掉。

  • rpc接口,传参-2M容量;

  • 为什么阿里代码规范,代码不能超过80行?为什么尽量减少递归?
    ——因为栈没有执行完的时候,栈上所有方法不能释放。如果不释放,恰巧来了请求,内存达到一定阈值,会出发gc。

参考文章:
为什么说递归效率低?
为何java不推荐使用递归?

文章来源:https://blog.csdn.net/moneywenxue/article/details/135073754
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。