- 垃圾对象
内存中不再被使用的对象。- 垃圾回收(GC)
jvm自动释放垃圾对象所占内存的机制。- 如果对象再没有被引用变量引用时,便称之为垃圾,其所占用的内存将很快被jvm回收。
public class TestStudent(){
public static void main(String[] args){
Student s = new Student();
System.out.println(s.getSname());
s = null;
Student s1 = new Student();
System.out.println(s1.getSname());
}
}
一、什么是垃圾回收?
垃圾回收是Java中自动内存管理的另一种叫法。垃圾回收的目的是为程序保持尽可能多的可用堆(heap)。 JVM会删除堆上不再需要从堆引用的对象。
二、什么是Java垃圾回收器?
Java垃圾回收器是Java虚拟机(JVM)的三个重要模块(另外两个是解释器和多线程机制)之一,为应用程序提供内存的自动分配(Memory Allocation)、自动回收(Garbage Collect)功能,这两个操作都发生在Java堆上(一段内存快)。某一个时点,一个对象如果有一个以上的引用(Rreference)指向它,那么该对象就为活着的(Live),否则死亡(Dead),视为垃圾,可被垃圾回收器回收再利用。垃圾回收操作需要消耗CPU、线程、时间等资源,所以容易理解的是垃圾回收操作不是实时发生的(对象死亡马上释放),当内存消耗完或者是达到某一个指标(Threshold,使用内存占总内存的比列,比如0.75)时,触发垃圾回收操作。有一个对象死亡的例外,java.lang.Thread类型的对象即使没有引用,只要线程还在运行,就不会被回收。
三、回收的机制
根据Java回收机制的不同,Java堆有可能拥有不同的结构。最为常见的一种构成是将整个Java堆分为新生代和老年代。其中新生代存放新生对象或者年龄不大的对象,老年代则存放老年对象。新生代有可能分为eden区、s0区、s1区,s0区和s1区也被称为from和to区,他们是两块大小相同、可以互换角色的内存空间。
依据统计分析可知,Java(包括一些其它高级语言)里面大多数对象生命周期都是短暂的,所以需要把Java内存分代管理。分代的目的无非就是为不同代的内存块运用不同的管理策略(算法),从而最大化性能。对象在新生代里面分配,新生代里面的对象经过多个回收周期依然存活的会自动晋升到老年代。相对于老年代,通常新生代需要垃圾回收的频率更高。
四、垃圾回收的类型
JVM分别对新生代和旧生代采用不同的垃圾回收机制
- 新生代的GC:
新生代通常存活时间较短,因此基于Copying算法来进行回收,所谓Copying算法就是扫描出存活的对象,并复制到一块新的完全未使用的空间中,对应于新生代,就是在Eden和FromSpace或ToSpace之间copy。新生代采用空闲指针的方式来控制GC触发,指针保持最后一个分配的对象在新生代区间的位置,当有新的对象要分配内存时,用于检查空间是否足够,不够就触发GC。当连续分配对象时,对象会逐渐从eden到survivor,最后到旧生代。- 在执行机制上JVM提供了串行GC(SerialGC)、并行回收GC(ParallelScavenge)和并行GC(ParNew)
1.串行GC
在整个扫描和复制过程采用单线程的方式来进行,适用于单CPU、新生代空间较小及对暂停时间要求不是非常高的应用上,是client级别默认的GC方式,可以通过-XX:+UseSerialGC来强制指定
2.并行回收GC
在整个扫描和复制过程采用多线程的方式来进行,适用于多CPU、对暂停时间要求较短的应用上,是server级别默认采用的GC方式,可用-XX:+UseParallelGC来强制指定,用-XX:ParallelGCThreads=4来指定线程数
3.并行GC
与旧生代的并发GC配合使用- 旧生代的GC:
旧生代与新生代不同,对象存活的时间比较长,比较稳定,因此采用标记(Mark)算法来进行回收,所谓标记就是扫描出存活的对象,然后再进行回收未被标记的对象,回收后对用空出的空间要么进行合并,要么标记出来便于下次进行分配,总之就是要减少内存碎片带来的效率损耗。在执行机制上JVM提供了串行GC(SerialMSC)、并行GC(parallelMSC)和并发GC(CMS),具体算法细节还有待进一步深入研究。- 新生代的构成
为了更好地理解GC,我们现在来学习新生代,新生代是用来保存那些第一次被创建的对象,他可以被分为三个空间
1.一个伊甸园空间(Eden )
2.两个幸存者空间(Survivor )
一共有三个空间,其中包含两个幸存者空间。每个空间的执行顺序如下:
1.绝大多数刚刚被创建的对象会存放在伊甸园空间。
2.在伊甸园空间执行了第一次GC之后,存活的对象被移动到其中一个幸存者空间。
3.此后,在伊甸园空间执行GC之后,存活的对象会被堆积在同一个幸存者空间。
4.当一个幸存者空间饱和,还在存活的对象会被移动到另一个幸存者空间。之后会清空已经饱和的那个幸存者空间。
5.在以上的步骤中重复几次依然存活的对象,就会被移动到老年代。
如果你仔细观察这些步骤就会发现,其中一个幸存者空间必须保持是空的。如果两个幸存者空间都有数据,或者两个空间都是空的,那一定标志着你的系统出现了某种错误。
五、典型的垃圾收集算法
在确定了哪些垃圾可以被回收后,垃圾收集器要做的事情就是开始进行垃圾回收,但是这里面涉及到一个问题是:如何高效地进行垃圾回收。由于Java虚拟机规范并没有对如何实现垃圾收集器做出明确的规定,因此各个厂商的虚拟机可以采用不同的方式来实现垃圾收集器,所以在此只讨论几种常见的垃圾收集算法的核心思想。
1.Mark-Sweep(标记-清除)算法
这是最基础的垃圾回收算法,之所以说它是最基础的是因为它最容易实现,思想也是最简单的。标记-清除算法分为两个阶段:标记阶段和清除阶段。标记阶段的任务是标记出所有需要被回收的对象,清除阶段就是回收被标记的对象所占用的空间。具体过程如下图所示:
从图中可以很容易看出标记-清除算法实现起来比较容易,但是有一个比较严重的问题就是容易产生内存碎片,碎片太多可能会导致后续过程中需要为大对象分配空间时无法找到足够的空间而提前触发新的一次垃圾收集动作。
2.Copying(复制)算法
为了解决Mark-Sweep算法的缺陷,Copying算法就被提了出来。它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用的内存空间一次清理掉,这样一来就不容易出现内存碎片的问题。具体过程如下图所示:
这种算法虽然实现简单,运行高效且不容易产生内存碎片,但是却对内存空间的使用做出了高昂的代价,因为能够使用的内存缩减到原来的一半。
很显然,Copying算法的效率跟存活对象的数目多少有很大的关系,如果存活对象很多,那么Copying算法的效率将会大大降低。
3.Mark-Compact(标记-整理)算法
为了解决Copying算法的缺陷,充分利用内存空间,提出了Mark-Compact算法。该算法标记阶段和Mark-Sweep一样,但是在完成标记之后,它不是直接清理可回收对象,而是将存活对象都向一端移动,然后清理掉端边界以外的内存。具体过程如下图所示:
4.Generational Collection(分代收集)算法
- 分代收集算法是目前大部分JVM的垃圾收集器采用的算法。它的核心思想是根据对象存活的生命周期将内存划分为若干个不同的区域。一般情况下将堆区划分为老年代(Tenured Generation)和新生代(Young Generation),老年代的特点是每次垃圾收集时只有少量对象需要被回收,而新生代的特点是每次垃圾回收时都有大量的对象需要被回收,那么就可以根据不同代的特点采取最适合的收集算法。
- 目前大部分垃圾收集器对于新生代都采取Copying算法,因为新生代中每次垃圾回收都要回收大部分对象,也就是说需要复制的操作次数较少,但是实际中并不是按照1:1的比例来划分新生代的空间的,一般来说是将新生代划分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden空间和其中的一块Survivor空间,当进行回收时,将Eden和Survivor中还存活的对象复制到另一块Survivor空间中,然后清理掉Eden和刚才使用过的Survivor空间。
- 而由于老年代的特点是每次回收都只回收少量对象,一般使用的是Mark-Compact算法。
注意,在堆区之外还有一个代就是永久代(Permanet Generation),它用来存储class类、常量、方法描述等。对永久代的回收主要回收两部分内容:废弃常量和无用的类。
5.根搜索算法
检查那些对象会被回收最重要的一个方法
由于引用计数算法的缺陷,所以JVM一般会采用一种新的算法,叫做根搜索算法。它的处理方式就是,设立若干种根对象,当任何一个根对象到某一个对象均不可达时,则认为这个对象是可以被回收的。
就拿上图来说,ObjectD和ObjectE是互相关联的,但是由于GC roots到这两个对象不可达,所以最终D和E还是会被当做GC的对象,上图若是采用引用计数法,则A-E五个对象都不会被回收。