java引用类型简述
主要内容:
1、引用类型简述
在Java语言中除了基本数据类型外,其他的都是指向各类对象的对象引用;Java中根据其生命周期的长短,将引用分为5类。
1) 强引用
特点:我们平常典型编码Object obj = new Object()中的obj就是强引用。通过关键字new创建的对象所关联的引用就是强引用。 当JVM内存空间不足,JVM宁愿抛出OutOfMemoryError运行时错误(OOM),使程序异常终止,也不会靠随意回收具有强引用的“存活”对象来解决内存不足的问题。对于一个普通的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显式地将相应(强)引用赋值为 null,就是可以被垃圾收集的了,具体回收时机还是要看垃圾收集策略。
2) 软引用
特点:软引用通过SoftReference类实现。 软引用的生命周期比强引用短一些。只有当 JVM 认为内存不足时,才会去试图回收软引用指向的对象:即JVM 会确保在抛出 OutOfMemoryError 之前,清理软引用指向的对象。软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。后续,我们可以调用ReferenceQueue的poll()方法来检查是否有它所关心的对象被回收。如果队列为空,将返回一个null,否则该方法返回队列中前面的一个Reference对象。
应用场景:软引用通常用来实现内存敏感的缓存。如果还有空闲内存,就可以暂时保留缓存,当内存不足时清理掉,这样就保证了使用缓存的同时,不会耗尽内存。
3) 弱引用
弱引用通过WeakReference类实现。 弱引用的生命周期比软引用短。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。由于垃圾回收器是一个优先级很低的线程,因此不一定会很快回收弱引用的对象。弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
应用场景:弱应用同样可用于内存敏感的缓存。
4) 虚引用
特点:虚引用也叫幻象引用,通过PhantomReference类来实现。无法通过虚引用访问对象的任何属性或函数。幻象引用仅仅是提供了一种确保对象被 finalize 以后,做某些事情的机制。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。因为PhantomReference的get()一直返回null, 如果没配合ReferenceQueue队列使用的话,将永远无法访达, 因此虚引用必须和引用队列 (ReferenceQueue)联合使用。
ReferenceQueue queue = new ReferenceQueue();
PhantomReference pr = new PhantomReference (object, queue);
程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取一些程序行动。
应用场景:可用来跟踪对象被垃圾回收器回收的活动,当一个虚引用关联的对象被垃圾收集器回收之前会收到一条系统通知。
5) FinalReference
特点:与FinalReference类紧密相关的是java.lang.ref.Fianlizer类。查看JVM线程会看到一个Fianlizer线程, 主要作用是进行的资源关闭最后环节(finalize机制), 然而也是内存泄漏的重灾区(JVM源码分析之FinalReference完全解读)。Fianlizer继承自FinalReference类,访问权限是package级别,因此应用程序不可进行拓展,全权受JVM实现控制。该类实现的目是垃圾回收之前进行资源回收。以下为使用jmap打印的JVM线程快照:
"Finalizer" #3 daemon prio=8 os_prio=0 tid=0x00002b88f00e7000 nid=0x6a38 in Object.wait() [0x00002b8920080000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143)
- locked <0x000000072a662190> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:164)
at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:209) Locked ownable synchronizers:
- None
2、对象的可达性
- 强可达(Strongly Reachable): An object is strongly reachable if it can be reached by some thread without traversing any reference objects. A newly-created object is strongly reachable by the thread that created it.
- 软可达状态(Softly Reachable) : An object is softly reachable if it is not strongly reachable but can be reached by traversing a soft reference.
- 弱可达状态(Weakly Reachable) : An object is weakly reachable if it is neither strongly nor softly reachable but can be reached by traversing a weak reference. When the weak references to a weakly-reachable object are cleared, the object becomes eligible for finalization.
- 幻象可达状态(Phantom Reachable):An object is phantom reachable if it is neither strongly, softly, nor weakly reachable, it has been finalized, and some phantom reference refers to it.
- 不可达状态(Unreachable): an object is unreachable, and therefore eligible for reclamation, when it is not reachable in any of the above ways.
除了虚引用(PhantomReference)的get()一直返回null意外,其他几个引用都会返回被引用的实际对象,转换为强可达状态。因此对于弱引用、软引用回收,垃圾回收机制会进行二次确认, 是否已经变为强引用。
负面影响如果转换强可达状态不当,例如赋值给static状态的变量,此时无法变回弱引用状态,即产生内存泄漏。
3、软引用的垃圾回收分析
SoftReference通常使用场景实现内存敏感缓存框架,如果还有空闲内存,就可以暂时保留缓存,当内存不足时清理掉,这样就保证了使用缓存的同时,不会耗尽内存。JVM回收机制SoftReference:
- 在OutOfMemoryError之前,Java 虚拟机一定会回收 SoftReference 对象;
- Java不保证SoftReference 对象何时被清除,相关的机制是JVM实现相关的(有关 SoftReference 的一些事实);
- Java提供SoftReference的期望是更好的实现缓存。
正由于SoftReference回收时机不定, 引发一系列问题:
- 如果你的进程所占的内存不是满到要抛 OutOfMemoryError 的程度,JVM 根本不清理 SoftReference 占用的内存。
- 软引用对象占用了一大堆内存,更糟糕的是它们都会进入 Old-Gen。这样你的进程会频繁触发 Full GC,但即使这样,JVM 也不一定会清理 SoftReference 占用的内存。
- 因为 Old-Gen 现在是满负荷工作,你会发现一次 FullGC 的时间变得异常的长。
回收SoftReference的工作是全权由JVM进行控制, 我们是无法进行干预. 但是可以利用小手段进行, 暴力回收(How to make the java system release Soft References?):
VM参数: -Xms200m -Xmx200m
public class SoftReferenceGc {
public static void main(String[] args) {
final SoftReference<Object> reference = new SoftReference<Object>(new Object()); // Sanity check
if(reference.get() == null){
throw new IllegalArgumentException("not empty") ;
} // Force an OOM
try {
Object[] ignored = new Object[(int) Runtime.getRuntime().maxMemory()];
} catch( OutOfMemoryError e ) {
System.out.println("OutOfMemoryError");
// great!
} // Verify object has been garbage collected
if(reference.get() != null){
throw new IllegalArgumentException(" not gc") ;
}else {
System.out.println("SoftReference have been gc");
}
}
}
输出结果:
OutOfMemoryError
SoftReference have been gc
此时验证了上述, 在OutOfMemoryError之前,Java 虚拟机一定会回收 SoftReference 对象
4、WeakHashMap分析
1) WeakHashMap声明如下, 实质上逻辑与HashMap一致, 都是使用[数组+链表]的形式存储数据 以及rehash过程.
public class WeakHashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>
2) 与HashMap不同在于 Entry类继承自WeakReference<Object> 即key为弱引用类型。 因此着即使Entry持有key的引用, 也不能保证它不被回收, 除非key被其他的强类型引用。
/**
* The entries in this hash table extend WeakReference, using its main ref
* field as the key.
*/
private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
V value;
final int hash;
Entry<K,V> next; /**
* Creates new entry.
*/
Entry(Object key, V value,
ReferenceQueue<Object> queue,
int hash, Entry<K,V> next) {
super(key, queue);
this.value = value;
this.hash = hash;
this.next = next;
} @SuppressWarnings("unchecked")
public K getKey() {
return (K) WeakHashMap.unmaskNull(get());
} public V getValue() {
return value;
} public V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
} public boolean equals(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
K k1 = getKey();
Object k2 = e.getKey();
if (k1 == k2 || (k1 != null && k1.equals(k2))) {
V v1 = getValue();
Object v2 = e.getValue();
if (v1 == v2 || (v1 != null && v1.equals(v2)))
return true;
}
return false;
} public int hashCode() {
K k = getKey();
V v = getValue();
return Objects.hashCode(k) ^ Objects.hashCode(v);
} public String toString() {
return getKey() + "=" + getValue();
}
}
Entry
3) Entry持有的key会被先回收, 而意味无法查找对应value并Entry持有该value不被回收 最终造成内存泄漏。核心函数expungeStaleEntries就是清除已经被回收的key对应的value 防止内存泄漏
/**
* Expunges stale entries from the table.
*/
private void expungeStaleEntries() {
// 被回收的key进入ReferenceQueue 循还非阻塞式清除
for (Object x; (x = queue.poll()) != null; ) {
synchronized (queue) {
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>) x;
int i = indexFor(e.hash, table.length); Entry<K,V> prev = table[i];
Entry<K,V> p = prev;
// 解决hash冲突的链表结构, 清除对应value Help GC
while (p != null) {
Entry<K,V> next = p.next;
if (p == e) {
if (prev == e)
table[i] = next;
else
prev.next = next;
// Must not null out e.next;
// stale entries may be in use by a HashIterator
e.value = null; // Help GC
size--;
break;
}
prev = p;
p = next;
}
}
}
}
expungeStaleEntries
4) 从expungeStaleEntries调用链图中看到增、删、改、查 都会调用该函数, 进行null key对应到value处理
5) WeekHashMap特点特别适用于需要缓存的场景。但使用的时候也需要谨慎, 如果缓存的对应过多而GC未能及时回收, 极易造成OOM等问题.
部分参考链接 https://liujiacai.net/blog/2015/09/27/java-weakhashmap/#WeakHashMap-Entry
5、ThreadLocal内存泄漏分析
1) ThreadLocal即为线程本地变量,利用线程绑定实现变量隔离解决并发共享的问题. 简单示例:
public class ThreadLocalAppli {
ThreadLocal<Integer> threadLocal = new ThreadLocal<>() ; public static void main(String[] args) throws InterruptedException {
ThreadLocalAppli app = new ThreadLocalAppli() ;
for (int i = 0; i < 4; i++) {
new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 4; j++) {
app.threadLocal.set(j);
System.out.println(Thread.currentThread().getName()+" :" +app.threadLocal.get());
}
}
}, "Thread-" + i).start();
} Thread.sleep(1000L);
}
}
执行结果: 可以看到线程之前变量互不影响, 实现了正确的计算.
Thread-0 :0
Thread-0 :1
Thread-0 :2
Thread-0 :3
Thread-1 :0
Thread-1 :1
Thread-1 :2
Thread-1 :3
Thread-2 :0
Thread-2 :1
Thread-2 :2
Thread-2 :3
Thread-3 :0
Thread-3 :1
Thread-3 :2
Thread-3 :3
2) ThreadLocal源码分析
ThreadLocal原理是利用内部类ThreadLocalMap实现, Thread会持有ThreadLocal.ThreadLocalMap对象, ThreadLocal并不实际存数据, 而是检索value的key 对应的value即为时机存储的值. 实质上ThreadLocal.ThreadLocalMap 和 WeakHashMap都是散列表 在解决哈希冲突方式上, 前者使用开放寻址而后者选用拉链式.
ThreadLocal.ThreadLocalMap.Entry的结构如下:
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value; Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
Entry.java
Entry结构与WeakHashMap的Entry类似, key都为弱引用类.
针对ThreadLocal.ThreadLocalMap核心源码分析:
- getEntry()
// 使用开放寻址方式查询: 如果hash值恰好为对应的值, 直接返回. 否则由该位置继续往后查找.
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
} private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length; // 循环查询, 并清除过时的Entry
while (e != null) {
ThreadLocal<?> k = e.get();
if (k == key)
return e;
// 说明key已经被回收, 直接清除该槽位
if (k == null)
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
getEntry
- set()
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1); // 使用开放寻址方式解决散列表冲突
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get(); // 说明key已经存在, 直接覆盖原来的值
if (k == key) {
e.value = value;
return;
} // 说明key已经被回收,直接占用改槽位
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
} // 当key不存在 找到空的槽位插入
tab[i] = new Entry(key, value);
int sz = ++size;
// 如果未对key已被回收的Entry清除 且 数量已经超过阀值则进行rehash
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
set
- remove()
// 使用开放寻址方式查找, 并清除符合的槽位
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
remove
与WeakHashMap类似在进行增、删、改、查的时候都会进行过时key的清理, 以防value内存泄漏.
3) ThreadLocal内存泄漏根源
如下图为引用关系,虚线为ThreadLocal的弱引用。 ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用来引用它,那么系统 GC 的时候,这个ThreadLocal势必会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话(比如遇到线程池的情况),这些key为null的Entry的value就会一直存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永远无法回收,造成内存泄漏。(参考至深入分析 ThreadLocal 内存泄漏问题)
那么既然知道是ThreadLocal的弱引用造成内存泄漏, 为什么不使用强应用类型呢?
- 假设key 使用强引用:如果没有手动删除,实际已经不在使用该ThreadLocal, 在每次GC时候, 由于ThreadLocalMap一直持有ThreadLocal的强引用,ThreadLocal不会被回收,导致Entry内存泄漏。
- 假设key 使用弱引用:引用的ThreadLocal的对象被回收了,由于ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocal也会被回收。value在下一次ThreadLocalMap调用set,get,remove等操作的时候会被清除。
由此可见key使用弱引用影响的范围更小, 同时在设计之初时机已经考虑到内存泄漏的问题, 故而增、删、改、查操作的会对过时Entry进行处理。导致内存泄漏的原因在于使用不当, 因此强调每次使用ThreadLocal之后, 必须手动remove() 。
经典分析:【死磕 Java 并发】—– 深入分析 ThreadLocal
java引用类型简述的更多相关文章
- 深入理解Java引用类型
深入理解Java引用类型 在Java中类型可分为两大类:值类型与引用类型.值类型就是基本数据类型(如int ,double 等),而引用类型,是指除了基本的变量类型之外的所有类型(如通过 class ...
- Java引用类型之软引用(2)
下面接着上一篇介绍第2阶段和第3阶段的处理逻辑. 2.process_phase2() 第2个阶段移除所有的referent还存活的Reference,也就是从refs_list中移除Referenc ...
- Java引用类型之弱引用与幻像引用
这一篇将介绍弱引用和幻像引用. 1.WeakReference WeakReference也就是弱引用,弱引用和软引用类似,它是用来描述"非必须"的对象的,它的强度比软引用要更弱一 ...
- Java引用类型之最终引用
FinalReference类只有一个子类Finalizer,并且Finalizer由关键字final修饰,所以无法继承扩展.类的定义如下: class FinalReference<T> ...
- Elasticsearch 2.X 版本Java插件开发简述
1:elasticsearch插件分类简述 2:Java插件开发要点 3:如何针对不同版本elasticsearch提供多版本的插件 4:插件具有外部依赖时遇到的一些问题(2016-09-07更新) ...
- WeakHashMap和Java引用类型详细解析
WeakHashMap是种弱引用的HashMap,这是说,WeakHashMap里的key值如果没有外部强引用,在垃圾回收之后,WeakHashMap的对应内容也会被移除掉. 1.1 Java的引用类 ...
- 你不可不知的Java引用类型之——虚引用
定义 虚引用是使用PhantomReference创建的引用,虚引用也称为幽灵引用或者幻影引用,是所有引用类型中最弱的一个.一个对象是否有虚引用的存在,完全不会对其生命周期构成影响,也无法通过虚引用获 ...
- 你不可不知的Java引用类型之——弱引用
定义 弱引用是使用WeakReference创建的引用,弱引用也是用来描述非必需对象的,它是比软引用更弱的引用类型.在发生GC时,只要发现弱引用,不管系统堆空间是否足够,都会将对象进行回收. 说明 弱 ...
- Java引用类型作为形参和返回值
一.什么是引用类型 在Java中引用类型包括三种:类.抽象类.接口. 二.引用类型作为形参使用 1.类作为形参 /** * 类作为形参,实际传递的是该类的对象 */ class Student { p ...
随机推荐
- NoNodeAvailableException[None of the configured nodes are available: [{#transport#-1}{HBmUtjMOQP2pgLFFwqa_Og}{172.16.0.163}{172.16.0.163:9300}] ]
1.找到elasticsearch的安装目录,在config目录找到elasticsearch.yml,查看cluster.name的赋值 2.在SpringBoot的yml文件中,不仅要配置clus ...
- 玩树莓派(raspberry pi) 2/3 raspbian的遇到的一些问题
raspberry pi买回来玩了一段时间,现在就记录一下入门遇到的一些问题吧. 首先是烧写镜像,和安装电脑系统是一样的道理. 先要有一个制作一个U启动盘.先将SD卡格式化,再用Win32DiskIm ...
- Object Detection
这篇博客对目标检测做了总结:https://handong1587.github.io/deep_learning/2015/10/09/object-detection.html
- webpack中的output.filename 和output.chunkFilename
filename应该比较好理解,就是对应于entry里面生成出来的文件名.比如: { entry: { "index": "pages/index.jsx" } ...
- SeaJS入门教程系列之SeaJS介绍(一)
前言SeaJS是一个遵循CommonJS规范的JavaScript模块加载框架,可以实现JavaScript的模块化开发及加载机制.与jQuery等JavaScript框架不同,SeaJS不会扩展封装 ...
- 在 laravel 的 DB::transaction 中,为外部变量赋值
例如,我想在 laravel 的事务中,对某个外部变量赋值,然后在后续的逻辑中判断该变量的属性 $user = null; // init DB::transaction(function() use ...
- poj1179 环形+区间dp
因为要用到模,所以左起点设置为0比较好 #include<iostream> #include<cstdio> #include<cstring> #define ...
- poj2763树链剖分边权+区间和
自己写的比原来的板子常数小了不少嘻嘻,边权处理起来比点权要复杂一下 由于根节点没有被映射的边,其被访问到的顺序是0,直接排除在线段树外 #include<iostream> #includ ...
- python 全栈开发,Day69(Django的视图层,Django的模板层)
昨日内容回顾 相关命令: 1 创建项目 django-admin startproject 项目名称 2 创建应用 python manage.py startapp app名称 3 启动项目 pyt ...
- git代码提交步骤,教程
代码提交 代码提交一般有五个步骤: 1.查看目前代码的修改状态 2.查看代码修改内容 3.暂存需要提交的文件 4.提交已暂存的文件 5.同步到服务器 1. 查看目前代码的修改状态 提交代码之前 ...