查看GC日志时需要用到的虚拟机参数:
-XX:+PrintGC 输出GC日志
-XX:+PrintGCDetails 输出GC的详细日志
-XX:+PrintGCTimeStamps 输出GC的时间戳(以基准时间的形式)
-XX:+PrintGCDateStamps 输出GC的时间戳(以日期的形式,如2017-12-13T12:07:59.234+0800)
-XX:+PrintHeapAtGC 在进行GC的前后打印出堆的信息
-Xloggc:../logs/gc.log 日志文件的输出路径
1.引用计数算法
给对象中添加一个医用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。
测试:
/**
* VM Args:-XX:+PrintGCDetails
*/
public class ReferenceCountingGC {
public Object instance = null;
private static final int _1MB = 1024*1024;
/**
* 这个成员属性的唯一意义就是占点内存,以便能在GC日志中看清楚是否被回收过
*/
private byte[] bigSize = new byte[2*_1MB];
public static void testGc(){
ReferenceCountingGC objA = new ReferenceCountingGC();
ReferenceCountingGC objB = new ReferenceCountingGC();
objA.instance = objB;
objB.instance = objA;
objA = null;
objB = null;
System.gc();
}
public static void main(String[] args) {
ReferenceCountingGC.testGc();
}
}
运行结果:
[GC [PSYoungGen: 6759K->712K(38400K)] 6759K->712K(124928K), 0.0011986 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC [PSYoungGen: 712K->0K(38400K)] [ParOldGen: 0K->610K(86528K)] 712K->610K(124928K) [PSPermGen: 2910K->2909K(21504K)], 0.0099723 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
Heap
PSYoungGen total 38400K, used 2330K [0x00000007d5c00000, 0x00000007d8680000, 0x0000000800000000)
eden space 33280K, 7% used [0x00000007d5c00000,0x00000007d5e46810,0x00000007d7c80000)
from space 5120K, 0% used [0x00000007d7c80000,0x00000007d7c80000,0x00000007d8180000)
to space 5120K, 0% used [0x00000007d8180000,0x00000007d8180000,0x00000007d8680000)
ParOldGen total 86528K, used 610K [0x0000000781400000, 0x0000000786880000, 0x00000007d5c00000)
object space 86528K, 0% used [0x0000000781400000,0x0000000781498b00,0x0000000786880000)
PSPermGen total 21504K, used 2927K [0x000000077c200000, 0x000000077d700000, 0x0000000781400000)
object space 21504K, 13% used [0x000000077c200000,0x000000077c4dbf70,0x000000077d700000)
GC日志分析:
[GC [PSYoungGen(使用PSYoungGen作为年轻代的垃圾回收器): 6759K(年轻代垃圾回收前的大小)->712K(年轻代垃圾回收以后的大小)(38400K)(年轻带的总大小)] 6759K(堆区垃圾回收前的大小)->712K(堆取垃圾回收后的大小)(124928K)(堆区总大小), 0.0011986 secs(回收时间)] [Times: user=0.00(Young GC用户耗时) sys=0.00(Young GC系统耗时), real=0.00 secs(Young GC实际耗时)]
[Full GC [PSYoungGen(年轻代): 712K->0K(38400K)] [ParOldGen(年老代): 0K->610K(86528K)] 712K->610K(124928K) [PSPermGen(持久代): 2910K->2909K(21504K)], 0.0099723 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
下面则是堆的具体信息,暂时笔者还未能解读
从上述的运行结果可以看到,进行GC后,年轻代的空间被GC进行了极大的清理,则说明objA和objB之间的循环引用并没有影响到GC回收,说明虚拟机采用的并不是引用计数算法。
2、可达性分析算法
在可达性分析算法中,一个很重要的概念就是“GC Roots”,"GC Root"顾名思义即是GC进行的根,GC会判断堆中的对象是否与“GC Roots”能够通过某条线路连接到。如下图所示:
图中object1~object4都与GC Roots能够连接到,因此不能回收,可以存活,而object5~object7无法连接,因此是可以回收的。
可以作为GC Roots的对象:
1、栈中引用的对象。根据内存模型知道,栈中是正在运行的线程存放的数据,因此当正在运行的程序在引用的对象不能回收,不然会造成问题。
2、方法区中静态属性引用的对象。静态属性会在堆中存一份对象数据,等待调用,这个也是不允许回收的。
3、方法区中常量引用的对象。方法区中有运行时常量池,在常量池中引用的对象也是不允许回收的。
4、本地方法栈中引用的对象。
3、强、软、弱、虚引用
无论用引用计数算法判断对象的引用数量,还是可达性分析算法判断引用链是否可达,都需要判断对象的引用。JDK1.2后,将引用分为四类:强引用(Strong Reference),软引用(SoftReference),弱引用(WeakReference),虚引用(PhantomReference)。这四种引用强度依次减弱。
强引用:普通的引用方法。例如new一个对象,Object obj = new Object();只有在这种引用关系失效的时候,GC才会考虑回收这个对象。
测试代码:
/**
* VM args:-XX:+PrintGCDetails -XX:+PrintAssembly
*/
public class StrongReferenceTest {
public static void main(String[] args) {
Object referent = new Object();
/**
* 通过赋值创建strongReference,此时new Object对象同事被两个变量引用到
*/
Object strongReference = referent;
System.out.println(referent);
System.out.println(referent.equals(strongReference));
referent = null;
System.gc();
System.out.println(strongReference);
}
}
测试结果:
java.lang.Object@4b1210ee
true
[Full GC (System.gc()) [Tenured: 0K->712K(87424K), 0.0029994 secs] 2795K->712K(126720K), [Metaspace: 3210K->3210K(1056768K)], 0.0030517 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
java.lang.Object@4b1210ee
Heap
def new generation total 39424K, used 1754K [0x0000000081400000, 0x0000000083ec0000, 0x00000000ab800000)
eden space 35072K, 5% used [0x0000000081400000, 0x00000000815b6850, 0x0000000083640000)
from space 4352K, 0% used [0x0000000083640000, 0x0000000083640000, 0x0000000083a80000)
to space 4352K, 0% used [0x0000000083a80000, 0x0000000083a80000, 0x0000000083ec0000)
tenured generation total 87424K, used 712K [0x00000000ab800000, 0x00000000b0d60000, 0x0000000100000000)
the space 87424K, 0% used [0x00000000ab800000, 0x00000000ab8b2078, 0x00000000ab8b2200, 0x00000000b0d60000)
Metaspace used 3232K, capacity 4494K, committed 4864K, reserved 1056768K
class space used 354K, capacity 386K, committed 512K, reserved 1048576K
软引用:比强引用稍弱的引用方法。当某个对象只有这一个软引用存在,且内存不足的时候,GC会考虑回收这部分资源。
如:
Object obj = new Object();
SoftReference<Object> softReference = new SoftReference<>(obj);
当使obj = null时,就只有softReference引用了Object对象,如果内存不足,则GC就会释放这部分资源,当内存充足时,这部分资源会依然存在。
测试代码:
/**
* VM args:-XX:+PrintGCDetails
*/
public class SoftReferenceTest {
public static void main(String[] args) {
Object referent = new Object();
/**
* 通过赋值创建softReference
*/
SoftReference<Object> softReference = new SoftReference<Object>(referent);
System.out.println(referent.equals(softReference.get()));
referent = null;
System.gc();
System.out.println(softReference.get());
}
}
测试结果:
true
java.lang.Object@4b1210ee
弱引用:比软引用强度更弱。不管内存够不够用,在下一次GC时,发现某对象只有弱引用时,GC会清理释放这部分资源。
测试代码:
/**
*
*/
public class WeakReferenceTest {
public static void main(String[] args) {
Object referent = new Object();
/**
* 通过赋值创建weakReference
*/
WeakReference<Object> weakReference = new WeakReference<Object>(referent);
System.out.println(referent.equals(weakReference.get()));
referent = null;
System.gc();
System.out.println(weakReference.get());
}
}
测试结果:
true
null
虚引用:是最弱的一种引用类型。GC回收时对于虚引用会当做没有引用存在一样。
测试代码:
/**
* phantom reference 的 get 方法永远返回 null
* PhantomReference 唯一的用处就是跟踪 referent何时被 enqueue 到 ReferenceQueue 中.
* 当一个 WeakReference 开始返回 null 时, 它所指向的对象已经准备被回收,
* 这时可以做一些合适的清理工作. 将一个 ReferenceQueue 传给一个 Reference 的构造函数,
* 当对象被回收时, 虚拟机会自动将这个对象插入到 ReferenceQueue 中,
* WeakHashMap 就是利用 ReferenceQueue 来清除 key 已经没有强引用的 entries.
*/
public class PhantomReferenceTest {
public static void main(String[] args) {
Object referent = new Object();
/**
* 通过赋值创建phantomReference
*/
PhantomReference<Object> phantomReference = new PhantomReference<>(referent,new ReferenceQueue<Object>());
/**
* phantom reference 的 get 方法永远返回 null
*/
System.out.println(referent.equals(phantomReference.get()));
referent = null;
System.gc();
System.out.println(phantomReference.get());
}
}
测试结果:
false
null
引用的用途
WeakReference:当对对象结构和拓扑不是很清晰的时候,可以通过弱引用,可以合理的释放对象,而不会造成不必要的内存泄漏。
如:
A a = new A();
WeakReference wr = new WeakReference(a);
//B b = new B(a);
引用关系如下图:
当使a=null时,A就只有弱引用依赖,因此GC会立刻回收A这个对象。
SoftReference:软引用有个很重要的特性就是在内存充足时会保留资源,当内存不足时会释放资源,因此很适合用来做缓存处理。
摘取网络上的一个应用softReference的代码:
public class ImageLoader {
private Map<String,SoftReference<Bitmap>> cacheImage = new HashMap<String,SoftReference<Bitmap>>();
public void loadImage(final String path,final Callback callback){
SoftReference<Bitmap> softReference = cacheImage.get(path);
if(softReference!=null){
Bitmap bm = softReference.get();
if(bm!=null){
callback.execute(bm);
return;
}
}
new Thread(new Runnable() {
public void run() {
HttpClient client = new DefaultHttpClient();
try {
HttpResponse response = client.execute(new HttpGet(path));
HttpEntity entity = response.getEntity();
byte []bs= EntityUtils.toByteArray(entity);
final Bitmap bm = BitmapFactory.decodeByteArray(bs, 0,bs.length);
SoftReference<Bitmap> reference = new SoftReference<Bitmap>(bm);
cacheImage.put(path,reference);
callback.execute(bm);
entity.consumeContent();
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
public static abstract class Callback{
abstract void execute(Bitmap bm);
}
}
这个就是用软引用来达到缓存处理的方法,这个在andriod中应用得较多,然而有人发现这个方法处理缓存并不是很理想,因此慢慢也在被人抛弃。因为当有10个对象只有软引用时,GC不知道到底clear哪几个或者keep哪几个,甚至,gc会不知道到底是clear资源还是扩展heap。
PhantomReference:虚引用要和ReferenceQueue搭配使用。
在构造Reference对象时,有两种构造函数可供选择:
/* -- Constructors -- */
Reference(T referent) {
this(referent, null);
}
Reference(T referent, ReferenceQueue<? super T> queue) {
this.referent = referent;
this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
}
一种是带ReferenceQueue的,另一种不带此参数。
根据http://blog.csdn.net/u012332679/article/details/57489179所说:
ReferenceQueue这个类有什么用呢,它跟Reference有什么关系呢,关系主要体现在这几个方面,首先Reference这个类里面在构造函数的时候有两种选择,一种是给它传入一个ReferenceQueue,一种是不传,如果不传的话,等这个对象的内存被回收了,直接从Active变为Inactive状态,如果我们传入了ReferenceQueue,那么当对象的内存回收的时候会经历一个过程,从Active->Pending->Enqueued->Inactive。pending状态就是等待着进入ReferenceQueue队列的这样一个状态,说白了它目前还没被回收,只是对象的引用(用户代码中的引用)被移除了,pending保存了这个引用,回收的过程中,ReferenceHandler这个线程会把该对象的引用(pending)放入到我们在构造函数时传入的那个队列里面
ReferenceQueue就是当一个对象gc调后,对象的信息会再保存一段时间,以便我们能够进行额外的操作。
有一个问题:当在ReferenceQueue中发现SoftReference或WeakReference对象时,并不能确定该对象引用的对象已被销毁,此时的对象只是进入了Finalizable状态,然而如果使用的是PhantomReference,在ReferenceQueue中发现了PhantomReference对象,此时referent则已经销毁了。