转自:

  http://www.cnblogs.com/qq78292959/archive/2011/07/25/2116123.html

总结:

  引用分类

    强引用,弱引用,软引用,虚引用。虚引用必有引用队列才能工作,其它可选。

  作用

    软引用在内存不足时才释放内存,弱引用在下次gc回收时释放内存。强引用要手动释放。虚引用可用来了解被引用的对象是否将要被垃圾回收。

    一般分配复杂,耗时且生命周期长的对象要用软引用。

    一般分配容易,不耗时且生命周期长的对象 用 弱引用。

    了解它们之后,可以写相应的数据类型存储相应数据。如 SoftHashMap extends AbstractMap {....}

Java内存管理机制

  在C++语言中,如果需要动态分配一块内存,程序员需要负责这块内存的整个生命周期。从申请分配、到使用、再到最后的释放。这样的过程非常灵活,但是却十分繁琐,程序员很容易由于疏忽而忘记释放内存,从而导致内存的泄露。Java语言对内存管理做了自己的优化,这就是垃圾回收机制。Java的几乎所有内存对象都是在堆内存上分配(基本数据类型除外),然后由GC(garbage collection)负责自动回收不再使用的内存。

  上面是Java内存管理机制的基本情况。但是如果仅仅理解到这里,我们在实际的项目开发中仍然会遇到内存泄漏的问题。也许有人表示怀疑,既然Java的垃圾回收机制能够自动的回收内存,怎么还会出现内存泄漏的情况呢?这个问题,我们需要知道GC在什么时候回收内存对象,什么样的内存对象会被GC认为是“不再使用”的。

  Java中对内存对象的访问,使用的是引用的方式。在Java代码中我们维护一个内存对象的引用变量,通过这个引用变量的值,我们可以访问到对应的内存地址中的内存对象空间。在Java程序中,这个引用变量本身既可以存放堆内存中,又可以放在代码栈的内存中(与基本数据类型相同)。GC线程会从代码栈中的引用变量开始跟踪,从而判定哪些内存是正在使用的。如果GC线程通过这种方式,无法跟踪到某一块堆内存,那么GC就认为这块内存将不再使用了(因为代码中已经无法访问这块内存了)。

  通过这种有向图的内存管理方式,当一个内存对象失去了所有的引用之后,GC就可以将其回收。反过来说,如果这个对象还存在引用,那么它将不会被GC回收,哪怕是Java虚拟机抛出OutOfMemoryError。

Java内存泄露

  一般来说内存泄漏有两种情况。一种情况如在C/C++语言中的,在堆中的分配的内存,在没有将其释放掉的时候,就将所有能访问这块内存的方式都删掉(如指针重新赋值);另一种情况则是在内存对象明明已经不需要的时候,还仍然保留着这块内存和它的访问方式(引用)。第一种情况,在Java中已经由于垃圾回收机制的引入,得到了很好的解决。所以,Java中的内存泄漏,主要指的是第二种情况。

  可能光说概念太抽象了,大家可以看一下这样的例子:

  public void leakFun() {
Vector v = new Vector();
for (int i = ; i < ; i++) {
Object o = new Object();
v.add(o);
o = null;
}
}

  在这个例子中,代码栈中存在Vector对象的引用v和Object对象的引用o。在For循环中,我们不断的生成新的对象,然后将其添加到Vector对象中,之后将o引用置空。问题是当o引用被置空后,如果发生GC,我们创建的Object对象是否能够被GC回收呢?答案是否定的。因为,GC在跟踪代码栈中的引用时,会发现v引用,而继续往下跟踪,就会发现v引用指向的内存空间中又存在指向Object对象的引用。也就是说尽管o引用已经被置空,但是Object对象仍然存在其他的引用,是可以被访问到的,所以GC无法将其释放掉。如果在此循环之后,Object对象对程序已经没有任何作用,那么我们就认为此Java程序发生了内存泄漏。

  尽管对于C/C++中的内存泄露情况来说,Java内存泄露导致的破坏性小,除了少数情况会出现程序崩溃的情况外,大多数情况下程序仍然能正常运行。但是,在移动设备对于内存和CPU都有较严格的限制的情况下,Java的内存溢出会导致程序效率低下、占用大量不需要的内存等问题。这将导致整个机器性能变差,严重的也会引起抛出OutOfMemoryError,导致程序崩溃。

