Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域:方法区、堆、虚拟机栈、本地方法栈、程序计数器,其中方法区和堆是所有线程共享的数据区,虚拟机栈、本地方法栈和程序计数器是线程私有的内存区域。
程序计数器:是一块较小的内存空间,他可以看作是当前线程所执行的字节码的行号指示器,Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,这块内存区域线程私有。
Java虚拟机栈:线程私有,生命周期与线程相同,虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的时候会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等,每个方法从调用到执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。局部变量表存放各种基本类型(boolean,byte,char,short,int,float,long,double)、对象引用(referrence)类型,其中64为的long和double类型的数据会占用2个局部变量空间,其余只占1个,局部变量表所需的内存空间在编译器完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间时完全确定的,如果线程请求的栈的深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;如果虚拟机栈可以动态扩展(当前大部分Java虚拟机都可动态扩展,只不过Java虚拟机规范中也允许固定长度的虚拟机栈),如果扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常。
本地方法栈:与Java虚拟机栈类似,线程私有,区别时虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务。
Java堆:被所有线程共享的一块内存区域,在虚拟机启动时创建,用于存放对象实例,是垃圾收集器管理的主要区域,也被称为“GC堆”。
方法区:线程共享,用于存储虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
(方法区也称为永久代,在JDK1.8,元空间区Metaspace取代了方法区,元空间区放在本地内存,在Java虚拟机以外,原本存放在方法区中的类信息,编译后的代码数据等移到了元空间区,常量池移到了堆内存中)
运行时常量池:是方法区的一部分,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。
图片来自:https://blog.csdn.net/wangcheng_/article/details/80086294
对象的访问:
在IDEA中设置虚拟机参数:
虚拟机参数:-Xms1m -Xmx2m -XX:-HeapDumpOnOutOfMemoryError
代码:
public class Test2 {
public static void main(String[] args) {
List<Object> list = new ArrayList<>();
while (true) {
list.add(new People());
}
}
}
结果:Java 堆内存溢出
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3210)
at java.util.Arrays.copyOf(Arrays.java:3181)
at java.util.ArrayList.grow(ArrayList.java:261)
at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:235)
at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:227)
at java.util.ArrayList.add(ArrayList.java:458)
at Test2.main(Test2.java:9)
虚拟机参数:-Xss1m
代码:
public class Test3 {
private int length = 0;
private void test(){
length++;
test();
}
public static void main(String[] args) {
Test3 test3 = new Test3();
try {
test3.test();
}catch (Throwable e){
System.out.println(test3.length);
throw e;
}
}
}
结果:Java虚拟机栈内存溢出
20108
Exception in thread "main" java.lang.StackOverflowError
at Test3.test(Test3.java:10)
at Test3.test(Test3.java:10)
at Test3.test(Test3.java:10)
栈容量只由-Xss参数设定,无论是由于栈帧太大还是虚拟机栈容量太小,当无法分配的时候,虚拟机抛出的都是StackOverflowError异常,操作系统分配给每个进程的内存是有限的,比如32位的Windows限制为2GB,每个线程分配到的栈容量越大,可以建立的线程数据自然减少,建立线程时就越容易吧剩下的内存耗尽,可以通过减少最大堆(Xmx)和减少栈容量(Xss)来换取更多的线程。
博客内容整理来自《深入理解Java虚拟机 JVM高级特性与最佳实践》