文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Java 内存安全问题的注意事项

2024-04-02 19:55

关注

前言

Java在内存管理方面是要比C/C++更方便的,不需要为每一个对象编写释放内存的代码,JVM虚拟机将为我们选择合适的时间释放内存空间,使得程序不容易出现内存泄漏和溢出的问题

不过,也正是因为Java把内存控制的权利交给了Java虚拟机,一旦出现内存泄漏和溢出方面的问题,如果不了解虚拟机是怎么使用内存的,那排查错误将会成为一项异常艰难的工作

下面先看看JVM如何管理内存的

内存管理

根据Java虚拟机规范(第3版) 的规定,Java虚拟机所管理的内存将会包括以下几个运行内存数据区域:

Java各版本内存管理改进

下图中永久代理解为堆的逻辑区域,移除永久代的工作从JDK7就已经开始了,部分永久代中的数据(常量池)在JDK7中就已经转移到了堆中,JDK8中直接去除了永久代,方法区中的数据大部分被移到堆里面,还剩下一些元数据被保存在元空间里

内存溢出

运行时数据区域的常见异常

在JVM中,除了程序计数器外,虚拟机内存的其他几个运行时数据区域都有发生OOM异常的可能。

堆内存溢出

不断的创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象。


public class HeapOOM {
    static class ObjectInHeap{
    }
    public static void main(String[] args) {
        List<ObjectInHeap> list = new ArrayList();
        while (true) {
            list.add(new ObjectInHeap());
        }
    }
}

栈溢出

单个线程下不断扩大栈的深度引起栈溢出。


public class StackSOF {
    private int stackLength = 1;
    public void stackLeak() {
        stackLength++;
        stackLeak();
    }
    public static void main(String[] args) {
        StackSOF sof = new StackSOF();
        try {
            sof.stackLeak();
        } catch (Throwable e) {
            System.out.println("Stack Length: " + sof.stackLength);
            throw e;
        }
    }
}

循环的创建线程,达到最大栈容量。


public class StackOOM {
    private void dontStop() {
        while (true) {
        }
    }
    public void stackLeadByThread() {
        while (true) {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    dontStop();
                }
            });
            thread.start();
        }
    }
    public static void main(String[] args) {
        StackOOM stackOOM = new StackOOM();
        stackOOM.stackLeadByThread();
    }
}

运行时常量池溢出

不断的在常量池中新建String,并且保持引用不释放。


public class RuntimeConstantPoolOOM {
    public static void main(String[] args) {
        // 使用List保持着常量池的引用,避免Full GC回收常量池
        List<String> list = new ArrayList<String>();
        int i = 0;
        while (true) {
            // intern()方法使String放入常量池
            list.add(String.valueOf(i++).intern());
        }
    }
}

方法区溢出

借助CGLib直接操作字节码运行时产生大量的动态类,最终撑爆内存导致方法区溢出。


public class MethodAreaOOM {
    static class ObjectInMethod {
    }
    public static void main(final String[] args) {
        // 借助CGLib实现
        while (true) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(ObjectInMethod.class);
            enhancer.setUseCache(false);
            enhancer.setCallback(new MethodInterceptor() {
                @Override
                public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                    return methodProxy.invokeSuper(o, objects);
                }
            });
            enhancer.create();
        }
    }
}

元空间溢出

助CG Lib运行时产生大量动态类,唯一的区别在于运行环境修改为Java 1.8,设置-XX:MaxMetaspaceSize参数,便可以收获java.lang.OutOfMemoryError: Metaspace这一报错

本机直接内存溢出

直接申请分配内存(实际上并没有真正向操作系统申请分配内存,而是通过计算得知内存无法分配,于是抛出异常)


public class DirectMemoryOOM {
    private static final int _1MB = 1024 * 1024;
    public static void main(String[] args) throws IllegalAccessException {
        Field unsafeField = Unsafe.class.getDeclaredFields()[0];
        unsafeField.setAccessible(true);
        Unsafe unsafe = (Unsafe) unsafeField.get(null);
        while (true) {
            unsafe.allocateMemory(_1MB);
        }
    }
}

常见案例

在工作中一般会遇到有以下几种情况导致内存问题

因为传输数量过大、或一些极端情况导致代码中间结果对象数据量过大,过大的数据量撑爆内存

这个多为SQL语句设置问题,SQL未设置分页,用户一次查询数据量过大、频繁查询SQL导致内存堆积、或是未作判空处理导致WHERE条件为空查询出超大数据量等

这类为外部接口性能较慢,占用内存较大,并且短时间内高QPS导致的,导致服务内存不足,线程堆积或挂起进而出现FullGC

使用了大量的反射代码,Java字节码存取器生成的类不断生成

问题排查

使用jmap分析内存泄漏

1.生成dump文件


jmap -dump:format=b,file=/xx/xx/xx.hprof pid

2.dump文件下载到本地

3.dump文件分析

可以使用MAT,MAT可作为Eclipse插件或一个独立软件使用,MAT是一个高性能、具备丰富功能的Java堆内存分析工具,主要用来排查内存泄漏和内存浪费的问题。

使用MAT打开上一部后缀名.hprof的dump文件

可以用这个工具分析出什么对象什么线程占用内存空间较大,对象是被什么引用的,线程内有哪些资源占用很高

以运行时常量池溢出为例

打开Histogram类实例表

Objects是类的对象的数量;Shallow是对象本身占用内存大小、不包含其他引用;

Retained是对象自己的Shallow加上直接或间接访问到对象的Shallow之和,也可以说是GC之后可以回收的内存总和

从图中可以看出运行时常量池溢出的情况,产生了大量的String和char[]实例

在char[]上右键可以得到上图所有char[]对象的被引用路径,可以看出这些char数组都是以String的形式存在ArrayList中,并且是由main这个线程运行的

可以看出是main线程中新建了一个数组,其中存了32w+个长度为6的char数组组成的String造成的内存溢出

关于MAT的详细使用可以从MAT官方教程学习更多

以上就是Java 内存安全问题的注意事项的详细内容,更多关于Java 内存安全问题的资料请关注编程网其它相关文章!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