JAVA内存问题定位方法总结

网友投稿 1130 2022-05-29

为了加深自己理解及方便日后复习,将学到的一些Java内存相关知识总结一下,如有错误,欢迎大家指正

1.JAVA内存基础

1.1 Linux与进程内存模型

硬件层面

从硬件层面上看,Linux系统的内存空间由两部分构成:物理内存和磁盘SWAP分区。物理内存是Linu使用的主要内存区域,当物理内存不足时,Linux会把一部分相对冷内存数据放到磁盘的SWAP分区,以便腾出更多的可用内存空间;而当命中数据处于SWAP分区中时,就会将其置换回物理内存中

系统层面

从Linux系统层面上看,除了引导系统的BIN区,整个内存空间被分为两个部分:内核态内存(Kernel Space)、用户态内存(User Space)。内核内存是Linux自身使用的内存空间,主要提供给程序调度、内存分配、硬件资源驱动等程序逻辑使用。用户态内存是提供给各进程的主要内存空间,Linux使用虚拟内存技术给各个进程提供相同的虚拟内存空间,这机制确保进程之间相互独立、互不干扰。

进程层面

从进程的角度,进程能直接访问的用户态内存(虚拟内存空间)被划分为5个部分

代码区:存放了应用程序的机器代码,运行过程中代码不能被修改,具有只读和固定大小的特点。

数据区:存放应用程序中的全局数据,静态数据和常量字符串等,其大小也是固定的。

堆区:运行时程序动态申请的空间,程序运行时动态申请、释放的内存资源。

未使用区:分配新内存空间的预备区域。

栈区:存放函数的传入参数、临时变量、返回地址等数据。

1.2 Java8 JVM内存模型

内存模型大致可以分为五个部分

程序计数器(Program Counter Register)

程序计数器(Program Counter Register)是一块较小的内存空间,是当前线程所执行的字节码的行号指示器,是线程私有的区域,各条线程之间的计数器互不影响,独立存储。在虚拟机概念模型里,字节码解释器通过改变这个计数器的值来执行不同的字节码指令,分支、跳转、循环、异常处理、线程恢复等基础操作都会依赖这个计数器来完成。

虚拟机栈(VM Stack)

JVM栈是线程私有的内存区域。它描述的是java方法的内存,每个方法执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个栈帧从入栈到出栈就代表着一个方法从调用到返回的过程。

本地方法栈(Native Method Stack)

本地方法栈与虚拟机栈类似,不同的是其为Native方法服务,而虚拟机栈为java方法服务。是规范中指明的概念,但是实现并无规定,如HotSpot直接将本地方法栈和虚拟机栈合并实现。

堆(Heap)

堆区是用来分配实例对象的空间,各个线程共享使用,由垃圾收集器自动回收管理。

元空间(MetaSpace)

用来存放类的元数据,也就是数据结构信息,如类常量,字符串常量,类定义等数据。java8之前,是由永久代进行存储。

2.JAVA OOM常见场景

2.1 内存溢出

2.1.1 堆内存溢出

问题现象为,创建对象失败,提示异常为java.lang.OutOfMemoryError:Java heap space

其导致原因为

代码BUG,使用的静态容器类(List等)没有及时清除对象

读入数据过大,比如在读取数据库表时,一次性捞出全部数据

数据积压,在生产者消费者模型中,生产很快,消费很慢,数据来不及处理,队列又没有限制,长时间运行导致内存溢出

解决方法为

确保代码没BUG时,通过压测调整-Xms参数,-Xmx参数

大量数据如大文件加载,大批量数据查询时分批处理

队列添加限制,提高执行效率,加速垃圾回收,避免并发高时无足够内存空间

import java.util.ArrayList; import java.util.List; /** * 堆内存溢出 * —Xmx10M * * @since 2022-04-15 */ public class HeapOom { public static void main(String[] args) { List list = new ArrayList<>(); int count = 1; while (true) { list.add(new byte[1024 * 1024]); count++; } } }

2.1.2 MetaSpace内存溢出

问题现象为 程序运行时报java.lang.OutOfMemoryError:Metaspace

该问题原因为

应用代码过多

引用三方库多

动态生成加载类

JAVA内存问题及定位方法总结

解决方法为

调整-XX:MaxMetaSpaceSize参数

不需要的三方库及时删除

动态生成类时做好压力测试

import java.util.ArrayList; import java.util.List; import javassist.CannotCompileException; import javassist.ClassPool; /** * 元数据溢出 * -XX:MaxMetaspaceSize=10m -XX:-UseCompressedClassPointers * * @since 2022-04-15 */ public class MetaspaceOom { public static ClassPool pool = ClassPool.getDefault(); public static void main(String[] args) throws CannotCompileException { List test = new ArrayList<>(); for (int i = 0; ; i++) { Class c = pool.makeClass("com.huawei.iit.Generated" + i).toClass(); } } }

2.1.3 堆外内存溢出

问题现象为,程序报java.lang.OutOfMemoryError … Native Method

问题原因就是采用了很多NIO相关操作,没有及时释放

解决方法则是减少长生命周期的堆外内存引用,及时释放空间

