Java虚拟机之深入理解Java内存区域
概述
C/C++语言,在内存管理领域,开发需要对创建的对象进行手工的释放或回收,而在我Java内存管理领域,我们的内存管理交给了虚拟机,在虚拟机的自动内存管理机制调度下,不太容易出现内存泄漏或者内存溢出的问题,然而在不了解虚拟机运作原理的情况下,一旦出现内存泄漏或者内存溢出的问题,将会无从下手分析,更别说解决。因此本篇着重介筛Java虚拟机内存中的各个区域,以及作用。
Java虚拟机的数据区域
Java虚拟机在运行程序时,会把管理的内存划分为几个数据区域,
1.程序技术器,
2.虚拟机栈,
3.本地方法栈,
4.堆,
5.方法区
下图为Java虚拟机运行时数据区域示意图,
1.线程共享的数据区域:方法区,堆
2.线程隔离的独有数据区域:虚拟机栈,本地方法栈,程序计数器。
看不懂没有关系,下面我逐一来介绍一下。
程序计数器
它是一块较小的内存空间,是当前线程所执行的字节码的行号指示器,在虚拟机的概念模型中,字节码的解释工作是通过改变计数器的值来选取下一条需要执行的字节码指令。
Java虚拟机中的多线程,是通过线程轮流切换以及分配处理器执行时间来实现的,在任何一个确定的时刻,单核的服务器,只处理一条线程,线程切换之后能恢复原来正确的位置,每条线程都需要一个独立的程序计数器,他是选取下一条需要执行的字节码指令的依据。
因此,程序计数器是线程隔离的独有数据区域
如果当前线程执行的是Java方法,那么程序计数器记录的是正在执行的虚拟机字节码指令的地址,
如果当前线程执行的是Native方法,这个计数器的值为空,
程序计数器是Java虚拟机规范中唯一没有规定OutOfMemoryError情况的内存区域。
虚拟机栈
虚拟机栈的生命周期与线程相同。它是Java方法执行的内存模型:每一个方法被执行的同时,都会去创建一个栈帧,栈桢用于存储局部变量表,
操作栈,动态链接,方法出口等信息。 → 每一个方法的调用至执行完成的过程,就意味着一个栈帧在虚拟机栈中的入栈到出栈的过程
栈的特点是先进后出。举个例子。现在有方法A,方法B,方法C....以此类推。A调用B,B调用C,C调用D。调用一个方法,比如A,首先要将A压入到栈。A调用B,再把B压入栈。以此类推。在最栈顶的是D。当D方法执行完之后,D出栈,C出栈,B出栈,A出栈。
本地方法栈
和虚拟机栈比较相似, 虚拟机栈描述的是Java方法执行的内存模型,而本地方法栈则是Native方法的内存模型。
native方法,我在之前分析ArrayList源码的博客中提到过一个 数组的拷贝,就是native方法。
1 public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);
native方法的实现是c、c++或其他底层语言。
堆
堆是java虚拟机管理的一块最大的内存区域,是一块被所有线程共享的内存区域,在虚拟机启动时被创建,用于存放对象的实例。
在虚拟机中的规范中是这么描述的:所有的对象实例以及数组都要在堆上分配,但是随着栈上分配,标量替换的优化技术将会导致微妙的变化,
因此所有的对象都分配在堆上的说法也不是那么绝对。
既然堆是存放对象实例的内存,那必然会涉及到gc操作,因此我们得出:Java堆也是垃圾器管理的主要区域。
方法区
方法区用于存储已被虚拟机加载的类信息、常量、静态常量,编译器编译后的代码等数据。
虚拟机规范把方法区描述为堆的一个逻辑部分,但是实际上是要和堆区分出来的。
以上简单的介绍了Java内存中的5块数据区域。 接下来介绍一下运行时常量
运行时常量
运行时常量是方法区的一部分,Class文件中除了有类的版本,字段,方法,接口等描述的信息之外,还有一项信息是常量池,用于存放编译期的字面量和符号引用,这部分将在类加载后存放到方法区的运行时常量中。
运行时常量池相对于Class文件的常量池的另外一个特性就是具备动态性,→ 并非只能Class文件中的常量池内容才能进入到运行时常量池中,运行期间也可能
将新的常量放入常量池中。String类的intern就是这个特性的应用。