《深入理解java虚拟机》学习笔记——第二章

java内存区域与内存溢出异常

运行时数据区域

image

  1. 程序计数器(线程私有)
  • 线程执行的字节码的行号指示器
  • 各个线程拥有独立的程序计数器
  • 执行java方法时,存的是字节码指令地址
  • 执行Native方法时,计数器值为空(Undefined)
  1. java虚拟机栈(线程私有)
  • 方法执行时创建栈帧(stack frame)
  • 栈帧储存局部变量表、操作数、动态链接、方法出口

线程申请的栈深度大于jvm允许的深度,抛出StackOverFlowError。
动态扩展时无法申请到足够的内存,抛出OutOfMemeryError。

  1. 本地方法栈(线程私有)
  • 与虚拟机栈类似,不过这里是执行Native方法的
  1. Java堆(线程共享)
  • 所有线程共享
  • 虚拟机启动时创建
  • 用于存放对象实例
  • 垃圾收集器管理的主要区域(java堆=GC堆)
  • 物理上可以不连续,逻辑上连续即可
  1. 方法区(线程共享)
  • 用于存储已被虚拟机加载的类信息、常量、静态变量、及时编译器编译后的代码
  • 很少有垃圾收集行为(主要用于回收常量池和对类型的卸载)
  1. 运行时常量池(线程共享)
  • 方法区的一部分
  • 存放编译期生成的各种字面量和符号引用
  • 运行期间也可以将新的常量放入池中(例如String类的intern()方法)
  1. 直接内存
  • 使用Native函数库直接分配堆外内存,通过存储在java堆中的DirectByteBuffer对象作为这块内存的引用进行操作
  • 避免了java堆和native堆中来回复制数据

HotSpot虚拟机对象

对象的创建

  1. 对于一条new指令,检查这个指令的参数是否能在常量池中定位到一个符号引用,检查符号引用代表的类是否被加载、解析和初始化过。没有则进行类加载过程。
  2. 为新生对象分配内存:
  • 内存连续:指针碰撞(Bump the Pointer),用过的内存放在一边,空闲的在另一边,中间是指针作为分界点指示器。罢指着你向空闲空间那边挪动一段与对象大小相等的距离。
  • 内存不连续:空闲列表(Free List),记录空闲堆
  1. 考虑是否线程安全,不同线程可能同时修改指针指向的位置,解决方案:
  • 同步处理 :CAS配上失败重试的方式保证更新操作的原子性
  • 本地线程分配缓冲TLAB:哪个线程要分配内存,就在哪个线程的TLAB上分配,只有TLAB用完并分配新的TLAB时,才需要同步锁定。
  1. 内存空间初始化成 零值
  2. 对对象进行必要的设置。包括对象属于哪个类,如歌才能找到类的元数据信息、对象的哈希码,对象的GC分代年龄。这些东西存在对象头(Object Header)中。
  3. 从java程序的角度出发,还需要执行方法

对象的内存布局

对象的内存布局可以分为3块区域:对象头(Header)、实例数据(Instance Data)和填充对象(Padding)。

  1. 对象头
  • 存储对象自身的运行时数据(mark word)
    Mark Word
  • 类型指针:对象指向它的类元数据的指针。JVM通过这个指针确定这个对象是哪个类的实例。
  1. 实例数据部分
  • 程序代码中定义的各种类型的字段内容。
  • 相同宽度的字段总是被分配到一起:longs/doubles 、 ints 、 shorts/chars 、 bytes/boolean 、 oops(Ordinary Object Pointers)。
  1. 对齐填充(不是必然存在的),起着占位符的作用。当对象实例数据没有对齐8字节整数倍时,就需要通过对齐填充补全。

对象的访问定位

目前主流的访问方式:使用句柄和直接指针两种。

访问对象

  1. 使用句柄
  • java堆上有一个句柄池,reference 存储句柄地址。
  • 句柄中包含实例数据和类型数据的地址。
  • 好处:对象被移动(GC)指挥改变句柄中的实例数据指针,reference地址不用改
  1. 使用指针(Hot Spot使用这种)
  • reference中存储对象地址
  • java堆上包含对象实例数据和对象类型数据的地址
  • 好处:使用指针速度更快,节省了一次指针定位的时间开销

OutOfMemeryError异常

  1. java堆溢出

-Xms代表堆最小值,-Xmx代表堆最大值。
-XX:+HeapDumpOnOutOfMemeryError可以让虚拟机在出现内存溢出异常时Dump当前的内存堆转储快照(*.hprof),以便分析

  • 分析出现了内存泄露(Memery Leak),还是内存溢出(Memery Overflow)
  • 如果内存泄露,通过工具查看泄露对象到GC Root的引用链,找到泄露对象是通过怎样的路径与GC Root关联才导致无法回收的,由此定位泄露代码的位置。
  • 如果是内存溢出,尝试调整堆参数(-Xms,-Xmx),对比物理机内存,尝试减少运行期内存消耗
  1. 虚拟机栈和本地方法栈溢出

HotSpot虚拟机不区分这两个栈,所以-Xoss(设置本地方法栈大小)存在,但是无效。栈容量只由-Xss设定。

  • 线程请求栈深度大于虚拟机允许最大深度,StackOverflowError
  • 虚拟机扩展栈时无法申请到足够的内存,OutOfMemeryError
  1. 方法区和运行时常量池溢出

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

  • jdk1.6以及之前的版本,常量池分配在永久代,用-XX:PermSize和-XX:MaxPermSize限制方法区大小和常量池大小
  1. 本机直接内存溢出

直接内存容量通过-XX:MaxDirectMemerySize指定,如果不指定,默认等于java堆最大值(-Xmx)

  • DirectByteBuffer在抛出异常时,并没有真正向操作系统申请分配内存,而是通过计算得知内存无法分配,手动抛出异常
  • 真正申请分配内存的方法是unsafe.allocateMemery()

猜你喜欢

转载自blog.csdn.net/qq_34064803/article/details/87857447