天天看点

JVM堆内存原理堆内存分类

堆内存分类

JVM 堆内存分为2块:Permanent Space 和 Heap Space。

Permanent 即 持久代(Permanent Generation),主要存放的是Java类定义信息,与垃圾收集器要收集的Java对象关系不大。

Heap = { Old + NEW = {Eden, from, to} },Old 即 年老代(Old Generation),New 即 年轻代(Young Generation)。年老代和年轻代的划分对垃圾收集影响比较大。

Permanent Space

用于存放静态类型数据,如Class,Method等。持久代对垃圾回收无显著影响。

Heap space

Heap space又可分为Old和New,即年老代与年轻代。

对象主要分配在新生代的 Eden 区上,如果启动了本地线程分配缓冲区,将线程优先在 (TLAB) 上分配。少数情况会直接分配在老年代中。

一般来说 Java 堆的内存模型如下图所示:

JVM堆内存原理堆内存分类

New 年轻代

年轻代分为3个区,分别是1个Eden区以及2个survivor区,Survivor区又可分为from和to。

对象在Eden区生成,当Eden区满时,仍存活的对象将被复制到Survivor区,当1个Survivor区满时,此区存活的对象将被复制到另一个Survivor区,当2个区全满时,从上一个Survivor区复制来的对象将会被转移至Old年老代。

2个survivor区是对称的,不存在先后关系。故同一个survivor区的对象有可能同时存在直接从Eden区复制过来和从上一个Survivor区复制过来两种情况。而复制到年老区的只有从上一个Survivor区复制来的对象。

因为需要交换的原因,至少有一个Survivor区要是空的。

针对年轻代的回收即Young GC。

Old 年老代

年老代中的对象都是经历数次垃圾回收后存活的对象,故年老区的对象通常是生命周期较长的对象。

针对年老代的回收即为Full GC。

所以,当一组对象生成时,内存申请过程如下:

  1. JVM会试图为相关Java对象在年轻代的Eden区中初始化一块内存区域。
  2. 当Eden区空间足够时,内存申请结束。否则执行下一步。
  3. JVM试图释放在Eden区中所有不活跃的对象(YoungGC)。释放后若Eden空间仍然不足以放入新对象,JVM则试图将部分Eden区中活跃对象放入Survivor区。
  4. Survivor区被用来作为Eden区及年老代的中间交换区域。当年老代空间足够时,Survivor区中存活了一定次数的对象会被移到年老代。
  5. 当年老代空间不够时,JVM会在年老代进行完全的垃圾回收(Full GC)。
  6. FullGC后,若Survivor区及年老代仍然无法存放从Eden区复制过来的对象,则会导致JVM无法在Eden区为新生成的对象申请内存,即出现“Out of Memory”。

一个对象的一生:

我是一个普通的Java对象,我出生在Eden区,在Eden区我还看到和我长的很像的小兄弟,我们在Eden区中玩了挺长时间。有一天Eden区中的人实在是太多了,我就被迫去了Survivor区的“From”区,自从去了Survivor区,我就开始漂了,有时候在Survivor的“From”区,有时候在Survivor的“To”区,居无定所。直到我18岁的时候,爸爸说我成人了,该去社会上闯闯了。于是我就去了年老代那边,年老代里,人很多,并且年龄都挺大的,我在这里也认识了很多人。在年老代里,我生活了20年(每次GC加一岁),然后被回收。

下面哪种情况会导致持久区jvm堆内存溢出?

  1. 循环上万次的字符串处理
  2. 在一段代码内申请上百M甚至上G的内存
  3. 使用CGLib技术直接操作字节码运行,生成大量的动态类
  4. 不断创建对象