一、JVM内存区域

JVM内存分布.png

一、运行时数据区域

  • 程序计数器:
    • 存储内容:可看做当前线程所执行的字节码行号指示器
    • 线程私有
    • 如果执行的是Native方法,计数值为空
    • 内存区域中唯一一个在JVM规范中没有任何规定OOM情况的区域
  • JVM栈:
    • 描述的是Java方法执行的内存模型;
    • 线程私有
    • 每个方法执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息
    • 局部变量表中存放了各种基本数据类型、对象引用(不是对象本身,仅仅是一个引用)、和返回类型
    • StackOverflowError:如果线程请求的栈深度大于虚拟机所允许的深度,抛出此异常;
    • OOM:如果虚拟机栈可以动态扩展,如果扩展时无法申请到足够的内存,抛出此异常;
  • 本地方法栈:
    • 同JVM栈,只不过执行的是Native方法(本地方法,C++编写)
  • Java堆:
    • 存放对象的实例,几乎所有的对象实例在这里分配内存
    • JVM所管理的内存最大的一块
    • 线程共享
    • 垃圾回收的主要区域
    • 可通过参数-Xmx(最大)和-Xms(最小)控制其大小
    • OOM:如果堆中没有内存完成实例分配,并且堆无法再扩展时,抛出此异常
  • 方法区:
    • 存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据
    • 线程共享
    • JDK1.7之后,已经把原本放在方法区中的字符串常量池移出
    • 通过-XX:MaxPermSize控制其大小
    • OOM:当方法区无法满足内存分配需求时,抛出此异常

不过在JDK1.8之后,方法区已经被移至Metaspace(元空间)、字符串常量池被移入堆中
关于元空间可查看https://www.cnblogs.com/duanxz/p/3520829.html

  • 运行时常量池:
    • 存放Class文件中常量池(存放编译器产生生成的各种字面量和符号引用)中的内容,还会存储翻译出来的直接引用
    • 属于方法区的一部分
    • OOM:当常量池无法再申请到内存时抛出该异常
  • 直接内存:
    • 它并不是JVM运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域
    • 在JDK1.4之后新加入了NIO类,引入了一种基于通道与缓冲区的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。
    • 在配置服务器JVM内存分布时,不可忽略该区域的分配,否则有可能出现OOM异常

二、HotSpot虚拟机对象探秘

1. 对象的创建

创建类实例的过程.png
1.1 为新生对象分配内存(两种方式)
  • 指针碰撞:假设Java堆是绝对规整的,所有用过的内存都在一边,空闲的内存在另一边,中间放着一个指针作为分界点的指示器,分配内存是仅仅就是将指针向空闲内存那边挪动一段与对象相等的距离
    指针碰撞.png
  • 空闲列表:如果Java堆中的内存不是规整的,已使用的内存和空闲内存相互交错,JVM会维护一个列表,并记录那些内存是可用的,在分配内存时从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录
    空闲列表.png
1.2 分配内存需要注意

对象创建在虚拟机中时非常频繁的行为,即使仅仅修改一个指针所指向的位置,在并发情况下并不是线程安全的,可能出现在给对象A分配内存,指针还没来得及修改,对象B有同时使用了原来指针来分配的情况。解决这个问题有两种方案;

    1. 采用CAS配上失败重试方法保证更新操作的唯一性
    1. 把内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在Java堆中预先分配一小块内存,称为本地线程分配缓冲(Thread Local Allocation Buffer --- TLAB)哪个线程要分配内存,就在那个线程的TLAB上分配,只有TLAB用完并分配新的TLAB时,才需要同步锁定。可通过使用-XX:+/-UseTLAB来设定使用此方式
1.3 设置对象信息

包括例如对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息。

2. 对象的内存布局

对象的内存布局 (1).png
1. 对象头
  • 存储对象自身运行时数据:如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等
  • 另一部分为类型指针(对象指向它的类元数据的指针):虚拟机通过这个指针来确定这个对象是哪个类的实例。
2. 实例数据

实例数据部分是对象真正存储的有效信息,也是程序中多定义的各种类型的字段内容。

3. 对齐填充
  • 该部分并不是必然存在的,也没有特殊的含义
  • 它仅仅起着占位符的作用
  • 因为HotSpot VM的自动内存管理系统要求对象的大小必须是8字节的整数倍,当没有对齐时,就需要通过对齐填充来补全

3. 对象的访问定位

  • 使用句柄:
    • Java堆中会划分出一块内存来作为句柄池
    • reference中存储就是对象的句柄地址,而句柄中包含了对象实例与类型数据各自具体的地址


      通过句柄访问对象.png
  • 使用直接指针:
    通过直接指针访问对象.png
  • 两种访问方式优势对比
    • 使用句柄最大的好处就是reference中存储的时稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而reference不需要改变
    • 使用直接指针的最大好处就是快(Sun HotSpot使用该种方式访问对象)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容