Java内存溢出分析案例
Java常见内存溢出类型:
常见的内存溢出主要有以下几种:
1、堆内存溢出(OutOfMemoryError:java heap space)
2、栈内存溢出(StackOverflowError)
3、永久代溢出(OutOfMemoryError:PermGen sapce)等
其中,堆内存溢出较为常见,堆内存是 Java 应用程序运行时所需要的内存空间,
应用程序如果分配堆内存太小或者造成垃圾回收无法及时进行,
都会导致堆内存溢出。
栈内存溢出较为少见,通常是调用栈的深度过多或者资源占用过多导致。
如果使用递归算法或者在方法中创建大量的局部变量等情况下容易发生栈溢出
堆和栈的区别:
-
栈内存:栈内存是线程私有的,用于存储程序执行过程中方法调用和参数传递(即局部变量)等数据。在线程结束时,栈内存也会随之销毁。栈内存大小由系统自动管理,一般较小,若数据量超过限制,可能会导致栈溢出错误。
-
堆内存:堆内存是所有线程共享的内存空间,用于存储对象数据,包括实例对象、类对象等。Java 虚拟机在启动时即从系统中分配一定的堆空间,并动态地增加或缩减堆的大小以满足应用程序的需要。由于堆内存是由 JVM 管理,所以存放了大量数据对象后可以调用 JVM 垃圾回收机制进行回收,避免了栈溢出的问题。
-
内存分配方式:栈内存的分配和释放速度比堆内存更快,但是空间较小,它使用“先进先出”的方式对存储和释放内容进行管理。而堆内存的分配和释放速度要慢一些,但是空间相对较大且灵活易用,允许程序员通过 new 和其他方式进行自由分配、释放,需要程序员自主管理。
-
存储的数据类型:栈内存只能存储基本数据类型和对象的引用(reference),而不能存储对象本身;堆内存则可以存储任何类型的对象,包括 Java 实例对象、数组、字符串、类信息等。
堆内存溢出现象
(此篇文章主要是针对堆内存溢出的分析案例)
1、Tps波动大,并慢慢降低,也有会变为0,响应时间也会随着波动,变高
2、通过jstat -gcutil 命令看到,FullGC可能非常频繁,对应的FullGC消耗的时间也会不断增加
3、通过jconsole/jvisualvm观察,堆内存曲线不断上升,逐渐达到上线,然后变成一直线
4、内存溢出到最后应用服务崩溃,日志报错java.Iang.OutOfMemoryError: Java heap space
正常情况下,堆是锯齿状的波动
异常情况下,堆内存慢慢往上爬
内存溢出分析
可以使用Mat工具分析
下载安装分析软件
在本机并建立一个新的文件夹,下载单机版本的mat(MemoryAnalyzer(JDK8)) 内存分析工具(不要下载新版,下载可支持1.8的)
下载 hprof 文件
1、可以直接用jvisualvm 点击dump,然后会在服务器上生成hprof堆内存文件。
2、也可以直接在服务器上使用命令敲
jmap -dump:format=b, file=xx.hprof PID
MAT 分析定位问题
打开Mat,导入刚开下载的hrpof文件
点击overview全局页面,再点击Leak Suspects:
内存溢出只需要关注这个功能就好了,这里展示了它怀疑内存溢出的所有问题
进入后可以看到整个页面如下:
前两个问题占用了90%,其中第一个占了将近80%
从图片中可以看到第一个问题,这个问题是entity.LinkInfo对象引起的,这个对象的类型是数组类型。也就是提示有大对象数组没有。keywords 也提示这个是数组对象
更详细的分析可以点击detail可以看到:
Shortest Paths To the Accumulation Point
表示GC root到内存消耗聚集点的最短路径,如果某个内存消耗聚集点有路径到达GC root,则该内存消耗聚集点不会被当做垃圾被回收
点击左上角,可以看到这个thread 是gc root,linkinfo跟它有关联,意味着linkinfo 对象不会被回收。
为啥gcroot 有关联就回收不了呢?
这是由于java的垃圾回收算法决定的:
GCRoot 是指一系列在程序运行过程中始终存在的引用,它们被作为垃圾收集的根节点,用于标识可达对象。只要程序的任何一个变量、静态成员或已加载类实例还存活下来,那么该对象就会被认为是无法回收的。因此,垃圾回收算法从 GCRoot 出发遍历所有可达对象,识别并确认所有已死亡的对象,将其空间回收以供后续使用
简单来说就是没有路径到达gcroot 的对象就是可以回收的。如下图,5、6、7是可以回收的对象,1、2、3、4则无法回收
如何看对象引用到了哪个gc root 。可以点击
merge shortest paths to gc roots
选择exclude all phantom/weak/soft etc.references,意思是查看排除虚引用/弱引用/软引用等的引用链,
因为被虚引用/弱引用/软引用的对象可以直接被GC给回收,我们要看的就是某个对象否还存在Strong 引用链
可作为GCRoot的对象有:
- 本地方法栈中的JNI引用的对象
- 方法区中的类静态变量引用的对象
- 方法区中的常量引用的对象
- Java虚拟机栈中引用的对象:
从最后的thread stack可以看到以下报错原因:
使用async 注解异步执行该任务,添加list对象数据的时候内存不够了,内存溢出了。
内存溢出分析
知道了哪个对象内存溢出,就需要根据代码判断是什么问题导致的。
性能分析中,定位到了问题也只是成功了一半,要能分析并解决问题才算是完全成功。
这个对象是写在java bean的,java bean 对象没有被释放,可能原因会有:
1、多线程发编程,对象被某些线程使用而未被释放:在某些情况下,Java Bean对象可能正在被某些线程所使用,这些线程持有对该对象的引用,使其不能被垃圾回收机制回收。在这种情况下,必须等到所有线程完成对该对象的操作后,才能使该对象成为垃圾对象。
2、异常未处理,在 Java Bean 对象使用过程中,如果发生异常或者错误,没有及时释放相关资源(例如文件句柄、数据库连接等),也会导致 Java Bean 对象没有被及时释放
3、使用静态变量保存对象
4、对象被循环引用
代码还在分析优化中,未完待续。。。。。
参考文档:
https://www.cnblogs.com/trust-freedom/p/6744948.html
https://www.cnblogs.com/feng-gamer/p/6039390.html
https://blog.csdn.net/qq_43562847/article/details/104283437
https://www.cnblogs.com/trust-freedom/p/6744948.html#paths_to_gc_roots