【第11次讨论主题:对象引用类型】
用自己接触的业务场景或自己的理解角度说明软引用和弱引用的价值(若有代码可附上)
概念理解
在jvm中主流的垃圾回收通过对象的可达性分析来确定对象是否回收,有四种饮用类型:强引用、弱引用、软引用、虚引用
- 强引用:JVM中默认的对象引用方式,通过强引用指向对象,只要有强引用存在,就可以表明对象还活着。任何强引用对象都不能被垃圾回收,当内存空间不足,Java虚拟机会抛出OutOfMemoryError错误,使程序异常终止,所以在使用强引用的时候,需要注意对象的大小和生命周期防止发生OOM,尤其是使用一些Map,List之类的成员变量时更要注意。
- 软引用(SoftReference):软引用是一种相对强引用弱化的引用,可以让对象豁免一些垃圾收集,直到JVM认为内存不足时才进行回收。
- 弱引用(WeakReference):弱引用需要用 WeakReference 类来实现,它比软引用的生存期更短,对于只有弱引用的对象来说,只要垃圾回收机制一运行,不管 JVM 的内存空间是否足够,总会回收该对象占用的内存.但是通过 ReferenceQueue 队列,可以再次获取数据信息
- 虚引用(PhantomReference):不能通过虚引用访问对象,虚引用仅提供一种确保对象被finalize以后,做某些事情的机制。虚引用主要用来跟踪对象被垃圾回收器回收的活动。
价值
-
强引用价值:强引用保障了应用程序中正在使用的对象,不会被垃圾回收器意外回收,为应用程序稳定运行提供了保障,但是当服务器内存资源耗尽,会发生OOM导致系统终止。
-
软引用价值:软引用区别于强引用,系统内存充足时,对象不会被垃圾回收,但是在要发生OOM前,软引用对象会被回收,软引用适合用在内存大小敏感的缓存.如果还有空闲内存,就可以暂时保留缓存,当内存不足时清理掉,保障使用缓存的同时不会耗尽内存。 比如用LRU实现定长内存缓存.
-
弱引用价值:
根据概念描述,比较适合的场景更倾向于对偶尔使用的数据做存储,随取随有.随用随销.
可以用来构建一种没有特定约束的关系,比如,维护一种非强制性的映射关 系,如果试图获取时对象还在,就使用它,否则重现实例化。它同样是很多缓存实现的选择。 -
虚引用价值:
通常用来做所谓的 Post-Mortem 清理机 制,我在专栏上一讲中介绍的 Java 平台自身 Cleaner 机制等,也有人利用幻象引用监控对象的 创建和销毁。
虚引用主要用来跟踪对象被垃圾回收器回收的活动。一般可以通过虚引用达到回收一些非java内的一些资源比如堆外内存的行为。
对象finalize会严重影响JVM垃圾回收性能,在jdk9已被废弃,不推荐使用。推荐使用java.lang.ref.Cleaner,Cleaner也利用了虚引用。https://docs.oracle.com/javase/9/docs/api/java/lang/ref/Cleaner.html
应用场景
软引用
项目里找了找SoftReference的应用,除了jvm里,主要是fastjson中有。
ThreadLocalCache,其作用是避免转换时分配char[]的开销。
在线程全局变量中缓存初始化的char数组,数组缓存存在解决分配数组的开销,内存不足清理数组,使用时再重新分配,不会对程序运行产生其他影响。
private final static ThreadLocal<SoftReference<char[]>> charsBufLocal = new ThreadLocal<SoftReference<char[]>>();
public static void clearChars() {
charsBufLocal.set(null);
}
public static char[] getChars(int length) {
SoftReference<char[]> ref = charsBufLocal.get();
// ref == null有可能是被当作垃圾清理了,这时需要重新获取
if (ref == null) {
return allocate(length);
}
char[] chars = ref.get();
if (chars == null) {
return allocate(length);
}
if (chars.length < length) {
chars = allocate(length);
}
return chars;
}
private static char[] allocate(int length) {
if(length> CHARS_CACH_MAX_SIZE) {
return new char[length];
}
int allocateLength = getAllocateLengthExp(CHARS_CACH_INIT_SIZE_EXP, CHARS_CACH_MAX_SIZE_EXP, length);
char[] chars = new char[allocateLength];
charsBufLocal.set(new SoftReference<char[]>(chars));
return chars;
}
弱引用
Eureka中StringCache的使用,先了解java中String#intern(…),该方法会从字符串常量池中查询当前字符串是否存在,若不存在就会将当前字符串放入常量池中。StringCache和核心也是intern,可以类比理解StringCache的意义。
- JDK 自带的 String Pool常量池 固定大小( jvm:-XX:StringTableSize配置),不支持自动扩容,大量使用 String#intern(…) 后,会导致性能大幅度下降。
- Eureka 的应用实例( InstanceInfo ) 的 appName、appGroupName、vipAddress、secureVipAddress、metadata 和应用( Application )的 name 等属性需要使用到 String Pool ,为了在大量的网络通信序列化反序列的过程中,速度更快,更节省内容。
缓存中存在则设置读锁获取,如果没有则缓存设置进去,引入缓存优化性能,随GC清除不会占用内存影响程序运行。
public class StringCache {
public static final int LENGTH_LIMIT = 38;
private static final StringCache INSTANCE = new StringCache();
private final ReadWriteLock lock = new ReentrantReadWriteLock();
private final Map<String, WeakReference<String>> cache = new WeakHashMap<String, WeakReference<String>>();
private final int lengthLimit;
public StringCache() {
this(LENGTH_LIMIT);
}
public StringCache(int lengthLimit) {
this.lengthLimit = lengthLimit;
}
public String cachedValueOf(final String str) {
if (str != null && (lengthLimit < 0 || str.length() <= lengthLimit)) {
// Return value from cache if available
try {
lock.readLock().lock();
WeakReference<String> ref = cache.get(str);
if (ref != null) {
return ref.get();
}
} finally {
lock.readLock().unlock();
}
// Update cache with new content
try {
lock.writeLock().lock();
WeakReference<String> ref = cache.get(str);
if (ref != null) {
return ref.get();
}
cache.put(str, new WeakReference<>(str));
} finally {
lock.writeLock().unlock();
}
return str;
}
return str;
}
public int size() {
try {
lock.readLock().lock();
return cache.size();
} finally {
lock.readLock().unlock();
}
}
public static String intern(String original) {
return INSTANCE.cachedValueOf(original);
}
}