自己动手写写:关于jvm的理解(2)

上篇文章描述了Runtime Date Areas(运行时数据区)这一大块中的Method Area(方法区),此篇文章接着上一篇的内容往下讲。

 Heap 堆

      java程序在运行时创建的所有类实例或数组都放在同一个堆中。而一个Java虚拟机实例只存在一个堆内存空间,因此所有的线程均共享这个堆。又由于一个java程序独占一个Java虚拟机实例,因而每个java程序都有他自己的堆内存----他们之间不会相互影响。但是同一个Java程序的多线程却共享着同一个堆空间,在这种情况下,就得考虑多线程访问对象(堆数据)的同步问题了。

java虚拟机规范中并没有规定具体的实现需要准备多少内存,也没有说他必须怎么去管理他的堆空间,它仅仅告诉设计者

java程序需要在堆内存中为对象分配空间,并且程序本身并不去释放他。因此堆空间的管理(包括垃圾收集)问题得由设计者自己自行去考虑处理方式。对象的引用在很多地方都存在,如java栈、堆、方法区、本地方法栈,所以垃圾收集技术的设计和使用在很大程度上会影响到运行时数据区的设计,对于垃圾收集技术的深入描述,在后续的文章中会单独拿出来讨论。

     和方法区一样,堆空间也不必是连续的内存区。在程序运行时,他可以动态的扩展或收缩。事实上,一个实现的方法区可以在堆顶实现。换句话说就是当虚拟机需要为某个新装载的类分配内存是,类型信息和实际对象可以都在同一个堆上。因此,负责回收无用对象的垃圾收集器可能也要负责无用类的释放。另外,某些实现允许程序员或用户指定堆的初始化内存大小以及最大、最小值等。

 

