JVM学习笔记之内存区域

JVM学习笔记之内存区域

运行时数据区:

java虚拟机在执行java程序的过程中把它所管理的内存划分为若干个不同的数据区域,用于不同的用途,并具有不同的创建和销毁时间。

  1. 程序计数器:当前线程所执行的字节码的行号指示器。字节码解释器工作时通过改变该计数器的值来选取下一条即将执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都要依赖该计数器。
    java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间来实现的。因此每个线程都需要有一个独立的程序计数器,互不干扰,即线程私有的内存。
    如果执行的是java方法,即该内存记录的是正在执行的字节码指令的地址;如果执行的Native方法,该计数器值为空。
    该内存区域是JVM规范中唯一一个没有规定任何OutOfMemoryError情况的区域。
  2. Java虚拟机栈:线程私有的内存,生命周期与程序计数器相同。它描述的是java方法执行的内存模型。每个方法在执行时都会创建一个栈帧用于存储局部变量表,操作数栈,动态链接,方法出口等信息。
    每个方法从调用直至执行结束对应着从虚拟机栈进行一次入栈到出栈的过程。平常所说的堆栈中的栈对应着虚拟机栈。
    局部变量表存放了编译时可知的基本数据类型、对象引用(不等同于对象本身)和returnAddress类型(指向了一条字节码指令的地址)。
    注意:long和double占用两个局部变量空间,其他基本数据类型占用一个。
    局部变量表所需的内存空间是编译期间完成分配的,运行期间不会改变。
    异常情况:(1)线程请求的栈深度大于虚拟机允许的栈深度:StackOverFlowError; (2)在支持扩展的JVM规范里,如果扩展无法申请到足够的内存,OutOfMemoryError。
  3. ** 本地方法栈**:与java虚拟机栈的区别是:前者为执行java方法(即字节码)服务,而后者为执行本地方法服务。
    异常情况:StackOverFlowError和OutOfMemoryError。
  4. java堆:java虚拟机管理内存中最大的一块,在虚拟机启动时创建,被所有线程共享。可以划分为多个线程私有的分配缓冲区(Thread Local Allocation Buffer, TLAB)。处于物理不连续但逻辑连续的内存空间。
    唯一目的:存放对象实例。几乎所有的对象实例和数组都在堆上分配。
    java堆是垃圾收集器管理的主要内存区域,亦称为GC堆。
    异常情况:在堆中没有完成内存分配且堆大小无法扩展时:OutOfMemoryError。
  5. 方法区:线程共享的内存区域,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。内存不一定连续,可扩展,甚至可以选择不实现垃圾收集。
    该区域的内存回收目标主要是:常量池的回收和类型的卸载。
    异常情况:OutOfMemoryError。
    运行时常量池:方法区的一部分。Class文件除类的版本、字段、方法和接口外,还具有常量池。该部分是用于存放编译时期生成的各种字面量和符号引用,在类加载后进入方法区的运行时常量池中存放。
    运行时常量池具备动态特性,因为java语言并不要求常量一定在编译期才能产生,因此也可以在运行期间将新的常量入池,比如基本类型包装类(包装类不管理浮点型,整型只会管理-128到127)和String(也可以通过String.intern()方法可以强制将String放入常量池)
  6. 直接内存:非虚拟机运行时数据区的部分
    在 JDK 1.4 中新加入 NIO (New Input/Output) 类,引入了一种基于通道(Channel)和缓存(Buffer)的 I/O 方式,它可以使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作。可以避免在 Java 堆和 Native 堆中来回的数据耗时操作。
    直接内存的大小并不受到java堆大小的限制,甚至不受到JVM进程内存大小的限制。它只受限于本机总内存(RAM及SWAP区或者分页文件)大小以及处理器寻址空间的限制(最常见的就是32位/64位CPU的最大寻址空间限制不同)
    异常情况:受到本机内存限制,如果内存区域总和大于物理内存限制从而导致动态扩展时出现OutOfMemoryError异常。

虚拟机对象:

