JVM虚拟机一:内存划分和溢出
JVM内存划分:
JVM把内存划分为以下几个主要的区域:
Method Area、VM Stack、Native Method Stack、Heap、Program Counter Register
方法区:是所有线程共享的内存区域,用于存储JVM加载的类信息、常量、静态变量、JIT的代码等数据;同时为了和堆区分,方法区也叫非堆(Non-Heap)。
方法区内部还包含一块叫“运行时常量区”,该区域主要存储类的各种字面量和符号引用等数据。
堆:是所有线程共享的内存区域,一般这是JVM中占用内存最大的区域,几乎所有的对象实例都存储在这个区域(像JIT的代码不在这个区域),该区域也是垃圾回收的主要区域,因此也叫GC堆。
虚拟机栈:是线程私有的,主要存储方法的内存模型,比如方法中的局部变量、操作数、方法出口等,当调用一个方法时,整个过程都对应到栈的IO操作,这个区域也就是平时所说的 栈。
本地方法栈:和虚拟机栈基本一样,区别就是调用本地方法,虚拟机栈是调用JAVA方法。
程序计数器:是线程私有的,主要是用来存储当前线程执行指令地址的,这样当多线程来回调度时,知道上次本线程执行到哪个指令。
JVM内存溢出:
JVM内存溢出常见两种:栈溢出、内存溢出,JVM每个区域都有可能发生溢出。
1. 堆溢出:
当不断的创建对象,并且对象无法被回收时,容易发生溢出。
// 一个栗子
public class HeapTest {
public static void main(String[] args) {
List<OOMTemp> list=new ArrayList<>();
AtomicInteger count=new AtomicInteger(0);
Thread.setDefaultUncaughtExceptionHandler((t,e)->{
System.out.println(count);
System.out.println(e.getMessage());
});
// 810325
while (true){
list.add(new OOMTemp());
count.incrementAndGet();
}
}
static class OOMTemp{ }
}
在执行之前,首先设置堆内存大小:-Xms20m -Xmx20m,其中-xms是堆的最小内存,-xmx是最大内存,都设置为20m
这样会输出(第二行开始显示创建了dump文件,是为了分析为什么溢出,在执行前加上opts:-XX:HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=D:/):
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid4612.hprof …
Heap dump file created [42076663 bytes in 0.208 secs]
1215487
Java heap spaceProcess finished with exit code 1
通过dump文件可以看出来是OOMTemp实力问题:
2.栈溢出
在单线程环境下,不太容易模拟OOM,但是SOF还是可以的,直接不停的递归调用一个方法,方法会不断的执行压栈操作,即会发生StackOverflowError。
在执行之前,还是首先配置内存大小:-Xss1m,发生SOF的原因就是栈被打满,请求地址已经超出栈的最大地址。
如果想模拟OOM,可以采用多线程方式,并且每个线程的栈内存越大,越容易发生OOM。
每个进程的内存是有限的,比如32位window最大2GBit,那么 剩余内存 = 2GBit – 堆内存(xmx) – 方法区内存(maxpermsize),栈内存只能在剩余内存中分割,所以每个线程中栈内存越大,线程多了就会OOM。