之前我们学习的虚拟机栈或者本地方法栈都是线程私有的,而接下来学习的堆和方法区都是线程共享的。
1.堆
1.1 定义:
Heap 堆:
- 通过new关键字,创建对象都会使用堆内存
特点: - 它是线程共享的,堆中对象都需要考虑线程安全的问题
- 有垃圾回收机制
1.2 堆内存溢出
为什么有垃圾回收机制还会有堆内存溢出的现象
首先要明白,垃圾回收机制是回收不使用的对象,但是如果不断产生对象,而且产生的这些对象仍然不断地被使用,从而导致堆内存耗尽,进而导致堆内存溢出。
观察堆内存溢出现象:
public class HeapOverFlow {
public static void main(String[] args) {
int i = 0;
String a = "hello";
List<String> list = new ArrayList();
try {
while (true){
list.add(a);
a = a + a;
i++;
}
}catch (Exception e){
e.printStackTrace();
}finally {
System.out.println(i);
}
}
}
输出结果:
27
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.base/java.util.Arrays.copyOf(Arrays.java:3744)
at java.base/java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:146)
at java.base/java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:512)
at java.base/java.lang.StringBuilder.append(StringBuilder.java:141)
at com.hspedu.HeapOverFlow.main(HeapOverFlow.java:18)
添加VMoption更改堆空间的最大值:-Xmx8m
8m是你设定的堆空间大小为8M
17
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.base/java.util.Arrays.copyOfRange(Arrays.java:4029)
at java.base/java.lang.StringLatin1.newString(StringLatin1.java:549)
at java.base/java.lang.StringBuilder.toString(StringBuilder.java:415)
at com.hspedu.HeapOverFlow.main(HeapOverFlow.java:18)
循环次数减少。
1.3 堆内存诊断
首先了解有哪些堆内存诊断的工具:
- jps工具:查看当前系统中有哪些java进程
- jmap工具:查看堆内存占用情况
- jconsole工具:图形界面的,多功能的监测工具,可以连续监测。
同样可以使用idea编译器自带的Profiler进行监控。
jps和jmap的使用:
执行程序:
public class HeapWatcherTest {
public static void main(String[] args) throws InterruptedException {
System.out.println("--1--此时什么都没有创建");
Thread.sleep(30000);
byte[] array = new byte[1024*1024*10];
System.out.println("--2--此时创建了byte数组");
Thread.sleep(30000);
array = null;
System.gc();
System.out.println("--3--进行了垃圾回收");
Thread.sleep(1000000L);
}
}
首先使用jps获取HeapWatcherTest 的进程号:
上述代码一共有3个阶段:
1.当程序在第一阶段,
什么都还没有创建的时候执行:jmap -heap 21004:
jmap -heap 24156
Attaching to process ID 24156, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.131-b11
using thread-local object allocation.
Parallel GC with 8 thread(s)
Heap Configuration: 这里是堆的一些设置
MinHeapFreeRatio = 0
MaxHeapFreeRatio = 100
MaxHeapSize堆的最大内存 = 4244635648 (4048.0MB)
NewSize = 88604672 (84.5MB)
MaxNewSize = 1414529024 (1349.0MB)
OldSize = 177733632 (169.5MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 17592186044415 MB
G1HeapRegionSize = 0 (0.0MB)
Heap Usage 堆内存的占用:
PS Young Generation 新生代
Eden Space 新创建的对象都会有一个Eden Space:
capacity = 66584576 (63.5MB) 总容量
used = 6695280 (6.3851165771484375MB) 此时一阶段还没有创建byte数组,所以堆内存只用了6MB
free = 59889296 (57.11488342285156MB)
10.055301696296752% used
From Space:
capacity = 11010048 (10.5MB)
used = 0 (0.0MB)
free = 11010048 (10.5MB)
0.0% used
To Space:
capacity = 11010048 (10.5MB)
used = 0 (0.0MB)
free = 11010048 (10.5MB)
0.0% used
PS Old Generation
capacity = 177733632 (169.5MB)
used = 0 (0.0MB)
free = 177733632 (169.5MB)
0.0% used
3190 interned Strings occupying 282080 bytes.
2.等程序执行到第二阶段:
再次执行jmap:
jmap -heap 24156
Attaching to process ID 24156, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.131-b11
using thread-local object allocation.
Parallel GC with 8 thread(s)
Heap Configuration:
MinHeapFreeRatio = 0
MaxHeapFreeRatio = 100
MaxHeapSize = 4244635648 (4048.0MB)
NewSize = 88604672 (84.5MB)
MaxNewSize = 1414529024 (1349.0MB)
OldSize = 177733632 (169.5MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 17592186044415 MB
G1HeapRegionSize = 0 (0.0MB)
Heap Usage:
PS Young Generation
Eden Space:
capacity = 66584576 (63.5MB)
used = 17181056 (16.3851318359375MB) 创建了byte数组后,堆内存占用提升到了16MB
free = 49403520 (47.1148681640625MB)
25.80335722194882% used
From Space:
capacity = 11010048 (10.5MB)
used = 0 (0.0MB)
free = 11010048 (10.5MB)
0.0% used
To Space:
capacity = 11010048 (10.5MB)
used = 0 (0.0MB)
free = 11010048 (10.5MB)
0.0% used
PS Old Generation
capacity = 177733632 (169.5MB)
used = 0 (0.0MB)
free = 177733632 (169.5MB)
0.0% used
3191 interned Strings occupying 282152 bytes.
3.等程序执行到第三阶段:
此时再执行jmap:
jmap -heap 24156
Attaching to process ID 24156, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.131-b11
using thread-local object allocation.
Parallel GC with 8 thread(s)
Heap Configuration:
MinHeapFreeRatio = 0
MaxHeapFreeRatio = 100
MaxHeapSize = 4244635648 (4048.0MB)
NewSize = 88604672 (84.5MB)
MaxNewSize = 1414529024 (1349.0MB)
OldSize = 177733632 (169.5MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 17592186044415 MB
G1HeapRegionSize = 0 (0.0MB)
Heap Usage:
PS Young Generation
Eden Space:
capacity = 66584576 (63.5MB)
used = 1331712 (1.27001953125MB) 垃圾回收之后,堆内存占用缩小到了1MB
free = 65252864 (62.22998046875MB)
2.0000307578740157% used
From Space:
capacity = 11010048 (10.5MB)
used = 0 (0.0MB)
free = 11010048 (10.5MB)
0.0% used
To Space:
capacity = 11010048 (10.5MB)
used = 0 (0.0MB)
free = 11010048 (10.5MB)
0.0% used
PS Old Generation
capacity = 177733632 (169.5MB)
used = 1102352 (1.0512847900390625MB)
free = 176631280 (168.44871520996094MB)
0.6202270147723082% used
3177 interned Strings occupying 281176 bytes.
此流程展示了一个对象创建到垃圾回收的堆内存占用的变化过程。
也可以使用idea自带的Profiler进行监控:
jconsole工具的使用:
同样执行上面的程序,然后再Terminal中输入jconsole,打开jconsole工具界面:
即可监控堆内存的使用情况。
jvisualvm的使用:
垃圾回收后,内存占用仍然很高,怎么利用工具进行排查?
首先执行jps获得当前运行的进程的进程号,然后使用jmap查看进程的堆内存占用情况:
PS E:\java程序代码\反射> jmap -heap 22012
Attaching to process ID 22012, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.131-b11
using thread-local object allocation.
Parallel GC with 8 thread(s)
Heap Configuration:
MinHeapFreeRatio = 0
MaxHeapFreeRatio = 100
MaxHeapSize = 4244635648 (4048.0MB)
NewSize = 88604672 (84.5MB)
MaxNewSize = 1414529024 (1349.0MB)
OldSize = 177733632 (169.5MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 17592186044415 MB
G1HeapRegionSize = 0 (0.0MB)
Heap Usage:
PS Young Generation
Eden Space:
capacity = 133169152 (127.0MB)
used = 89985360 (85.81672668457031MB)
free = 43183792 (41.18327331542969MB)
67.57222573588214% used
From Space:
capacity = 11010048 (10.5MB)
used = 0 (0.0MB)
free = 11010048 (10.5MB)
0.0% used
To Space:
capacity = 11010048 (10.5MB)
used = 0 (0.0MB)
free = 11010048 (10.5MB)
0.0% used
PS Old Generation
capacity = 277872640 (265.0MB)
used = 127634960 (121.72218322753906MB)
free = 150237680 (143.27781677246094MB)
45.932899331146814% used
3174 interned Strings occupying 280968 bytes.
尝试使用jconsole进行垃圾回收,点击右上角的执行GC进行垃圾回收:
虽然从折线图上直观看来执行GC后内存占用下降很多,其实也只是下降了10几MB,此时再使用jmap重新看下:
Heap Usage:
PS Young Generation
Eden Space:
capacity = 133169152 (127.0MB)
used = 8970248 (8.554695129394531MB)
free = 124198904 (118.44530487060547MB)
6.735980416846088% used
From Space:
capacity = 11010048 (10.5MB)
used = 0 (0.0MB)
free = 11010048 (10.5MB)
0.0% used
To Space:
capacity = 11010048 (10.5MB)
used = 0 (0.0MB)
free = 11010048 (10.5MB)
0.0% used
PS Old Generation
capacity = 277872640 (265.0MB)
used = 212116288 (202.28985595703125MB)
free = 65756352 (62.71014404296875MB)
76.33579470076651% used
可见Eden Space中的内存占用已经很低了,但是Old Generation老年代中的内存占用仍然很高,我们就要考虑是不是因为编程失误而导致一些对象始终被引用而无法释放他们的内存?
这里我们可以使用jvisualvm进行查看:
在Terminal中输入jvisualvm即可打开:
堆Dump(堆转储)功能:可以抓取堆的快照,可以对其中的详细内容进行分析:
点击右侧查找20个保留大小最大的对象(就是看看占用内存最大的20个对象):
可以看到是一个ArrayList类型的对象占用内存很大,差不多200MB,点进去:
点进element查看其中存放的元素:
看到是一些Member对象,点开一个Member对象:
一个Member对象中有一个名为big的byte类型数组,大小大约为1MB,整个ArrayList的size为200,大概也就是占用200MB,也就是这个ArrayList占用的内存较高,查看其源代码:
public class Demo01_1 {
public static void main(String[] args) throws InterruptedException {
List<Member> members = new ArrayList<>();
for(int i = 0;i < 200;i++){
members.add(new Member());
}
Thread.sleep(10000000000L);
}
}
class Member{
private byte[] big = new byte[1024*1024];
}
果然有一个ArrayList中存放着200个名为big的byte类型数组,且此ArrayList一直都在其生存范围内,一直未被回收,导致其内存占用过大。