Java 核心技术面试精讲
杨晓峰
前 Oracle 首席工程师
124523 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 44 讲
Java 核心技术面试精讲
15
15
1.0x
00:00/00:00
登录|注册

第25讲 | 谈谈JVM内存区域的划分,哪些区域可能发生OutOfMemoryError?

今天,我将从内存管理的角度,进一步探索 Java 虚拟机(JVM)。垃圾收集机制为我们打理了很多繁琐的工作,大大提高了开发的效率,但是,垃圾收集也不是万能的,懂得 JVM 内部的内存结构、工作机制,是设计高扩展性应用和诊断运行时问题的基础,也是 Java 工程师进阶的必备能力。
今天我要问你的问题是,谈谈 JVM 内存区域的划分,哪些区域可能发生 OutOfMemoryError?

典型回答

通常可以把 JVM 内存区域分为下面几个方面,其中,有的区域是以线程为单位,而有的区域则是整个 JVM 进程唯一的。
首先,程序计数器(PC,Program Counter Register)。在 JVM 规范中,每个线程都有它自己的程序计数器,并且任何时间一个线程都只有一个方法在执行,也就是所谓的当前方法。程序计数器会存储当前线程正在执行的 Java 方法的 JVM 指令地址;或者,如果是在执行本地方法,则是未指定值(undefined)。
第二,Java 虚拟机栈(Java Virtual Machine Stack),早期也叫 Java 栈。每个线程在创建时都会创建一个虚拟机栈,其内部保存一个个的栈帧(Stack Frame),对应着一次次的 Java 方法调用。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结
仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《Java 核心技术面试精讲》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(64)

  • 最新
  • 精选
  • I am a psycho
    如果仅从jvm的角度来看,要看下新生代和老年代的垃圾回收机制是什么。如果新生代是serial,会默认使用copying算法,利用两块eden和survivor来进行处理。但是默认当遇到超大对象时,会直接将超大对象放置到老年代中,而不用走正常对象的存活次数记录。因为要放置的是一个byte数组,那么必然需要申请连续的空间,当空间不足时,会进行gc操作。这里又需要看老年代的gc机制是哪一种。如果是serial old,那么会采用mark compat,会进行整理,从而整理出连续空间,如果还不够,说明是老年代的空间不够,所谓的堆内存大于100m是新+老共同的结果。如果采用的是cms(concurrent mark sweep),那么只会标记清理,并不会压缩,所以内存会碎片化,同时可能出现浮游垃圾。如果是cms的话,即使老年代的空间大于100m,也会出现没有连续的空间供该对象使用。

    作者回复: 非常不错的总结

    3
    303
  • Len
    从不同的垃圾收集器角度来看: 首先,数组的分配是需要连续的内存空间的(据说,有个别非主流JVM支持大数组用不连续的内存空间分配🤔)。所以: 1)对于使用年轻代和老年代来管理内存的垃圾收集器,堆大于 100M,表示的是新生代和老年代加起来总和大于100M,而新生代和老年代各自并没有大于 100M 的连续内存空间。 进一步,又由于大数组一般直接进入老年代(会跳过对对象的年龄的判断),所以,是否可以认为老年代中没有连续大于 100M 的空间呢。 2)对于 G1 这种按 region 来管理内存的垃圾收集器,可能的情况是没有多个连续的 region,它们的内存总和大于 100M。 当然,不管是哪种垃圾收集器以及收集算法,当内存空间不足时,都会触发 GC,只不过,可能 GC 之后,还是没有连续大于 100M 的内存空间,于是 OOM了。

    作者回复: 很好的视角,g1 region之类确实有影响,另外g1还是有年代的概念的

    43
  • 夏洛克的救赎
    Tomcat运行中突然出现java.lang.OutOfMemoryError: PermGen space有什么工具可以排查原因吗?

    作者回复: 简单点处理,可以: 先看看永久带给了多大,如果太小,可以适当增大,使用'-XX:MaxPermSize=NNNm'; 如果没开启classunloading,可以根据GC选项做配置,例如,如果使用的CMS,可以加上“-XX:+CMSClassUnloadingEnabled -XX:+CMSPermGenSweepingEnabled” 通常就能解决问题了,如果还是有问题,那就要看看是不是出现了classloader leak,常见做法如,取Heap dump,然后用类似Eclipse MAT这样的工具,看看有没有不回收的自定义classloader实例之类

    20
  • 任鹏斌
    老师既然元数据区也存在溢出,那么为什么要用元数据区替换永久代呢,有什么好处吗?

    作者回复: metaspace 默认是自增的,永久带做不到

    17
  • markin
    老师,能否跟我们介绍一下您平时获取资料的渠道。比如apache的一些开源项目,官网上就有很丰富的文档。但是我们获取jvm相关文档的渠道少之又少,无非就是博客或者书籍,这些都比较繁杂,并且可能参杂着很多难以识别的错误观点。授人以鱼不如授人以渔,先谢谢老师了。

    作者回复: Oracle官网也提供了很多好的文档: 虚拟机规范 https://docs.oracle.com/javase/specs/jvms/se8/html/index.html 诊断指南 https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/index.html 调优指南 https://docs.oracle.com/javase/10/gctuning/ Openjdk网站,或者那些感兴趣的邮件列表 http://mail.openjdk.java.net/mailman/listinfo YouTube上查查javaone, JVM summit之类 回头有必要整理个书单之类 但这些东西太多了,自己把握一下

    17
  • 爱吃芒果的董先森
    因为给数组分配的是连续地址,而显示的是总的地址,不管是不是连续的。

    作者回复: 也对,最好综合考虑堆内存结构、gc区别等,后续会讲解

    11
  • 鹅米豆发
    可能一,新生代没有足够的连续空间,且不能直接在老年代分配。比如E+S0+S1>100MB,但E<100MB,S0<100MB。 可能二,大对象直接进入老年代,但老年代也没有足够的连续空间。参数+XX:PretenureSizeThreshold。 可能三,线程数量太多,导致物理内存不足。 可能四,直接内存使用太多,导致物理内存不足。

    作者回复: 不错,下一章会有更多内存结构细节

    9
  • tyson
    堆内存100M 包含了新生代(eden+s0+1)和老年代,大对象一般分配在老年代,那么最有可能在分配过程中老年代的空间不足。

    作者回复: 不错,可能性很多,其实和gc的选择也有关,例如g1 region比较小

    8
  • 代码狂徒
    老师,您是说方法区就是有永久代?那也就是说方法区在jdk8中已经不存在了?元数据区跟方法区有什么区别呢?那您的图是jdk7的图,有8得图吗?求解

    作者回复: 不是,方法区只是个逻辑概念,永久带和元数据区是具体设计、实现的选择; 以前放到永久带,而且永久带内部还有类似intern字符串之类内容; 元数据区具体内容和永久带也有区别,文章介绍了; 那个图只是个简化示例,8去掉永久带就是了,具体到比较复杂的gc比如g1,就不是这个结构,请看后面讲

    3
  • 李二木
    老师,关于这篇文章留的问题你可以给个你的答案吗?

    作者回复: 嗯,参考我的回复,下一讲中有更多细节,具体堆内结构还是会划分,例如tlab,eden等,可以简单理解,对象分配是试图tlab,太大就eden,还不行就oldgen,所以我们需要的是相应区域有连续空闲

    3
收起评论
显示
设置
留言
64
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部