初识Java虚拟机:运行时数据区域与对象访问方式

Java虚拟机:Java与C++之间进行内存动态分配和垃圾收集技术的虚拟机

JDK(Java Development Kit): 由 Java程序设计语言、Java虚拟机、JavaAPI类库这三部分组成,用于支持Java程序开发的最小环境。

JRE (Java Runtime Environment) :由 JavaAPI 类库中的 JavaSE API 子集 和 Java 虚拟机这两部分组成,用于支持Java程序运行的标准环境。

Java虚拟机将其管理的内存划分为若干个不同的数据区域,我们通过对线程、进程经过每个数据区域的作用来了解JVM

运行经过的数据区如图所示:

 

程序计数器(Program Counter Register):

       一块较小的内存空间,可以看作当前线程执行的字节码的行号指示器。为了让多线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间的计数器互不影响。这个计数器的值是选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成,线程私有。

Java虚拟机栈(Java Virtual Machine Stacks):

       描述Java方法执行的内存模型,每一个方法被执行时会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈从入栈到出栈的过程。线程私有。

本地方法栈(Native Method Stacks):

       本地方法栈与虚拟机栈发挥的作用十分相似,其区别在于虚拟机栈执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native方法服务。它们都会抛出 StackOverflowError 和 OutOfMemoryError 异常。

Java堆(Java Heap):

      Java虚拟机所管理的内存中最大的一块,被所有线程共享,在虚拟机启动时创建。该内存区域唯一目的就是存放对象实例,它是垃圾收集器管理的主要区域,因此也被成为“GC堆”(Garbage Collected Heap)

方法区(Method Area):

       线程共享,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。和 Java堆不需要连续内存和可以选择固定大小或者可扩展外,还可以选择不实现垃圾收集。当方法区无法满足内存分配需求时将抛出OutOfMemoryError异常。

 

那么在JAVA语言中,对象访问是如何进行的呢?

哪怕是最简单的对象访问也会涉及 Java栈、 Java堆、 方法区这三个最重要内存区域之间的关联关系。如下面的这句代码:

          Object obj = new Object();

        假设这句代码出现在方法体中,那“Object obj” 这部分的语义将会反映到 Java栈的本地变量表中,作为一个reference(引用)类型出现。

       “new Object()”这部分的语义将会反映到 Java堆中,形成一块存储了Object类型所有实例数据值(Instance Data,对象中各个实例字段的数据)的结构化内存,根据具体类型以及虚拟机实现的对象内存布局的不停,这块内存的长度是不固定的。另外,在Java堆中还必须包含能找到能查找到此对象类型数据(如对象类型、父类、实现的接口、方法等)的地址信息,这些类型数据则存储方法区中。

 

是不是觉得有点难懂?

       总结来说, Java栈的本地变量表存放了对象的引用类型,Java堆存放具体实例字段数据和类型数据的地址,方法区存放了该对象类型数据,比如其父类,对象类型,实现了什么接口,具体有什么方法等

 

         由于reference类型在Java虚拟机规范里面只规定了一个指向对象的引用,并没有定义这个引用应该通过哪种方式去定位,以及访问到Java堆中的对象的具体位置,因此不同虚拟机实现的对象访问方式会有所不同,主流的访问方式有两种:使用句柄和直接指针。

 使用句柄的对象访问方式:

         Java堆中将会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据和类型数据各自的具体地址信息,如下图所示

         

使用直接指针访问方式:

       Java堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,reference中直接存储就是对象地址。如下图所示

      这两种对象的访问方式各有优势,使用句柄访问方式的最大好处就是reference中存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动是非常普遍的行为)时只会改变句柄中的实例数据指针,而reference本身不需要被修改。

     使用直接指针访问方式的最大好处就是速度更快,节省了一次指针定位的时间开销,由于对象的访问在Java中非常频繁,因为这类开销的执行成本也不小。

 总结:我们可以把这两种方式看做一级指针访问与二级指针访问的差异比较。句柄访问方式属于二级指针的访问方式,当对象移动十分频繁时,这种访问方式不需要改变Java栈里本地变量表的信息,需要改变的句柄池的指针地址。(改变句柄池的指针地址开销比改变Java栈的变量表信息开销的小)。而直接指针访问方式就是节省访问开销,当系统对象被高频率访问时这种访问方式更好。

 

       

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值