参考链接
背景介绍
- Java优势之一就是其具有垃圾回收机制。在大部分情况下,JVM的GC(垃圾回收器)能够帮助我们回那些不可到达的对象(就是未被引用的对象)。
- 当然,在一些情况下,我们仍然需要自己去释放内存(就是把对象置null,把容器、数组清空),否则就会引起内存泄漏,内存泄漏严重时将容易引发
OutOfMemoryError
,详情见内存泄漏。 - 此外,由于GC会停止所有的线程,包括UI线程,所以频繁的GC必然会导致画面卡顿(Android中每16ms为一帧),因此还应避免GC的频繁发生。一个导致GC频繁发生的原因就是内存抖动,点击链接看详情。
- 所以,理解Java的内存机制,有助于帮助我们在写代码的过程中避免内存泄漏。
基本概念
Java内存层级
Heap Segment
在Java8之后,Heap Segment真正意义上的是由Young Generiation和Old Generiation组成的。对象在其中是标记复制算法来判定一个对象是否应该被清理掉。
Heap Segment中发生的GC称为Major GC,只会影响Heap Segment区。
Young Generiation中的GC变化
- 当对象被创建后,首先会被加入eden区。当eden区满了之后,就会触发一次GC,存活下来的对象会被复制到survivor区。
- 当不为空的Survivor区满了,同样会触发一次GC。
- 当短时间内有大量对象创建和释放同样会造成内存抖动,会触发CG。
- 如图所示,survivor有两个区域,其中一个总是保持为空。
- 现假设两个Survivor区分别为S0,S1,并且首次GC时,eden区中存活的对象被复制到S0中。当再次发生GC时,S0和eden中仍然存活的对象就会被复制到空的S1中,此时S0为空;再次发生GC时,S1和eden中存活的对象将被复制到S0中,此时S1为空;再次发生GC…就是这样进行的。当一个对象被来回复制转移的次数达到阀值(默认为15次,可以通过使用
-XX:MaxTenuringThreshold
该命令来调整阀值)时,这个对象将被复制到Old Generiation区中,此时该对象将会变的相对安全,因为Old Segment区的GC频率相对较低。
Old Segment中的GC变化
- 该区域满了之后会触发一次GC,在该次GC中,一些年龄较大的对象会被清理掉。
- 若多次触发GC后,该区域仍然处于满的状态,则会抛出
OutOfMemoryError
。 - 以两种情况下,新建对象会被直接复制到该区域中:
- 当新建对象所需要的内存大于1/2的单个survivor区内存时;
- 当新建对象被该区中的对象引用时,或者引用了该区域中的对象。
总结
- Java内存块被划分为:Code Segment、DataSegment、StackSegment、Heap Segment,其中Heap Segment由于设计对象的创建与销毁,所以它应该重点关注的对象。
- Heap Segment又被划分为两块:Young Generiation和Old Generiation。
- Young Genertiation中又被划分为Eden区和两个Survivor区,对象在其中采用标记复制算法来判定一个对象是应该清理还是移到Old Generiation中。该内存区域发生GC的频率较高。
- Old Generiation发生GC的频率相对较低。当有大对象被创建,或者和该区域有关的对象被创建时,它将会被直接移动到该区域中。