Java和C++之间有一堵由内存分配和垃圾回收技术所围成的“高墙”,墙外面的人想进去,墙里面的人却想出来。
《深入理解JVM》
运行时数据区域
可以看到数据区分为方法区,堆,虚拟机栈,本地方法栈,程序计数器。
其中方法区和堆是所有线程共享的数据区,而虚拟机栈和本地方法栈,程序计数器是线程隔离的数据区
程序计数器
程序计数器是一块较小的内存空间,它可以看做是当前线程所执行的字节码的行号指示器。字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支,循环,跳转,异常处理,线程恢复等基础功能都需要依赖这个计数器来完成。
由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器都只会执行一条线程中的指令。因此,为了线程切换以后能恢复到正确的执行位置。每条线程都需要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,所以这类内存区域为“线程私有”内存。
如果线程正在执行的是一个java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Native方法,这个计数器值为空。(undefined)。
Java虚拟机栈
与程序计数器一样,Java虚拟机栈也是线程私有的,他的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧用于存储局部变量表,操作数栈,动态链接,方法出口等信息。每个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
虚拟机栈最重要的是局部变量表。
局部变量表存放了编译期可知的各种基本数据类型,对象引用,和returnAddress类型(指向了一条字节码指令的地址)
局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。
异常:
如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;
如果虚拟机栈可以动态扩展,如果扩展时无法申请到足够的内存,就会抛出OutofMemoryError异常。
本地方法栈
本地方法栈与虚拟机栈所发挥的作用非常相似,他们的区别是虚拟机为虚拟机执行java方法服务,而本地方法栈为虚拟机使用到的Native方法服务。
Native方法:简单地讲,一个Native Method就是一个java调用非java代码的接口。
Java堆
此内存区域的唯一作用就是存放对象实例。
Java堆是垃圾收集器管理的主要区域,因此很多时候被称作“GC堆”。从内存回收的角度来看,由于现在收集器基本都采用分代收集算法,所以Java堆中还可以细分为:新生代和老年代。从内存分配的角度来看,线程共享的Java堆中可能划分出多个线程私有的分配缓冲区。
可以处于物理上不连续的内存空间中,只要逻辑上连续即可。
方法区
方法区与Java堆一样,是各个线程共享的内存区域,他用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。
Java虚拟机规范对方法区的限制非常宽松,除了和Java堆一样不需要连续的内存和可以选择固定大小或者可扩展外, 还可以选择不实现垃圾收集。