存储结构
Thread对象中会存储属性java.lang.Thread.threadLocals,它的类型是java.lang.ThreadLocal.ThreadLocalMap,类型中的实体类型是
static class ThreadLocalMap {
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal k, Object v) {
super(k); // 注意:将ThreadLocal对象交由弱引用对象管理,当其它地方没有该强引用时,k对象将被GC回收
value = v;
}
}
...
ThreadLocal存储对象时实现如下:
/**
* Sets the current thread's copy of this thread-local variable
* to the specified value. Most subclasses will have no need to
* override this method, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
*
* @param value the value to be stored in the current thread's copy of
* this thread-local.
*/
public void set(T value) { // java.lang.ThreadLocal.set(T)
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
/**
* Set the value associated with key.
*
* @param key the thread local object
* @param value the value to be set
*/
private void set(ThreadLocal key, Object value) { // java.lang.ThreadLocal.ThreadLocalMap.set(ThreadLocal, Object)
// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
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();
if (k == key) {
e.value = value;
return;
}
if (k == null) { // 如果k对象已被GC,则原value也被清楚保存的引用
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
测试案例
测试案例一:
/**
* 作者:赵北云 链接:https://www.zhihu.com/question/35250439/answer/62942987
* 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
*/
public class ThreadLocalTest { // -Xms32m -Xmx32m
public static void main(String[] args) {
Thread[] threads = new Thread[100];
for (int j = 0; j < 10; j++) {
threads[j] = new Thread() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
new ThreadLocalTest().buffers.set(new byte[1024 * 1024 * 4]); // 正常运行
// new ThreadLocalTest().buffers.set(new byte[1024 * 1024 * 5]); // 个别线程抛出内存溢
}
}
};
threads[j].start();
try {
threads[j].join();
} catch (InterruptedException e) {
}
}
}
private ThreadLocal buffers = new ThreadLocal<>();
}
结果分析:
测试案例二:
public class ThreadLocalTest { // -Xms32m -Xmx32m
public static void main(String[] args) {
Thread[] threads = new Thread[100];
for (int j = 0; j < 100; j++) {
threads[j] = new Thread() {
@Override
public void run() {
for (int i = 0; i < 1; i++) {
buffers.set(new ClassByte());
}
}
};
threads[j].start();
try {
threads[j].join();
} catch (InterruptedException e) {
}
}
}
private static ThreadLocal buffers = new ThreadLocal<>();
}
class ClassByte {
public byte[] bytes = new byte[1024 * 1024];
@Override
protected void finalize() throws Throwable {
System.out.println("释放对象ClassByte"); // 打印出来了
}
}
结果分析:
TODO
原创文章转载请注明出处: Java的Finalizer引发的内存溢出 英文原文链接
Finalizable对象的生命周期和普通对象的行为是完全不同的,列举如下:
JVM创建Finalizable对象
JVM创建 java.lang.ref.Finalizer实例,指向刚创建的对象。
java.lang.ref.Finalizer类持有新创建的java.lang.ref.Finalizer的实例。这使得下一次新生代GC无法回收这些对象。
新生代GC无法清空Eden区,因此会将这些对象移到Survivor区或者老生代。
垃圾回收器发现这些对象实现了finalize()方法。因为会把它们添加到java.lang.ref.Finalizer.ReferenceQueue队列中。
Finalizer线程会处理这个队列,将里面的对象逐个弹出,并调用它们的finalize()方法。
finalize()方法调用完后,Finalizer线程会将引用从Finalizer类中去掉,因此在下一轮GC中,这些对象就可以被回收了。
Finalizer线程会和我们的主线程进行竞争,不过由于它的优先级较低,获取到的CPU时间较少,因此它永远也赶不上主线程的步伐。
-
程序消耗了所有的可用资源,最后抛出OutOfMemoryError异常。
import java.util.concurrent.atomic.AtomicInteger; class Finalizable { static AtomicInteger aliveCount = new AtomicInteger(0); Finalizable() { aliveCount.incrementAndGet(); } @Override protected void finalize() throws Throwable { Finalizable.aliveCount.decrementAndGet(); } public static void main(String args[]) { // -Xms10m -Xmx10m for (int i = 0;; i++) { Finalizable f = new Finalizable(); if ((i % 100_000) == 0) { System.out.format("After creating %d objects, %d are still alive.%n", new Object[] { i, Finalizable.aliveCount.get() }); } } } }