Java语言从出现到现在,一直占据编程语言前列,他很大的一个原因就是由于java应用程序所运行的平台有关。我们大家都知道java应用程序运行在java虚拟机上。这样就大大减少了java应用程序和底层操作系统打交道的频率。这也就为java程序的跨平台提供了良好的基础。在java虚拟机中为我们提供了一个很重要的机制就是java虚拟机的自动的内存管理机制。也就是我们平时所说的垃圾回收机制,这使得开发人员不用自己来管理应用中的内存。C/C++开发人员需要通过malloc/free 和new/delete等函数来显式的分配和释放内存。这对开发人员提出了比较高的要求,容易造成内存访问错误和内存泄露等问题。今天我们就一起来看一下java虚拟机给我们提供的这个强大的功能——自动垃圾回收机制。
我们在c/c++的程序中,他们没有java中的自动垃圾回收机制,这就需要开发人员手动的去分配和释放内存,这样就要求我们的开发人员要有一定的细心和对内存管理的经验。如果内存管理不好,很容易产生最常见的两个问题。一是“悬挂引用”,二是内存溢出。所为的悬挂引用就是一个对象引用所指向的内存区块已经被错误的回收并重新分配给新的对象了,程序如果继续使用这个引用的话会造成不可预期的结果。第二个内存溢出就很好理解了,开发人员在做开发的过程中,只显示的申请内存而忘记用完释放掉内存,这样长时间会导致内存溢出的情况。而像java这种具有自动管理内存机制的语言来说,我们开发人员只需考虑引用的运用就可以,把内存管理这块交给我们的语言运行环境来管理。。开发人员并不需要关心内存的分配和回收的底层细节。Java平台通过垃圾回收器来进行自动的内存管理。这样就大大减少了开发人员的工作量
一、Java垃圾回收机制
Java 的垃圾回收器要负责完成3 件任务:
1.分配内存
2.确保被引用的对象的内存不被错误回收
3.回收不再被引用的对象的内存空间。
垃圾回收是一个复杂而且耗时的操作。如果JVM 花费过多的时间在垃圾回收上,则势必会影响应用的运行性能。一般情况下,当垃圾回收器在进行回收操作的时候,整个应用的执行是被暂时中止(stop-the-world)的。这是因为垃圾回收器需要更新应用中所有对象引用的实际内存地址。不同的硬件平台所能支持的垃圾回收方式也不同。比如在多CPU 的平台上,就可以通过并行的方式来回收垃圾。而单CPU 平台则只能串行进行。不同的应用所期望的垃圾回收方式也会有所不同。服务器端应用可能希望在应用的整个运行时间中,花在垃圾回收上的时间总数越小越好。而对于与用户交互的应用来说,则可能希望所垃圾回收所带来的应用停顿的时间间隔越小越好。对于这种情况,JVM 中提供了多种垃圾回收方法以及对应的性能调优参数,应用可以根据需要来进行定制。
二、判断对象是否该被回收算法
1.引用计数算法
给对象添加一个引用计数器,每当有一个地方引用它时,计数器值就加1,当引用失效时,计数器值就减1;任何时刻计数器值都为0时对象就表示它不可能被使用了。这个算法实现简单,但很难解决对象之间循环引用的问题,因此Java并没有用这种算法!这是很多人都误解了的地方。
2.根搜索算法
通过一系列名为“GC ROOT”的对象作为起始点,从这些结点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC ROOT没有任何引用链相连时,则证明这个对象是不可用的。如果对象在进行根搜索后发现没有与GC ROOT相连接的引用链,则会被第一次第标记,并看此对象是否需要执行finalize()方法(忘记finalize()这个方法吧,它可以被try-finally或其他方式代替的),当第二次被标记时,对象就会被回收。
三、Java虚拟机基本垃圾回收算法:
1.标记-清除(Mark-Sweep)
此算法执行分两阶段。第一阶段从引用根节点开始标记所有被引用的对象,第二阶段遍历整个堆,把未标记的对象清除。它停止所有工作,收集器从根开始访问每一个活跃的节点,标记它所访问的每一个节点。走过所有引用后,收集就完成了,然后就对堆进行清除(即对堆中的每一个对象进行检查),所有没有标记的对象都作为垃圾回收并返回空闲列表。下图 展示了垃圾收集之前的堆,阴影块是垃圾,因为用户程序不能到达它们:
标记-清除实现起来很简单,可以容易地回收循环的结构,并且不像引用计数那样增加编译器或者赋值函数的负担。但是它也有不足 ―― 收集暂停可能会很长,在清除阶段整个堆都是可访问的,这对于可能有页面交换的堆的虚拟内存系统有非常负面的性能影响。
标记-清除的最大问题是,每一个活跃的(即已分配的)对象,不管是不是可到达的,在清除阶段都是可以访问的。因为很多对象都可能成为垃圾,这意思着收集器花费大量精力去检查并处理垃圾。标记-清除收集器还容易使堆产生碎片,这会产生区域性问题并可以造成分配失败,即使看来有足够的自由内存可用。此算法需要暂停整个应用,同时,会产生内存碎片。
2.复制(Copying)
此算法把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾回收时,遍历当前使用区域,把正在使用中的对象复制到另外一个区域中。次算法每次只处理正在使用中的对象,因此复制成本比较小,同时复制过去以后还能进行相应的内存整理,不过出现“碎片”问题。当然,此算法的缺点也是很明显的,就是需要两倍内存空间。
3.标记-整理(Mark-Compact)
此算法结合了“标记-清除”和“复制”两个算法的优点。也是分两阶段,第一阶段从根节点开始标记所有被引用对象,第二阶段遍历整个堆,把清除未标记对象并且把存活对象“压缩”到堆的其中一块,按顺序排放。此算法避免了“标记-清除”的碎片问题,同时也避免了“复制”算法的空间问题。
4.增量收集(Incremental Collecting)
实施垃圾回收算法,即:在应用进行的同时进行垃圾回收。不知道什么原因JDK5.0中的收集器没有使用这种算法的。
5.分代(Generational Collecting)