基于常用的HotSpot虚拟机和常用的内存区域java堆

  1. 对象的创建:关键字:new
    (1)虚拟机遇到new指令,先根据该指令的参数去常量池定位对应的类的符号引用并且检查这个类符号代表的类是否被加载,解析和初始化过。如果没有,执行相应的类加载过程。
    (2)接着为该新生对象分配内存。对象所需的内存大小在类加载完成后便可确定。
    内存分配方法:指针碰撞法:如果java堆中的内存是规整的,一边是空闲内存,一边是使用过的内存,则将指针移动与对象内存大小相同的距离。
    空闲列表法:如果java堆中的内存是不规整的,则需要维护一个列表来记录哪些内存块是可用的。分配的时候从列表中找到一块足够大的空间分配给该对象,并更新列表上的记录。
    java堆的内存是否规整是由采用的垃圾收集器是否带有压缩整理功能决定的,指针碰撞:Serial、ParNew等带Compact过程的垃圾收集器, 空闲列表法:CMS这种基于Mark-Swap算法的垃圾收集器。
    内存分配的线程安全问题:对分配内存空间的动作进行同步处理,实际上虚拟机采用的是CAS配上失败重试的方法来保证更新操作的原子性;
    把内存分配的操作按照线程划分在不同的空间之中进行,即每个线程在java堆中预先分配一小块内存,称为本地线程分配缓冲(TLAB),哪个线程要分配内存就在那个线程的TLAB上分配。只有TLAB用完并分配新的TLAB时,才需要同步锁定。虚拟机是否使用TLAB,可以根据-XX:+/-UseTLAB参数来设定。

    	   (3)内存分配完成后,虚拟机将分配的内存空间都初始化为零值(不包括对象头)。如果使用TLAB时,该初始化工作也可以提前至TLAB分配时同时进行。该操作保证了对象的实例字段在java中不需要初始化赋值就可以直接使用。
    	   (4)接下来就是填充对象头,把对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息存入对象头。
    	   (5)执行 new 指令后执行 init 方法,将对象按照程序员的意愿进行初始化后才算一份真正可用的对象创建完成。
    
  2. 对象的内存布局
    对象的内存布局包括:对象头,实例数据以及对齐填充。

    1. 对象头:包含两部分,第一部分用于存储对象自身的运行时数据,如哈希码、GC 分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等,32 位虚拟机占 32 bit,64 位虚拟机占 64 bit。官方称为 ‘Mark Word’。
      MarkWord是非固定的数据结构以便在极小的空间存储尽可能多的信息,它会根据对象的状态复用自己的存储空间。
      第二部分是类型指针,即对象指向它的类元数据(存储在方法区)的指针。虚拟机通过这个指针确定这个对象是哪个类的实例。另外,如果是 Java 数组,对象头中还必须有一块用于记录数组长度的数据,因为普通对象可以通过 Java 对象元数据确定大小,而数组对象不可以。此外,并非所有的虚拟机实现都必须在对象数据上保留类型指针。
    2. 实例数据:对象真正存储的有效信息,即程序代码中定义的各种类型的字段。存储顺序受虚拟机分配策略参数和字段在java源码中的定义顺序的影响。
      例如:HotSpot虚拟机默认的分配策略为longs/doubles、ints、shorts/chars、bytes/booleans、opps(Oridinary Object Pointer)。相同宽度的字段被分配到一起,在满足该前提的情况下,在父类中定义的变量会出现在子类之前。如果CompactFields的参数值为true(默认为true),则子类中较窄的变量也可能会插入在父类之前。
    3. 对齐填充:可选,仅仅起占位符的作用。譬如:HotSpot虚拟机的自动内存管理系统要求对象的起始地址必须是8字节的整数倍,即对象大小必须是8字节的整数倍。因为对象头部分正好是8字节的倍数,所以如果实例数据没有对齐时,就需要通过对齐填充来补全。
  3. 对象的访问定位
    java程序需要通过栈上的reference数据来操作堆上的具体对象。访问方式有:

    1. 句柄访问:java堆分配出一块内存作为句柄池,reference中存储的就是对象的句柄地址。而句柄中包含了对象实例数据与类型数据各自的具体地址信息。优点是:在对象被移动时,只需要修改句柄中的实例数据指针,而不需要修改reference本身。
    2. 直接指针访问:reference中存储的是对象地址,java堆对象的布局需要考虑如何放置访问类型数据的相关信息。优点:速度更快,节省了一次指针定位的时间开销。因为对象访问在java中是非常频繁的,所以节省的开销是非常可观的。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值