首页 > 代码库 > Java 内存区域划分 备忘录
Java 内存区域划分 备忘录
最近看了《深入理解虚拟机》的内存分配与管理这部分的内容,这里做一个的总结,以加深我对知识点的理解,如有错误的地方,还望大神们指出,我及时更正;
内存区域划分
首先是下面这幅图:
图 1.0
这幅图是网上download下来的,但是它可以很直观告诉我们Java虚拟机所管理的几个内存区域(包括方法区、堆、虚拟机栈、本地方法栈、程序计数器五个区域),以及线程作用在这些内存区域时的数据访问方式(虚拟机栈、本地方法栈、程序计数器三个区域是线程独有;方法区、堆两个区域是线程间共享);
1.我们首先看看灰色模块中的 程序计数器模块;
它属于灰色模块,所以它的第一个特点就是它是线程私有的,各条线程之间的计数器互不影响,独立存储(在多线程环境下保证每条线程执行字节码指令时,指示的下一条指令不会错乱);
第二个特点是,它的用途。学过计算机组成原理的都知道PC寄存器,Java虚拟机中的这个PC寄存器功能和硬件级的PC寄存器相似,看作当前线程所执行的字节码的行号指示器(保存着下一条需要执行的字节码指令或者指令的地址),字节码解析器通过改变这个计数器的值来选取下一条需要执行的字节码指令,如条件分支(如if)、循环(如for)、跳转(如switch)、异常处理(异常表跳转地址)、线程恢复等基础功能都需要依赖这个计数器来完成;
2.再看灰色模块中的 虚拟机栈模块;
它的一个特点也是线程私有的,它的生命周期和线程相同,即一个线程执行开始,创建一个虚拟机栈,线程结束也会相应的销毁虚拟机栈;
第二个特点 虚拟机栈中的栈元素叫做栈针(如下图1.1所示),即虚拟机栈中有一个元素就表示还有一个栈针在虚拟机栈中;栈针的入栈和出栈对应着线程中对一个方法的调用到这个方法执行结束返回,当线程中的所有方法调用都执行结束,那么线程栈中的栈针也就全部出栈完成,线程执行结束,线程栈也就相应的被销毁,回收;
第三,再来看看栈针这个结构中存储的内容是什么;(如下图1.1所示),栈针中主要存有
1.局部变量表:其实这个表就是用于存储我们当前所调用的函数的参数,以及我们在函数中定义的局部变量;在Java程序被编译成Class文件时,就在方法表集合的Code属性的max_locals数据项中确定了该方法所需要分配的最大局部变量表的容量(方法表集合这部分的知识涉及到Class类文件结构中方法表集合的知识点,请自行查阅),说白了就是编译完成后,局部变量表的最大尺寸就是定了的;局部变量表的容量以变量槽(Slot)为最小单位,32位虚拟机中一个Slot可以存放一个32位以内的数据类型(boolean、byte、char、short、int、float、reference和returnAddress八种)。
2.操作数栈:Java虚拟机的解释执行引擎被称为"基于栈的执行引擎",其中所指的栈就是指-操作数栈。操作数栈也常被称为操作栈。和局部变量表一样,操作数栈也是被组织成一个以字长为单位的数组。但是和前者不同的是,它不是通过索引来访问,而是通过标准的栈操作—压栈和出栈—来访问的。比如,如果某个指令把一个值压入到操作数栈中,稍后另一个指令就可以弹出这个值来使用。虚拟机把操作数栈作为它的工作区——大多数指令都要从这里弹出数据,执行运算,然后把结果压回操作数栈。比如,iadd指令就要从操作数栈中弹出两个整数,执行加法运算,其结果又压回到操作数栈中,看看下面的示例,它演示了虚拟机是如何把两个int类型的局部变量相加,再把结果保存到第三个局部变量的:
Java 字节码指令(可以看解释说明文字)
1 begin 2 iload_0 // push the int in local variable 0 onto the stack 3 iload_1 // push the int in local variable 1 onto the stack 4 iadd // pop two ints, add them, push result 5 istore_2 // pop int, store into local variable 2 6 end
图 1.2
在前边的字节码序列里,前两个指令iload_0和iload_1将存储在局部变量中索引为0和1的整数压入操作数栈中,其后iadd指令从操作数栈中弹出那两个整数相加,再将结果压入操作数栈。第四条指令istore_2则从操作数栈中弹出结果,并把它存储到局部变量区索引为2的位置。上图1.2 详细表述了这个过程中局部变量和操作数栈的状态变化,图中没有使用的局部变量区和操作数栈区域以空白表示。
3. 动态链接
这里先解释一下方法调用的静态解析和动态链接,静态解析是指在编译期就确定了要调用方法的具体地址,方法调用指令的符号引用参数就是直接引用;具体在Java语言中的体现为1.类的静态方法调用,类的私有方法调用,对父类的方法调用;动态链接是指,方法调用的具体地址只有在运行时才能确定;具体在Java语言中的体现为1.类的虚方法调用,典型的特征就是有override注解修饰的方法;就是,如果符号引用是在类加载阶段或者第一次使用的时候转化为直接引用,那么这种转换成为静态解析,如果是在运行期间转换为直接引用,那么这种转换就成为动态连接;动态链接用于存储运行期转换的直接引用;
4.返回地址
方法的返回分为两种情况,一种是正常退出,退出后会根据方法的定义来决定是否要传返回值给上层的调用者,一种是异常导致的方法结束,这种情况是不会传返回值给上层的调用方法.对于异常退出的情况如果这个异常没有在方法体内被catch捕获并处理,那么这个方法是不会给它的上层调用者产生任何返回值的;程序会直接跳到匹配的异常处理器的位置,第一个能够catch到这个异常的位置;
图 1.1
虚拟机栈学习体会:用了较长的篇幅介绍虚拟机栈,主要是因为这部分的内容和我们编程中的最经常使用的方法调用息息相关,理解了这些,我们在编写代码时就会有更全面和深入的考虑,写出的代码也会更健壮;
3. 接下来看图1.0中的 的第三个灰色模块 本地方法栈;
它和虚拟机栈发挥的作用非常相似,它们的区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务, 而本地方法栈则为虚拟机使用到的Native方法服务;在 HotSpot虚拟机中直接就把它们合二为一;
4. 线程独占的内存区域都说完了,接下来看一看线程间共享的内存区域 绿色区域中的方法区;
它用于存储被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码、 等数据;
这里是一篇对方法区进行了详细介绍的博文,写的很详细;
http://blog.csdn.net/bingduanlbd/article/details/8548916/
5.绿色区域的 Java堆
Java堆随虚拟机启动时创建,它的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存;这也是GC主要管理的区域;当我们new 一个对象时,对象的数据部分就存储在堆中,该对象的引用则通常存储在栈中;
需要注意的特点是它是线程间共享访问的,所以在使用堆中的对象时,在多线程的环境下需要考虑,线程对对象的并发访问的问题;
内存泄漏和内存溢出的区别
1.内存泄漏是指我们不再使用的需要清理的对象,由于还存在引用链,这些对象仍然占用着内存,虚拟机迟迟没有进行清理,这一部分没有被正常清理的内存就是内存泄漏;
2.内存溢出是指我们真正需要使用的对象内存,超出了虚拟机能够分配给我们的内存,这时就会造成内存溢出;
Java 内存区域划分 备忘录