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

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

© 2012 中级黑马   /  2013-9-12 00:18  /  1939 人查看  /  4 人回复  /   0 人收藏 转载请遵从CC协议 禁止商业使用本文

大家都知道java语言最大的特点就是引入了垃圾回收机制。我想知道垃圾回收的实现原理是什么?为什么java已经引入了垃圾回收机制还存在内存泄漏的问题?这两个问题我都去查过相关的资料,但说得都很模糊,不能真正的理解到。那位大神能给我一个详细的答案。如果是百度的答案就不用回答了,非常谢谢。

评分

参与人数 1技术分 +1 收起 理由
EYE_SEE_YOU + 1

查看全部评分

4 个回复

倒序浏览
1.首先要明确两个概念,内存泄露(memory leak) 和 内存溢出(out of memory)
memory leak,程序创建对象分配空间使用后,没有释放相应的内存,多次循环后导致可用物理内存减少。
out of memory, 程序申请的内存大小超出了系统所能提供的内存大小。

内存泄露会导致内存溢出!

2.然后讲一讲垃圾回收机制(GC--garbage collection)
GC将自动回收本系统认为是垃圾的内存空间(堆空间)。
   他的特点是:
1.垃圾收集是一种从无用对象收其所占用的内存,并使回收的内存能被再次使用的机制。
2.无用对象是值它不能被程序中处于活动状态的部分引用(个人理解就是没有引用指向该对象时)。
3.垃圾回收机制(gc)处于低优先级的线程内,当使用内存较少时运行,但不能保证何时运行
4.不可能强制运行垃圾回收线程,但是调用语句System.gc()有可能激活垃圾收集程序。
5.在垃圾回收机制中无法保证对象被垃圾回收的顺序,也无法保证finalize()的方法被调用的顺序。
6.环形引用并不能阻止对象被回收


java中创建一个对象需要:在栈中创建一个引用变量,在堆中创建对象,引用变量指向对象。
当GC被调用时,他将按照栈中开始跟踪当发现没有被引用的空间时就释放。

一般来说内存泄漏有两种情况。一种情况如在C/C++语言中的,在堆中的分配的内存,在没有将其释放掉的时候,就将所有能访问这块内存的方式都删掉(如指针重新赋值);另一种情况则是在内存对象明明已经不需要的时候,还仍然保留着这块内存和它的访问方式(引用)。第一种情况,在Java中已经由于垃圾回收机制的引入,得到了很好的解决。所以,Java中的内存泄漏,主要指的是第二种情况。
下面举个小例子~
  1. Vector v=new Vector(10);
  2. for (int i=1;i<100; i++){
  3. Object o=new Object();
  4. v.add(o);
  5. o=null;
  6. }
复制代码
该例子中引用变量o的值被手动赋值为null,也就是说coder主观上要释放其指向对象所站的空间。但事实上呢,当GC顺着栈查过来时,发现该内存在v引用变量所引用的内存中被引用了(链式引用)。。GC就放过了这块内存,于是乎内存泄露就产生了。

再举个更实际的例子:
  1. public class FileSearch{

  2.      private byte[] content;
  3.      private File mFile;
  4.      
  5.      public FileSearch(File file){
  6.          mFile = file;
  7.      }

  8.      public boolean hasString(String str){
  9.          int size = getFileSize(mFile);
  10.          content = new byte[size];
  11.          loadFile(mFile, content);
  12.          
  13.          String s = new String(content);
  14.          return s.contains(str);
  15.      }
  16. }
复制代码
FileSearch类中有一个函数hasString,用来判断文档中是否含有指定的字符串,coder的本意是,根据文件名新建一个FileSearch对象,然后调用hasString方法,传入要查找的字符串,然后返回。
实际上呢,当调用hasString方法后,整片文章被导入到  成员变量content数组,由于content不是函数局部变量,在函数调完后并没有释放栈中的content引用变量,于是乎 就泄了(我指的是内存。。囧),当有人用该类编写的代码时,也没有释放FileSearch引用的话,多查几次之后就会拖慢整个系统。。。

很明显,这样的内存泄露都来自于代码逻辑层面,有良好的编码风格,考虑好变量的生命周期,那么会减少内存泄露产生的可能。

另,百度大多讲的是内存溢出的原因。。这挺让人纠结。。
希望对你有帮助~^_^~

评分

参与人数 1技术分 +2 收起 理由
EYE_SEE_YOU + 2

查看全部评分

回复 使用道具 举报
本来我略知一二,看完楼上,真是透彻,受教了{:soso_e179:}
回复 使用道具 举报
补充一下除了垃圾回收机制外的释放内存的方法:
1,object=null;就可以了。java 虚拟机会自己调用gc()方法去释放内存
2,finalize ()
3, System.gc();

评分

参与人数 1技术分 +1 收起 理由
杨增坤 + 1

查看全部评分

