定义
在JVM中通过new关键字创建的对象都会被放在堆内存中
- 所有线程共享,堆内存中的对象都需要考虑线程安全问题
- 有垃圾回收机制(当对象不再不引用时才会被回收)
堆内存溢出
有代码如下所示:
package org.slumberjax.jvm.d05;
import java.util.ArrayList;
import java.util.List;
/**
* 演示堆内存溢出 -Xmx8m
*/
public class T01 {
public static void main(String[] args) {
int i = 0;
try {
/*
list的生存范围在整个try语句块
由于下面的代码中有while死循环导致该list一直处于被引用,无法被回收
同时while死循环也在不断的往list中添加指数级别增长的hello字符串,这些字符串也处于被引用状态,无法被回收
最终撑爆堆区内存
*/
List list = new ArrayList<>();
String a = "hello";
/*
循环往list中添加指数级别增长的hello的字符串
*/
while (true) {
list.add(a);//hello,hellohello,hellohellohellohello,hellohellohellohellohellohellohellohello,...
a = a + a;
i++;
}
} catch (Throwable e) {
e.printStackTrace();
System.out.println(i);
}
}
}
运行输出结果如下:
26
java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3332)
at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124)
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:448)
at java.lang.StringBuilder.append(StringBuilder.java:136)
at org.slumberjax.jvm.d05.T01.main(T01.java:22)
从结果得知java.lang.OutOfMemoryError: Java heap space是java堆空间不足导致的内存溢出,循环了26次就堆空间内存溢出了
此时重新运行该程序添加堆空间大小参数-Xmx8m
设置堆空间大小为8M,结果如下:
java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3332)
at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124)
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:448)
at java.lang.StringBuilder.append(StringBuilder.java:136)
at org.slumberjax.jvm.d05.T01.main(T01.java:33)
17
循环17次就堆空间内存溢出了
注: 生产环境上某个服务器内存比较大的情况下,不太容易暴露堆空间内存溢出,也许随着时间的累积最终会导致溢出,短时间内无法复现这个情况,在排查这种问题时,将堆空间内存的虚拟机参数-Xmx
的值设置小一些能更快的定位到问题原因
堆内存诊断工具
- jps 查看当前系统中有哪些java进程
- jmap 查看堆内存占用情况(只能查询某一个时刻堆内存占用情况)
- jconsole 图形化界面,多功能的检测工具,可以连续检测
- jvirsualvm 图形化页面的虚拟机检测工具(更为强大)
堆内存诊断演示
使用如下代码进行演示:
package org.slumberjax.jvm.d05;
/**
* 演示堆内存
*/
public class T02 {
public static void main(String[] args) throws InterruptedException {
System.out.println("1...");
Thread.sleep(30000);
byte[] bytes = new byte[1024 * 1024 * 10];//10 MB new关键字创建占用堆空间
System.out.println("2...");
Thread.sleep(30000);
bytes = null;//将bytes指向null 原有的10MB的bytes将不被引用
System.gc();//显示调用gc进行垃圾回收,此时上面的10MB的未被引用的bytes将会被回收
System.out.println("3...");
Thread.sleep(1000000L);
}
}
jps,jmap演示
-
运行T02程序后使用jps查看该程序进程
得到T02程序的进程号为14348
-
分别在T02程序输出1...,2...,3...时使用jmap抓取此时的堆内存快照进行分析
jps -heap 进程编号
-heap参数查看堆区内存
jconsole演示
运行T02程序后,终端输入jconsole
命令进行检测,演示截图如下
选择T02进程
检测数据与jmap快照数据是一致的
jconsole还可以检测线程死锁
堆内存诊断案例
案例描述: 垃圾回收后,内存占用仍然很高
有程序T03(先不给出源码),运行后发现内存占用过高,执行垃圾回收后仍然过高,对该程序进行诊断分析(同时演示另一个更为强大的jvisualvm工具)
-
终端执行
jvisualvm
并查看T03程序发现堆区内存高达250MB之多 -
执行一次垃圾回收后,内存居高不下
-
此时查看堆Dump
-
查找占用内存最大的20个对象
-
发现有多个占用1MB的Something对象,并且有一个ArrayList占用了200MB的内存
-
查看该ArrayList的详情,发现该ArrayList中存放了200个1MB的Something对象
-
此时再来看T03程序的源码,导致该现象的原因一目了然
package org.slumberjax.jvm.d05; import java.util.ArrayList; import java.util.List; class Something { private final byte[] bytes = new byte[1024 * 1024];//1MB } public class T03 { public static void main(String[] args) throws InterruptedException { List
list = new ArrayList<>();//该list的生命作用域在整个main代码块中 for (int i = 0; i < 200; i++) { list.add(new Something()); } Thread.sleep(1000000000L);//由于此处超长时间的睡眠 导致list的引用无法释放 垃圾回收无法回收该list } }