[深度理解java虚拟机] 第2章 java内存区域与内存溢出异常

个人博客文章链接

2.1 概述

  • 了解java虚拟机内存分配,解决内存泄漏和溢出方面的问题。

2.2 运行时数据区域

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0p395Rdg-1579516376624)(en-resource://database/848:1)]

2.2.1 程序计数器

  • 是一块较小的内存区域,可以看作是当前线程所执行的字节码的行号指示器,每个线程都有一个独立的程序计数器。
  • 如果执行的是Java方法,计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Native方法,这个计数器为Undefined;
  • 此内存区域是在java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。

2.2.2 java虚拟机栈

  • 每个方法在执行的同时都会创建一个栈帧用于存储局部变量表,操作数栈,动态连接,方法出口信息。每一个方法从调用到执行完成,都对应着一个栈帧在虚拟机栈中的入栈到出栈。
  • 在规范中定义了两种异常状况:如果线程请求栈的深度大于虚拟机栈所允许的深度,抛出StackOverflowError异常;如果虚拟机栈可以动态扩展,当扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常。

2.2.3 本地方法栈

  • 与虚拟机栈所发挥的作用非常相似,区别是虚拟机栈为虚拟机执行的是java方法服务,本地方法栈为虚拟机执行Native方法服务。

2.2.4 java堆

  • 用来存放对象实例,是java虚拟机所管理的内存中最大的一块,也是所有线程共享的一块内存区域。
  • Java堆是垃圾回收的主要区域。
  • 堆可以实现固定大小,也可以是可扩展的,主流虚拟机按照可扩展来实现(通过-Xmx和-Xms控制)。扩展时申请不到内存则会抛出OutOfMemoryError异常。

2.2.5 方法区

  • 和java堆一样,是各个线程共享的内存区域,用于存储虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
  • 当方法区无法满足内存分配需求时,抛出OutOfMemoryError异常。

2.2.6 运行时常量池

  • 是方法区的一部分。存放编译时期生成的各种字面量和符号引用。
  • 具有动态性。运行期间也可以将新的变量放入池中,用的最多的是Sring类的intern()方法。
  • 常量池无法申请到内存时抛出OutOfMenoryError异常。

2.2.7 直接内存

  • 并不是虚拟机运行时数据区的一部分,也不是java虚拟机规范中定义的内存区域。属于本机内存,即堆外内存。
  • 本机直接内存不会受到java堆大小的限制,但是会受到本机总内存大小以及处理器寻址空间的限制。
  • 在配置虚拟机参数时,会根据实际内存设置-Xmx等参数,但往往忽略直接内存使得各内存总和大于物理内存,从而导致动态扩展时出现OutOfMeoryError异常。

2.3 HotSpot虚拟机对象探秘

2.3.1 对象的创建

  • 创建的过程
    1. new 类名。
    2. 根据new的参数在常量池中定位一个类的符号引用。
    3. 如果没有找到这个符号引用,说明类还没有被加载,则进行类的加载、解析和初始化。
    4. 虚拟机为对象分配内存(位于堆中)。
    5. 将分配的内存初始化为零值(不包括对象头),即给字段的数据类型赋初始值。
    6. 虚拟机对对象进行必要的设置,即设置对象头中的数据,例如对象是那个类的实例、如何才能找到类的元数据信息、对象的哈希码等。
    7. 调用对象的方法。
  • 给对象分配内存方式
    1. 指针碰撞;要求java堆中的内存是绝对规整的,即所有用过的内存放在一边,空闲的内存放在另一边,中间放着一个指针作为分界点的指示器。
    2. 空闲列表;虚拟机维护一个列表,记录哪些空闲块是可用的。可以用于java堆内存不是规整的。java堆的规整取决于垃圾回收器是否带有压缩整理功能。
  • 在分配内存中可能存在线程安全性问题
    1. 保证更新操作的原子性,即上锁。
    2. 采用本地线程分配缓冲(Thread Local Allocation Buffer)TLAB,即给每个线程预先分配一块小内存,分配内存时直接在TLAB上分配,只有TLAB用完并分配新的TLAB参数时,才需要同步锁定。虚拟机是否使用TLAB,可以通过-XX:+/-UseTLAB参数来设定。

2.3.2 对象的内存布局

  • 对象头(Header)
    1. 用于存储对象自身运行时的数据,如哈希码、GC分代年龄等。
    2. 存储类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是那个类的实例。
  • 实例数据(Instance Data);是对象真正存储的有效信息,也是在程序代码中所定义的各种类型的字段内容。
  • 对齐填充(Padding);不是必然存在的,也没有特殊的含义,它仅仅起着占位符的作用。

2.3.3 对象的访问定位

  • 使用句柄访问;即在java堆中划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象的实例数据与类型数据各自的具体的地址信息。
  • 使用直接指针访问;即reference中直接存储对象的地址。
  • 优势
    1. 句柄访问;就是reference中存储的是稳定的句柄地址,当对象移动时只会改变句柄中的实例数据指针,reference本身不需要修改。
    2. 直接指针访问;最大的好处就是速度快,节省了一次指针定位的时间开销。

2.4 实战:OutOfMemoryError异常

2.4.1 java堆溢出

2.4.2 虚拟机栈和本地方法栈溢出

2.4.3 方法区和运行时常量池溢出

2.4.4 本机直接内存溢出

发布了84 篇原创文章 · 获赞 50 · 访问量 7014

猜你喜欢

转载自blog.csdn.net/qq_43115606/article/details/104055185