认识垃圾回收
垃圾:指的是存在于内存中的、不会再使用的对象。
如果垃圾不会收会占用大量内存导致内存溢出。
常用的垃圾回收算法
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次数减少,但是每次耗时增加。同时由于复制算法的特征,浪费而大量的内存空间的做复制区。