知识大全 Java垃圾收集算法与内存泄露

Posted

篇首语:男儿欲遂平生志,六经勤向窗前读。本文由小常识网(cha138.com)小编为大家整理,主要介绍了知识大全 Java垃圾收集算法与内存泄露相关的知识,希望对你有一定的参考价值。

Java垃圾收集算法与内存泄露  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!

   垃圾收集算法的核心思想

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

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

   触发主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是反复进行的

   减少GC开销的措施

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

   gc与finalize方法

  ⑴gc方法请求垃圾回收

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

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

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

  protected void finalize() throws Throwable

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

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

  java 代码

  protected void finalize()

  

  // finalization code here

  

  ⑶代码示例

  java 代码

  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(); //请求运行垃圾收集器

  

  

   Java 内存泄漏

  由于采用了垃圾回收机制 任何不可达对象(对象不再被引用)都可以由垃圾收集线程回收 因此通常说的Java 内存泄漏其实是指无意识的 非故意的对象引用 或者无意识的对象保持 无意识的对象引用是指代码的开发人员本来已经对对象使用完毕 却因为编码的错误而意外地保存了对该对象的引用(这个引用的存在并不是编码人员的主观意愿) 从而使得该对象一直无法被垃圾回收器回收掉 这种本来以为可以释放掉的却最终未能被释放的空间可以认为是被 泄漏了

  考虑下面的程序 在ObjStack类中 使用push和pop方法来管理堆栈中的对象 两个方法中的索引(index)用于指示堆栈中下一个可用位置 push方法存储对新对象的引用并增加索引值 而pop方法减小索引值并返回堆栈最上面的元素 在main方法中 创建了容量为 的栈 并 次调用push方法向它添加对象 此时index的值为 随后又 次调用pop方法 则index的值变为 出栈意味着在堆栈中的空间应该被收集 但事实上 pop方法只是减小了索引值 堆栈仍然保持着对那些对象的引用 故 个无用对象不会被GC回收 造成了内存渗漏

  java 代码

  public class ObjStack

  private Object[] stack;

  private int index;

  ObjStack(int indexcount)

  stack = new Object[indexcount];

  index = ;

  

  public void push(Object obj)

  stack[index] = obj;

  index++;

  

  public Object pop()

  index ;

  return stack[index];

  

  

  public class Pushpop

  public static void main(String[] args)

  int i = ;

  Object tempobj;

  //new一个ObjStack对象 并调用有参构造函数 分配stack Obj数组的空间大小为 可以存 个对象 从 开始存储

  ObjStack stack = new ObjStack( );

  while (i < )

  

  tempobj = new Object();//循环new Obj对象 把每次循环的对象一一存放在stack Obj数组中

  stack push(tempobj);

  i++;

  System out println( 第 + i + 次进栈 + \\t );

  

  while (i > )

  

  tempobj = stack pop();//这里造成了空间的浪费

  //正确的pop方法可改成如下所指示 当引用被返回后 堆栈删除对他们的引用 因此垃圾收集器在以后可以回收他们

  /*

  * public Object pop() index ;Object temp = stack [index];stack [index]=null;return temp;

  */

  i ;

  System out println( 第 + ( i) + 次出栈 + \\t );

  

  

  

   如何消除内存泄漏

  虽然Java虚拟机(JVM)及其垃圾收集器(garbage collector GC)负责管理大多数的内存任务 Java软件程序中还是有可能出现内存泄漏 实际上 这在大型项目中是一个常见的问题 避免内存泄漏的第一步是要弄清楚它是如何发生的 本文介绍了编写Java代码的一些常见的内存泄漏陷阱 以及编写不泄漏代码的一些最佳实践 一旦发生了内存泄漏 要指出造成泄漏的代码是非常困难的 因此本文还介绍了一种新工具 用来诊断泄漏并指出根本原因 该工具的开销非常小 因此可以使用它来寻找处于生产中的系统的内存泄漏

  垃圾收集器的作用

  虽然垃圾收集器处理了大多数内存管理问题 从而使编程人员的生活变得更轻松了 但是编程人员还是可能犯错而导致出现内存问题 简单地说 GC循环地跟踪所有来自 根 对象(堆栈对象 静态对象 JNI句柄指向的对象 诸如此类)的引用 并将所有它所能到达的对象标记为活动的 程序只可以操纵这些对象;其他的对象都被删除了 因为GC使程序不可能到达已被删除的对象 这么做就是安全的

  虽然内存管理可以说是自动化的 但是这并不能使编程人员免受思考内存管理问题之苦 例如 分配(以及释放)内存总会有开销 虽然这种开销对编程人员来说是不可见的 创建了太多对象的程序将会比完成同样的功能而创建的对象却比较少的程序更慢一些(在其他条件相同的情况下)

  而且 与本文更为密切相关的是 如果忘记 释放 先前分配的内存 就可能造成内存泄漏 如果程序保留对永远不再使用的对象的引用 这些对象将会占用并耗尽内存 这是因为自动化的垃圾收集器无法证明这些对象将不再使用 正如我们先前所说的 如果存在一个对对象的引用 对象就被定义为活动的 因此不能删除 为了确保能回收对象占用的内存 编程人员必须确保该对象不能到达 这通常是通过将对象字段设置为null或者从集合(collection)中移除对象而完成的 但是 注意 当局部变量不再使用时 没有必要将其显式地设置为null 对这些变量的引用将随着方法的退出而自动清除

  概括地说 这就是内存托管语言中的内存泄漏产生的主要原因 保留下来却永远不再使用的对象引用

  典型泄漏

  既然我们知道了在Java中确实有可能发生内存泄漏 就让我们来看一些典型的内存泄漏及其原因

  全局集合

  在大的应用程序中有某种全局的数据储存库是很常见的 例如一个JNDI树或一个会话表 在这些情况下 必须注意管理储存库的大小 必须有某种机制从储存库中移除不再需要的数据

  这可能有多种方法 但是最常见的一种是周期性运行的某种清除任务 该任务将验证储存库中的数据 并移除任何不再需要的数据

  另一种管理储存库的方法是使用反向链接(referrer)计数 然后集合负责统计集合中每个入口的反向链接的数目 这要求反向链接告诉集合何时会退出入口 当反向链接数目为零时 该元素就可以从集合中移除了

  缓存

  缓存是一种数据结构 用于快速查找已经执行的操作的结果 因此 如果一个操作执行起来很慢 对于常用的输入数据 就可以将操作的结果缓存 并在下次调用该操作时使用缓存的数据

  缓存通常都是以动态方式实现的 其中新的结果是在执行时添加到缓存中的 典型的算法是

  检查结果是否在缓存中 如果在 就返回结果

  如果结果不在缓存中 就进行计算

  将计算出来的结果添加到缓存中 以便以后对该操作的调用可以使用

  该算法的问题(或者说是潜在的内存泄漏)出在最后一步 如果调用该操作时有相当多的不同输入 就将有相当多的结果存储在缓存中 很明显这不是正确的方法

  为了预防这种具有潜在破坏性的设计 程序必须确保对于缓存所使用的内存容量有一个上限 因此 更好的算法是

  检查结果是否在缓存中 如果在 就返回结果

  如果结果不在缓存中 就进行计算

  如果缓存所占的空间过大 就移除缓存最久的结果

  将计算出来的结果添加到缓存中 以便以后对该操作的调用可以使用

  通过始终移除缓存最久的结果 我们实际上进行了这样的假设 在将来 比起缓存最久的数据 最近输入的数据更有可能用到 这通常是一个不错的假设

  新算法将确保缓存的容量处于预定义的内存范围之内 确切的范围可能很难计算 因为缓存中的对象在不断变化 而且它们的引用包罗万象 为缓存设置正确的大小是一项非常复杂的任务 需要将所使用的内存容量与检索数据的速度加以平衡

  解决这个问题的另一种方法是使用java lang ref SoftReference类跟踪缓存中的对象 这种方法保证这些引用能够被移除 如果虚拟机的内存用尽而需要更多堆的话

  ClassLoader

  Java ClassLoader结构的使用为内存泄漏提供了许多可乘之机 正是该结构本身的复杂性使ClassLoader在内存泄漏方面存在如此多的问题 ClassLoader的特别之处在于它不仅涉及 常规 的对象引用 还涉及元对象引用 比如 字段 方法和类 这意味着只要有对字段 方法 类或ClassLoader的对象的引用 ClassLoader就会驻留在JVM中 因为ClassLoader本身可以关联许多类及其静态字段 所以就有许多内存被泄漏了

  确定泄漏的位置

  通常发生内存泄漏的第一个迹象是 在应用程序中出现了OutOfMemoryError 这通常发生在您最不愿意它发生的生产环境中 此时几乎不能进行调试 有可能是因为测试环境运行应用程序的方式与生产系统不完全相同 因而导致泄漏只出现在生产中 在这种情况下 需要使用一些开销较低的工具来监控和查找内存泄漏 还需要能够无需重启系统或修改代码就可以将这些工具连接到正在运行的系统上 可能最重要的是 当进行分析时 需要能够断开工具而保持系统不受干扰

