内存分布

把内存管理交给虚拟机,但不可依赖于虚拟机的内存管理机制,这就是学习虚拟机的原因之一


  • 内存划分
  • 对象创建
  • 对象内存布局
  • 对象访问定位

Java代码是要运行在虚拟机上的,而虚拟机在执行Java程序的过程中会把所管理的内存划分为若干个不同的数据区域,如下图,这些区域都有各自的用途。其中有些区域随着虚拟机进程的启动而存在,而有些区域则依赖用户线程的启动和结束而建立和销毁内存划分

  • 程序计数器(PC):和计组中的概念一样,都是存放程序,或者说是进程要执行的下一条字节指令,每个线程都有自己私有的程序计数器
  • 栈:描述方法执行的内存模型,每个方法在执行的时候都会创建一个栈帧,用于存放局部变量表、操作数帧、动态链接、方法出口等信息,方法的开始结束对应于栈帧的入栈出栈
  • 本地方法栈:和栈类似,不过对应的是native方法,即Java调用非Java代码的接口
  • 堆:线程共享,所有对象和数组都要在堆上分配内存,堆是垃圾收集器作用的区域,因此成为GC堆
  • 方法区:线程共享,存储已被虚拟机加载的类信息、常量、静态常量、JIT编译后的代码数据
  • 运行时常量池:存放编译期产生的各种字面量和符号引用,在运行期间也能将常量放入池中
  • 直接内存:可以使用native函数库直接在堆外分配内存,然后通过堆中的DirectByteBuffer对象引用这块内存

对象的内存布局分为对象头、实例数据和对齐填充三部分
对象内存布局

  • 对象头:运行时数据指的就是GC分代年龄、HashCode、锁状态标志等数据,类型指针即对象指向它的类元数据的指针,通过这个指针来确定这个对象是哪个类的实例
  • 实例数据:存储真正的有效信息,即程序代码中定义的各种类型的字段内容
  • 对齐填充:自动内存管理系统要求对象的大小必须是8字节的整数倍,对象头固定是8字节的1倍或2倍,所有当实例数据部分没有对齐是,就需要对齐填充

堆上的对象访问分为两种

  • 句柄访问:在堆划分出一块句柄池,reference存储的就是对象的句柄地址,而句柄则保存对象实际的真实地址,这种方式的好处就是reference保存的是稳定的句柄地址,对象被移动时,只改变句柄中对应实例的数据指针
  • 直接指针:reference直接保存对象的实际地址,好处就是速度快
    对象访问