diff --git a/core/src/main/java/com/taobao/arthas/core/command/express/ExpressFactory.java b/core/src/main/java/com/taobao/arthas/core/command/express/ExpressFactory.java index d2d578bc17f..886c910f1bb 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/express/ExpressFactory.java +++ b/core/src/main/java/com/taobao/arthas/core/command/express/ExpressFactory.java @@ -1,5 +1,7 @@ package com.taobao.arthas.core.command.express; +import java.lang.ref.WeakReference; + /** * ExpressFactory * @author ralf0131 2017-01-04 14:40. @@ -7,12 +9,14 @@ */ public class ExpressFactory { - private static final ThreadLocal expressRef = new ThreadLocal() { - @Override - protected Express initialValue() { - return new OgnlExpress(); - } - }; + /** + * 这里不能直接在 ThreadLocalMap 里强引用 Express(它由 ArthasClassLoader 加载),否则 stop/detach 后会被业务线程持有, + * 导致 ArthasClassLoader 无法被 GC 回收。 + * + * 用 WeakReference 打断强引用链:Thread -> ThreadLocalMap -> value(WeakReference) -X-> Express。 + */ + private static final ThreadLocal> expressRef = ThreadLocal + .withInitial(() -> new WeakReference(new OgnlExpress())); /** * get ThreadLocal Express Object @@ -20,7 +24,13 @@ protected Express initialValue() { * @return */ public static Express threadLocalExpress(Object object) { - return expressRef.get().reset().bind(object); + WeakReference reference = expressRef.get(); + Express express = reference == null ? null : reference.get(); + if (express == null) { + express = new OgnlExpress(); + expressRef.set(new WeakReference(express)); + } + return express.reset().bind(object); } public static Express unpooledExpress(ClassLoader classloader) { @@ -29,4 +39,4 @@ public static Express unpooledExpress(ClassLoader classloader) { } return new OgnlExpress(new ClassLoaderClassResolver(classloader)); } -} \ No newline at end of file +} diff --git a/core/src/main/java/com/taobao/arthas/core/command/monitor200/TimeTunnelAdviceListener.java b/core/src/main/java/com/taobao/arthas/core/command/monitor200/TimeTunnelAdviceListener.java index 6c9faf7084f..7a088f16f04 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/monitor200/TimeTunnelAdviceListener.java +++ b/core/src/main/java/com/taobao/arthas/core/command/monitor200/TimeTunnelAdviceListener.java @@ -21,12 +21,22 @@ */ public class TimeTunnelAdviceListener extends AdviceListenerAdapter { private static final Logger logger = LoggerFactory.getLogger(TimeTunnelAdviceListener.class); - private final ThreadLocal argsRef = new ThreadLocal() { - @Override - protected ObjectStack initialValue() { - return new ObjectStack(512); - } - }; + /** + * 用 JDK 的 Object[] 做一个固定大小的 ring stack(只存业务对象),避免把 ArthasClassLoader 加载的 ObjectStack 放进 + * 业务线程的 ThreadLocalMap 里,导致 stop/detach 后 ArthasClassLoader 无法被 GC 回收。 + * + *
+     * 约定:
+     * - store[0] 存储 int[1] 的 pos(0..cap)
+     * - store[1..cap] 存储 args(Object[])
+     * 
+ */ + private static final int ARGS_STACK_SIZE = 512; + private final ThreadLocal argsRef = ThreadLocal.withInitial(() -> { + Object[] store = new Object[ARGS_STACK_SIZE + 1]; + store[0] = new int[1]; + return store; + }); private TimeTunnelCommand command; private CommandProcess process; @@ -46,7 +56,7 @@ public TimeTunnelAdviceListener(TimeTunnelCommand command, CommandProcess proces @Override public void before(ClassLoader loader, Class clazz, ArthasMethod method, Object target, Object[] args) throws Throwable { - argsRef.get().push(args); + pushArgs(args); threadLocalWatch.start(); } @@ -54,7 +64,7 @@ public void before(ClassLoader loader, Class clazz, ArthasMethod method, Obje public void afterReturning(ClassLoader loader, Class clazz, ArthasMethod method, Object target, Object[] args, Object returnObject) throws Throwable { //取出入参时的 args,因为在函数执行过程中 args可能被修改 - args = (Object[]) argsRef.get().pop(); + args = popArgs(); afterFinishing(Advice.newForAfterReturning(loader, clazz, method, target, args, returnObject)); } @@ -62,10 +72,46 @@ public void afterReturning(ClassLoader loader, Class clazz, ArthasMethod meth public void afterThrowing(ClassLoader loader, Class clazz, ArthasMethod method, Object target, Object[] args, Throwable throwable) { //取出入参时的 args,因为在函数执行过程中 args可能被修改 - args = (Object[]) argsRef.get().pop(); + args = popArgs(); afterFinishing(Advice.newForAfterThrowing(loader, clazz, method, target, args, throwable)); } + private void pushArgs(Object[] args) { + Object[] store = argsRef.get(); + int[] posHolder = (int[]) store[0]; + + int cap = store.length - 1; + int pos = posHolder[0]; + if (pos < cap) { + pos++; + } else { + // if stack is full, reset pos + pos = 1; + } + store[pos] = args; + posHolder[0] = pos; + } + + private Object[] popArgs() { + Object[] store = argsRef.get(); + int[] posHolder = (int[]) store[0]; + + int cap = store.length - 1; + int pos = posHolder[0]; + if (pos > 0) { + Object[] args = (Object[]) store[pos]; + store[pos] = null; + posHolder[0] = pos - 1; + return args; + } + + pos = cap; + Object[] args = (Object[]) store[pos]; + store[pos] = null; + posHolder[0] = pos - 1; + return args; + } + private void afterFinishing(Advice advice) { double cost = threadLocalWatch.costInMillis(); TimeFragment timeTunnel = new TimeFragment(advice, LocalDateTime.now(), cost); @@ -103,57 +149,4 @@ private void afterFinishing(Advice advice) { abortProcess(process, command.getNumberOfLimit()); } } - - /** - * - *
-     * 一个特殊的stack,为了追求效率,避免扩容。
-     * 因为这个stack的push/pop 并不一定成对调用,比如可能push执行了,但是后面的流程被中断了,pop没有被执行。
-     * 如果不固定大小,一直增长的话,极端情况下可能应用有内存问题。
-     * 如果到达容量,pos会重置,循环存储数据。所以使用这个Stack如果在极端情况下统计的数据会不准确,只用于monitor/watch等命令的计时。
-     * 
-     * 
- * - * @author hengyunabc 2020-05-20 - * - */ - static class ObjectStack { - private Object[] array; - private int pos = 0; - private int cap; - - public ObjectStack(int maxSize) { - array = new Object[maxSize]; - cap = array.length; - } - - public int size() { - return pos; - } - - public void push(Object value) { - if (pos < cap) { - array[pos++] = value; - } else { - // if array is full, reset pos - pos = 0; - array[pos++] = value; - } - } - - public Object pop() { - if (pos > 0) { - pos--; - Object object = array[pos]; - array[pos] = null; - return object; - } else { - pos = cap; - pos--; - Object object = array[pos]; - array[pos] = null; - return object; - } - } - } } diff --git a/core/src/main/java/com/taobao/arthas/core/shell/command/internal/TermHandler.java b/core/src/main/java/com/taobao/arthas/core/shell/command/internal/TermHandler.java index a8b3dae2212..fc2d29662d0 100644 --- a/core/src/main/java/com/taobao/arthas/core/shell/command/internal/TermHandler.java +++ b/core/src/main/java/com/taobao/arthas/core/shell/command/internal/TermHandler.java @@ -19,4 +19,4 @@ public String apply(String data) { term.write(data); return data; } -} +} \ No newline at end of file diff --git a/core/src/main/java/com/taobao/arthas/core/util/ThreadLocalWatch.java b/core/src/main/java/com/taobao/arthas/core/util/ThreadLocalWatch.java index 9a5e1e257c8..a47657a5e15 100644 --- a/core/src/main/java/com/taobao/arthas/core/util/ThreadLocalWatch.java +++ b/core/src/main/java/com/taobao/arthas/core/util/ThreadLocalWatch.java @@ -8,25 +8,31 @@ */ public class ThreadLocalWatch { - private final ThreadLocal timestampRef = new ThreadLocal() { - @Override - protected LongStack initialValue() { - return new LongStack(1024 * 4); - } - }; + /** + * 用 long[] 做一个固定大小的 ring stack,避免把 ArthasClassLoader 加载的对象塞到业务线程的 ThreadLocalMap 里, + * 从而在 stop/detach 后导致 ArthasClassLoader 无法被 GC 回收。 + * + *
+     * 约定:
+     * - stack[0] 存储当前 pos(0..cap)
+     * - stack[1..cap] 存储数据
+     * 
+ */ + private static final int DEFAULT_STACK_SIZE = 1024 * 4; + private final ThreadLocal timestampRef = ThreadLocal.withInitial(() -> new long[DEFAULT_STACK_SIZE + 1]); public long start() { final long timestamp = System.nanoTime(); - timestampRef.get().push(timestamp); + push(timestampRef.get(), timestamp); return timestamp; } public long cost() { - return (System.nanoTime() - timestampRef.get().pop()); + return (System.nanoTime() - pop(timestampRef.get())); } public double costInMillis() { - return (System.nanoTime() - timestampRef.get().pop()) / 1000000.0; + return (System.nanoTime() - pop(timestampRef.get())) / 1000000.0; } /** @@ -42,39 +48,31 @@ public double costInMillis() { * @author hengyunabc 2019-11-20 * */ - static class LongStack { - private long[] array; - private int pos = 0; - private int cap; - - public LongStack(int maxSize) { - array = new long[maxSize]; - cap = array.length; - } - - public int size() { - return pos; + static void push(long[] stack, long value) { + int cap = stack.length - 1; + int pos = (int) stack[0]; + if (pos < cap) { + pos++; + } else { + // if stack is full, reset pos + pos = 1; } + stack[pos] = value; + stack[0] = pos; + } - public void push(long value) { - if (pos < cap) { - array[pos++] = value; - } else { - // if array is full, reset pos - pos = 0; - array[pos++] = value; - } + static long pop(long[] stack) { + int cap = stack.length - 1; + int pos = (int) stack[0]; + if (pos > 0) { + long value = stack[pos]; + stack[0] = pos - 1; + return value; } - public long pop() { - if (pos > 0) { - pos--; - return array[pos]; - } else { - pos = cap; - pos--; - return array[pos]; - } - } + pos = cap; + long value = stack[pos]; + stack[0] = pos - 1; + return value; } -} \ No newline at end of file +} diff --git a/core/src/test/java/com/taobao/arthas/core/util/LongStackTest.java b/core/src/test/java/com/taobao/arthas/core/util/LongStackTest.java index 009e0473850..9b6d14b6be9 100644 --- a/core/src/test/java/com/taobao/arthas/core/util/LongStackTest.java +++ b/core/src/test/java/com/taobao/arthas/core/util/LongStackTest.java @@ -3,8 +3,6 @@ import org.junit.Assert; import org.junit.Test; -import com.taobao.arthas.core.util.ThreadLocalWatch.LongStack; - /** * * @author hengyunabc 2019-11-20 @@ -14,76 +12,76 @@ public class LongStackTest { @Test public void test() { - LongStack stack = new LongStack(100); + long[] stack = new long[101]; - stack.push(1); - stack.push(2); - stack.push(3); + ThreadLocalWatch.push(stack, 1); + ThreadLocalWatch.push(stack, 2); + ThreadLocalWatch.push(stack, 3); - Assert.assertEquals(3, stack.pop()); - Assert.assertEquals(2, stack.pop()); - Assert.assertEquals(1, stack.pop()); + Assert.assertEquals(3, ThreadLocalWatch.pop(stack)); + Assert.assertEquals(2, ThreadLocalWatch.pop(stack)); + Assert.assertEquals(1, ThreadLocalWatch.pop(stack)); } @Test public void test2() { - LongStack stack = new LongStack(100); + long[] stack = new long[101]; - stack.push(1); - stack.push(2); - stack.push(3); + ThreadLocalWatch.push(stack, 1); + ThreadLocalWatch.push(stack, 2); + ThreadLocalWatch.push(stack, 3); - Assert.assertEquals(3, stack.pop()); - Assert.assertEquals(2, stack.pop()); - Assert.assertEquals(1, stack.pop()); - Assert.assertEquals(0, stack.pop()); + Assert.assertEquals(3, ThreadLocalWatch.pop(stack)); + Assert.assertEquals(2, ThreadLocalWatch.pop(stack)); + Assert.assertEquals(1, ThreadLocalWatch.pop(stack)); + Assert.assertEquals(0, ThreadLocalWatch.pop(stack)); } @Test public void test3() { - LongStack stack = new LongStack(2); + long[] stack = new long[3]; - stack.push(1); - stack.push(2); - stack.push(3); + ThreadLocalWatch.push(stack, 1); + ThreadLocalWatch.push(stack, 2); + ThreadLocalWatch.push(stack, 3); - Assert.assertEquals(3, stack.pop()); - Assert.assertEquals(2, stack.pop()); - Assert.assertEquals(3, stack.pop()); - Assert.assertEquals(2, stack.pop()); + Assert.assertEquals(3, ThreadLocalWatch.pop(stack)); + Assert.assertEquals(2, ThreadLocalWatch.pop(stack)); + Assert.assertEquals(3, ThreadLocalWatch.pop(stack)); + Assert.assertEquals(2, ThreadLocalWatch.pop(stack)); } @Test public void test4() { - LongStack stack = new LongStack(2); + long[] stack = new long[3]; - stack.push(1); - stack.push(2); + ThreadLocalWatch.push(stack, 1); + ThreadLocalWatch.push(stack, 2); - Assert.assertEquals(2, stack.pop()); - Assert.assertEquals(1, stack.pop()); - Assert.assertEquals(2, stack.pop()); - Assert.assertEquals(1, stack.pop()); + Assert.assertEquals(2, ThreadLocalWatch.pop(stack)); + Assert.assertEquals(1, ThreadLocalWatch.pop(stack)); + Assert.assertEquals(2, ThreadLocalWatch.pop(stack)); + Assert.assertEquals(1, ThreadLocalWatch.pop(stack)); } @Test public void test5() { - LongStack stack = new LongStack(10); - - stack.push(1); - stack.push(2); - stack.push(3); - stack.pop(); - stack.pop(); - stack.push(4); - stack.push(5); + long[] stack = new long[11]; + + ThreadLocalWatch.push(stack, 1); + ThreadLocalWatch.push(stack, 2); + ThreadLocalWatch.push(stack, 3); + ThreadLocalWatch.pop(stack); + ThreadLocalWatch.pop(stack); + ThreadLocalWatch.push(stack, 4); + ThreadLocalWatch.push(stack, 5); - stack.push(6); - stack.pop(); + ThreadLocalWatch.push(stack, 6); + ThreadLocalWatch.pop(stack); - Assert.assertEquals(5, stack.pop()); - Assert.assertEquals(4, stack.pop()); - Assert.assertEquals(1, stack.pop()); - Assert.assertEquals(0, stack.pop()); + Assert.assertEquals(5, ThreadLocalWatch.pop(stack)); + Assert.assertEquals(4, ThreadLocalWatch.pop(stack)); + Assert.assertEquals(1, ThreadLocalWatch.pop(stack)); + Assert.assertEquals(0, ThreadLocalWatch.pop(stack)); } } diff --git a/pom.xml b/pom.xml index 6d4d8c99159..6511a8cdb7b 100644 --- a/pom.xml +++ b/pom.xml @@ -109,7 +109,7 @@ com.alibaba.middleware termd-core - 1.1.7.14 + 1.1.7.15 com.alibaba.middleware