Understand JVM:Java内存区域与内存溢出异常
运行时内存区域
Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域,每个区域有各自的用途,有着不同的创建和销毁时间。
程序计数器
程序计数器是一块较小的内存空间,可以看做是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型中,字节码解释器工作就是通过改变这个计数器的值来选取下一个需要执行的字节码的指令,分支、循环、跳转、异常处理、线程恢复等基础的功能都需要依赖这个计数器来完成。
Java虚拟机栈
Java虚拟机栈是描述Java方法栈执行的内存模型,每一个方法被执行的时候都会同时创建一个栈帧用于存储局部变量表、操作栈、动态链接等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在Java虚拟机栈的从入栈到出栈的过程。
本地方法栈
与Java虚拟机栈不同的是,本地方法栈是为Java虚拟机执行native方法服务的。
Java堆
Java堆是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建,目的就是为了存放对象实例。同时Java堆也是垃圾收集器管理的主要区域。
方法区
方法区是也是一个线程共享的内存区域,用于存放已被虚拟机加载的类的信息、常量、静态变量、即时编译器编译后的代码等。
运行时常量池
运行时常量池是方法区的一部分,主要用于存放编译期间生成的各种字面量和符号引用。
在Java中对象访问是如何进行的?
在Java语言中,对象访问无处不在,任何对象的访问都会涉及到Java栈、Java堆和方法区这三个最重要的内存区域。1
Object obj = new Object();
分析上面一行代码,Object obj会翻译到Java栈的本地变量表中,最为一个reference类型数据出现,
new Object()这部分语义会反应到Java堆中,形成一块存储了Object类型的结构化内存,
在方法区中会存放此段对象数据类型的地址信息,包括类型,父类等等的地址信息。
句柄访问方式
java堆中将划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据和类型数据各自的具体地址信息。
直接指针访问方式
reference变量中直接存储的就是对象的地址,而java堆对象一部分存储了对象实例数据,另外一部分存储了对象类型数据。
这两种访问对象的方式各有优势,使用句柄访问方式最大好处就是reference中存储的是稳定的句柄地址,在对象移动时只需要改变句柄中的实例数据指针,而reference不需要改变。使用指针访问方式最大好处就是速度快,它节省了一次指针定位的时间开销,就虚拟机而言,它使用的是第二种方式(直接指针访问)。
内存溢出异常(OutOfMemoryError)
- Java堆溢出
Java堆用于存放对象实例,如果我们不断地创建对象,并且在GC不会回收的情况下,就会造成Java堆溢出。 - 虚拟机栈和本地方法栈溢出
- 运行时常良池溢出
- 方法区溢出
- 本机直接溢出