第2章 Java内存区域与内存溢出异常

本文详细介绍了Java内存区域,包括程序计数器、虚拟机栈、本地方法栈、堆和方法区(元空间),以及对象创建、内存分配和并发问题。讨论了各种内存溢出异常,如堆溢出、栈溢出和方法区溢出,并提供了示例代码。还提到了JDK8后方法区的变化和对象内存布局。
摘要由CSDN通过智能技术生成

目录

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

运行时数据区域

对象的创建

并发分配内存的问题

对象的内存布局

对象的访问定位

Java堆溢出,OOM测试

虚拟机栈溢出

方法区溢出


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

运行时数据区域

  1. 程序计数器
可以看做是,当前线程所执行的字节码的行号指示器。每个线程需要一个独立的程序计数器。
  1. Java虚拟机栈
1.生命周期与线程相同,每个方法执行的时候都会同步创建一个栈帧,用于存储局部变量表、方法出口等信息。
2.局部变量表的槽数量是在编译期间完成完成,当进入某个方法时,栈帧需要多大的局部变量空间是确定的,在运行期间不会改变表大小。
3.线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常。
*.与通常C++所说堆栈中的栈不同,其更多情况下指的只是虚拟机栈中的局部变量表。
*.虚拟机的栈容量是不可以动态扩展的。但是线程在进入方法时申请栈失败时仍然会出现OOM。
  1. 本地方法栈
1.Java虚拟机栈为字节码服务,而本地方法栈为被native修饰的本地方法服务。
2.栈深度溢出也会抛StackOverflowError异常。
3.栈扩展失败时会跑OOM。
*.HotSpot虚拟机把虚拟机栈和本地方法栈合二为一。
是垃圾收集器管理的区域,也被称为GC堆。几乎所有对象存放的位置。
  1. 方法区
1.也称作非堆。与堆一样,是各个线程共享的内存区域。
2.存储被虚拟机加载的常量、静态变量、类型信息、代码缓存等数据。
分为:
1)运行时常量池:用于存放字面量和符号引用。String.intern()可在运行期将新常量放入常量池。
  String.intern()如果存在则返回常量池那个对象的引用,否则写入常量池,并返回当前对象的引用。
2)元空间
3.方法区的回收目标是,对常量池的回收和堆类型的卸载。

*.jdk1.8完全废弃永久代,把字符串常量池,静态变量移到运行时常量池,把类型信息等移到元空间中。

对象的创建

当Java虚拟机遇到一条字节码new时:

  1. 在常量池定位类的符号引用
  2. 检查类是否被加载 解析 初始化过
  3. 执行相应类加载过程(第七章)
  4. 为新生对象分配内存,根据内存是否规整,采用不同的内存分配方式:
    1. 带有空间压缩整理的垃圾收集器,例如,Serial、ParNew等收集器:采用简单高效的指针碰撞分配。
    2. 采用CMS基于清除(Sweep)算法收集器:采用较为复杂的空间列表分配
  5. 将分配到的内存初始化为零
  6. 对对象进行必要的设置,例如,对象是哪个类的实例、类的元数据信息、对象的GC分代年龄。这些存放在对象头中
  7. 执行构造函数的<init>()方法
  8. 可用的对象才算完全被构造出来

并发分配内存的问题

两种解决方法:

  1. CAS配上失败重试方式保证原子性(默认使用)
  2. 每个线程在Java堆中预先分配一小块内存,称为本地线程分配缓冲TLAB,如果本地缓冲用完了就需要使用1方法。

可以通过参数设置,-XX:+UseTLAB-XX:-UseTLAB

对象的内存布局

对象在堆中的布局划分为三部分:

  1. 对象头

    1. 存储对象自身的运行时数据,哈希吗、GC分代年龄、锁状态标志等,长度对应32位和64位虚拟机。
    2. 类型指针,虚拟机通过这个指针确定该对象是哪个类的实例。
  2. 实例数据

    父类继承的或者自身的都必须记录起来。相同宽度类型的字段会被分配到一起。

    存储顺序收到虚拟机配置策略参数和源码顺序影响。

    父类定义的变量会排列到子类之前,通过-XX:CompactFields,默认也为true,将子类较窄的变量插入父类变量的间隙中,以节省一点点的空间。

  3. 对齐填充

    起占位符的作用。任何对象的大小都必须是8字节的整数倍。

对象的访问定位

Java程序通过栈上引用的数据来操作堆上的对象,访问方式主要有:

  1. 句柄

    需要句柄池间接访问实例。

  2. 直接指针

    直接访问实例,HotSpot使用。

Java堆溢出,OOM测试

目录下多了java_pid3468.hprof堆转储快照

import java.util.ArrayList;
import java.util.List;

/**
 * VM Args: -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
 * @author liuguanliang
 * @since 2020-5-24 22:45:45
 */
public class HeapOOM {
    static class OOMObject {
        
    }
    
    public static void main (String[] args) {
        List<OOMObject> list = new ArrayList<>();
        
        while (true) {
            list.add(new OOMObject());
        }
    }
}

虚拟机栈溢出

  1. 线程请求的栈深度大于虚拟机所允许的最大深度,抛出StackOverflowError

  2. 栈内存动态扩展,当无法申请足够时抛出OOM

    HotSpot虚拟机选择不支持栈动态扩展

减少栈内存容量:

VM Args: -Xss128k

方法区溢出

JDK 8以后,永久代完全退出历史舞台,元空间作为其替代者出场。很难再迫使虚拟机产生方法区的溢出异常了。


参考:《深入理解Java虚拟机》 第3版 周志明

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值