JVM故障诊断与性能优化--垃圾回收概念与算法(三)

认识垃圾回收

垃圾:指的是存在于内存中的、不会再使用的对象。

如果垃圾不会收会占用大量内存导致内存溢出。

常用的垃圾回收算法

1.引用计数法

只要有其它对象引用了此对象,该对象的引用计数器+1,当引用失效时计数器-1。当计数值为0时,对象就可能不再使用。

缺点:无法处理循环引用的情况,加法操作和减法操作对系统性能有影响。


2.标记清除法

标记阶段,首先通过根节点,标记所有根节点开始的可达对象,因此未被标记的均为存活对象。

清楚阶段,清除所有未被标记的对象。


缺点:产生大量的空间碎片。回收后的空间是不连续的,在堆空间分配过程中,尤其是大对象的分配,不连续的内存空间的工作效率会低于连续的空间。

3.复制算法

在新生代串行收集器中,使用了复制算法的思想。将使用eden和survivor区中的一块,而另一块作为复制区。


4.标记压缩法

从根节点开始,对所有可达对象做一次标记,之后将所有存活的对象压缩到另一端,在清理边界外所有空间。

适用于存活对象少,垃圾对象多。

5.分代收集算法

对不同的区域应用不同的垃圾收集算法。


6.分区算法

将整个堆空间分为连续的不同的不同小区间,每个小区间都独立使用,独立回收。可以根据目标的停顿时间,每次合理地回收若干个小区间。


判断可触性

可触及的:从根节点开始,可以到达这个对象。

可复活的:所有引用都被释放,但是有可能在finalize函数中复活。

不可触及的:对象的fianlize函数被调用,并且没有复活,不可触及的对象不能被复活。

1.对象的复活

示例1--对象使用finalize复活

package chapter4;

public class CanReliveObj {
	public static CanReliveObj obj;
	@Override
	protected void finalize() throws Throwable {
		// TODO Auto-generated method stub
		super.finalize();
		System.out.println("CanReliveObj finalize called");
		obj = this;
	}
	@Override
	public String toString() {
		// TODO Auto-generated method stub
		return "I am CanReliceObj";
	}
	public static void main(String[] args) throws InterruptedException {
		obj = new CanReliveObj();
		obj =null;
		System.gc();
		Thread.sleep(1000);
		if(obj==null){
			System.out.println("obj is null");
		}else{
			System.out.println("obj is useful");
		}
		System.out.println("second GC");
		obj=null;
		System.gc();
		Thread.sleep(1000);
		if(obj==null){
			System.out.println("obj is null");
		}else{
			System.out.println("obj is useful");
		}
	}
}

输出结果:


2.引用和可触及性的强度

1.强引用

特点:可以直接访问目标对象 。在任何时候不会被系统回收。可能会导致内存泄漏。

2.软引用

示例1--软引用的失效时间

package chapter4;
//创建实例
public class User {
	public int id;
	public String name;

	public User(int id, String name) {
		super();
		this.id = id;
		this.name = name;
	}

	@Override
	public String toString() {
		return "User [id=" + id + ", name=" + name + "]";
	}
}
package chapter4;

