1、Java虚拟机之内存区域
为什么要了解JAVA内存管理?
我们知道,用JAVA进行编程,最大的优势在于内存管理。我们可以完全忽略内存管理的细节,专注于逻辑业务上。但是,并不代表它就不会出现内存溢出或泄露的问题。而且,因为JAVA有自动管理内存的机制而对这一块不了解,显然是有点low的。所以,了解一下JAVA的内存管理,既有助于提高逼格,也能让我们在进行开发的时候,可以针对内存溢出或泄露的问题进行一些优化。
JAVA运行时内存区域
JAVA虚拟机在执行JAVA程序的过程中会把它所管理的内存分为若干个不同的数据区域。这些区域都有各自的用途、创建和销毁的时间,有的是随着虚拟机进程启动而存在的,有的则依赖用户线程的启动和结束而建立和销毁的。
通过下面这张图,我们可以比较直观的看出,虚拟机划分的几个数据区域。
根据JAVA虚拟机规范,内存被划分为:方法区、虚拟机栈、本地方法栈、堆、程序计数器、运行时常量池(程序运行时,由方法区分配的区域,所以在图中没画出来)
- 程序计数器(线程私有)
记载了线程中正在运行的JAVA方法的地址,如果执行的是本地方法,这个计数器则为空地址。
每个线程都有独立的程序计数器,该计数器主要是用来支持多线程的阻塞、挂起、恢复等操作。所以,这个计数器也是每个线程私有的内存区域。而且也是唯一一个JAVA虚拟机规范中没有规定任何OutOfMemoryError情况的区域。
- 虚拟机栈(线程私有)
虚拟机栈也是线程私有的。每个线程创建的同时跟着一起创建的,用于存储栈桢。
每个方法在执行时都会创建一个栈桢,用于存储局部变量表,操作数栈,动态链接,方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
在JAVA虚拟机规范中,这个区域规定了两种异常情况:
1. 线程请求的栈深度大于虚拟机运行深度,抛出StackOverFlowError异常;
2. 虚拟机栈可动态拓展时,没有申请到足够内存,抛出OutOfMemoryError异常。
- 本地方法栈(线程私有)
与虚拟机栈类似,也是线程私有的。它是用来支持Native方法的执行的。在虚拟机规范中,对本地方法栈中的方法使用的语言、使用方法、数据结构都没有强制规定,可以自由实现它。和虚拟机栈一样,也会抛出StackOverFlowError异常和OutOfMemoryError异常。
- 堆(全局共享的)
用于存储对象实例及数组的。这个区域也是垃圾收集器(GC)的主要管理区域。为了方便内存回收,该区域还会进行很多划分,不变的是,存放的内容都是对象实例。该区域的大小可以通过-Xmx和-Xms来控制。如果没有内存可给实例分配,且无法拓展时,会抛出OutOfMemoryError异常。
- 方法区(全局共享的)
用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。
在JAVA虚拟机规范中,没有强制要求实现这部分的内存管理。垃圾收集行为在这部分也比较少。当方法区无法满足内存分配需求时,会抛出OutOfMemoryError异常。
内存的管理其实就是内存分配和内存回收。而内存分配的几个区域可以分为两类,程序计数器、本地方法栈、虚拟机栈这几个区域是属于线程私有的,随着线程创建而创建,销毁而销毁,销毁时内存也会释放掉,所以这几个区域是不需要进行GC的。方法区因为它的定义GC行为相对会比较少,所以,堆则成为了垃圾回收器的重点维护对象。