JVM笔记(一.java内存区域详解)
1. java内存区域概述
对于熟悉java的人来说,都知道java将内存控制的权力交给了Java虚拟机,不需要像c,c++一样需要自己分配管理内存。这样虽然带来了方便,但如果出现了内存泄露等问题,如果不熟悉JVM的内存
管理机制就很难定位到到底是哪里出了问题。以下简要介绍一下java虚拟机内存的各个区域。
2. 运行时数据区域
java虚拟机在执行java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。如下图所示
其中方法区和堆是线程共享的数据区,其余的都是线程隔离的数据区。
下面分别介绍这几个区域
2.1 程序计数器
程序计数器是一块较小的内存空间,顾名思义可知它就是当前线程所执行的字节码的行号指示器,虚拟机工作时通过改变这个计数器的值来选取下一条需要执行的字节码指令,java多线程是通过轮流切换分配时间的方式来实现的,在任何一个确定的时刻,一个处理器只会执行一条线程中的指令,因此,需要给每个线程单独配备程序计数器,且各个线程间的程序计数器互不影响,所以程序计数器的内存区域是线程私有的内存。此外,如果线程正在执行的是Native方法,则这个计数器的值为空。因为所谓的Native方法就是在java程序中调用非java的代码,通常是更靠近底层的c或者c++代码,所以很显然程序计数器是jvm的,它只负责维护java代码的执行行号,而没有办法去维护其他代码的字节码行号。
2.2 java虚拟机栈
java虚拟机栈描述的是java方法执行的内存模型:每个方法在执行的同时,都会生成一个栈帧用于存储局部变量表、操作数栈、动态链接,方法出口灯信息。每个方法从调用直到执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈出栈的过程。由这个定义可知,虚拟机栈也是线程私有的,它的生命周期和线程相同。这个区域会出现两种异常状况:StackOverflowError异常和OutOfMemoryError异常,前者抛出的情况是线程请求的栈深度大于虚拟机所允许的深度,后者抛出的情况是固定长度的虚拟机栈在扩展时无法申请到足够的内存。
2.3 本地方法栈
本地方法栈与虚拟机栈的作用基本一样,唯一的区别在于虚拟机栈为虚拟机执行的java方法服务,而本地方法栈是为虚拟机使用的Native方法所服务。与虚拟机栈一样,本地方法栈也会抛出:StackOverflowError异常和OutOfMemoryError异常。
2.4 java堆
通常java堆就是java虚拟机所管理的内存中最大的一块,它被所有线程共享,在虚拟机启动时创建。java堆的主要作用就是存放对象实例,几乎所有的对象实例都放在这块内存区域。因为所有的对象实例都在堆内,所以堆内存也是java垃圾收集器管理的主要区域,有时候被称作“GC堆”。
从内存回收的角度来看java堆,可以把它分为新生代和老年代。
从内存分配的角度来看java堆,java堆中可能划分出多个线程私有的分配缓冲区TLAB(Thread Local Allocation Buffer)。
此外,java堆可以处于物理上不连续的内存空间,只要逻辑上是连续的即可,就像磁盘空间一样。当堆中再也没有内存完成实例的分配,而且堆再也无法扩展的时候,将会抛出OutOfMemoryError异常。
2.5 方法区
方法区与java堆一样是一个线程共享的内存区域,它用于存储已经被虚拟机加载的类信息,常量,静态变量等数据。常见的就是存储类,即class文件。方法区和java堆一样不需要连续的内存,只要逻辑连续即可。此外,方法区还可以选择不实现垃圾回收,因为它存储的是类的信息以及一些常量,对这些数据的回收的频率还是比较低的,但也不能不回收。
方法区里有一块很重要的区域叫运行时常量池,顾名思义可知这块内存区域主要存放的是编译期生成的各种字面量和符号引用。运行时常量池相对于Class文件常量池的一个重要特征就是它具备动态性,在运行期间也能将新的常量放进常量池中,常见的例子有String类的intern()方法。所谓的intern()方法就是在常量池中查找是否存在调用该方法的字符串常量,如不存在,则在常量池中创建该字符串常量并返回它的引用;如存在,则返回池中的字符串。
3.直接内存
首先明确直接内存并不是虚拟机运行时数据区的一部分,也不是java虚拟机规范中定义的内存区域,但这部分内存在使用NIO(New Input/Output)类时被频繁的使用,并且也可能导致OutOfMemoryError异常出现。NIO是一种基于通道(Channel)与缓冲区(Buffer)的I/O 方式,它可以使用native 函数库直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在Java堆和Native堆中来回复制数据。
需要注意的是本机内存的分配不会受到java堆大小的限制,不要忘记配置直接内存,防止发生异常。