JVM学习--Java栈

         java和C++很大的一点区别再与java提供了近乎完善的垃圾回收机制,但是并不是说我们就不需要去关心这个问题,很多时候为了提高性能,依然需要了解java的北村分配机制和垃圾回收机制

一、JVM将内存划分为如下五个区域

这里写图片描述

1.程序计数器(Program Counter Register)
2.java栈(Stack)
3.本地方法栈(Native Method Stack)
4.方法去(Method Area)
5.堆 (Heap)

二、栈的组成

         如图,java栈、本地方法栈、程序计数器三个区域的数据是线程私有的,而堆、方法区是线程共享的。
这里写图片描述
         栈和堆分别保存对象的引用和对象的实例,如Object obj = new Object;obj是对new出来的Object实例对象的引用,obj自身数据将保存在栈中,其数据(也就是引用只想地址)指向位于堆内存中的实例地址。java程序的执行以main方法为入口,方法间嵌套使用,过程满足栈的先入后出原则,所以java栈中保存的是每个方法对应的栈帧。所以说java栈的存在是为了解决程序的执行问题,只不过方式是通过对进行数据处理来实现而已。

  1. 局部变量表—–保存方法执行时的相关局部变量(最小单位为Slot(变量槽)),如对象的引用,返回地址,基本类型数据等等,局部变量表在编译期间就会生成,所以编译期间会为表中数据分配空间,此时如果栈帧请求的栈深度大于虚拟机允许的最大深度,则会跑出StackOverFlowError(栈溢出异常)
             注:在局部变量表中的基本类型中,long和dobule类型的数据会占用两个Slot,其他的则只占用一个
  2. 操作数栈—–类似于计算器栈的实现原理,操作数栈也是保存有执行某一指令所需要的数据和操作,其内部遵循栈的基本原理,每个操作指令所需指令由此出栈,执行后得到的数据再次压入栈顶
  3. 指向运行时常量池的引用—–保存指向运行期间定义的常量,这些常量被保存在运行时常量池,如final对象。
  4. 方法返回地址—–方法返回的地址(指向一条字节码指令的地址)
  5. 动态链接—–使用javap命令查看.class文件会发现其中有一块Constant pool(常量池)区域,其中保存由很多符号引用,方法的调用就是通过常量池中指向方法的符号引用作为参数进行的,一部分通过静态解析,在类加载阶段或者第一次使用时解析为直接引用,一部分则在每次执行时解析为直接引用,这部分便是动态链接。

三、栈和堆的关系

         java栈的存在是为了解决程序的执行问题,而堆的存在则是解决程序执行期间涉及的数据存放问题
同样以Object obj = new Object;为例,这个过程需要考虑以下几个问题:

1. 为什么我们需要将栈和堆分开来处理?
         答案是,当我们在处理问题的过程中,我们需要解决的其实是数据之间的联系,也就是说我们在编写一个类的时候,我们做的是在组装一个数据结构,用于保存我们所需要的数据,而方法的执行,本质上是对数据进行的处理,那么为什么不将数据和要执行的操作放在同一个区域?答案是显然的,计算机的运行并不是无限制的,程序运行有其自己被分配的空间,如果全部在同一个区域,如何解决数据增长的问题?而且,数据是动态的,动态增长该如何解决?当分为栈和堆后,堆的结构特点是可以动态的增长,而栈有着先入后出的特点,两者匹配,可以完美解决上述问题,栈中只需要保存实例数据的引用即可。
         同时也正是这种方式,回答了java方法运行时传递的是值还是引用的问题,java并没有指针的概念,其传递都是引用,只要该引用所指的对象没变,在其基础上进行的操作,将会影响到对象本身。
2. 栈中的引用如何找到对应堆中的实例?
         在C++中有句柄的概念,句柄的实现方式是首先在句柄池中查找到该对象实例数据指针,再通过该指针找到堆内存中的对象实例数据,然后根据实例数据得到对象类型数据指针,然后去方法区得到对象类型数据。通过这种类似于三次握手的操作得到引用指向的实例本身和实例的对象类型。
         而java HotSpot虚拟机的实现并不是通过句柄的实现方式,其栈中引用保存的直接就是对象实例的地址,这种方式大大提高了运行效率。

猜你喜欢

转载自blog.csdn.net/time_travel/article/details/78637646