目录
对于Java程序员来说,在虚拟机自动内存管理机制的帮助下,不在需要为每一个new操作去写配对的delete/free代码,不容易出现内存泄漏和内存溢出问题。程序员把对内存控制的权利交给了Java虚拟机,一旦出现内存的泄漏和溢出方面的问题,若不了解虚拟机怎样使用内存,则排查错误将成为一项异常艰难的工作
1、程序计数器——线程私有
程序计数器是一块较小的内存空间,他可以看做是当前线程所执行的字节码的行号指示器。
由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器都只会执行一条线程中的指令。
为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,我们称之为内存区域的“线程私有”的内存。
此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。
2、Java虚拟机栈——线程私有
与程序计数器一样,Java虚拟机栈也是线程私有的,它的生命周期与线程相同。
虚拟机栈描述的是:Java方法执行的内存模型——每个方法在执行的同时都会创建一个栈帧(stack frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
- 局部变量表——存放了编译器可预知的各种基本类型(Boolean、byte、char、short、int、float、long、double)、对象引用和returnAddress类型(指向了一条字节码指令的地址)。 —— (long、double)占据两个局部变量空间slot,其余1个slot
这个区域规定了两种异常:
- 如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;
- 如果虚拟机栈可以动态扩展,如果扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常;
3、本地方法栈
native method stack本地方法栈与虚拟机栈所发挥的作用类似,他们之间的区别是
- 虚拟机栈为虚拟机执行Java方法(即字节码)服务;
- 本地方法栈为虚拟机使用到的native方法服务。
本地方法栈也会抛出StackOverflowError异常和OutOfMemoryError异常
4、Java堆——线程共享
Java Heap是Java虚拟机所管理的内存中最大的一块。
Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。
此内存区域的唯一目的就是:存放对象实例,几乎所有的对象实例都在这里分配内存。
Java堆是垃圾收集器管理的主要区域,也被称为“GC堆”。
- 从内存回收角度看,由于现在收集器基本都采用分代收集算法,可分为新生代和老年代;再细致一些,可分为Eden空间、From Survivor空间、To Survivor空间等。
- 从内存分配角度看,线程共享的Java堆中可能划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB)。
划分的目的都是为了更好的回收内存或更好的分配内存。
Java堆可以处于不连续的内存空间中,只要逻辑上是连续的即可,就像磁盘空间一样。
5、方法区——线程共享
方法区与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但它却有一个别名叫Non-Heap(非堆),目的应该是与Java堆区分开来。
除了和堆一样不需要连续的内存和可以选择固定大小或者可扩展外,还可以选择不实现垃圾收集。
这个区域的内存回收目标主要是:针对常量池的回收和对类型的卸载。
会抛出OutOfMemoryError异常。
6、运行时常量池——方法区的一部分
Runtime Constant Pool是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。
运行时常量池相对于Class文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定只有在编译期才能产生,运行期间也可能将新的常量放入池中。这种特性被开发人员利用得比较多的是String类的intern()方法。
会抛出OutOfMemoryError异常。
7、直接内存
direct memory并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域。但这部分内存也被频繁地使用,而且也可能会导致OutOfMemoryError异常。
8、对象的创建
虚拟机遇到一条new指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。若没有,就必须先执行相应的类加载过程。
在类加载通过后,接下来虚拟机将为新生对象分配内存。对象所需内存的大小在类加载完成后便可完全确定。为对象分配空间的任务等同于把一块确定大小的内存从Java堆中划分出来。