JVM自动内存管理机制
文章目录
1)Java虚拟机
2)JVM内存区域的划分
3)内存溢出
Java虚拟机
- 虚拟机给每个对象都分配了一个锁
- 监视器
- 锁是通过监视器实现的,监视器主要功能是监控一段代码,确保在同一时间只有一个线程在执行
- 每个监视器都与一个对象相关联
- 多次加锁
- 同一个线程可以对同一个对象进行多次加锁,每个对象维护者一个记录着被锁次数的计数器,当一个线程获得锁后,计数器自增,释放锁的时候,计数器自减
JVM内存区域的划分
- 1.运行时数据区
- 线程私有数据区
- 1.程序计数器
- 当前线程所执行的字节码的行号指示器
- 字节码解释器工作时,就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
- 2.虚拟机栈
- Java方法执行的内存模型
- 如果线程请求的栈深度大于虚拟机所允许的深度,将抛出
StackOverflowError
异常; - 如果虚拟机栈可动态扩展且扩展时无法申请到足够的内存,将抛出
OutOfMemoryError
异常。
- 3.本地方法栈
- 虚拟机使用到的Native方法服务
- 1.程序计数器
- 线程共享数据区
- 1.Java堆
- 存放几乎所有的对象实例和数组
- 可能划分出多个线程私有的分配缓冲区(TLAB)
- 垃圾收集器管理的主要区域,“GC堆”
- 2.方法区(包含常量池)
- 存储已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
- JVM规范将方法区描述为堆的一个逻辑部分,但它却还有一个别名叫做Non-Heap(非堆),目的就是要和堆分开。
- 永久代是方法区的一个实现,jdk1.7的版本中,已经将原本放在永久代的字符串常量池移走。
- JDK1.8,元数据区取代了永久代。元空间的本质和永久代类似,都是对 JVM 规范中方法区的实现。不过元数据空间并不在虚拟机中,而是使用本地内存,所以1.8元空间不存在OOM内存溢出的情况。
- 运行时常量池是方法区的一部分
- 1.Java堆
- 线程私有数据区
- 2.HotSpot虚拟机对象
- 语言层面上,创建对象仅仅是一个new关键字
- 当虚拟机遇到一条new指令时,(对象的创建):
- 类加载检查
- 检查new指令的参数能否在常量池中定位到一个类的符号引用且该符号引用代表的类是否已被加载,解析和初始化,若没有,则需要先进行类加载
- 分配内存
- 内存规整,则使用"指针碰撞"分配方式
- 内存不规整,则使用"空闲列表"分配方式
- 设置对象头
- 类加载检查
- 对象的内存布局
- 对象头
- 包含用于存储对象自身的运行时数据
- 包含类型指针,即对象指向它的类元数据的指针
- 实例数据
- 存储真正有效的信息
- 对象填充
- 非必需的占位符
- 对象头
- 对象的访问
- 通过句柄访问对象
- 通过直接指针访问对象
内存溢出
- 内存溢出和内存泄露的区别
- 内存溢出(Out Of Memory)
- 指程序在申请内存时,没有足够的内存空间供其使用
- 内存泄露(Memory Leak)
- 程序申请内存后,由于某种原因无法释放已申请的内存空间,导致这块内存无法再次被利用,造成系统内存的浪费
- 内存泄露堆积会导致内存溢出
- 内存溢出(Out Of Memory)
- Java堆溢出
- Java堆用于存储对象实例,只要不断的创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象,达到堆的最大容量就会产生内存溢出异常
-Xms
参数:堆的最小值,-Xmx
参数:堆的最大值
- 栈溢出
- 线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverfloeError异常
- 虚拟机在扩展栈是无法申请到足够的内存空间,则抛出OutOfMemoryError异常
- 可以通过
-Xss
参数来指定虚拟机栈的最大深度,也可以将虚拟机栈设置为可动态扩展。
- 方法区和运行时常量池溢出
- 方法区中保存Class对象没有被及时回收掉或者Class信息占用的内存超过了我们配置
-XX:PermSize
和-XX:MaxPermSize
限制方法区大小,从而间接限制其中常量池的容量
参考
《深入理解Java虚拟机》
JVM内存溢出详解
JVM之内存溢出
堆、栈、方法区、直接内存、堆和栈区别