分配出去的内存得不到及时的回收,就会引起系统运行速度下降、甚至导致程序瘫痪,这种现象被称为内存泄漏。
C/C++等编程语言使用的是 显式垃圾回收:是由程序员直接进行控制内存回收。显式垃圾回收有以下两个缺点。
1、程序忘记计时回收无用内存,从而导致内存泄漏,降低系统性能,
2、程序错误的回收程序核心类库的内存,从而导致程序崩溃。
java的内存分配和回收都是由JRE在后台自动进行的,JRE会负责回收哪些不再使用的内存,这种机制被称为垃圾回收(Garbage Collection ,GC),JRE会提供一个后台线程来进行检测和控制,一般都是在CPU空闲或者内存不足时自动进行垃圾回收,程序员无法精确控制垃圾回收的时间和顺序等。
java的堆内存是一个运行时数据区,用以保存类的实例,java虚拟机的堆内存中存储着正在运行的应用程序所建的所有对象,这些对象不需要程序通过代码来显式的释放。堆内存的回收是由垃圾回收来负责的,所有的JVM实现都有一个由垃圾回收器管理的堆内存,垃圾回收是一种观念动态存储管理技术。会自动释放不再被程序引用的对象。按照特定的垃圾回收算法来实现内存资源的自动回收功能。
在java中,当没有引用变量指向原先分配给某个其他对象的内存时,该内存便成为垃圾,JVM的一个超级线程会自动释放该内存区,垃圾回收意味着程序不再需要的对象是‘垃圾信息’,这些信息将被丢弃。当一个对象不再被引用时,内存回收其占领的空间。以便空间被新的对象使用,除了释放没用的对象外,垃圾回收也可以清楚内存记录碎片,由于创建对象和垃圾回收器释放丢弃对象所占用的内存空间,内存就会出现碎片,碎片是分配给对象的内存块之间的空闲内存区,碎片整理将所占用的堆内存移到堆的一端,JVM将整理出来的内存分配给新的对象。垃圾回收能自动释放内存空间,优点:
1、垃圾回收机制能很好的提高编程效率,不需要花费时间解决难懂的存储器问题。
2、垃圾回收机制保护程序的完整性。
垃圾回收潜在的缺点:他的开销会影响程序性能,JVM必须跟踪程序中所有的对象,才能确定哪些对象是无用的对象,并最终释放这些对象。这个过程是需要花费时间的。垃圾回收有一下几个特点:
1、垃圾回收机制的工作目标是回收无用的对象的内存空间,只能回收内存资源,对其他物理资源如数据库连接,磁盘I/O等资源则无法回收。
2、为了更快的让垃圾回收机制回收哪些不再使用的对象,可以将对象的引用变量设置为null,通过这种方式暗示垃圾回收机制可以回收该对象。
3、垃圾回收发生的不可预知性,由于不同JVM才用了不同的垃圾回收机制和不同的垃圾回收算法,所以有可能是定时发生,有可能是当CPU空闲时发生,也可能和原始的垃圾回收机制一样,等到内存消耗出现极限时发生,与垃圾回收机制的选择和具体的设置有关。程序员可以通过调用Runtime对象的gc()或者System.gc()等方法建议系统进行垃圾回收。
4、垃圾回收的精确性包括两方面:一是垃圾回收机制能够精确的标记活着的对象,前提是完全回收所有的废弃对象,否则则会造成内存泄漏,二是垃圾回收器能够精确的定位对象之间的引用,他是实现归并和复制等算法的必要条件,通过这种引用关系可以保证所有的对象都能被可靠的回收。所有对象都能被重新分配,从而有效的减少内存碎片的产生。
典型的垃圾收集算法
在确定了哪些垃圾可以被回收后,垃圾收集器要做的事情就是开始进行垃圾回收,但是这里面涉及到一个问题是:如何高效地进行垃圾回收。由于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类、常量、方法描述等。对永久代的回收主要回收两部分内容:废弃常量和无用的类。
典型的垃圾收集器
垃圾收集算法是 内存回收的理论基础,而垃圾收集器就是内存回收的具体实现。下面介绍一下HotSpot(JDK 7)虚拟机提供的几种垃圾收集器,用户可以根据自己的需求组合出各个年代使用的收集器。
1.Serial/Serial Old
Serial/Serial Old收集器是最基本最古老的收集器,它是一个单线程收集器,并且在它进行垃圾收集时,必须暂停所有用户线程。Serial收集器是针对新生代的收集器,采用的是Copying算法,Serial Old收集器是针对老年代的收集器,采用的是Mark-Compact算法。它的优点是实现简单高效,但是缺点是会给用户带来停顿。
2.ParNew
ParNew收集器是Serial收集器的多线程版本,使用多个线程进行垃圾收集。
3.Parallel Scavenge
Parallel Scavenge收集器是一个新生代的多线程收集器(并行收集器),它在回收期间不需要暂停其他用户线程,其采用的是Copying算法,该收集器与前两个收集器有所不同,它主要是为了达到一个可控的吞吐量。
4.Parallel Old
Parallel Old是Parallel Scavenge收集器的老年代版本(并行收集器),使用多线程和Mark-Compact算法。
5.CMS
CMS(Current Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器,它是一种并发收集器,采用的是Mark-Sweep算法。
6.G1
G1收集器是当今收集器技术发展最前沿的成果,它是一款面向服务端应用的收集器,它能充分利用多CPU、多核环境。因此它是一款并行与并发收集器,并且它能建立可预测的停顿时间模型。