对于某些编程语言,内存管理的工做须要开发人员处理,好比C/C++。以C++为例,程序经过new操做符建立新对象以后,就会分配相应的内存资源,当程序再也不须要这些对象时,须要在代码中将其显式释放,通常经过delete操做符完成。内存分配和释放的正确性,由开发人员来保证。开发过程当中可能出现的与内存释放相关的问题主要有:悬挂引用(野指针)和内存泄露。java
C/C++中常说的野指针。悬挂引用指的是对某个对象引用实际上指向一个错误的内存地址。好比,算法
1 int * a = new int [10]; 2 int * b = a; // b 和 a 指向同一块内存区域 3 delete [] a; // 释放 a 指向的那块内存区域 4 printf("b[0]:%d", b[0]); // 打印出无效的值 5 b[0] = 2; // 再次引用已经释放的内存区域,结果没法预料。
上述代码中指针b就是一个野指针。b和a指向同一块内存控件,"delete a;"语句已经将该空间释放,对b[0]进行打印会出现莫名的值,而非初始化的0,而"b[0] = 2;"试图再次引用该内存区域将会出现不可预知的错误。编程
某些对象所占用的内存没有被释放,又没有对象引用指向这些内存。这样就致使这部份内存对程序来讲既不可用,又没法被释放,出现内存泄露。C/C++中的内存泄露指的是内存不可达。缓存
由于显式内存管理比较容易出错,所以很多语言引入了内存自动管理机制,典型的表明为Java语言。Java提供了垃圾回收器(GC)来自动回收程序以后再也不使用的内存。GC不只负责内存的回收,还负责内存的分配。编程语言
当Java程序运行时,GC也同时运行在一个单独的线程中,它会根据虚拟机当前的内存状态决定何时进行垃圾回收工做。而GC的执行回收的具体时间和频率没法预估,取决于GC的实现算法。程序能够经过System.gc方法建议GC当即执行垃圾回收,不过在这种状况下GC也可能选择不执行回收。ide
某些状况,当系统可用内存愈来愈少,而GC又没法找到足够可用的空闲内存时,建立新对象的操做就会抛出OOM(OutOfMemory)错误,致使虚拟机退出。好比,一个图像处理程序可能同时打开了多个图像文件进行编辑,而同一时刻只有一张图片处于编辑状态,当同时打开多张图片时,程序占用的内存空间就会变大,而GC又没法回收这些处于活动的对象所占用的内存,使得可用内存愈来愈少。以后建立新对象的操做就可能致使OOM错误。spa
对于上述状况,咱们的Java程序须要经过一种方式把对象在内存需求方面的特征告诉GC,GC能够根据对象特征进行更好地回收。这种方式就是经过Java中对象的引用类型来实现。程序运行中,对于同一个对象,可能存在多个指向它的引用,若是再也不有引用指向该对象,那么这个对象就会成为GC的候选目标。Java中存在不一样的对象引用类型,不一样类型的引用对GC的含义是不一样的。分别为强引用、软引用、弱引用以及虚引用。线程
最多见的引用类型,也是默认的引用类型。使用new操做符建立一个的新对象,并将其复制给一个变量的时候,这个变量就成为指向该对象的一个强引用。例如:指针
"Object a = new Object();", 变量a就是一个强引用,指向一个new操做符建立的Object类型的对象。对于强引用的对象,GC是不会将其做为垃圾收集的候选目标。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具备强引用的对象来解决内存不足的问题。code
强引用会阻止GC对其进行内存释放,若是某个对象以后再也不使用,而程序中却一直保存着其强引用,就会致使GC没法对这块内存进行释放,形成了“Java中所说的内存泄露”。这种内存泄露和以前提到的C/C++的内存泄露意思不一样,C/C++中内存泄露是指某些内存不可达,而又没法释放。Java中不可达的内存直接就被GC回收了,Java中的内存泄露本质上是这些内存是可达的,而以后程序不想继续使用它却没法被GC回收,形成了“泄露”。
Java中的内存泄露主要分两种状况:
第一种内存泄露的例子以下:
1 public class LeakedQueue<T> { 2 private List<T> backendList = new ArrayList<T>(); 3 private int topIndex = 0; 4 5 public void enqueue(T value) { 6 backendList.add(value); 7 } 8 9 public T dequeue() { 10 if (topIndex < backendList.size()) { 11 T result = backendList.get(topIndex); 12 topIndex++; 13 return result; 14 } 15 return null; 16 } 17 }
出对方法会形成内存泄露。对于dequeue的对象,backendList没有将其删除,而依然保存了该对象的强引用,所以该对象没法被GC回收。屡次执行enqueue和dequeue后将会使虚拟机可用内存愈来愈少,致使OOM错误。
第二种内存泄露状况一般发生在基于内存实现的缓存的时候,例子以下:
1 public class Calculator { 2 private Map<String, Object> cache = new HashMap<String, Object>(); 3 4 public Object calculate(String expr) { 5 if (cache.containsKey(expr)) { 6 return cache.get(expr); 7 } 8 Object result = doCalculate(expr); 9 cache.put(expr, result); 10 return result; 11 } 12 13 private Object doCalculate(String expr) { 14 return new Object(); 15 } 16 }
缓存的存活时间和Calculator同样长,只要Calculator没法被回收,其中所包含的计算结构对象也没法被回收,不断执行新的calculate,将会致使内部缓存愈来愈大,可用内存愈来愈少。
当强引用存在的时候,所指向的对象没法被GC回收,为了加强程序与GC的交互能力,JDK 1.2引入了java.lang.ref包,提供了三种新的引用类型,分别是软引用,弱引用和虚引用。这些引用类型除了能够引用对象以外,还能够在不一样程度上影响GC对被引用对象的处理行为。
软引用强度上弱于强引用,用SoftReference类来表示。若是一个对象不是强引用可达,同时能够经过软引用来访问,则该对象时软引用可达的。软引用传递给GC的信息是:只有你还有足够的内存,就不要回收软引用指向的对象。GC会在抛出OOM错误发生以前,回收掉软引用指向的对象。
使用软引用:
1 Object aObject = new Object(); 2 SoftReference<Object> ref = new SoftReference<Object>(aObject); 3 aObject = null; // 必需要释放掉强引用,不然无心义。由于只要强引用存在,GC就不会进行回收。 4 5 Object bObject = ref.get(); // 经过get方法获取引用的对象,获取以后须要判断是否为NULL,由于可能已经被GC回收 6 if (bObject != null) { 7 // your opr 8 }
弱引用在强度上弱于软引用,用WeakReference表示。弱引用传递给GC的信息是:在判断一个对象是否存活时,能够不考虑弱引用的存在。只具备弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程当中,一旦发现了只具备弱引用的对象,无论当前内存空间足够与否,都会回收它的内存。不过,因为垃圾回收器是一个优先级很低的线程,所以不必定会很快发现那些只具备弱引用的对象。
虚引用是强度最弱的一种引用,用PhantomReference类表示。虚引用的主要目的是在一个对象所占的内存被实际回收以前获得通知,从而能够进行一些相关的清理工做。虚引用并不会决定对象的生命周期。若是一个对象仅持有虚引用,那么它就和没有任何引用同样,在任什么时候候均可能被垃圾回收器回收。虚引用在使用上与前两种引用有很大不一样:首先,虚引用建立时必须提供一个引用队列做为参数;其次,虚引用对象的get方法老是返回null,所以没法经过虚引用获取被引用的对象。
程序能够经过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。若是程序发现某个虚引用已经被加入到引用队列,那么就能够在所引用的对象的内存被回收以前采起必要的行动。
引用队列的主要做用是做为一个通知机制。当对象的可达状态发生变化时,若是程序但愿获得通知,可使用引用队列。当从引用队列中获取了引用对象后,不可能再获取所指向的具体对象。由于,对于软引用和弱引用,在被放入队列以前,它们的引用关系就已经被清除。而虚引用的get方法老是返回null。