A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

© 黄玉昆 黑马帝   /  2013-3-25 07:32  /  2423 人查看  /  6 人回复  /   1 人收藏 转载请遵从CC协议 禁止商业使用本文

Java中有两种核心机制,一个是java虚拟机|(JavaViretul Machine),另一个就是垃圾收集机制(GarbageCollection),下面就简单说一说关于java中的垃圾收集机制,并简单总结一下面试中如何回答此类问题。
一、概述:
1、简述:
Java的垃圾回收机制是java虚拟机提供的能力,用于在空闲时间以不定时的方式动态回收无任何引用的对象占据的内存空间。它会自动进行,程序员无法进行精确的控制和干预。
2、垃圾收集的意义:
1)释放废弃对象的内存空间为新对象所用。
Java中,当没有对象引用指向原先分配给某个对象的内存时,该内存便成为垃圾。JVM的一个系统级线程会自动释放该内存块。垃圾收集意味着程序不再需要的对象是"无用信息",这些信息将被丢弃。当一个对象不再被引用的时候,内存回收它占领的空间,以便空间被后来的新对象使用。
2)垃圾回收可清楚内存记录碎片,即创建和释放对象时产生的碎片,以及对象内存块之间的空闲内存洞。碎片整理将所占用的堆内存移到堆的一端,JVM将整理出的内存分配给新的对象。
3垃圾收集能自动释放内存空间,减轻编程的负担。
这使Java 虚拟机具有一些优点。首先,它能使编程效率提高。在没有垃圾收集机制的时候,可能要花许多时间来解决一个难懂的存储器问题。在用Java语言编程的时候,靠垃圾收集机制可大大缩短时间。其次是它保护程序的完整性, 垃圾收集是Java语言安全性策略的一个重要部份。
3、垃圾收集的缺点:
垃圾收集的一个潜在的缺点是它的开销影响程序性能。Java虚拟机必须追踪运行程序中有用的对象, 而且最终释放没用的对象。这一个过程需要花费处理器的时间。
二、关于垃圾回收机制的几个重要问题
要想了解java中垃圾收集机制是如何运作的,就要先明白几个重要问题,才能更好地了解垃圾收集机制的具体算法。
1、谁在做GCGarbageCollection)?
Java采用一种不同于c++的,很方便的方法:GarbageCollectionJava垃圾回收机制放在JVM里。JVM完全负责垃圾回收事宜,应用只在需要时申请空间,而在抛弃对象时不必关心空间回收问题。
垃圾收集的实现和具体的JVM 以及JVM的内存模型有非常紧密的关系。不同的JVM 可能采用不同的垃圾收集,而JVM 的内存模型决定着该JVM可以采用哪些类型垃圾收集。现在,HotSpot系列JVM中的内存系统都采用先进的面向对象的框架设计,这使得该系列JVM都可以采用最先进的垃圾收集。
2、对象在什么时候被丢弃?
每个对象都是有自己的作用域的,确切说是对象的引用和作用域有关,因为没有引用的对象,一般被认为是废弃的对象。一个对象,可以有多个引用指向它,但当一个对象不再有任何的引用变量指向它的时候,那么这个对象就被抛弃了,或者说这个对象可以被垃圾收集器回收了(是可以,但是不知道在何时)。
这就是说,当不存在对某对象的任何引用时,就意味着,应用告诉JVM:我不要这个对象,你可以回收了。JVM的垃圾回收机制会对堆空间做实时检测。当发现某对象无引用(或者说在引用计数算法中计数为0)时,就将该对象列入待回收列表中。但是,并不是马上予以销毁。
3、对象被丢弃就被回收?
该对象被认定为没有存在的必要了,那么它所占用的内存就可以被释放。被回收的内存可以用于后续的再分配。但是,并不是对象被抛弃后当即被回收的。JVM进程做空间回收有较大的系统开销。如果每当某应用进程丢弃一个对象,就立即回收它的空间,势必会使整个系统的运转效率非常低下。
下面将说道,JVM的垃圾回收机制有多个算法。除了引用计数法是用来判断对象是否已被抛弃外,其它算法是用来确定何时及如何做回收。JVM的垃圾回收机制要在时间和空间之间做个平衡。
因此,为了提高系统效率,垃圾回收器通常只在满足两个条件时才运行:即有对象要回收且系统需要回收。切记垃圾回收要占用时间,因此,Java运行时系统只在需要的时候才使用它。因此你无法知道垃圾回收发生的精确时间。


6 个回复