一般情况下内存泄漏的避免

  在不涉及复杂数据结构的一般情况下,Java的内存泄露表现为一个内存对象的生命周期超出了程序需要它的时间长度。我们有时也将其称为“对象游离”。

例如:

 public class FileSearch {
private byte[] content;
private File mFile; public FileSearch(File file) {
mFile = file;
} public boolean hasString(String str) {
int size = getFileSize(mFile);
content = new byte[size];
loadFile(mFile, content); String s = new String(content);
return s.contains(str);
}
}

  在这段代码中,FileSearch类中有一个函数hasString,用来判断文档中是否含有指定的字符串。流程是先将mFile加载到内存中,然后进行判断。但是,这里的问题是,将content声明为了实例变量,而不是本地变量。于是,在此函数返回之后,内存中仍然存在整个文件的数据。而很明显,这些数据我们后续是不再需要的,这就造成了内存的无故浪费。

  要避免这种情况下的内存泄露,要求我们以C/C++的内存管理思维来管理自己分配的内存。第一,是在声明对象引用之前,明确内存对象的有效作用域。在一个函数内有效的内存对象,应该声明为local变量,与类实例生命周期相同的要声明为实例变量……以此类推。第二,在内存对象不再需要时,记得手动将其引用置空。

复杂数据结构中的内存泄露问题

  在实际的项目中,我们经常用到一些较为复杂的数据结构用于缓存程序运行过程中需要的数据信息。有时,由于数据结构过于复杂,或者我们存在一些特殊的需求(例如,在内存允许的情况下,尽可能多的缓存信息来提高程序的运行速度等情况),我们很难对数据结构中数据的生命周期作出明确的界定。这个时候,我们可以使用Java中一种特殊的机制来达到防止内存泄露的目的。

  之前我们介绍过,Java的GC机制是建立在跟踪内存的引用机制上的。而在此之前,我们所使用的引用都只是定义一个“Object o;”这样形式的。事实上,这只是Java引用机制中的一种默认情况,除此之外,还有其他的一些引用方式。通过使用这些特殊的引用机制,配合GC机制,就可以达到一些我们需要的效果。

Java中的几种引用方式

  从jdk 1.2 开始,引用分为 强引用,软引用、弱引用 和虚引用, 其中 软引用、弱引用 和虚引用 和 ReferenceQueue 关联。

  在JDK 1.2以前的版本中,若一个对象不被任何变量引用,那么程序就无法再使用这个对象。也就是说,只有对象处于可触及(reachable)状态,程序才能使用它。从JDK 1.2版本开始,把对象的引用分为4种级别,从而使程序能更加灵活地控制对象的生命周期。这4种级别由高到低依次为:强引用、软引用、弱引用和虚引用。

1,强引用(Strong Reference, 没有具体的类来标识强引用,正常的使用的对象引用都是强引用,由vm实现)

  强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。

当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。

2,软引用(SoftReference)

  如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。

只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。

软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。

3,弱引用(WeakReference)

  弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。

在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。

不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。

弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。

4,虚引用(PhantomReference)

  “虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。

虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用

当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之 关联的引用队列中。

 ReferenceQueue queue = new ReferenceQueue ();
PhantomReference pr = new PhantomReference (object, queue);

程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。

5,ReferenceQueue是作为 JVM GC与上层Reference对象管理之间的一个消息传递方式, 软引用、弱引用等的入队操作有vm的gc直接操作

GC、Reference与ReferenceQueue的交互

A、 GC无法删除存在强引用的对象的内存。

B、 GC发现一个只有软引用的对象内存,那么:

  ① SoftReference对象的referent 域被设置为null,从而使该对象不再引用heap对象。

  ② SoftReference引用过的heap对象被声明为finalizable。

  ③ 当 heap 对象的 finalize() 方法被运行而且该对象占用的内存被释放,SoftReference 对象就被添加到它的 ReferenceQueue(如果后者存在的话)。

