一、运行时数据区
JAVA运行时数据区包括程序计数器、本地方法栈、虚拟机栈、堆、方法区等,其中程序计数器、本地方法栈、虚拟机栈为线程私有内存、堆、方法区等为线程共享内存。
1、程序计数器,用来记录当前线程所执行的字节码的行号指示器。此内存为JAVA虚拟机规范中唯一一个没有规定OutOfMemoryError的内存区域。
2、JAVA虚拟机栈,在JAVA执行每一个方法时,虚拟机都会为其创建一个栈帧,,每一个方法从调用,直到执行完成,对应着一个栈帧在虚拟机栈中的入栈和出栈的过程。。栈帧用来存放局部变量表、操作数栈、动态链接、方法出口等信息。局部变量表存放了编译器可知的各种基本数据类型、对象引用、以及returnAddress类型,局部变量存储的单位为Slot。这个区域会出现两种异常类型:当请求的栈深度超过虚拟机所要求的最大深度时会出现StackOverFlowError、当线程数量过多导致所有线程加起来所需的内存超过最大内存时出现OutOfMemoryError。
3、本地方法栈,与虚拟机栈类似,用来执行本地方法
4、JAVA堆(Heap) 内存中最大的一块内存,主要用来存放JAVA对象实例,也是垃圾回收管理的最主要的区域,因此也被称为GC堆。从垃圾回收内存管理的角度来看,可以把堆内存分为新生代、老年代,再细一点又可以分为Eden Survivor等。配置时可以通过-Xmx和-Xms控制,当存活数量过多时会出现OutOfMemoryError。
5、方法区,也是线程共享内存,别名Non-Heap以与java堆进行区分。方法区中用来存储已被虚拟机加载的类信息、常量、静态变量等。垃圾回收在这个区域出现的比较少,很多人也把这个区域称为永久代(Permanent Generation),本质上两者并不等价。运行时常量池也是方法区的一部分,用来存放编译期生成的各种字面量和符号引用,直接引用也会被存储在其中。方法区内存不足时也会抛出OutOfMemoryError。
二、虚拟机中的对象
1、对象在虚拟机中的创建过程
new-> 是否已加载->为对象分配内存->将对象的内存空间赋零值(这样保证程序能够直接访问到对象的实例字段的零值)->对象头的设置-><init>初始化
2、对象的内存布局:对象头、实例数据、对齐填充
三、几种引起OutOfMemoryError的代码
1、java堆溢出
public class Test {
public static void main(String[] args) {
List<OOMObject> list = new ArrayList<>();
while(true){
list.add(new OOMObject());
}
}
static class OOMObject{
}
}
不停创建对象,直到堆内存溢出
2、虚拟机栈溢出
public class Test {
private static int statckLength = 1;
public static void statckLeak(){
statckLength++;
//递归
statckLeak();
}
public static void main(String[] args) {
statckLeak();
}
}
递归调用,导致栈帧请求深度过大,导致StackOverFlowError
public class Test {
public static void main(String[] args) {
while(true){
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
while(true){
System.out.println("Hello world");
}
}
});
thread.start();
}
}
}
每次新建线程,虚拟机都会为每个线程分配私有内存,当线程过多,无内存可分配时抛出OutOfMemoryError