《深入理解Java虚拟机》个人读书总结——JAVA虚拟机内存
最近在读《深入理解Java虚拟机》,网上对Java虚拟机的总结有很多,自己觉得自己也应该记录一点个人的读书总结,以便日后复习方便。
随着开发工作的逐渐深入,对Java的理解不能止步于crud,Java不像C语言,Javaer是不需要自己控制内存的,一旦出现常见的OutOfMemoryError或*Error,如果不了解虚拟机是怎样使用内存的,那么排查错误将会出现一定的阻碍。
运行时数据区域
Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。具体如图所示:
由图可以看出,JVM将内存主要划分为:方法区、虚拟机栈、本地方法栈、堆、程序计数器五大块。
程序计数器
程序计数器是一块较小的内存空间,在大学操作系统一课中我们知道程序计数器是用于存放下一条指令所在单元的地址的地方。为了保证程序(在操作系统中理解为进程)能够连续地执行下去,处理器必须具有某些手段来确定下一条指令的地址。而程序计数器正是起到这种作用,所以通常又称为指令计数器。在虚拟机的概念模型中,字节码解释器工作时也是通过改变这个计数器的值来选取下一条需要执行的字节码指令。
Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间片来实现,在任何一个确定的时刻,一个处理器都只会执行一条线程肿的指令,因此,为了确保线程切换之后能恢复到正确的执行位置,每条线程都需要一个独立的程序计数器,这样就互不影响独立存储,因此程序计数器是线程私有的内存。
此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。
虚拟机栈
虚拟机栈也是线程私有的,它的生命周期与线程相同。那个栈可以理解成我们平时所熟悉的数据结构的那个栈,里面的一个个栈元素我们叫它栈帧(其实是一种数据结构,后面会说)。每个方法在执行的同时会创建一个栈帧,用于存储局部变量表、操作数栈,动态链接、方法出口等信息。然后每个方法从调用到执行结束就相当于这些栈帧在虚拟机栈中出栈入栈的过程。
在Java虚拟机规范中,对这个区域规定了两种异常状况:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出*Error异常;如果虚拟机栈可以动态扩展(通过虚拟机参数-Xss控制),如果扩展到无法申请到足够的内存就会抛出OutOfMemoryError异常。
本地方法栈
这个栈和上面所说的栈其实是作用相似的,虚拟机栈是为Java方法服务,这个本地方法栈则是为native方法服务。虚拟机规范抛出的异常也是和虚拟机规范一样的,正因为虚拟机规范中没怎么硬性规定,sun的HotSpot虚拟机直接就将虚拟机栈和本地方法栈合二为一了。
堆
堆可以说是虚拟机中所管理的内存中最大的一块了。这块内存是被所有线程都共享的,主要的作用是用来存放对象实例的。所有的对象实例和数组都要在堆上分配。当然,随着JIT编译器的发展现在也不是那么”绝对”了。
Java堆是垃圾收集器管理的主要区域,由于现在的收集器基本上采用的都是分代收集算法,所有Java堆可以细分为:新生代和老年代。在细致分就是把新生代分为:Eden空间、From Survivor空间、To Survivor空间。在HotSpot中还提出了一个永久代的概念,从内存中抠出一部分用于存储类的元信息,类变量等内容,将其当成方法区来用,不过在Java8之后,这个概念被去掉了,换汤不换药,现在改叫元空间。
Java虚拟机规范规定,堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可。在实现上即可以是固定大小的,也可以是可动态扩展的(通过虚拟机参数 -Xmx和-Xms控制)。如果在堆中没有内存完成实例分配,并且堆大小也无法在扩展时,将会抛出OutOfMemoryError异常。
方法区
方法区和堆一样,同样是线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据,可以通过虚拟机的参数-XXpermSize和-XX:MaxPermSize来限制方法区大小。
运行时常量池
常量池用于存放编译期间生成的各种字面量和符号引用。这是一个class文件中的一个区域。
Java程序并不一定要求常量在编译期间产生,有可能在运行的时候会产生一个新的常量。为了实现这种动态性,在方法区中还有一个区域叫运行时常量池。在运行期间这里的内容是可以修改的。同时,在类的加载后会把常量池的东西存放到运行时常量池。
这里还要说的一个是字符串常量池,我想说的是这个和运行时常量池并没有什么关系。字符串常量池是线程共享的,在Java7之前是存在于方法区里的,Java7之后被转移到堆上了。现在要我说这两个常量池是平级。
运行时方法区和方法区一样,内存分配不足时会抛出OutOfMemoryError异常。
直接内存
直接内存不是虚拟机运行是数据区的一部分。也不是Java虚拟机规范中定义的内存区域。但是这部分内存也被频繁地使用,也有可能会导致OutOfMemoryError异常。
在JDK1.4中新加入了NIO,它可以使用Native函数库直接分配堆外内存,这内存是不受虚拟机控制的,但会受到本机的限制。所以在设置虚拟机的参数-Xmx等参数时要考虑到各内存区域总和要小于物理内存限制才行,从而避免动态扩展的时候出现OutOfMemoryError异常。