Java 虚拟机具有一个堆,堆是运行时数据区域,所有类实例和数组的内存均从此处分配。
JVM主要管理两种类型内存:堆和非堆,堆内存(Heap Memory)是在 Java 虚拟机启动时创建,非堆内存(Non-heap Memory)是在JVM在堆之外的内存。
简单来说,堆是Java代码可及的内存,留给开发人员使用的;非堆是JVM留给自己用的,包含方法区、JVM内部处理或优化所需的内存(如 JIT Compiler,Just-in-time Compiler,即时编译后的代码缓存)、每个类结构(如运行时常数池、字段和方法数据)以及方法和构造方法的代码。
- 堆内存(Heap Memory): 存放Java对象
- 非堆内存(Non-Heap Memory): 存放类加载信息和其它meta-data
- 其它(Other): 存放JVM 自身代码等
在JVM启动时,就已经保留了固定的内存空间给Heap内存,这部分内存并不一定都会被JVM使用,但是可以确定的是这部分保留的内存不会被其他进程使用,这部分内存大小由-Xmx 参数指定。
而另一部分内存在JVM启动时就分配给JVM,作为JVM的初始Heap内存使用,这部分内存是由 -Xms 参数指定。
在下图中,我们可以看到在Young Generation中有一个叫Eden Space的空间,主要是用来存放新生的对象,还有两个Survivor Spaces(from,to),它们的大小总是一样,它们用来存放每次垃圾回收后存活下来的对象。
首先我们需要知道JVM内存申请过程:
- JVM 会试图为相关Java对象在Eden中初始化一块内存区域
- 当Eden空间足够时,内存申请结束;否则到下一步
- JVM 试图释放在Eden中所有不活跃的对象(这属于1或更高级的垃圾回收),释放后若Eden空间仍然不足以放入新对象,则试图将部分Eden中活跃对象放入Survivor区
- Survivor区被用来作为Eden及OLD的中间交换区域,当OLD区空间足够时,Survivor区的对象会被移到Old区,否则会被保留在Survivor区
- 当OLD区空间不够时,JVM 会在OLD区进行完全的垃圾收集(0级)
- 完全垃圾收集后,若Survivor及OLD区仍然无法存放从Eden复制过来的部分对象,导致JVM无法在Eden区为新对象创建内存区域,则出现”out of memory”错误
接下来介绍GC:
以下概念截取自: http://www.daniel-journey.com/archives/97
GC:自动内存管理程序,它负责保证被引用的对象始终在内存中;同时把不被应用的对象从内存中释放。被引用的对象称之为Live 对象;不被引用的对象就是Dead对象,是需要回收的。GC会自动计算对象被引用的情况,只要对象不在被引用,相应的内存就会被回收,而C++中需要开发人员通过代码来“显示”地回收内存,如果程序员没有回收就会导致内存的泄露(内存泄露的原因有很多种这只是其中一个)。C++中还有经常出现的一个问题是一个对象在还有其他引用存在的情况下,就被程序给回收了,导致其他引用访问该对象时出现严重错误。另外,GC非常重要的一点就避免内存碎片,道理跟windows的磁盘整理一样,把使用中各个内存块整合起来,这样才能保证有足够的空间来存储大对象。总而言之,GC试图使开发者的程序更加有效有序地使用有限的内存。
Hostspot虚拟机采用了“分代回收”的策略,而“分”的非常重要的一个依据就是根据对象存在的时间的长短分成若干个“代(Gerneration)”,每个代上可以采取不同的GC策略。而采用这种“分代回收”策略是利用了2条潜规则,而且这两条潜规则不只限于Java
- 绝大对数的对象不会被长时间引用,这些对象在他的“青年期”就会被回收。
- 几乎不存在很老和很新对象之间的引用
依据这两条潜规则,Hotspot分离出新生代(Yound Generation)和旧生代(Old Generation)。由于新生代的空间通常都比较小而且可能存在大量不再被引用的对象,所以针对新生代的GC执行频率高、速度快。在新生代中存在了一定时间还没被GC掉的对象最终会被提升到旧生代。旧生代空间比新生代的要大,但它的占用率增长会比较缓慢,因此,旧生代的GC执行频率低,但需要更长的时间来完成。
另外还有一个 永生代 (Permanent Generation),永生代中的保存的对象都是JVM用来方便管理GC的,例如类和方法对象以及它们的描述对象。( Sun的JVM不回收PermGen,由于动态语言产生类会比较多,有时就会出现PermGen Overflow。另外推荐用JRockit JVM)
有了这些基础,我们就能够描述GC的执行过程:
- 在Young Generation块中,垃圾回收一般用Copying的算法,速度快。每次GC的时候,存活下来的对象首先由Eden拷贝到某个SurvivorSpace, 当Survivor Space空间满了后,剩下的live对象就被直接拷贝到OldGeneration中去。因此,每次GC后,Eden内存块会被清空。
- 在Old Generation块中,垃圾回收一般用mark-compact的算法,速度慢些,但减少内存要求。
- 垃圾回收分多级,0级为全部(Full)的垃圾回收,会回收OLD段中的垃圾;1级或以上为部分垃圾回收,只会回收Young中的垃圾,内存溢出通常发生于OLD段或Perm段垃圾回收后,仍然无内存空间容纳新的Java对象的情况