import java.lang.reflect.Field; import sun.misc.Unsafe; /** * 堆外内存溢出 * -Xmx10m * * @since 2022-04-15 */ public class DirectMemoryOomError { public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException { Field field = Unsafe.class.getDeclaredField("theUnsafe"); field.setAccessible(true); Unsafe unsafe = (Unsafe) field.get(null); while (true) { unsafe.allocateMemory(1024 * 1024); } } }

2.1.4 栈内存溢出

问题现象为,应用抛出异常java.lang.StackOverflowError

问题原因则是

方法调用太深,导致栈中内存被消耗殆尽,主要是递归操作

问题解决方法是

合理设置-Xss值

递归操作注意终止条件,关注递归层数

/** * 栈溢出 * * @since 2022-04-15 */ public class StackOverflowOom { public static void testStack(){ Byte[] temp = new Byte[1024*1024]; testStack(); } public static void main(String[] args) { testStack(); } }

2.1.5 本地线程内存溢出

问题现象为,应用抛出异常java.lang.OutOfMemoryError:unable to create new native thread error.

问题原因为

线程数过多,在新创建线程时,剩余内存无法满足线程创建需求

线程数量超过操作系统限制

问题解决方法为

检查操作系统线程限制,如Linux,可以用ulimit -a查看

应用明确最大线程数,使用线程池,不随意创建线程及线程池

import java.util.concurrent.Executor; import java.util.concurrent.Executors; /** * 本地线程内存溢出 * -XX:ThreadStackSize=1G * * @since 2022-04-15 */ public class UnableCreateNativeThreadError { public static void main(String[] args) { while (true) { Executor pool = Executors.newCachedThreadPool(); pool.execute(()-> System.out.println("Test")); } } }

2.1.6 数组超限内存溢出

问题现象为,应用抛出异常java.lang.OutOfMemoryError: Requested array size exceeds VM limit

问题原因为

试图分配超出该平台下JVM最大数组限制大小的数组

解决办法

避免分配大数组

/** * 数组内存超限制 * * @since 2022-04-15 */ public class ArrayLimitOom { public static void main(String[] args) { int[] arr = new int[Integer.MAX_VALUE-1]; } }

2.1.7 超出交换分区

问题现象为,启动时抛出java.lang.OutOfMemoryError:Out of swap space

问题原因是

jvm启动时,按照-Xms及其他参数申请内存,当请求内存大于可用内存时,进程请求分配内存失败

解决方法是

清理环境内存,腾出空间

2.1.8 OS Killer进程

问题现象为,java进程突然消失了,日志未见明显异常,用dmesg发现Out of memory: Kill process ** (java) score ** or sacrifice child

问题原因为

Out-Of-Memory killer机制监控机器的内存资源,当发现内存临近耗尽,就会扫描所有进程(按照一定规则计算,内存占用、时间等),然后kill掉最高得分的进程,保护机器运行环境

2.1.8 GC回收超时

问题现象为,应用响应慢,抛出java.lang.OutOfMemoryError:GC overhead limit exceeded

问题原因为:

应用在内存不足时进行GC操作回收内存,而JVM花费大量时间进行GC却只回收了些微内存,超过一定次数便会触发该错误

解决方法:

减少对象生命周期,尽量做到朝生夕灭

可分析Stop World停顿日志周围GC情况

import java.util.HashMap; import java.util.Map; import java.util.Random; /** * GC回收超时 * -Xmx10m * * @since 2022-04-15 */ public class OverHeadLimitOom { public static void main(String[] args) { Map map = new HashMap<>(); Random r = new Random(); while(true){ map.put(r.nextInt(),"Test"); } } }

2.2 内存泄漏

问题现象为:实例数不断增加

问题原因为

从GC ROOT对象往下,无用对象依然是可达的

实际就是一些不用的,无引用的,应该被GC回收到的对象,无法被回收,一直保留,导致内存泄漏,一般常见于HASH容器中,修改了KEY的值,导致后续删除操作实际上无法删除,一直存留。

/** * 内存泄漏 * -Xmx20m * * @since 2022-04-15 */ public class MemoryLeakOom { static class Email { private String address; public Email(String address) { this.address = address; } public int hashCode() { return address.hashCode(); } } public static void main(String[] args) { HashSet emails = new HashSet<>(); for (int i = 1; ; i++) { Email email = new Email(i + "."); emails.add(email); email.address = i + ".in"; emails.remove(email); } } }

3 问题定位

问题场景:应用运行一段时间后,悄无声息结束了,或者容器重启了

定位方法;

步骤1: dmesg|grep -i kill 观察是否是OS kill了进程

步骤2: JVM启动参数查看

jinfo -flags $PID

步骤3: 堆内存分析

jmap -heap $PID 查看堆内存

jmap -histo $PID 查看堆内对象

步骤4: 线程情况分析

jstack -m $PID 查看线程

内存占用分析

在启动时加入启动参数-XX:NativeMemoryTracking=summary

创建基线 jcmd $pid VM.native_memory baseline

查看内存变化 jcmd $pid VM.native_memory summary.diff

需要注意的是NativeMemoryTracking功能大约会有5%-10%的性能损耗,测试环境添加尚可。

Java

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:推箱子小游戏的简易实现
下一篇:matlab 2017帮助页目录
相关文章