JVM内存区概述——InteviewOrientation

来源:互联网 发布:idm for mac 破解 编辑:程序博客网 时间:2024/06/08 07:25

java虚拟机所管理的内存包含以下几个运行时数据区域:


1.程序计数器

(1)线程私有,即每个线程都会有一个,线程之间互不影响,独立存储

(2)可以看做当前线程所执行的字节码的行号指示器

(3)该内存区域是java虚拟机规范中唯一一个没有规定任何OutOfMemoryError的区域


2.java虚拟机栈

(1)线程私有,生命周期和线程相同

(2)描述的是java方法执行的内存模型:每个方法在执行的同时多会创建一个栈帧用于存储局部变量表、操作数栈、动态链表、方法出口等信息。

每一个方法从调用直至完成的过程,就对应着一个栈帧在虚拟机中入栈到出栈的过程。

(3)我们常说的堆内存与栈内存(笼统的划分),其中的栈指的就是虚拟机栈,或者更确切的说是局部变量表部分。

 局部变量表存放了编译期可知的各种基本数据类型和对象引用(注意是对象引用,不是对象本身,可能是一个指向对象首地址的“指针”)和returnAddress类型。

(4)进入一个方法时,这个方法需要在帧中分配多大的局部变量空间在编译期即可确定。在方法运行期间,不会改变局部变量表的大小。

(5)Java虚拟机栈可能出现两种类型的异常:

线程请求的栈深度大于虚拟机允许的栈深度,将抛出StackOverflowError。

虚拟机栈空间可以动态扩展,当动态扩展是无法申请到足够的空间时,抛出OutOfMemoryError异常


譬如以下代码:

void func_A(arg_A1, arg_A2);void func_B(arg_B1, arg_B2);int main(int argc, char *argv[], char **envp){func_A(arg_A1, arg_A2);}void func_A(arg_A1, arg_A2){var_A;func_B(arg_B1, arg_B2);}void func_B(arg_B1, arg_B2){var_B1;var_B2;}





函数调用大致包括以下几个步骤:

参数入栈:将参数从右向左依次压入系统栈中
返回地址入栈:将当前代码区调用指令的下一条指令地址压入栈中,供函数返回时继续执行
代码区跳转:处理器从当前代码区跳转到被调用函数的入口处
栈帧调整:具体包括
保存当前栈帧状态值,已备后面恢复本栈帧时使用(EBP入栈)
将当前栈帧切换到新栈帧。(将ESP值装入EBP,更新栈帧底部)
给新栈帧分配空间。(把ESP减去所需空间的大小,抬高栈顶)

ESP:栈指针寄存器(extended stack pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的栈顶
EBP:基址指针寄存器(extended base pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的底部
函数栈帧:ESP和EBP之间的内存空间为当前栈帧,EBP标识了当前栈帧的底部,ESP标识了当前栈帧的顶部。

EIP:指令寄存器(extended instruction pointer), 其内存放着一个指针,该指针永远指向下一条待执行的指令地址。



【作者:李根 链接:https://www.zhihu.com/question/22444939/answer/22200552 来源:知乎】


3.本地方法栈
(1)本地方法栈位为虚拟机使用到的native方法服务。 Sun HotSpot虚拟机把本地方法栈和虚拟机栈合二为一。
(2)异常类型同虚拟机栈

4.java堆
(1)线程共享,所以在虚拟机启动时就创建
(2)主要用于分配对象实例和数组。是垃圾收集器管理的主要区域。
(3)java堆可以处在物理上不连续的内存空间中,只要逻辑上连续即可,就像我们的磁盘空间一样。

5.方法区

(1)线程共享
(2)用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
(3)垃圾收集行为在此区域比较少见,主要针对常量池的回收和类型的卸载

运行时常量池——方法区的一部分

Class文件中的常量池:用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。

运行时常量池相对于Class文件常量池的一个重要特征是具备动态性:即除了Class文件中常量池的内容能被放到运行时常量池外,运行期间也可能将新的常量放入池中,比如String类的intern()方法。



△直接内存

直接内存不是虚拟机运行时数据区的一部分,又称堆外内存

NIO中会使用



问题:基本数据类型的成员变量放在jvm的哪块内存区域里?


答案:堆


解析:

成员变量i(非静态,实例变量),他是存放在java堆中。因为它不是静态的变量,不会独立于类的实例而存在,而该类实例化之后,放在堆中,当然也包含了它的属性i。
如果在方法中定义了int i = 0;则在局部变量表创建了两个对象:引用i和0。 这两个对象都是线程私有(安全)的。 比如定义了int[] is = new int[10]. 定义了两个对象,一个是is引用,放在局部变量表中,一个是长度为10的数组,放在堆中,这个数组,只能通过is来访问,方法结束后出栈,is被销毁,根据java的根搜索算法,判断数组不可达,就将它销毁了。


为什么要把堆和栈区分出来呢?栈中不是也可以存储数据吗

    第一,从软件设计的角度看,栈代表了处理逻辑,而堆代表了数据。这样分开,使得处理逻辑更为清晰。分而治之的思想。这种隔离、模块化的思想在软件设计的方方面面都有体现。

    第二,堆与栈的分离,使得堆中的内容可以被多个栈共享(也可以理解为多个线程访问同一个对象)。这种共享的收益是很多的。一方面这种共享提供了一种有效的数据交互方式(如:共享内存),另一方面,堆中的共享常量和缓存可以被所有栈访问,节省了空间。

    第三,栈因为运行时的需要,比如保存系统运行的上下文,需要进行地址段的划分。由于栈只能向上增长,因此就会限制住栈存储内容的能力。而堆不同,堆中的对象是可以根据需要动态增长的,因此栈和堆的拆分,使得动态增长成为可能,相应栈中只需记录堆中的一个地址即可。

    第四,面向对象就是堆和栈的完美结合。其实,面向对象方式的程序与以前结构化的程序在执行上没有任何区别。但是,面向对象的引入,使得对待问题的思考方式发生了改变,而更接近于自然方式的思考。当我们把对象拆开,你会发现,对象的属性其实就是数据,存放在堆中;而对象的行为(方法),就是运行逻辑,放在栈中。我们在编写对象的时候,其实即编写了数据结构,也编写的处理数据的逻辑。不得不承认,面向对象的设计,确实很美。



 Java中的参数传递时传值呢?还是传引用

    要说明这个问题,先要明确两点:

         1. 不要试图与C进行类比,Java中没有指针的概念

         2. 程序运行永远都是在栈中进行的,因而参数传递时,只存在传递基本类型和对象引用的问题。不会直接传对象本身。

    明确以上两点后。Java在方法调用传递参数时,因为没有指针,所以它都是进行传值调用(这点可以参考C的传值调用)。因此,很多书里面都说Java是进行传值调用,这点没有问题,而且也简化的C中复杂性。

    但是传引用的错觉是如何造成的呢?在运行栈中,基本类型和引用的处理是一样的,都是传值,所以,如果是传引用的方法调用,也同时可以理解为“传引用值”的传值调用,即引用的处理跟基本类型是完全一样的。但是当进入被调用方法时,被传递的这个引用的值,被程序解释(或者查找)到堆中的对象,这个时候才对应到真正的对象。如果此时进行修改,修改的是引用对应的对象,而不是引用本身,即:修改的是堆中的数据。所以这个修改是可以保持的了。




0 0
原创粉丝点击