此文章是研究netty过程中的记录,很有可能有很多不对的地方,欢迎指正
之所以研究netty Recycler,是因为生产环境中遇到了由其引发的堆内存占用率过高的现象。
实验所用源码
回收对象分为两种:同一线程 不同线程。
同一线程
在同一线程中,对象回收至Recycler$Stack中
实验代码
package test.recycler;
import io.netty.util.Recycler;
import io.netty.util.concurrent.FastThreadLocalThread;
import java.rmi.server.ExportException;
import java.util.ArrayList;
import java.util.List;
/**
* unreachable Recycler$DefaultHandle
*/
public class RecycleNoRelease1 {
private static final Recycler<User> RECYCLER = new Recycler<User>() {
//没有对象的时候,新建一个对象, 会传入一个handler,在Recycler池里,所有的对象都会转成DefaultHandle对象
@Override
protected User newObject(Handle<User> handle) {
return new User(handle);
}
};
private static ThreadLocal<Long> numLong = new ThreadLocal<Long>();
private static class User {
private final Recycler.Handle<User> handle;
public long a1 = 1;//1kb
public long a2 = 2;
public long a3 = 1;//1kb
public long a4 = 2;
public long a5 = 1;//1kb
public long a6 = 2;
public long a7 = 1;//1kb
public long a8 = 2;
public User(Recycler.Handle<User> handle) {
this.handle = handle;
}
public void recycle() {
//通过handler进行对象的回收
handle.recycle(this);
}
}
public static void main(String[] args) throws InterruptedException {
// try {
// //此处添加延时是为了让我来得及打开JVisualVM去查看动态变化并使用jmap生成dump文件
// Thread.sleep(20000);
// } catch (Exception e) {
// e.printStackTrace();
// }
System.out.println("begin");
FastThreadLocalThread thread = new FastThreadLocalThread(new Runnable() {
public void run() {
long nnn = 1024 * 32;
List<User> list1 = new ArrayList<User>();
for (int i = 0; i < nnn; ++i) {
list1.add(RECYCLER.get());
}
for (int i = 0; i < nnn; ++i) {
list1.get(i).recycle();
}
}
});
thread.start();
thread.join();
System.out.println("=======================over===================================");
while (true) {
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
通过debug发现,回收对象时会将对象存储至stack中
上面两张图说明线程的ThreadLocal里存储了592这个对象,而本次会回收的对象就是592
并不是每一次调用回收方法的时候对象都能回收到stack中,回收的过程中有一个判断
boolean dropHandle(DefaultHandle<?> handle) { if (!handle.hasBeenRecycled) { if (handleRecycleCount < interval) { handleRecycleCount++; // Drop the object. return true; } handleRecycleCount = 0; handle.hasBeenRecycled = true; } return false; }
如果上面的结果为true,对象必然不会进入stack中。如果为false,上层函数还会进行其他判断。
interval是间隔的意思。当运行RecycleInterval(github中的代码)时可以看到对象周期性的入stack
当线程结束时,592这种对象就会变成unreachable
线上曾经遇到DefaultHandle直接为unreachable,推测可能是jvm将DefaultHandle上一层对象(此例中为RecycleNoRelease1$User)回收了。但是jvm会只回收上一层对象而不回收DefaultHandle吗?这部分还需要做实验。
不同线程
实验代码
package test.recycler;
import io.netty.util.Recycler;
import io.netty.util.concurrent.FastThreadLocalThread;
import java.util.ArrayList;
import java.util.List;
/**
* thread用于创建对象,thread2用于回收对象。用于引发WeakOrderQueue$Link的unreachable
*/
public class RecycleMultiThread {
private static final Recycler<User> RECYCLER = new Recycler<User>() {
//没有对象的时候,新建一个对象, 会传入一个handler,在Recycler池里,所有的对象都会转成DefaultHandle对象
@Override
protected User newObject(Handle<User> handle) {
return new User(handle);
}
};
private static Object lock = new Object();
private static class User {
private final Recycler.Handle<User> handle;
public long a1 = 1;//1kb
public long a2 = 2;
public long a3 = 1;//1kb
public long a4 = 2;
public long a5 = 1;//1kb
public long a6 = 2;
public long a7 = 1;//1kb
public long a8 = 2;
public User(Recycler.Handle<User> handle) {
this.handle = handle;
}
public void recycle() {
//通过handler进行对象的回收
handle.recycle(this);
}
}
public static void main(String[] args) throws InterruptedException {
try {
//此处添加延时是为了让我来得及打开JVisualVM去查看动态变化并使用jmap生成dump文件
Thread.sleep(20000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("begin");
final List<User> list1 = new ArrayList<User>();
FastThreadLocalThread thread = new FastThreadLocalThread(new Runnable() {
public void run() {
long nnn = 1024 * 32;
for (int i = 0; i < nnn; ++i) {
list1.add(RECYCLER.get());
}
try {
System.out.println("thread lock begin");
synchronized (RecycleMultiThread.class) {
RecycleMultiThread.class.notify();//对象创建完毕
RecycleMultiThread.class.wait();
}
System.out.println("thread lock over");
} catch (Exception e) {
e.printStackTrace();
}
}
});
FastThreadLocalThread thread2 = new FastThreadLocalThread(new Runnable() {
public void run() {
long nnn = 1024 * 32;
synchronized (RecycleMultiThread.class) {
try {
RecycleMultiThread.class.wait();//等待对象创建完毕
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for (int i = 0; i < 1024 * 32; ++i) {
list1.get(i).recycle();
}
try {
System.out.println("thread2 notify");
synchronized (RecycleMultiThread.class) {
RecycleMultiThread.class.notify();
}
System.out.println("thread2 notify over");
} catch (Exception e) {
e.printStackTrace();
}
}
});
thread.start();
thread2.start();
thread.join();
thread2.join();
System.out.println("=======================over===================================");
while (true) {
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
回收至WeakOrderQueue中
查找上图中@0xffd8aa30的最短路径
为unreachable
此现象与线上系统的dump文件相似(线上dump文件直接就是WeakOrderQueue L i n k u n r e a c h a b l e ) , 据 此 推 测 线 上 老 年 代 出 现 大 量 不 可 达 对 象 的 原 因 就 在 于 线 程 退 出 时 其 T h r e a d L o c a l 依 旧 在 老 年 代 占 有 大 量 空 间 , 并 且 由 于 线 程 已 经 退 出 , 所 以 W e a k O r d e r Q u e u e Link unreachable),据此推测线上老年代出现大量不可达对象的原因就在于线程退出时其ThreadLocal依旧在老年代占有大量空间,并且由于线程已经退出,所以WeakOrderQueue Linkunreachable),据此推测线上老年代出现大量不可达对象的原因就在于线程退出时其ThreadLocal依旧在老年代占有大量空间,并且由于线程已经退出,所以WeakOrderQueueLink直接变成unreachable