应用计数器法没办法规避循环引用,忽略它。。。
跟搜索算法root节点:
- 虚拟机栈,本地方法栈中引用的对象
- 方法区引用的类静态变量(java8后已经属于堆了) // TODO 待确认
- 方法区中的常量引用的对象(java8后已经属于堆了) // TODO 待确认
java的强软弱虚引用
- 强引用不多说,jvm无论如何都不会回收有强引用的对象
- 软引用,内存快溢出前会回收软引用对象。(安卓端一些图片缓存,全部放进内存又太大,从磁盘慢慢读又太慢,那就用软引用缓存)
- 弱引用,不确定的时间回收,垃圾收集器发现弱引用对象就会回收它。
WeakHashMap
就是弱引用的map实现,它的key是关联到一个软引用的,一旦key在外面不存在引用了,会被自动清除。可以将ReferenceQuene传入WeakHashmap的构造方法(constructor)中,这样,一旦这个弱引用指向的对象成为垃圾,这个弱引用将加入ReferenceQuene。看个例子:
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.WeakHashMap;
public class Test {
public static void main(String[] args) throws Exception {
String a = new String("a");
String b = new String("b");
Map weakmap = new WeakHashMap();
Map map = new HashMap();
map.put(a, "aaa");
map.put(b, "bbb");
weakmap.put(a, "aaa");
weakmap.put(b, "bbb");
map.remove(a);
a=null;
b=null;
System.gc();
Iterator i = map.entrySet().iterator();
while (i.hasNext()) {
Map.Entry en = (Map.Entry)i.next();
System.out.println("map:"+en.getKey()+":"+en.getValue());
}
Iterator j = weakmap.entrySet().iterator();
while (j.hasNext()) {
Map.Entry en = (Map.Entry)j.next();
System.out.println("weakmap:"+en.getKey()+":"+en.getValue());
}
}
}
当把a和b都置为null后,hashmap中也不存在对它的引用了(remove掉了),外边a也被置null了,weakhashmap将会自动移除a为key的记录,b虽然被置为null了,但是new String("bbb")
出来的对象在hashmap中还存在引用,所以不会被回收。
(弱引用最大的用处是,你需要用一个对象,但也只是用一用,不能影响它的垃圾回收,比如需要监控虚拟机中某些对象的属性值,如果直接用强引用获取,那么被监控的对象将不会被gc回收,此时弱引用就派上了用场)
- 虚引用,它唯一的作用就是跟
ReferenceQueue
一起使用,通知对象被回收。
import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.reflect.Field;
public class Test {
public static boolean isRun = true;
@SuppressWarnings("static-access")
public static void main(String[] args) throws Exception {
String abc = new String("abc");
System.out.println(abc.getClass() + "@" + abc.hashCode());
final ReferenceQueue<String> referenceQueue = new ReferenceQueue<String>();
new Thread() {
public void run() {
while (isRun) {
Object obj = referenceQueue.poll();
if (obj != null) {
try {
Field rereferent = Reference.class
.getDeclaredField("referent");
rereferent.setAccessible(true);
Object result = rereferent.get(obj);
System.out.println("gc will collect:"
+ result.getClass() + "@"
+ result.hashCode() + "\t"
+ (String) result);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}.start();
PhantomReference<String> abcWeakRef = new PhantomReference<String>(abc,
referenceQueue);
abc = null;
Thread.currentThread().sleep(3000);
System.gc();
Thread.currentThread().sleep(3000);
isRun = false;
}
}
我们可以声明虚引用来引用我们感兴趣的对象,在gc要回收的时候,gc收集器会把这个对象添加到referenceQueue(如上例中,abcWeakRef虚引用就对abs这个对象感兴趣,abc被回收时会被放入referenceQueue中,我们只用监听referenceQueue就知道哪些对象被回收了),这样我们如果检测到referenceQueue中有我们感兴趣的对象的时候,说明gc将要回收这个对象了。此时我们可以在gc回收之前做一些其他事情(如安卓图片滚动显示,每次只显示一张图片,但是图片特别大,内存只能容得下一张图片的大小,此时就需要虚引用来监听前一张图片是否已经被移除,移除之后才能加载下一张图片进来)
finalize方法
没有实现finallize方法的对象不会执行这一堆逻辑,对象回收时干的事情,执行后对象会被放入F-queue中,如果此时对象又被引用上,对象将起死回生。每个对象虚拟机只会执行一次finalize方法。建议对象回收监听使用虚引用(幽灵引用)来实现。
回收方法区
- 回收常量池
某个字符串已经没有任何引用指向它时即可回收。
- 回收类条件
- 没有任何类实例(全部被回收)
- 加载该类的类加载器已经被回收
- 类的Class对象没有在任何地方被引用(不能反射创建实例)
类回收相关虚拟机参数:
- -verbose:class可以查看类加载信息
- -XX:+TraceClassLoading,-XX:+TraceClassUnLoading可以查看类的加载和卸载信息。
垃圾收集算法
- 标记清除
- 复制
- 标记整理
- 分代收集
垃圾收集器
Seria收集器(串行收集器,单线程,采用复制算法)年轻代收集器
ParNew(和Seria一样,只是多线程版本而已)年轻带收集器
SeriaOld 串行收集器,老年代版本,但是使用
标记整理
算法。CMS并发老年代收集器。(标记清除)
初始标记,并发标记,重新标记,并发清除。G1收集器:http://blog.csdn.net/renfufei/article/details/41897113 待学习
内存分配回收策略
- 优先分配到eden区域
参数:-Xms20M -Xmx20M 堆最小值及最大值,-Xmn10M堆的新生代大小,设置为10MB后,老年代大小就为20m-10m=10m了。 -XX:SurvivorRatio=8代表survicor:eden=1:8,设置为8后,新生代中eden区域就占8/10,两个survivor分别占1/10;
- 大对象(大数组或超长字符串)直接进入老年代
因为对象太大先存入eden区,再进survivor进行复制算法拷贝代价就会比较大。
可以使用虚拟机参数-XX:PretenureSzieThreshold:13723来设定判断大对象的阈值(需要注意,这里单位默认为b,写的时候不能自定义单位,另外,只有parNew和Seria收集器会识别该参数)
- 长期存活对象进入老年代
并发那一块的笔记里对对象头的介绍,除了偏向锁等信息等之外就有一个age属性,记录对象的垃圾收集年龄,在survivor区每次被复制都会+1,虚拟机默认达到15岁进入老年代,可以手动设置年龄阈值,-XX:MaxTenuringThreshold:5
tips:虚拟机有另外一个动态年龄判断规则,如果某个年龄的对象大小占据了survivor区的一半,则会将年龄大于等于该年龄的所有对象晋升到老年区。
- 空间分配担保
担保,就是年轻代没有容纳能力了,老年代替你存。(这里老年代替年轻代存的是新的需要分配空间的对象还是survivor区的最老的对象?????!!!回答:当然是最老的对象了咯,除非是大对象)
!!空间分配担保是一直都开启的,HandlePromotionFailure参数只是设置是否允许担保失败。
HandlePromotionFailure参数配置的影响:每次minorgc前先判断老年代是否能容纳全部当前年轻代的大小,成立->直接minorgc即可,不成立-> (老年代剩余空间小于平均晋升大小 || 不允许担保失败 -> fullgc , else -> minorgc)
结论:
- HandlePromotionFailure设置成了false,每次minorgc时只要老年代容不下所有年轻代了,都会被改为fullgc!!
- 设置为true后,只有靠平均晋升大小来判断是否触发fullgc了,这样的经验判断,肯定会有失误的时候,一旦某次晋升对象大小大于经验值,此时会再触发一次fullgc
- 所以,一般都会将HandlePromotionFailure设置为true,不要让虚拟机频繁的fullgc