深入理解JVM--Java内存区域之栈,堆,方法区

JVM运行时数据区域介绍

最近开始学习JVM,及时整理一下,Java虚拟机在执行Java程序的过程中会把其管理的内存分为若干个不同的数据区域。
在这里插入图片描述

1、程序计数器(线程私有)

程序计数器是一块较小的内存空间,他可以看作是当前线程所执行的字节码的行号指示器。如果正在执行的是java方法,计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Native方法,计数器值为空。此内存区域没有OutOfMemoryError区域。

2、Java虚拟机栈(线程私有)

线程在运行时需要在虚拟机栈中申请线程栈,虚拟机栈中存放着每个方法在执行的同时创建的栈帧,栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。当有一个方法被调用时,代表这个方法的栈帧入栈;当这个方法返回时,其栈帧出栈。它的生命周期与线程相同。
局部变量存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,是对象的起始地址的指针或者对象相关位置)和returnAddress类型。局部变量所需要的内存空间在编译期间完成分配,进入一个方法区,这个方法需要在帧中分配多大的局部变量是完全确定的,在方法运行期间不会改变这个变量表的大小。

3、本地方法栈(线程共享)

存放着虚拟机使用到的Native方法服务,即调用操作系统提供接口的方法。

4、Java堆(线程共享)

Java堆是Java虚拟机所管理的内存最大的一块,也是被所有线程共享的一块内存区域。其在虚拟机创建时创建,所有的对象实例以及数组都要在堆上分配。
Java堆也是垃圾收集器管理的主要区域(GC堆),同时堆中还有进一步的划分,划分的目的是更好的回收内存,或更快的分配内存。
Java虚拟机规范中规定:Java堆可以处于物理上不连续的内存空间,只要逻辑上是连续的即可,这个是方便拓展,实现时可以固定大小也可以是可扩展的。
根据Java虚拟机规范的规定,Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可,就像我们的磁盘空间一样。在实现时,既可以实现成固定大小的,也可以是可扩展的,不过当前主流的虚拟机都是按照可扩展来实现的(通过-Xmx和-Xms控制)。如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。

5、方法区(线程共享)

方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。Java虚拟机规范对这个区域的限制非常宽松,除了和Java堆一样不需要连续的内存和可以选择固定大小或者可扩展外,还可以选择不实现垃圾收集。

6、运行时常量池(线程共享)

运行时常量池是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述等信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。

栈,堆,方法区三者关系

对象访问定位方式

在我们访问一个对象时,JAVA虚拟机内部是通过栈上的reference数据来操作堆上的具体对象,同时方法区又存储着这个对象的类型数据即对象所属的类的信息。JAVA虚拟机规范中只规定了一个指向对象的引用,并没有定义这个引用应该通过哪种方式去定位、去找到对象所在堆中的具体位置,所以对象的方法方法是取决于虚拟机实现而定的。
现在主流的方法方式有两种:使用句柄和直接指针

1.通过句柄访问对象
在这里插入图片描述
句柄:
可能大家不知道句柄的含义,其实很好理解,Java将一切都“看作”对象,但操纵的标识符实际是指向一个对象的“句柄”(Handle)。在其他Java参考书里,还可看到有的人将其称作一个“引用”,甚至一个“指针”。可将这一情形想象成用遥控板(句柄)操纵电视机(对象)。只要握住这个遥控板,就相当于掌握了与电视机连接的通道。但一旦需要“换频道”或者“关小声音”,我们实际操纵的是遥控板(句柄),再由遥控板自己操纵电视机(对象)。如果要在房间里四处走走,并想保持对电视机的控制,那么手上拿着的是遥控板,而非电视机。
此外,即使没有电视机,遥控板亦可独立存在。也就是说,只是由于拥有一个句柄,并不表示必须有一个对象同它连接。所以如果想容纳一个词或句子,可创建一个String句柄:
String s;
但这里创建的只是句柄,并不是对象。若此时向s发送一条消息,就会获得一个错误(运行期)。这是由于s实际并未与任何东西连接(即“没有电视机”)。因此,一种更安全的做法是:创建一个句柄时,记住无论如何都进行初始化:
String s = “asdf”;
使用句柄访问的话,那么Java堆中将会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含啥了对象的实例数据和类型数据各自的具体地址信息。
使用句柄访问的好处是reference中储存的是稳定的对象的句柄地址,当对象被移动(垃圾回收时进行对象移动)时候,只需要更新句柄中的对象实例部分指针,reference本身不用被修改。

2.通过直接指针访问对象
在这里插入图片描述
使用直接指针访问,那么Java堆中的布局中就必须考虑如何放置访问类型数据的相关信息,而reference中存储的直接就是对象地址。
使用直接指针访问的最大好处是 速度更快,它节省一次指针定位时间的开销(节省了句柄访问方法中的句柄池寻找实例池中的寻址时间),由于对象的访问在Java中非常频繁,因此这类开销积少成多时也是一项非常可观的成本。
一般的虚拟机都是通过指针直接访问对象而通过句柄的这种方法并不是很常用。

栈、堆、方法区的引用关系

假设变量a,b都为整形,变量book为Book类型,它引用了一个Book的实,bookName是Book类的非静态成员变量,为String类型。以下是堆栈及方法区的引用关系
在这里插入图片描述

详解栈的作用
栈的基本单位是栈帧
每当一个Java线程运行的时候 ,Java虚拟机会为该线程分配一个Java栈,该线程在执行某个Java方法的时候,向Java栈压入一个帧,这个帧用于存储参数、局部变量、操作数、中间运算结果等。 当这个方法执行完的时候,帧会从栈中弹出。
栈上的所有数据是私有的,其他线程,都不能访问该线程的栈数据,在函数中定义的一些基本类型的变量数据,和对象的引用变量都在函数的栈内存中分配,在一段代码块中定义一个变量时,Java就在栈中为这个变量分配内存空间 ,当该变量退出该作用域后,Java会自动释放掉为该变量所分配的内存空间。
可以看出,栈中存储的元素具有按照顺序入栈出栈的特征时才有必要存在栈中。

  • 3
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值