关于Java虚拟机二三事(二)

来源:互联网 发布:淘宝网,爱仕达炒锅 编辑:程序博客网 时间:2024/06/10 09:36

前言:

  在了解了JVM内存的结构之后,我们可以更进一步了解,对象在创建到销毁时,内存分配及回收的具体过程和策略。此处先学习一下Java对象的创建过程。

  Java虚拟机执行过程中,对象的生命周期可以大概分成三个阶段:对象的创建、对象的使用、对象的销毁三个阶段,具体可如下图所示。
  对象生命周期

  上图给出的是较为概要的对象生命周期。并针对对象的创建进行了具体的步骤描述:
    1、当Java源文件被编译成Java字节码文件(.class)之后,JVM便开始按照Java字节码文件中的代码执行顺序逐步执行
    2、当遇到new指令时,JVM首先判断目标类是否被加载,若未被加载,则先用类加载器进行加载,若已加载,则先在堆上进行内存分配。
   3、堆上分配有两种方式,第一种指针碰撞,第二种则为空闲列表。内存分配完毕之后,开始按照既定的类结构进行对象初始化零值,此阶段是将对象的所有属性初始化为系统的默认值(整型初始化默认值为0,之类的)。
   4、对象初始化方法,此阶段才正式将对象按照程序员的要求进行初始化。该阶段完成之后,对象的创建全部完成。
   5、对象创建完成之后,便可以被使用
   6、当对象不再有任何地方存在引用时,则会被垃圾回收器进行回收。

  至此,整个对象的生命周期结束。

  这里需要解释一下,对象在堆上的分配的两种方式:
   1.指针碰撞:
  假设Java堆中内存是绝对规整的,即所有用过的内存都放在一边,空闲的内存放在另一边,中间放着一个指针作为分界点的指示器,那所分配内存就是仅仅把整个指针向空闲指针那边移动一段与对象大小相等的距离。,这种分配方式即为“指针碰撞”
  指针碰撞

   2.空闲列表:
如果Java堆中的内存并不是绝对规整的,已使用的内存和空闲内存相互交错,那么就没有办法简单地进行指针碰撞,虚拟机就必须额外维护一个列表,并记录上哪些内存块是可用的,在分配的时候从列表中找出一块足够大的空间划分给对象实例,并更新列表上的记录。这种分配方式成为“空闲列表”。
空闲列表

注意:
  除了如何划分可用空间之外,还需要考虑一个问题是,对象创建在虚拟机中是非常频繁的行为,即使仅仅修改一个指针所指向的位置,在并发情况下也未必会是线程安全的,可能出现正在给对象A分配内存,指针尚未来得及修改,对象B又同时使用了原来的指针来分配内存的情况。
  
  解决上述问题有两种方案:
   方案一:对分配内存空间的动作进行同步处理,即采用CAS(Campare And Swap)+失败重试机制进行不断循环操作,保证操作原子性
  方案二:内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在Java堆中预先分配好一小块内存,称为本地线程分配缓冲(TLAB,Thread Local Allocation Buffer,要是不理解的话,可以参考Java程序设计语言中ThreadLocal的相关知识),哪个线程需要分配内存,就在哪个线程的TLAB上分配,只有TLAB用完并分配新的TLAB时,才需要同步锁定。

  对象的内存布局:
   在了解了JVM内存分配机制之后,我们会存在这么一个疑问,内存分配仅仅是对象创建过程的一部分,那么对象在内存中的存储布局到底是怎么样的,即一个对象必须包含哪些内容?
  
   在HotSpot虚拟机中,对象子啊内存中存储的布局可分为三个区域:对象头(Header)、实例数据(Instance Data)和对齐填充。

  • 对象头:用于存储对象自身的运行时数据,这部分数据包含了并发情况下的基本信息。
    • 哈希码(HashCode),对象的唯一标识
    • GC分代年龄,垃圾回收时,对象存活的时间
    • 锁状态标识:并发情况下,对象是否被锁定
    • 线程持有的锁:并发情况下,线程所持有的锁,一般分为轻量级锁和重量级锁,后续会详细介绍
    • 偏向线程ID:并发情况下,针对锁优化而新增的一类锁:偏向锁,偏向线程ID表示当前对象持有该锁
    • 偏向时间戳:获得偏向锁的时间戳。
    • 类型指针:即对象指向它的类元数据的指正,虚拟机通过这个指针来确定对象是哪个类的实例。
  • 实例数据:记录对象中属性、方法等信息。

    对象的访问方式:
      建立对象的主要目的是为了访问对象,Java程序需要通过栈上的reference数据来操作堆上的具体对象。由于reference类型在Java虚拟机规范中只规定了一个指向对象的应用,并没有定义这个引用应该通过何种方式去定位、访问堆中的对象的具体位置,所以对象访问方式也取决于虚拟机的实现而定。目前主流的访问方式有使用句柄和直接指针两种。
      对象访问方式有两种句柄和指针
      1.通过句柄的访问:
    使用句柄
       2、通过指针的访问方式
       直接指针

      访问方式的比较:
      句柄访问方式的好处:reference中存储的是句柄的地址,在对象被移动时只会改变句柄中的实例数据指针,而reference本身不需要被修改。
      指针访问的好处是访问速度快。减少了一次指针定位的开销。

原创粉丝点击