垃圾回收机制主要完成下面两件事情:
- 跟踪并监控每个Java对象,当某个对象处于不可达状态时,回收该对象所占用的内存。
- 清理内存分配、回收过程中产生的内存碎片。
垃圾回收的基本算法
上一节讲到,垃圾回收机制判断某个对象是否可以回收的标准是:是否还有引用指向该对象。实际上,垃圾回收机制不可能实时检测到每个Java对象的状态,因此当一个对象失去引用后,它也不会被立即回收,只有等垃圾回收机制运行时才能被回收。
对于一个垃圾回收器的设计算法来说,有如下几个选择:
- 串行回收和并行回收:串行回收就是不管系统有多少个cpu,始终只用一个cpu来执行垃圾回收操作;并行回收就是把整个回收工作拆分成多部分,每个部分由一个cpu负责,从而让多个cpu并行回收。并行回收的执行效率高,复杂度增加,另外有一些副作用,比如内存碎片增加等。
- 并发执行和应用程序停止:应用程序停止的垃圾回收方式在执行垃圾回收的同时会导致应用程序停止。并发执行的垃圾回收虽然不会导致应用程序暂停,但由于并发执行垃圾回收需要解决和应用程序的执行冲突(应用程序可能会在垃圾回收的过程中修改对象),因此并发执行垃圾回收的系统开销比应用程序停止更高,而且执行时需要更多的堆内存。
- 压缩/不压缩和复制:为了减少内存碎片,支持压缩的垃圾回收器会把所有的活对象搬迁到一起,然后将之前占用的内存全部回收。不压缩的垃圾回收器只是回收内存,这样回收回来的内存不可能是连续的,将会有较多的碎片。
上面介绍的复制、不压缩、压缩都是垃圾回收器回收已用内存空间的方式,详细解释如下:
- 复制:将内存分成两个相同空间,从根开始访问每一个关联的可达对象,将空间A的可达对象全部复制到空间B,然后一次性回收整个空间A。
- 标记清除:也就是不压缩回收方式。垃圾回收器先从跟开始访问所有可达对象,将它们标记为可达状态,然后再遍历整个内存区域,对所有的没有标记为可达的对象进行回收处理。
- 标记压缩:这是压缩回收方式,这种方式充分利用上述两种算法的优点,回收器先从根开始访问所有的可达对象,并标记。接下来垃圾回收器将这些对象搬迁到一起,然后再回收那些不可达的对象的内存空间。
现行的垃圾回收器用分代的方式采用不同的回收设计。分代的基本思路是根据对象生存时间的长短,把堆内存分成三个代:
垃圾回收器会根据不同代的特点采取不同的回收算法,从而充分利用各种回收算法的优点。
堆内存的分代回收
采用分代回收的策略是基于如下两个事实:
- 绝大多数对象不会被长时间引用,这些对象在其Young期间就会被回收。
- 很老的对象和很新的对象之间很少存在相互引用的情况。
Young代
对于Young代的对象而言,大部分对象都会很快进入不可达状态,只有少量的对象能熬到垃圾回收执行时,因此,可以使用
复制算法。
Young代由一个Eden区和Survivor区构成。Eden区一般是Survivor区的几十倍,默认是32倍。Survivor区分两部分,来交替保存可达对象。绝大多数对象先分配到Eden区中(有一部分大的对象直接被分配到Old代中),Survivor区中的对象都至少在Young代中经历过一次垃圾回收,所以这些对象在被转移到Old代之前,会先保留在Survivor空间中。同一时间,两个Survivor空间中有一个用来保存对象,另一个是空的,用来在下次垃圾回收时保存Young代中的对象。每次复制就是将Eden和第一个Survivor区的可达对象复制到第二个Survivor区,然后清空Eden与第一个Survivor区。
Old代
如果Young代中的对象经过数次垃圾回收依然没有被回收掉,垃圾回收机制就会将这个对象转移到Old代。
Old代的大部分对象没那么容易死,随着时间的推移,Old代的对象会越来越多,因此Old代的空间要比Young代的空间更大。Old代的垃圾回收具有如下特征:
- Old代垃圾回收的执行频率无须太高,因为很少有对象会死掉。
- 每次对Old代执行垃圾回收都需要更长的时间来完成。
基于以上考虑,垃圾回收器通常会使用标记压缩算法,这种算法可以避免复制Old代的大量对象,而且由于Old代的对象不会很快死亡,回收过程不会大量地产生内存碎片,因此相对比较划算。
Permanent代
Permanent代主要用于装作Class、方法等信息,默认为64MB,垃圾回收机制通常不会回收Permanent代中的对象。对于那些需要加载很多类的服务程序,往往需要加大Permanent代的内存。
总结
当Young代的内存将要用完时,垃圾回收机制会对Young代进行垃圾回收,垃圾回收机制会采用较高的频率对Young代进行扫描和回收。因为这种回收的系统开销比较小,被成为次要回收(minor collection)。当Old代的内存将要用完时,垃圾回收机制会进行全回收,也就是对Young代和Old代都要进行回收,此时回收成本就大得多,因此成为主要回收(major collection)。
通常来说,Young代的内存会先被回收,而且会使用专门的回收算法(复制算法)来回收Young代的内存;对于Old代的回收频率则要低得多,因此也会采用专门的回收算法。如果需要进行内存压缩,那么每个代都独立地进行压缩。
编程注意事项
了解java的内存回收机制是为了让程序员写出高效率的代码,这里有几个建议:
尽量使用直接量
当需要使用字符串时,不应该采用new的方式创建对象,而直接采用直接量来创建。
使用StringBuilder和StringBuffer进行字符串连接
使用多个String对象进行字符串连接,在运行时将生成大量的临时字符串,这些字符串会保存在内存中导致程序性能下降
尽早释放无用对象的引用
这个不用多说了吧!
尽量少用静态变量
前面介绍了Permanent代基本不会被回收,static修饰的变量是属于Class的,就存在Permanent代,因此尽量少用static变量