Java堆是虚拟机所管理的内存中最大的一块,此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。这块区域是“线程共享”的。
Java堆是垃圾收集器管理的主要区域,因此也被称作“GC堆”。由于现在垃圾收集器基本采用分代收集算法,所以Java堆还可以细分为新生代和老年代。Java堆可以处于物理上不连续的内存空间中,只要逻辑上连续即可。如果堆中没有内存完成实例分配,并且堆也无法扩展时,将会抛出OutOfMemoryError异常。
堆区被分为新生代和年老代。这样分是为了方便GC操作。
一般对象都在新生代创建,因为Java程序中的对象绝大部分是朝生夕死的,所以新生代中每次GC都会有大量对象被回收,新生代的GC操作也更频繁。
年老代中一般存放那些对象存活率高、生命周期长的对象。一般位于新生代中的对象满足某些条件(比如大对象、经历了几次新生代GC还存活的对象等)会转到年老代中去。年老代中GC不频繁,但GC效率要比新生代中GC慢许多。
既然堆区被分为新生代和年老代,那么对象是如何在这两个区域分配的?最普遍的几条策略如下:
新生代更细分为一个Eden区和两个Survivor区,具体这些分区的作用在GC算法中详细讲解。这里只要了解对象优先在新生代分配即可。
所谓的大对象是指,需要大量连续内存空间的Java对象,最典型的大对象就是那种很长的字符串以及数组。经常出现大对象容易导致内存还有不少空间的时候提前触发GC。
虚拟机提供一个配置参数,可以令所有大于这个值的对象直接在年老代分配。
虚拟机给每一个对象设置了一个对象年龄计数器。如果对象在Eden出生并经历了一次Minor GC后仍然存活,那么她会被置入Survivor区(要保证Survivor能容纳),并且年龄置为1。对象在Survivor区每熬过一个Minor GC就增加1岁,当年龄增加到一个值(默认15),会转入老年代。
为了能更好地适应不同程序的内存情况,虚拟机提供动态对象年龄判定机制。如果Survivor区中相同年龄所有对象的总和大于等于Survivor空间的一半,那么所有大于等于该年龄的对象直接进入年老代。
浅堆(Shallow Heap)和深堆(Retained Heap)是两个非常重要的概念,注意这里的“堆”和上面的堆意义不一样。这个“堆”是针对单个对象而言的。
比如Java中String类型结构包含三个字段:hash32、hash和一个ref引用,该引用保存一个静态的字符串地址。那么2个int值共占8字节,对象引用占用4字节,对象头8字节,合计20字节,向8字节对齐,故占24字节。所以不论String保存的是什么字符串,它占用的内存都是24字节。
深堆表示一个对象被GC回收后,可真实释放的内存大小。先来理解保留集:一个对象的保留集是一个对象集合,集合中的所有对象都可以在该对象被回收之后被回收。然后说深堆,深堆是指对象的保留集中所有的对象的浅堆大小之和。
注意:浅堆指对象本身占用的内存,不包括其内部引用对象的大小。一个对象的深堆指只能通过该对象访问到的(直接或间接)所有对象的浅堆之和,即对象被回收后,可以释放的真实空间。