C、 GC发现一个只有弱引用的对象内存,那么:

  ① WeakReference对象的referent域被设置为null,从而使该对象不再引用heap对象。

  ② WeakReference引用过的heap对象被声明为finalizable。

  ③ 当heap对象的finalize()方法被运行而且该对象占用的内存被释放时,WeakReference对象就被添加到它的ReferenceQueue(如果后者存在的话)。

D、 GC发现一个只有虚引用的对象内存,那么:

  ① PhantomReference引用过的heap对象被声明为finalizable。

  ② PhantomReference在堆对象被释放之前就被添加到它的ReferenceQueue。

值得注意的地方有以下几点:

1、GC在一般情况下不会发现软引用的内存对象,只有在内存明显不足的时候才会发现并释放软引用对象的内存。

2、GC对弱引用的发现和释放也不是立即的,有时需要重复几次GC,才会发现并释放弱引用的内存对象。
3、软引用和弱引用在添加到ReferenceQueue的时候,其指向真实内存的引用已经被置为空了,相关的内存也已经被释放掉了。而虚引用在添加到ReferenceQueue的时候,内存还没有释放,仍然可以对其进行访问。

代码示例

通过以上的介绍,相信您对Java的引用机制以及几种引用方式的异同已经有了一定了解。光是概念,可能过于抽象,下面我们通过一个例子来演示如何在代码中使用Reference机制。

      String str = new String("hello"); //①
ReferenceQueue<String> rq = new ReferenceQueue<String>(); //②
WeakReference<String> wf = new WeakReference<String>(str, rq); //③
str = null; //④取消"hello"对象的强引用
String str1 = wf.get(); //⑤假如"hello"对象没有被回收,str1引用"hello"对象
Reference<? extends String> ref = rq.poll(); //⑥假如"hello"对象没有被回收,rq.poll()返回null

  在以上代码中,注意⑤⑥两处地方。假如“hello”对象没有被回收wf.get()将返回“hello”字符串对象,rq.poll()返回null;而加入“hello”对象已经被回收了,那么wf.get()返回null,rq.poll()返回Reference对象,但是此Reference对象中已经没有str对象的引用了(PhantomReference则与WeakReference、SoftReference不同)。

 

引用机制与复杂数据结构的联合应用

  了解了GC机制、引用机制,并配合上ReferenceQueue,我们就可以实现一些防止内存溢出的复杂数据类型。

例如,SoftReference具有构建Cache系统的特质,因此我们可以结合哈希表实现一个简单的缓存系统。这样既能保证能够尽可能多的缓存信息,又可以保证Java虚拟机不会因为内存泄露而抛出OutOfMemoryError。这种缓存机制特别适合于内存对象生命周期长,且生成内存对象的耗时比较长的情况,例如缓存列表封面图片等。对于一些生命周期较长,但是生成内存对象开销不大的情况,使用WeakReference能够达到更好的内存管理的效果。

  附SoftHashmap的源码一份,相信看过之后,大家会对Reference机制的应用有更深入的理解。

 package com.***.widget;
