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

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

1、运行时区域
1.1、程序计数器
  • 特点
    • 线程私有
    • 没有规定OutOfMemoryError情况的区域
    • 执行Native方法,计数器值为空
  • 作用
    • 当前线程所执行的字节码的行号指示器,通过改变计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等
    • 帮助线程切换之后能恢复到正确的执行位置。
1.2、Java虚拟机栈
  • 特点
    • 线程私有,生命周期与线程相同
    • 方法执行时产生的内存模型:即是一个创建一个栈帧,存储局部变量表、操作栈、动态链接、方法出口等
    • 每个方法被调用直至完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程
    • 局部变量表存储各种 的基本数据类型、对象引用(但是不等同于对象本省、可能是一个指向对象起始地址的引用指针)。
    • 局部变量表所需的内存空间在编译期间完后完成分配
    • 如果线程请求的栈深度大于虚拟机所允许的深度将抛出StackOverFlowError异常,如果虚拟机栈动态扩展无法申请到足够的内存时会抛出OutOfMemoryError异常。
  • 作用
    • 存储方法创建的栈帧
1.3、本地方法栈
  • 特点
    • 线程私有
    • 也会抛出StackOverFlowErrorOutOfMemoryError异常
  • 作用
    • 与虚拟机栈相似
  • 区别
    • 虚拟机栈为虚拟机执行Java方法服务,而本地方法栈则为虚拟机使用到Native方法服务。
1.4、Java堆
  • 特点
    • 所占的内存最大
    • 线程共享
    • 逻辑上连续,物理上不连续
    • Java堆可以分为新生代和老年代(Eden空间、From Survivor空间、To Survivor空间)
  • 作用
    • 存放对象实例
1.5、方法区
  • 特点
    • 线程共享
    • 不需要连续的存储和可以选择固定大小或者可扩展
    • 可能出现垃圾收集行为,针对常量池的回收和对类型的卸载。
  • 作用
    • 存储被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等
1.6、运行时常量池
  • 特点

    • 方法区的一部分
    • 当常量池无法在申请到内存时会抛出OutOfMemoryError异常。
  • 作用

    • 用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。
    • 除了保存Class文件中描述得符号引用外,还会把翻译出来的直接引用也存储在运行时常量池中。
  • 题外

    • 运行期间也可能将新的常量放入到池中,如String类的intern()方法。
1.7、直接内存
  • 特点

    • 不是Java虚拟机规范中定义的内存区域,也不是虚拟机运行时数据区的一部分。
    • 使用过程中可能会导致OutOfMemoryError
    • 不受Java堆大小限制,但是手袋 本机总内存大小以及处理器寻址空间的限制
  • 作用

    • NIO通过一个存储在Java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作,提高性能,避免在Java堆和Native堆中来回复制数据。
2、对象访问
  • 特点

    • 即使是最简单的访问,也会涉及Java栈、Java堆、方法区这三个区域
  • 分析Object obj = new Object()

    • Object obj会反映到Java栈中的本地变量表中,作为引用类型数据出现
    • new Object()会反映到Java堆中,存储Object类型所有实例数据值,根据不同虚拟机,内存长度不用
    • 在Java堆中,对象类型数据(对象类型、父类、实现的接口、方法)的地址信息,都存放在方法区。
  • 对象访问的方式

    • 使用句柄

      • 划分一块内存作为句柄池、引用中存储的就是对象的句柄地址、而句柄中包含了对象实例数据和类型数据各自的具体地址信息
        在这里插入图片描述
    • 使用直接指针

      • 引用总直接存储的就是对象地址

        在这里插入图片描述

    • 对比

      • 如果使用句柄,在对象被移动时(如垃圾收集)只会改变句柄中的实例数据指针,而引用本身不需要被修改
      • 使用直接指针访问速度更快,节省一次指针定位的时间开销
3、OOM异常
3.1、Java堆异常
  • 产生
    • 创建对象,在对象数量到达最大堆的容量限制后产生内存溢出异常,出现Java堆内存溢出时,异常堆栈信息OOM会跟进异步提示Java heap space。
  • 解决
    • 确认内存中的嗯对象是否是必要的,分清楚出现内存泄漏还是内存溢出
    • 如果是内存泄漏,进一步通过工具查看泄漏对象到GC Roots的引用链,分析垃圾收集器是如何导致垃圾无法自动回收
    • 如果不存在泄漏,则检查虚拟机堆参数,与机器物理内存对比看看是否还可以调大,从代码上检查是否存在某些对象生命周期过长、持有状态时间过长的情况,尝试减少程序运行期的内存消耗。
3.2、虚拟机栈和本地方法栈溢出
  • 栈容量只由-Xss参数设定

  • 产生

    • 如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverFlowError异常
    • 如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常、
  • 在单线程下,无论是由于栈帧太大,还是虚拟机栈容量太小,当内存无法分配的时候,虚拟机抛出的都是StackOverFlowError异常

  • 如果是建立过多线程导致的内存溢出,在不能减少线程数或者更换64位虚拟机的情况下,就只能通过减少最大堆和减少栈容量来换取更多的线程。

3.3、运行时常量池溢出
  • 我们可以通过-XX:PermSize 和 -XX:MaxPermSize限制方法区的大小。从而限制常量池的容量

  • 在运行期间,我们可以通过使用String.intern()这个方法来往常量池里面添加常量。当超出一定范围之后会抛出OOM后面跟随的提示纤细是PermGen space,说明运行时常量池属于方法区

3.4、方法区溢出
  • 产生
    • 当产生大量的类时,就需要越大的方法区来保证动态生成的Class可以家载入内存
    • 方法区溢出是昌建的内存溢出,在经常动态生成大量的Class的应用中,需要特别注意类的回收状况。
3.5、本机直接内存溢出
  • DirectMemory容量可以通过-XX:maxDirectMemorySize指定,如果不指定,则默认与Java堆的最大值(-Xmx指定)一样,
  • 虽然通过DirectByteBuffer分配内存也会抛出内存溢出异常,但它抛出异常时并没有真正向操作系统申请分配内存,而是通过计算得知内存无法 分配,于是手动抛出异常。以通过-XX:maxDirectMemorySize指定,如果不指定,则默认与Java堆的最大值(-Xmx指定)一样,
  • 虽然通过DirectByteBuffer分配内存也会抛出内存溢出异常,但它抛出异常时并没有真正向操作系统申请分配内存,而是通过计算得知内存无法 分配,于是手动抛出异常。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值