docker java 内存溢出_【JDK源码阅读】Java内存区域与内存溢出异常

目前在结合「深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)- 周志明 」阅读OpenJDK11的源码,本文参考原书第二章内容,总结整理学习过程于此。

运行时数据区域

Java虚拟机会把他所管理的内存划分为若干个不同的数据区域:

d8877aa35b86b0165a4229adedbfed60.png

1.程序计数器

1.程序计数器可以看作是当前线程所执行的字节码的行号指示器

2.在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)都只会执行一条线程中的指令

3.如果正在执行一个Java方法,计数器记录的是正在执行的虚拟机字节码指令的地址;如果是本地(Native)方法,则计数器值为空(Undefined)

2.Java虚拟机栈

1.虚拟机栈描述的是Java方法执行的内存模型,它也是线程私有的,每个方法在执行的同时都会创建一个栈帧,用于存储局部变量、操作数栈、动态链接、方法出口等信息

2.通常所说的“Java内存分为堆内存和栈内存”是过于粗糙的,其中的栈所指的实际上是虚拟机栈,确切的说是虚拟机栈中的局部变量表

3.long 和 double型的数据会占用2个局部变量空间,其余的数据类型只占用1个

4.局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法在帧中要分配多大的局部变量空间是完全确定的

3.本地方法栈

1.本地方法栈和虚拟机栈的区别在于,虚拟机栈为虚拟机执行Java方法服务,而本地方法栈则为Native放法服务

2.有的虚拟机,如HotSpot,直接把本地方法栈和虚拟机栈合二为一

4.Java堆

1.Java堆是Java虚拟机所管理的内存中最大的一块

2.Java堆在虚拟机启动时创建,被所有线程共享

3.Java堆唯一的目的是存放对象实例,几乎所有的对象实例和数组都在这里

4.Java堆为了便于更好的回收和分配内存,可以细分为:“新生代和老生代”,或者“Eden From Survivor To Survivor等”

5.Java堆可以处在物理上不连续的内存空间中,只要逻辑上是连续的即可

5.方法区

1.方法区用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据

2.如何实现方法区属于虚拟机的实现细节,不受虚拟机规范约束

3.方法区可以选择不实现垃圾回收

4.方法区的内存回收目标主要针对常量池的回收和对类型的卸载

6.其他

1.运行时常量池用于存放编译器生成的各种字面量和符号引用

2.直接内存不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域

虚拟机对象探秘

1.对象的创建

1.虚拟机在遇到一条new指令时,首先会检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查该类是否已加载。如果没有加载,还要先执行相应的类加载过程 2.内存分配方式有两种,选择哪种方式由Java堆是否规整决定,若规整,则采用“指针碰撞”方式,若不规整,则采用“空闲列表方式”;而是否规整又由所采用的垃圾回收机制决定 3.解决并发环境下的分配内存有两种方式:

  • 对分配内存空间的动作进行同步处理
  • 把内存分配的动作按照线程划分在不同的空间中进行,即每个线程在堆中预先分配一小块内存(TLAB)

4.内存分配完成后还要将分配的内存空间都初始化为零值,从而保证对象的实例字段在Java代码中可以不赋初始值就直接使用

5.初始化后还要给对象进行相关各设置,诸如哈希码、GC分代年龄信息等

6.上述工作结束后,从虚拟机视角来看,一个新的对象已经产生了;但从Java程序的角度来看,对象的创建才刚刚开始——进行Java代码的初始化操作

2.对象的内存布局

1.对象在内存中存储的布局可以分为三块区域:对象头、实例数据、对齐填充

2.对象头一搬包含两部分内容:对象的运行时数据和类型指针。如果对象是数组,则还有一个部分来记录数组的长度

3.运行时数据指哈希码、GC分代年龄等信息,被设计成一个非固定的数据结构以便在极小的空间存储尽量多的信息,会根据对象的状态复用自己的存储空间

4.类型指针是对象指向他的类元数据的指针,虚拟机通过这个指针确定对象是哪个类的实例

5.实例数据是对象真正存储的有效信息,即程序中的字段内容

6.实例数据默认的分配策略是按顺序,相同宽度的字段总是分配在一起

7.对齐填充不是必然的,仅仅起着占位符的作用

8.对象的大小必须是8字节的整数倍,对象头部分正好是8字节的倍数

3.对象的访问定位

1.Java程序通过栈上的reference数据来操作堆上的具体对象

2.对象的访问形式取决于虚拟机,目前主流的有两种方式:使用句柄和直接指针

3.使用句柄是间接访问,优点是reference中存储的是稳定的句柄地址,对象移动时只会改变句柄中的实例数据指针

4.使用直接指针是直接访问,优点就是速度快

OutOfMemoryError异常

1.Java堆溢出

Java堆用于存储对象实例,只要不断的创建对象,且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象,那么在对象数量达到最大堆的容量限制之后就会产生内存溢出的异常

2.虚拟机和本地方法栈的溢出

1.HotSpot虚拟机中并不区分虚拟机栈和本地方法栈

2.关于虚拟机栈和本地方法栈,Java虚拟机规范中描述了量中异常:

  • 如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常
  • 如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常

3.在单个线程下,无论是由于栈太小还是虚拟机栈容量太小,当内存无法分配时,虚拟机抛出的都是StackOverflowError异常

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

1.在JDK 1.6中intern() 方法会把首次遇到的字符串实例复制到永久代中,返回的也是永久代中这个字符串实例的引用

2.在JDK 1.7中intern() 方法不会再复制实例,只是在常量池中记录首次出现的实例引用

3.StringBuilder 创建的字符串实例在Java堆上

4.本机直接内存溢出

1.直接内存(Direct Memory)的容量大小可通过-XX:MaxDirectMemorySize参数来指定,如果不去指定,则默认与Java堆最大值(由-Xmx指定)一致

2.由直接内存导致的内存溢出,一个明显的特征是在Heap Dump文件中不会看见有什么明显的异常情况

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值