运行时数据区域

  • 程序计数器(线程私有)
  • Java虚拟机栈(线程私有)
  • 本地方法栈(线程私有,执行本地方法)
  • Java堆:所有的对象实例及数组都在堆上分配。基本采用分代回收,新生代(Eden/From Survivor/To Survivor),老年代。
  • 方法区:存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。Non-Heap非堆。HotSpot称之为永久代(也有回收,主要针对常量和对类的卸载。但效果一般,因为回收条件苛刻。)
  • 运行时常量池:本是方法区的一部分,已经独立移出。用于存放编译期生成的各种字面量和符号引用。
  • 直接内存:不是虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域。JDK1.4 NIO 使用Native函数库直接分配堆外内存。

对象探秘

  1. 对象创建

    • 为新生对象分配内存,有两种方式可用。受到不同收集器的影响。
      • 指针碰撞(Bump the pointer)。堆中内存规整,用指针划分使用过的和未使用的两块空间,分配内存就是指针向未使用空间移动。
      • 空闲列表(Free List)。虚拟机需要维护一个列表,记录哪些内存块是可用的,分配空间时从列表中找到一块足够大的空间划分给新对象,同时更新列表。
      • Serial、ParNew带等Compact过程:指针碰撞;CMS等基于Mark-Sweep:空闲列表。
    • 并发情况下争用同一地址创建对象,如何解决?
      • 虚拟机采用CAS+失败重试来保证更新操作的原子性。
      • 方案二:本地线程分配缓冲(Thread local allocation buffer, TLAB). 每个线程预先在堆中拿出一小块私有,缓冲区分配满了,再同步锁定,分配新TLAB. 通过-XX:+/-UseTLAB设定。
  2. 对象的内存布局

    • 对象头(Header,Mark Word)
      • 存储对象自身的运行时数据,如哈希码,GC份代年龄,锁状态标志,线程持有的锁,偏向线程ID,偏向时间戳等。
      • 有一部分虚拟机实现,会在对象头中存储类型指针。与对象的访问定位有关。
    • 实例数据(Instance data)
      • 父类+子类的各种类型的字段内容。存储顺序受到VM分配策略和字段在源码中定义顺序影响。
    • 对其填充(Padding)
      • HotSpot虚拟机要求对象起始地址必须是8字节的整数倍。
  3. 对象的访问定位
    • 句柄访问:包含了对象实例数据和类型数据各自的具体地址信息。
    • 直接指针访问:实例对象中存储类型数据指针。

异常

  • OutOfMemoryError
    • 堆溢出
    • 虚拟机栈和本地方法栈溢出
    • 方法区和运行时常量池溢出
    • 本机直接内存溢出