Bootstrap

JVM(三、运行时数据区)

1、运行时数据区

 数据区按照线程使用情况分两大类:

  • 由所有线程共享的数据区:字面意思,所有线程都使用同一个。

        堆和方法区、存储类的静态数据和对象数据、需要垃圾回收

  • 线程隔离的数据区:每个线程有自己的,例如:每个线程都有自己的程序计数器

        虚拟机栈、本地方法栈、程序计数器、不需要垃圾回收

1.1、程序计数器

        程序计数器(Program Counter Register)是一块较小的内存空间,线程私有,如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是本地(Native)方法,这个计数器值则应为空Undefined)。此内存区域是唯一一个《Java虚拟机规范》中没有规定任何OutOfMemoryError情况的区域。

1.2、Java虚拟机栈

        线程私有,是Java方法执行的线程内存模型,每个方法执行的时候,会创建一个栈帧,栈帧存储局部变量表、操作数栈、动态链接、方法出口等信息,每一个方法被调用就会有一个栈帧入栈,执行完毕则出栈。

        在《Java虚拟机规范》中,对这个内存区域规定了两类异常状况:如果线程请求的栈深度大于虚 拟机所允许的深度,将抛出StackOverflowError异常;如果Java虚拟机栈容量可以动态扩展,当栈扩展时无法申请到足够的内存会抛出OutOfMemoryError异常。

1.3、本地方法栈

        本地方法栈和虚拟机栈类似,区别是虚拟机栈执行的是Java方法,本地方法栈执行的是本地方法服务。Hot-Spot虚拟机直接就把本地方法栈和虚拟机栈合二为一。

1.4、堆

        Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例。
        Java堆是垃圾收集器管理的内存区域,由于垃圾回收器都是基于分代收集理论设计的,所以堆又被划分为新生代、老年代、永久代。新生代被划分为:Eden、From Survivor、To Survivor。当前Java虚拟机一般都是按照内存可扩展来实现的,通过-Xmx和-Xms设定,如果堆中没有内存完成实例分配,并无法扩展时,则会抛出OutOfMemoryError 异常。

1.5、方法区

        方法区各个线程共享的区域,用于存储已被虚拟机加载的类型信息、常量、静态变量,即时编译后的代码缓存等数据。

1.6、运行时常量池

        运行时常量池时方法区的一部分,

2、对象的创建、布局、访问

2.1、对象的创建

        在运行时数据区中,我们知道方法区中会存储类的信息,所以当用new关键字创建对象的时候,首先会检查这个常量池是否有类符号引用,并检查这个符号引用代表的类是否以为加载,如果没有则会进行类加载过程。

        类加载检查通过后,虚拟机将为新生对象分配内存,对象所需的内存大小在类加载完成后就可以确定。

        给对象分配内存可以根据内存是否规整分为两类:

  • 内存规整:指针碰撞
            当使用Serial、ParNew等带压缩整理过程的收集器时,垃圾回收后,会把使用过的内存放在一边,没有使用过的放在另一边,这样的话,在中间放一个指针作为分配内存的指示器,分配内存时把对象放在没有使用的内存,指针移动对象大小的位置。
  • 内存不规整:空闲列表
    当使用CMS这种基于清楚算法的收集器时,就会采用空闲列表的方式,空闲列表是虚拟机维护一个列表,记录哪些内存是可用的,在分配的时候从列表中找到一块足够的内存划分给对象,更新列表的值。

        分配内存时如何保证线程安全:

  • 对分配内存操作做同步处理-CAS
  • 对每个线程预先分配一小块内存(Thread Local Allocation Buffer TLAB),缓冲区用完了才需要同步操作,是否使用TLAB,可以通过-XX:+/-UseTLAB参数来设定。

在虚拟机中对象就算完成了,但是对于Java程序来看,后面还有初始化,执行Init方法等。

2.2、对象的内存布局

对象可以划分为三个部分:对象头、实例数据、对齐填充

2.2.1、对象头

对象头分为两个部分:
        Mark Word:哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,在32位和64位的机器中分别位32个bit和64个bit

        类型指针:对象执行类型元数据的指针

实例数据:真正存放对象数据的部分。

对齐填充:hotspot自动内存管理要求任何对象的大小都是8字节的倍数。所有需要对齐部分凑够8字节的倍数。

2.3、对象的访问定位

        访问方式主要有两种:句柄、直接指针

         句柄: 如果使用句柄访问的话,Java堆中将可能会划分出一块内存来作为句柄池, reference 中存储的就 是对象的句柄地址。

        直接指针:如果使用直接指针访问的话,Java堆中对象的内存布局就必须考虑如何放置访问类型数据的相关信息,reference中存储的直接就是对象地址。

 

        使用直接指针来访问最大的好处就是速度更快,它节省了一次指针定位的时间开销,由于对象访 问在Java 中非常频繁,因此这类开销积少成多也是一项极为可观的执行成本,就本书讨论的主要虚拟 机HotSpot 而言,它主要使用第二种方式进行对象访问。
;