对象的内部表示

     Java虚拟机规范并没有规定Java对象在堆中是如何表示的。对象的内部表示也影响着整个堆以及垃圾收集器的设计,他由虚拟机的实现者决定。

     java对象中包含的基本数据类型由他所属的类及其所有超类声明的实例变量组成。只要有一个对象引用虚拟机就必须能够快速定位对象实例的数据。另外,他也必须能够通过该对象引用访问相应的类数据(存储于方法区的类型信息)。因此在对象中通常会有一个指向方法区的指针

     一种可能的堆空间设计就是,把堆分为两个部分:一个句柄池,一个对象池如下图所示,

 

      一个对象引用就是一个指向句柄池的指针。句柄池的每个条目有两个部分:一个指向对象实例变量的指针一个指向方法区中类型数据的指针。这种设计的好处是有利于碎片的整理,当移动对象池中的对象时,句柄部分只需要更改一下指针指向对象的新地址即可----就是句柄池中的那个指针。缺点是每次访问对象的实例变量时都要经过两次指针传递。

     

      另一种设计方式是使对象指针直接指向一组数据,而该数据包括对象实例数据以及指向方法区中类数据的指针

        这样设计的优缺点正好和前面的相反,他只需要一个指针就可以访问对象的实例数据,但是移动对象就变得更加复杂。当使用这种堆的虚拟机为了减少内存碎片而移动对象的时候,他必须在整个运行时数据区中更新指向被移动对象的引用。

 

      有如下几个理由要求虚拟机必须能够通过对象引用得到类数据:当程序在运行时需要转换某个对象引用为另一种数据类型时,虚拟机必须要检查这种转换是否被允许,被转换的对象是否的确是被引用的对象或者他的超类,当程序在执行instanceof操作时,虚拟机也进行了同样的检查。在这两种情况下,虚拟机都需要查看被引用对象的类数据。最后,当程序中调用某个实例方法时,虚拟机必须进行动态绑定,换句话说,他不能按照引用类型来决定将要调用的方法,而必须根据对象的实际类,为此,虚拟机必须再次通过对象的引用去访问类数据。

      不管虚拟机的实现使用什么样的对象表示法,很可能每个对象都有一个方法表,因为对象表加快了调用实例方法时的效率,从而对java虚拟机实现的整体性能起着非常重要的作用。但是Java虚拟机规范中并未要求必须是使用方法表,所以并不是所有的实现中都会使用它。比如那些具有严格内存资源限制的实现,或者他们根本不可能有足够的额外内存资源来存储方法表。如果一个实现中使用方法表,那么仅仅使用一个指向对象的引用,就可以很快访问到对象的方法表。    


 

 

     上图展示了一种把方法表和对象引用联系起来的实现方式。每个对象的数据都包含一个指向特定数据结构的指针,这个数据结构位于方法区,他包括两部分:

      一个指向方法区对于类数据的指针

      此对象的方法表

      此方法的操作数栈和局部变量区的大小

      此方法的字节码

      异常表

 

       图5-7的堆内存设计会占用更多的内存,但是可以轻微提高一些效率,一般来讲,该方案只适用于内存足够充裕的系统。

 

         图5-5和图5-6中显示的还有另外一种数据,堆上的对象数据中还有一个逻辑部分,那就是对象锁,这是一个互斥对象。虚拟机中的每个对象都有一个对象锁,他被用于协调多个线程访问同一个对象时的同步。在任何时刻,只能有一个线程“拥有”这个对象锁,因此只有这个线程才能访问该对象的数据。此时其他希望访问这个对象的线程只能等待,直到拥有对象锁得线程释放锁,当某个线程拥有一个对象锁后,可以继续对这个所追加请求。但请求几次,必须对应地释放几次,之后才能轮到其他线程。比如一个线程请求了三次锁,在他释放三次锁之前,他一直保持“拥有”这个锁。

 

         很多对象在其整个生命周期内都没有被任何线程枷锁。在线程实际请求某个对象的锁之前,实现对象锁所需要的数据是不必要的。这样正如图5-5和图5-6所示,很多实现不在对象自身内部保持一个指向锁数据的指针。而只有当第一次需要枷锁的时候才分配对应的锁数据,但这是虚拟机需要某种间接方法来联系对象数据和对应的锁数据。例如把锁数据放在一个以对象地址为索引的搜索树中。

 

         除了实现锁所必须的数据外,每个Java对象逻辑上还与实现等待集合(wait set)的数据相关联。锁是用来实现多线程对共享数据的互斥访问的,而等待集合是用来让多个线程为完成一个共同目标而协调工作的。

 

         等待集合由等待方法和通知方法联系使用。每个类都从Object哪里集成了三个等待方法(三个名为wait()的重载方法)和两个通知方法(notify()以及notifyAll())。当某个线程在一个对象上调用等待方法时,虚拟机就阻塞这个线程,并把他放在了这个对象的等待集合中。知道另外一个线程在同一个对象上调用通知方法,虚拟机才会在之后的某个时刻唤醒一个或多个在等待集合中被阻塞的线程。正像锁数据一样,在实际调用对象的等待方法或通知方法之前,实现对象的等待集合的数据并不是必须的。二次,许多虚拟机实现都吧等待集合数据与实际对象数据分开,只有在需要时菜为此对象创建同步数据(通常是在第一次调用等待方法或通知方法时)。关于锁和等待集合的内容,在特定章节再具体阐述

 

        最后一种数据类型--------可以作为堆中某个对象映像的一部分,是与垃圾收集器有关的数据。垃圾收集器必须(以某种方式)跟踪程序引用的每个对象,这个任务不可避免地要附加一些数据给这些对象,数据的类型要视垃圾收集使用的算法而定。此外,对于不再被引用的对象,还需要指明他的终结方法(finalizer)是否已经运行过了。像线程锁一样,这些数据也可以放在对象数据外。有一些垃圾收集技术只在垃圾收集器运行时需要额外的数据。例如“标记并清除”算法就使用一个独立的位图来标记对象的引用情况。

 

        除了标记对象的引用外,垃圾收集器还要区分对象是否调用了终结方法。对于在其类中声明了终结方法的对象,在回收他之前,垃圾收集器必须调用它的终结方法。java语言对反之处,垃圾收集器对每个对象只能调用一次终结方法,但是允许终结方法复活(resurrect)这个对象,即允许改对象被再次引用。这样当这个对象再次被回收时,就不用再调用终结方法了。需要终结方法的对象不多,而需要复活的更少,所以对一个对象回收两次的情况很少见。这种用来标识终结方法的逻辑上时对象的一部分,但通常实现上不随对象保存在堆中。大部分情况下,垃圾收集器会在一个单独的空间保存这个信息。

 

数组的内部表示

在Java中,数组是真正的对象。和其他对象一样,数组总是存储在堆中。同样,和普通对象一样,实现的设计者将决定数组在堆中的表示形式。

和其他所有对象一样,数组也拥有一个与他们的类相关联的Class实例,所有具有相同维度和类型的数组都是同一个类的实例,而不管数组的长度(多维数组每一维的长度)是多少。例如一个包含3个int整数的数组和一个包含300个int整数的数组拥有同一个类。数组的长度只与实例数据有关。

数组类的名称由两部分组成:每一维用一个方括号“[”表示,用字符或字符串表示元素类型。比如,元素类型为int整数的、以为数组的类名为“[]”,元素类型为byte的三维数组为“[[[B”,元素类型为Object的二位数组为“[[Ljava/lang/Object”。

多为数组被表示为数组的数组。比如,int类型的二维数组,将表示为一个一维数组,其中的每个元素是一个一维int数组的引用,入下图所示:  


在堆中的每个数组对象还必须保存的数据是数组的长度、数组数据,以及某些指向数组的类数据的引用。虚拟机必须能够通过一个数组对象的引用得到此数组的长度,通过索引访问其元素(其间要检查数组边界是否越界)、调用所有数组的直接超类Object声明的方法等等。

未完待续 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值