cha138/Article/program/Java/hx/201311/25597

相关参考

知识大全 JVM内存模型及垃圾收集策略解析(2)

JVM内存模型及垃圾收集策略解析(2)  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  二JAVA

知识大全 JAVA垃圾回收算法摘要

JAVA垃圾回收算法摘要  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  垃圾收集的算法分析  j

知识大全 发现Java虚拟机内存泄露问题

发现Java虚拟机内存泄露问题  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!内存泄露问题对于程序来

知识大全 Java人员谈论内存泄露问题分析

Java人员谈论内存泄露问题分析  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  很多人在谈论内存

知识大全 如何计算java对象占用的内存

  Java有一个很好的地方就是java的垃圾收集机制这个机制集成于jvm的对程序员来说是隐藏且不透明的这种情况下如何得到某个对象消耗的内存呢?      曾经看到过有人用以下方法来计算在生成该obj

知识大全 JVM内存模型及垃圾收集策略解析(1)

JVM内存模型及垃圾收集策略解析(1)  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  一JVM内

知识大全 消除对过期对象的引用

  Java比c++方便的一点就是java有自己的垃圾收集机制可以自动帮你收集垃圾但是这并非意味着使用java我们就不需要考虑内存管理了  这一点相信很多java程序员都是知道的就是当一个对象我们决定

知识大全 算法面试题 精心收集

腾讯算法题服务器内存G有一个G的文件里面每行存著一个QQ号(位数)怎么最快找出出现过最多次的QQ号G内存两个G的文件每个文件格式为每行是一个url地址找出这个两个文件中重复的url地址(典型的url去

知识大全 Java的垃圾回收之算法

Java的垃圾回收之算法  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  引言  Java的堆是一

知识大全 Java的垃圾收集机制

Java的垃圾收集机制  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  通常我们把分配出去后却无法