//: SoftHashMap.java
import java.util.*;
import java.lang.ref.*;
import android.util.Log; public class SoftHashMap extends AbstractMap {
/**
* The internal HashMap that will hold the SoftReference.
*/
private final Map hash = new HashMap();
/**
* The number of "hard" references to hold internally.
*/
private final int HARD_SIZE;
/**
* The FIFO list of hard references, order of last access.
*/
private final LinkedList hardCache = new LinkedList();
/**
* Reference queue for cleared SoftReference objects.
*/
private ReferenceQueue queue = new ReferenceQueue(); //Strong Reference number
public SoftHashMap() {
this();
} public SoftHashMap(int hardSize) {
HARD_SIZE = hardSize;
} public Object get(Object key) {
Object result = null;
// We get the SoftReference represented by that key
SoftReference soft_ref = (SoftReference) hash.get(key);
if (soft_ref != null) {
// From the SoftReference we get the value, which can be
// null if it was not in the map, or it was removed in
// the processQueue() method defined below
result = soft_ref.get();
if (result == null) {
// If the value has been garbage collected, remove the
// entry from the HashMap.
hash.remove(key);
} else {
// We now add this object to the beginning of the hard
// reference queue. One reference can occur more than
// once, because lookups of the FIFO queue are slow, so
// we don't want to search through it each time to remove
// duplicates.
//keep recent use object in memory
hardCache.addFirst(result);
if (hardCache.size() > HARD_SIZE) {
// Remove the last entry if list longer than HARD_SIZE
hardCache.removeLast();
}
}
}
return result;
} /**
* We define our own subclass of SoftReference which contains
* not only the value but also the key to make it easier to find
* the entry in the HashMap after it's been garbage collected.
*/
private static class SoftValue extends SoftReference {
private final Object key; // always make data member final /**
* Did you know that an outer class can access private data
* members and methods of an inner class? I didn't know that!
* I thought it was only the inner class who could access the
* outer class's private information. An outer class can also
* access private members of an inner class inside its inner
* class.
*/
private SoftValue(Object k, Object key, ReferenceQueue q) {
super(k, q);
this.key = key;
}
} /**
* Here we go through the ReferenceQueue and remove garbage
* collected SoftValue objects from the HashMap by looking them
* up using the SoftValue.key data member.
*/
public void processQueue() {
SoftValue sv;
while ((sv = (SoftValue) queue.poll()) != null) {
if (sv.get() == null) {
Log.e("processQueue", "null");
} else {
Log.e("processQueue", "Not null");
}
hash.remove(sv.key); // we can access private data!
Log.e("SoftHashMap", "release " + sv.key);
}
} /**
* Here we put the key, value pair into the HashMap using
* a SoftValue object.
*/
public Object put(Object key, Object value) {
processQueue(); // throw out garbage collected values first
Log.e("SoftHashMap", "put into " + key);
return hash.put(key, new SoftValue(value, key, queue));
} public Object remove(Object key) {
processQueue(); // throw out garbage collected values first
return hash.remove(key);
} public void clear() {
hardCache.clear();
processQueue(); // throw out garbage collected values
hash.clear();
} public int size() {
processQueue(); // throw out garbage collected values first
return hash.size();
} public Set entrySet() {
// no, no, you may NOT do that!!! GRRR
throw new UnsupportedOperationException();
}
}
 

最新文章

  1. [LeetCode] Sequence Reconstruction 序列重建
  2. 中文Locale
  3. DotNetMQ的一个小demo
  4. wikioi 1205 单词倒排
  5. Config the Android 5.0 Build Environment
  6. sharepoint 备份和还原site脚本
  7. js 浮点数加减问题
  8. 疯狂Android第二章:Adapter以及部分控件使用
  9. hibernate--session的CRUD方法, delete, load,get,update,saveorupdate, clear, flush
  10. 深度解析PHP数组函数array_combine
  11. linux仅修改文件夹权限 分别批量修改文件和文件夹权限
  12. [转]理解 Bias 与 Variance 之间的权衡----------bias variance tradeoff
  13. mac book pro macOS10.13.3安装qt、qt creator C++开发环境,qt5.11.1,并解决cmake构建:qt mac this file is not part of any project the code
  14. Atcoder F - LCS (DP-最长公共子序列,输出字符串)
  15. Nginx系列7:SSL证书的公信力是如何保证的?
  16. (转)一位资深程序员大牛给予Java初学者的学习路线建议
  17. flask 使用宏渲染表单(包含错误信息)
  18. openresty火焰图安装
  19. 搭建本地离线yum仓库
  20. IDEA下载插件超时的原因

热门文章

  1. 自己写一个HashMap
  2. 【01】CSS3 Gradient 分为&#160;linear-gradient(线性渐变)和&#160;radial-gradient(径 向渐变)(转)
  3. codevs1127 接水问题
  4. HDU——1133 Buy the Ticket
  5. ScrollView双击图片定点放大
  6. subclipse 和 eclipse结合遇到的问题
  7. Linux: 通过命令行上传文件到ftp服务器
  8. HDU1026 Ignatius and the Princess I 【BFS】+【路径记录】
  9. Workspace in use or cannot be created, choose a different one.--错误解决的方法
  10. UIView的层介绍