倒序浏览
4、没有引用变量指向的对象有用吗?
前面说道,没有引用的对象即被丢弃,但有个例外,对于一次性使用的对象(临时对象),可以不用引用指向它,如:
System.out.println(”I am java”);
这里创建了一个字符串对象,直接传递给println()方法。
5、应用能干预垃圾回收吗?
对于垃圾回收机制来说,应用只有两个途径发消息给JVM
第一、指向某个对象的所有引用变量全部移除,相当于给JVM发送一个消息:这个对象已被丢弃,可以回收了。
第二、通过调用库方法System.gc(),这需要让java来做。
第一个是一个告知,而第二个也只是一个请求。JVM接受这个消息后,并不是立即做垃圾回收,而只是对几个垃圾回收算法做了加权,使垃圾回收操作容易发生,或提早发生,或回收较多而已。
另外,有时并不希望java回收垃圾,这要求运行速度最快的实时系统,特别是嵌入式系统,往往希望如此。
Java的垃圾回收机制是为所有Java应用进程服务的,而不是为某个特定的进程服务的。因此,任何一个进程都不能命令Java垃圾回收机制做什么、怎么做或做多少。
6、对象被回收需要做的事及满足的条件
1)垃圾收集算法要做的两件基本的事情:
第一、发现无用信息对象
第二、回收被无用对象占用的内存空间,使内存空间被程序复用
2)垃圾回收满足的两个条件:
第一、有兑现是不再有引用指向,需要回收其内存空间
第二、垃圾回收机制可以检测到这个无用的信息。


回复 使用道具 举报
三、垃圾收集的算法分析
垃圾收集的目的在于清除不再使用的对象。GC通过确定对象是否被活动对象引用来确定是否收集该对象。GC首先要判断该对象是否是时候可以收集。下面主要介绍两种常用的方法:引用计数和对象遍历法:
1 引用计数法(Reference Counting Collector)
引用计数法是唯一没有使用根集的垃圾回收的法,该算法使用引用计数器来区分存活对象和不再使用的对象。一般来说,堆中的每个对象对应一个引用计数器。当每一次创建一个对象并赋给一个变量时,引用计数器置为1。当对象被赋给任意变量时,引用计数器每次加1当对象出了作用域后(该对象丢弃不再使用),引用计数器减1,一旦引用计数器为0,对象就满足了垃圾收集的条件。
基于引用计数器的垃圾收集器运行较快,不会长时间中断程序执行,适宜地必须 实时运行的程序。但引用计数器增加了程序执行的开销,因为每次对象赋给新的变量,计数器加1,而每次现有对象出了作用域生,计数器减1
优点:引用计数收集器可以很快的执行,交织在程序运行中。对程序不被长时间打断的实时环境比较有利。
缺点 无法检测出循环引用。如父对象有一个对子对象的引用,子对象反过来引用父对象。这样,他们的引用计数永远不可能为0.
2、跟踪收集器:
早期的JVM使用引用计数,现在大多数JVM采用对象引用遍历。对象引用遍历从一组对象开始,沿着整个对象图上的每条链接,递归确定可到达(reachable)的对象。如果某对象不能从这些根对象的一个(至少一个)到达,则将它作为垃圾收集。在对象遍历阶段,GC必须记住哪些对象可以到达,以便删除不可到达的对象,这称为标记(marking)对象。
下一步,GC要删除不可到达的对象。删除时,有些GC只是简单的扫描堆栈,删除未标记的未标记的对象,并释放它们的内存以生成新的对象,这叫做清除(sweeping)。这种方法的问题在于内存会分成好多小段,而它们不足以用于新的对象,但是组合起来却很大。因此,许多GC可以重新组织内存中的对象,并进行压缩(compact),形成可利用的空间。
为此,GC需要停止其他的活动活动。这种方法意味着所有与应用程序相关的工作停止,只有GC运行。结果,在响应期间增减了许多混杂请求。另外,更复杂的 GC不断增加或同时运行以减少或者清除应用程序的中断。有的GC使用单线程完成这项工作,有的则采用多线程以增加效率。


