这是jdk7以后的版本
1. Heap(堆区)
Heap OOM 障最主要的发源地, 它存储着几乎所有的实例对象, 堆由垃圾收集器自动回收, 堆区由各子线程共享使用。通常情况下 它占用的空间是所有内存 区域中最大的,但如 果无节制地创建大量对象,也容易消耗完所有的空间。堆的内存 空间既可以固定大小, 也可以在运行时动态地调整,通过如下参数设定初始值和最大值,比如 -Xms256M -Xmxl024M ,其中 -X 表示它是 JVM 运行参数, ms是 memory start 的简称, mx是 memory max 的简称,分别代表最小堆容量和最大堆容量。但是 在通常情况下,服务器在运 行过程中,堆空间不断地扩容与回缩,势必形成不必要的系统压力, 所以在线上生产环境中 JVM ms mx 设置成 样大小,避免在GC 后调整堆大小时带来的额外压力。
堆分成两大块 新生代和老年代。对象产生之初在新生代 步入暮年时进入老年代,
但是老年代也接纳在新生代无法容纳的超大对象。新生代= 1个 Eden区+2个Survivor区。
绝大部分对象在 Eden 区生成,当Eden区装填满的时候,会触发 Young
Garbage Collection 即YGC 。垃圾回收的时候,在Eden 区实现清除策略, 没有被引用的对象则直接回收。
依然存活的对象会被移送到 Survivor区,这个区真是名副其实的存在。 Survivor 区分为 so和Sl两块内存空间,
送到哪块空间呢?每次 YGC的时候,
全清除 交换两块空间的使用状态。如果 YGC 要移送的对象大于 Survivor 区容量的上限 ,则直接移交给老年代。假如一些没有进取心的对象以为可以一直在新生代的
Survivor 区交换来交换去,那就错了。每个对象都有 个计数器,每次 YGC 都会加1。-XX:MaxTenuringThreshold 参数能配置计数器的值到达某个阐值的时候 对象从
新生 晋升至老年代。如果该参数配置为 ,那么从新生代的 Eden 区直接移至老年代。
看一下直观的流程图
如果 Survivor 区无法放下,或者超大对象的闹值超过上限,则 尝试在老年代中进行分配 如果老年代也无法放下,贝lj 会触发 Full Garbage Collection,
即FGC。如果依然无法放下, 则抛出 OOM。堆内存出现 OOM 的概率是所有内存耗尽异常中最高的。出错时的堆内信息对解决问题非常有帮助 所以给 NM 设置运行参
数 -XX:+HeapDumpOnOutOfMemoryError ,让口瓜 遇到 OOM 异常时能输出堆内信息,特别是对相隔数月才出现的 OOM 异常尤为重要。
在不同的 NM 实现及不同的回收机制中 堆内存的划分方式是不一样的。
2. Metaspace (元空间)
早 在JDK8 版本中,无空间的前身 Perm 区已经被淘汰。在 JDK7 及之前的版本中,只有 Hotspot 才有 Perm 区(在方法区内),译为永久代 它在启动时固定大小,很难进行调优,并且 FGC 时会移动类无信息。在某些场景下,如果动态加载类过多,容易产生 Perm 区的 OOM。比如某个实际 We 工程中,因为功能点比较多,在运行过程中,要不断动态加载 很多的类,经常出现致命错误。
"exception in thread 'dubbo client x.x connector' java.lang.OutO emoryE or PennGen space"
为了解决该问题 需要设定运行参数 XX: MaxPermSize= 1280m ,如果部署到新机器上,往往会因为 NM 参数没有修改导致故障再现。不熟悉此应用的人排查问题时往往苦不堪 言,除此之外,永久代在垃圾回收过程中还存在诸多问题。所以, JDK8 使用元空间替换永久代。在 JDK8 及以上版本中,设定 MaxPermSize数, NM 在启动时并不会报锚,但是会提示 Java HotSpot 64Bit Server VM warning: ignoring option MaxPem1Size=2560m; support was removed i11 8.0。
区别于永久代 元空间在本地内存中分配。在 JDK8 里, Perm区中的所有 内容中字符串常量移至堆内存,其他内容包括类元信息、字段、静态属性、方法、常量等都移动至无空间 内, 中的 Object 元信息、静态属性 System.out 、整型常量 10000000 等。图 4-10 中显示在常量池中的 trir毡, 其实际对象是被保存在堆内存中的。