理解java内存模型

为了开发、压测、部署、调优一个java应用,必须要理解java的内存模型,
这篇文章介绍JVM各个内存区域如何支持程序的正常运行。

JVM Memory Model

为了更好地运行一个java应用必须设置以下参数

  • -XmsSetting — 初始化heap大小
  • -XmxSetting — 最大heap占用
  • -XX:NewSizeSetting — 年轻代大小
  • -XX:MaxNewSizeSetting — 最大年轻代占用
  • -XX:MaxPermGenSetting — 最大permsize(java 8版本后更名为XX:MaxMetaspaceSize)
  • -XX:SurvivorRatioSetting — 年轻代中eden与survivor的比例 (如果年轻代的大小设置为10M,并且–XX:SurvivorRatio=2,则Eden分配的内存为5M,两个survivor(survivor0、survivor1)分别为2.5M 默认为8)
  • -XX:NewRatio — 老年代与年轻代的比例 (默认值 = 2)

jvm在主机上的内存占用如下

JVM并不等同于Heap,JVM包含(Heap、非Heap、Cache)用来存储运行时数据和编译java代码.

1) Heap Memory

  • Heap 分为两部分 — 年轻代 and 老年代
  • Heap 在JVM启动时(也可理解为应用启动时)被分配 (I初始化大小: -Xms)
  • Heap size 程序运行的时候JVM可以增大/减小
  • 最大Heap Size设置: -Xmx

1.1) 年轻代(Young Generation)

  • 这里用来存放新创建的对象.
  • 年轻代被分为三个部分 — Eden Memory 和两个 Survivor Memory spaces (S0, S1).
  • 新创建的对象被放在年轻代的Eden区域.
  • 当 Eden space 被对象填满, Minor GC(年轻代gc) (a.k.a. Young Collection) 将所有还在使用的对象转移至一个Survivor Memory spaces (S0或者S1)
  • 年轻代GC 不仅检测Eden区域的存活对象,还会检测已经有对象的(S0或者S1)如果有存活对象也会被转移至空的Survivor Memory spaces (S0或者S1). 所以总有一个Survivor Memory spaces是空的
  • 如果对象经过多次(默认15次可通过参数设置次数)的Minor GC(年轻代gc)对象仍然被引用, 这些对象将会被转移至老年代. (对象从S0到S1就会增长一岁=对象经过一次Minor GC仍然存活则增加一岁,到15岁(可设置)后转移至老年代)

1.2)老年代 (Old Generation)

  • 存储长期被引用的对象
  • 当老年代存储区域被使用完会进行 Major GC (a.k.a. Old Collection) ,Major GC一般占用较长时间,频繁的Major GC会让使用者感到应用卡顿(GC 过程中应用不会有任何响应)。

2) 非Heap区(Non-Heap Memory)

  • 包含 Permanent Generation ( Java 8 改为Metaspace )
  • Perm Gen 存储预加载类架构、方法(method)代码
  • 设置方法  -XX:PermSize and -XX:MaxPermSize

3)缓存区 Cache Memory

  • 包含代码缓存( Code Cache)
  • 存储已编译的代码 (i.e. native code) 。
  • 当到达某个阈值将会被刷新。

栈(Stack Memory)

到目前为止还没有提到Java的栈内存,这里要重点说明一下栈内存(Stack Memory)

如下图所示

Java栈内存用来执行线程,栈内存中包含了方法的值并关联Heap中的其他对象。

线程thread从permsize中的调用方法。并将调用记录一行一行记录到栈中(个人理解)

Memory相关报错

当应用Crash,日志中有以下报错抛出。

  • java.lang.StackOverFlowError — 栈内存溢出,(可能是方法调用进入循环,检查代码)
  • java.lang.OutOfMemoryError: Java heap space — Heap内存溢出(扩大堆内存或者出heapdump确认堆栈中内容)
  • java.lang.OutOfMemoryError: GC Overhead limit exceeded — 频繁的Gc说明要增大堆内存或者出heapdump确认堆栈中内容是否合理
  • java.lang.OutOfMemoryError: Permgen space —permsize占满了 扩展maxpermsize
  • java.lang.OutOfMemoryError: Metaspace — Metaspace 满扩展maxMetaspace 
  • java.lang.OutOfMemoryError: Unable to create new native thread — 创建了太多的线程 本地系统内存、jvm内存均被占满
  • java.lang.OutOfMemoryError: request size bytes for reason — swap内存被java应用占满
  • java.lang.OutOfMemoryError: Requested array size exceeds VM limit– 数组超过

However, what you have to thoroughly understand is that these outputs can only indicate the impact that the JVM had, not the actual error. The actual error and its root cause conditions can occur somewhere in your code (e.g. memory leak, GC issue, synchronization problem), resource allocation, or maybe even hardware setting. Therefore, I can’t advise you to simply increase the affected resource size to solve the problem. Maybe you will need to monitor resource usage, profile each category, go through heap dumps, check and debug/optimize your code etc. And if none of your efforts seems to work and/or your context knowledge indicates that you need more resources, go for it.

Leave a Reply