import java.lang.ref.SoftReference;
/*jvm参数设置为:-Xmx10M
public class SoftRef {
	public static void main(String[] args) {
		User u = new User(1, "geym");
		SoftReference<User> userSoftRef = new SoftReference<User>(u);
		u = null;
		System.out.println(userSoftRef.get());
		// 进行一次垃圾回收
		System.gc();
		System.out.print("After first GC:");
		System.out.println(userSoftRef.get());
		// 创建对象,进行第二次垃圾回收
		try {
			byte[] b = new byte[1024 * 894 * 7];
			System.gc();
		} catch (OutOfMemoryError e) {
			System.out.println("After second GC:"+userSoftRef.get());
		}
	}
}

输出结果:


由此可得出,GC未必回收软引用的对象,但是在内存资源紧张的时候会被回收,所以软引用的对象不会引起内存溢出。

示例2--引用队列

package chapter4;

import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
//jvm参数设置:-Xmx10M
public class SoftRefQ {

	public static class UserSoftReference extends SoftReference<User> {
		int uid;

		public UserSoftReference(User referent, ReferenceQueue<? super User> q) {
			super(referent, q);
			uid = referent.id;
		}
	}

	static ReferenceQueue<User> softQueue = null;
	//创建守护线程,判断引用更改后的对象是否在引用队列中
	public static class checkRefQueue extends Thread {
		@Override
		public void run() {
			while (true) {
				if (softQueue != null) {//如果引用队列不为null,则获取引用队列中的元素
					UserSoftReference obj = null;
					try {
						obj = (UserSoftReference) softQueue.remove();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					if (obj != null) {
						System.out.println("user id " + obj.uid + " is delete");
					}
				}
			}
		}
	}

	public static void main(String[] args) throws InterruptedException {
		Thread t = new checkRefQueue();
		t.setDaemon(true);
		t.start();
		User u = new User(1, "geym");
		softQueue = new ReferenceQueue<User>();
		UserSoftReference userSoftRef = new UserSoftReference(u, softQueue);
		u = null;
		System.out.println(userSoftRef.get());
		System.gc();
		System.out.print("After first GC:");
		System.out.println(userSoftRef.get());
		System.out.println("try to create byte array and GC");
		// 创建对象,进行第二次垃圾回收
		try {
			byte[] b = new byte[1024 * 894 * 7];
			System.gc();
		} catch (OutOfMemoryError e) {
			Thread.sleep(1000); 
			System.out.println("After second GC:" + userSoftRef.get());
		}
	}
}

输出结果:


则可以看出,当对象的可达性状态发生改变时,软引用队列就会进入引用队列。垃圾回收器对引用队列中的对象进行回收。

3.弱引用

在系统GC时,无论堆内存使用情况如何都会对其进行回收。在构造软引用时也可以指定引用队列跟踪对象的回收情况。

示例3--弱引用的回收

package chapter4;

import java.lang.ref.WeakReference;

public class WeakRef {
	public static void main(String[] args) {
		User u = new User(1, "geym");
		WeakReference<User> userWeakRef = new WeakReference<User>(u);
		u=null;
		System.out.println(userWeakRef.get());
		System.gc();
		System.out.print("After first GC:");
		System.out.println(userWeakRef.get());
	}
}

输出结果:


5.虚引用

需引用和没有引用几乎是相同的,必须与引用队列一起使用通知应用程序的使用情况。

示例4--可复活对象的跟踪

package chapter4;

import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;

public class TraceCanReliveObj {
	public static TraceCanReliveObj obj;
	static ReferenceQueue<TraceCanReliveObj> phantomQueue=null;
	public static class CheckRefQueue extends Thread{
		@Override
		public void run() {
			while(true){
				if(phantomQueue!=null){
					PhantomReference<TraceCanReliveObj> objt = null;
					try{
						objt = (PhantomReference<TraceCanReliveObj>)phantomQueue.remove();
					}catch(InterruptedException e){
						e.printStackTrace();
					}
					if(objt!=null){
						System.out.println("TraceCanReliveObj is delete by GC");
					}
				}
			}
		}
	}
	@Override
	protected void finalize() throws Throwable {
		super.finalize();
		System.out.println("CanReliveObj finalize called");
		obj=this;
	}
	@Override
	public String toString() {
		// TODO Auto-generated method stub
		return "l am CanReliveObj";
	}
	public static void main(String[] args) throws InterruptedException {
		Thread t = new CheckRefQueue();
		t.setDaemon(true);
		t.start();
		phantomQueue = new ReferenceQueue<TraceCanReliveObj>();
		obj = new TraceCanReliveObj();
		PhantomReference<TraceCanReliveObj> phantomRef = new PhantomReference<TraceCanReliveObj>(obj, phantomQueue);
		obj = null;
		System.gc();
		Thread.sleep(1000);
		if(obj==null){
			System.out.println("obj is null");
		}else{
			System.out.println("obj is used");
		}
		System.out.println("second GC");
		obj = null;
		System.gc();
		Thread.sleep(1000);
		if(obj==null){
			System.out.println("obj is null");
		}else{
			System.out.println("obj is used");
		}
	}
}

输出结果:


4.停顿现象

示例5--程序停顿演示

package chapter4;

import java.util.HashMap;

public class StopWorldTest {
	public static class MyThread extends Thread{
		HashMap<Long, byte[]> map = new HashMap<Long, byte[]>();
		@Override
		public void run() {
			try{
				while(true){
					if(map.size()/1024>=900){
						map.clear();
						System.out.println("clean map");
					}
					byte[] b1;
					for(int i=0;i<100;i++){
						b1 = new byte[1024];
						map.put(System.nanoTime(), b1);
					}
					Thread.sleep(1);
				}
			}catch(Exception e){
				
			}
		}
	}
	public static class PrintThread extends Thread{
		public static final long starttime = System.currentTimeMillis();
		@Override
		public void run() {
			try{
				while(true){
					long t = System.currentTimeMillis()-starttime;
					System.out.println(t/1000+"."+t%1000);
					Thread.sleep(100);
				}
			}catch(InterruptedException e){
				
			}
		}
	}
	public static void main(String[] args) {
		MyThread t = new MyThread();
		PrintThread p = new PrintThread();
		t.start();
		p.start();
	}
}

1.jvm参数设置为:

-Xmx1G -Xms1G -Xmn512k	-XX:+UseSerialGC -Xloggc:gc.log -XX:+PrintGCDetails

输出结果:


对应的gc日志:


可见垃圾回收的停顿对程序运行的影响。用visualVM查看运行过程:


由此可得,新生代GC频繁,每一次GC耗时短,老年代GC发生次数较少,但每次耗时较长。

2.jvm参数设置为:

-Xmx1G -Xms1G -Xmn900M -XX:SurvivorRatio=1 -XX:+UseSerialGC -Xloggc:gc.log -XX:+PrintGCDetails

修改map清空的条件


输出结果:

4.381: [GC4.381: [DefNew: 307200K->182615K(614400K), 0.1821557 secs] 307200K->182615K(741376K), 0.1822856 secs] [Times: user=0.11 sys=0.06, real=0.18 secs] 
9.517: [GC9.517: [DefNew (promotion failed) : 489815K->464730K(614400K), 0.3091456 secs]9.826: [Tenured: 126976K->126976K(126976K), 0.2168418 secs] 489815K->339101K(741376K), [Perm : 7591K->7591K(21248K)], 0.5260825 secs] [Times: user=0.44 sys=0.08, real=0.52 secs] 
16.504: [Full GC16.504: [Tenured: 126976K->81872K(126976K), 0.1005374 secs] 741375K->81872K(741376K), [Perm : 7669K->7669K(21248K)], 0.1005940 secs] [Times: user=0.09 sys=0.00, real=0.10 secs] 
21.340: [GC21.340: [DefNew: 307200K->307200K(614400K), 0.0000193 secs]21.341: [Tenured: 81872K->126975K(126976K), 0.1393429 secs] 389072K->192867K(741376K), [Perm : 7690K->7690K(21248K)], 0.1394275 secs] [Times: user=0.14 sys=0.00, real=0.14 secs] 
30.306: [Full GC30.306: [Tenured: 126975K->126975K(126976K), 0.3141229 secs] 741374K->592639K(741376K), [Perm : 7695K->7692K(21248K)], 0.3141795 secs] [Times: user=0.31 sys=0.00, real=0.31 secs] 
可以看到,新生代的容量增大会导致新生代GC次数减少,但是每次耗时增加。同时由于复制算法的特征,浪费而大量的内存空间的做复制区。

猜你喜欢

转载自blog.csdn.net/yinweicheng/article/details/80656549