回复 使用道具 举报
1.垃圾收集算法的核心思想

  Java语言建立了垃圾收集机制,用以跟踪正在使用的对象和发现并回收不再使用(引用)的对象。该机制可以有效防范动态内存分配中可能发生的两个危险:因内存垃圾过多而引发的内存耗尽,以及不恰当的内存释放所造成的内存非法引用。

  垃圾收集算法的核心思想是:对虚拟机可用内存空间,即堆空间中的对象进行识别,如果对象正在被引用,那么称其为存活对象,反之,如果对象不再被引用,则为垃圾对象,可以回收其占据的空间,用于再分配。垃圾收集算法的选择和垃圾收集系统参数的合理调节直接影响着系统性能,因此需要开发人员做比较深入的了解。

  2.触发主GC(Garbage Collector)的条件

  JVM进行次GC的频率很高,但因为这种GC占用时间极短,所以对系统产生的影响不大。更值得关注的是主GC的触发条件,因为它对系统影响很明显。总的来说,有两个条件会触发主GC:

  ①当应用程序空闲时,即没有应用线程在运行时,GC会被调用。因为GC在优先级最低的线程中进行,所以当应用忙时,GC线程就不会被调用,但以下条件除外。

  ②Java堆内存不足时,GC会被调用。当应用线程在运行,并在运行过程中创建新对象,若这时内存空间不足,JVM就会强制地调用GC线程,以便回收内存用于新的分配。若GC一次之后仍不能满足内存分配的要求,JVM会再进行两次GC作进一步的尝试,若仍无法满足要求,则 JVM将报“out of memory”的错误,Java应用将停止。

  由于是否进行主GC由JVM根据系统环境决定,而系统环境在不断的变化当中,所以主GC的运行具有不确定性,无法预计它何时必然出现,但可以确定的是对一个长期运行的应用来说,其主GC是反复进行的。

  3.减少GC开销的措施

  根据上述GC的机制,程序的运行会直接影响系统环境的变化,从而影响GC的触发。若不针对GC的特点进行设计和编码,就会出现内存驻留等一系列负面影响。为了避免这些影响,基本的原则就是尽可能地减少垃圾和减少GC过程中的开销。具体措施包括以下几个方面:

  (1)不要显式调用System.gc()

  此函数建议JVM进行主GC,虽然只是建议而非一定,但很多情况下它会触发主GC,从而增加主GC的频率,也即增加了间歇性停顿的次数。

  (2)尽量减少临时对象的使用

  临时对象在跳出函数调用后,会成为垃圾,少用临时变量就相当于减少了垃圾的产生,从而延长了出现上述第二个触发条件出现的时间,减少了主GC的机会。

  (3)对象不用时最好显式置为Null

  一般而言,为Null的对象都会被作为垃圾处理,所以将不用的对象显式地设为Null,有利于GC收集器判定垃圾,从而提高了GC的效率。

  (4)尽量使用StringBuffer,而不用String来累加字符串(详见blog另一篇文章JAVA中String与StringBuffer)

  由于String是固定长的字符串对象,累加String对象时,并非在一个String对象中扩增,而是重新创建新的String对象,如Str5=Str1+Str2+Str3+Str4,这条语句执行过程中会产生多个垃圾对象,因为对次作“+”操作时都必须创建新的String对象,但这些过渡对象对系统来说是没有实际意义的,只会增加更多的垃圾。避免这种情况可以改用StringBuffer来累加字符串,因StringBuffer是可变长的,它在原有基础上进行扩增,不会产生中间对象。

  (5)能用基本类型如Int,Long,就不用Integer,Long对象

  基本类型变量占用的内存资源比相应对象占用的少得多,如果没有必要,最好使用基本变量。

  (6)尽量少用静态对象变量

  静态变量属于全局变量,不会被GC回收,它们会一直占用内存。

  (7)分散对象创建或删除的时间

  集中在短时间内大量创建新对象,特别是大对象,会导致突然需要大量内存,JVM在面临这种情况时,只能进行主GC,以回收内存或整合内存碎片,从而增加主GC的频率。集中删除对象,道理也是一样的。它使得突然出现了大量的垃圾对象,空闲空间必然减少,从而大大增加了下一次创建新对象时强制主GC的机会。

  4.gc与finalize方法

  ⑴gc方法请求垃圾回收

  使用System.gc()可以不管JVM使用的是哪一种垃圾回收的算法,都可以请求Java的垃圾回收。需要注意的是,调用System.gc()也仅仅是一个请求。JVM接受这个消息后,并不是立即做垃圾回收,而只是对几个垃圾回收算法做了加权,使垃圾回收操作容易发生,或提早发生,或回收较多而已。

  ⑵finalize方法透视垃圾收集器的运行

  在JVM垃圾收集器收集一个对象之前 ,一般要求程序调用适当的方法释放资源,但在没有明确释放资源的情况下,Java提供了缺省机制来终止化该对象释放资源,这个方法就是finalize()。它的原型为:

  protected void finalize() throws Throwable

  在finalize()方法返回之后,对象消失,垃圾收集开始执行。原型中的throws Throwable表示它可以抛出任何类型的异常。

  因此,当对象即将被销毁时,有时需要做一些善后工作。可以把这些操作写在finalize()方法里。

  protected void finalize()

  {

  // finalization code here

  }

  ⑶代码示例

  class Garbage

  {

  int index;

  static int count;

  Garbage()

  {

  count++;

  System.out.println("object "+count+" construct");

  setID(count);

  }

  void setID(int id)

  {

  index=id;

  }

  protected void finalize() //重写finalize方法

  {

  System.out.println("object "+index+" is reclaimed");

  }

  public static void main(String[] args)

  {

  new Garbage();

  new Garbage();

  new Garbage();

  new Garbage();

  System.gc(); //请求运行垃圾收集器

  }

  }
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马