回复 使用道具 举报
四、finalize方法透视垃圾收集器的运行
1、在JVM垃圾收集器收集一个对象之前 ,一般要求程序调用适当的方法释放资源,但在没有明确释放资源的情况下,Java提供了缺省机制来终止化该对象心释放资源,这个方法就是finalize()。它的原型为:
protected void finalize() throws Throwable
finalize()方法返回之后,对象消失,垃圾收集开始执行。原型中的throws Throwable表示它可以抛出任何类型的异常。
2、之所以要使用finalize(),是由于有时需要采取与Java的普通方法不同的一种方法,通过分配内存来做一些具有C风格的事情。这主要可以通过"固有方法"来进行,它是从Java里调用非Java方法的一种方式。也就是说我们不能过多地使用finalize(),它并不是进行普通清除工作的理想场所。
3Java不允许我们创建本地(局部)对象--无论如何都要使用new。但在Java中,没有"delete"命令来释放对象,因为垃圾收集器会帮助我们自动释放存储空间。所以如果站在比较简化的立场,我们可以说正是由于存在垃圾收集机制,所以Java没有破坏器。然而,随着以后学习的深入,就会知道垃圾收集器的存在并不能完全消除对破坏器的需要,或者说不能消除对破坏器代表的那种机制的需要(而且绝对不能直接调用finalize(),所以应尽量避免用它)。若希望执行除释放存储空间之外的其他某种形式的清除工作,仍然必须调用Java中的一个方法。它等价于C++的破坏器,只是没后者方便。
4、下面这个例子向大家展示了垃圾收集所经历的过程,并对前面的陈述进行了总结。
  1. class Chair {
  2. static boolean gcrun = false;
  3. static boolean f = false;
  4. static int created = 0;
  5. static int finalized = 0;
  6. int i;
  7. Chair() {
  8. i = ++created;
  9. if(created == 47)
  10. System.out.println("Created 47");
  11. }
  12. protected void finalize() {
  13. if(!gcrun) {
  14. gcrun = true;
  15. System.out.println("Beginning to finalize after " + created + " Chairs have been created");
  16. }
  17. if(i == 47) {
  18. System.out.println("Finalizing Chair #47, " +"Setting flag to stop Chair creation");
  19. f = true;
  20. }
  21. finalized++;
  22. if(finalized >= created)
  23. System.out.println("All " + finalized + " finalized");
  24. }
  25. }
  26. public class Garbage {
  27. public static void main(String[] args) {
  28. if(args.length == 0) {
  29. System.err.println("Usage: \n" + "java Garbage before\n or:\n" + "java Garbage after");
  30. return;
  31. }
  32. while(!Chair.f) {
  33. new Chair();
  34. new String("To take up space");
  35. }
  36. System.out.println("After all Chairs have been created:\n" + "total created = " + Chair.created +
  37. ", total finalized = " + Chair.finalized);
  38. if(args[0].equals("before")) {
  39. System.out.println("gc():");
  40. System.gc();
  41. System.out.println("runFinalization():");
  42. System.runFinalization();
  43. }
  44. System.out.println("bye!");
  45. if(args[0].equals("after"))
  46. System.runFinalizersOnExit(true);
  47. }
  48. }
复制代码
回复 使用道具 举报
5、使用finalize()方法需要注意的几点:
1)每个对象只能调用finalize()方法一次。如果在finalize()方法执行时产生异常(exception),则该对象仍可以被垃圾收集器收集。
2)垃圾收集器跟踪每一个对象,收集那些不可触及的对象(即该对象不再被程序引用 了),回收其占有的内存空间。但在进行垃圾收集的时候,垃圾收集器会调用该对象的finalize(  )方法(如果有)。如果在finalize()方法中,又使得该对象被程序引用(俗称复活了),则该对象就变成了可触及的对象,暂时不会被垃圾收集了。但是由于每个对象只能调用一次finalize(  )方法,所以每个对象也只可能 "复活 "一次。
3Java语言允许程序员为任何方法添加finalize(   )方法,该方法会在垃圾收集器交换回收对象之前被调用。但不要过分依赖该方法对系统资源进行回收和再利用,因为该方法调用后的执行结果是不可预知的。
五、关于垃圾收集器需要注意的地方
1不要试图去假定垃圾收集发生的时间,这一切都是未知的。比如,方法中的一个临时对象在方法调用完毕后就变成了无用对象,这个时候它的内存就可以被释放。
2Java中提供了一些和垃圾收集打交道的类,而且提供了一种强行执行垃圾收集的方法--调用System.gc(),但这同样是个不确定的方法。它只不过会向JVM发出这样一个申请,Java 中并不保证每次调用该方法就一定能够启动垃圾收集。垃圾收集器不可以被强制执行,但程序员可以通过调研System.gc方法来建议执行垃圾收集。记住,只是建议。一般不建议自己写System.gc,因为会加大垃圾收集工作量。
3、挑选适合自己的垃圾收集器。一般来说,如果系统没有特殊和苛刻的性能要求,可以采用JVM的缺省选项。否则可以考虑使用有针对性的垃圾收集器,比如增量收集器就比较适合实时性要求较高的系统之中。系统具有较高的配置,有比较多的闲置资源,可以考虑使用并行标记/清除收集器。
4、关键的也是难把握的问题是内存泄漏。良好的编程习惯和严谨的编程态度永远是最重要的,不要让自己的一个小错误导致内存出现大漏洞。
5、尽早释放无用对象的引用。大多数程序员在使用临时变量的时候,都是让引用变量在退出活动域(scope)后,自动设置为null,暗示垃圾收集器来收集该对象,还必须注意该引用的对象是否被监听,如果有,则要去掉监听器,然后再赋空值。
关于面试中如何介绍java的垃圾回收机制
Java的垃圾回收机制是Java虚拟机提供的能力,用于在空闲时间以不定时的方式动态回收无任何引用的对象占据的内存空间。
需要注意的是:垃圾回收回收的是无任何引用的对象占据的内存空间而不是对象本身。
System.gc()
Runtime.getRuntime().gc()  
上面的方法调用时用于显式通知JVM可以进行一次垃圾回收,但真正垃圾回收机制具体在什么时间点开始发生动作这同样是不可预料的,这和抢占式的线程在发生作用时的原理一样。

回复 使用道具 举报
说得很全面,有助于理清垃圾回收机制这个知识点,先收藏先{:soso_e179:}
回复 使用道具 举报
张权 中级黑马 2013-3-25 10:42:59
7#
知识很全面, 不过单词错了, 应该是java virtual machine, 哈哈!11
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马