diff --git a/arthas-vmtool/src/main/java/arthas/VmTool.java b/arthas-vmtool/src/main/java/arthas/VmTool.java index ad15d127175..6f7cc0a2a57 100644 --- a/arthas-vmtool/src/main/java/arthas/VmTool.java +++ b/arthas-vmtool/src/main/java/arthas/VmTool.java @@ -2,8 +2,6 @@ import java.util.ArrayList; -import com.taobao.arthas.common.OSUtils; - /** * @author ZhangZiCheng 2021-02-12 * @author hengyunabc 2021-04-26 @@ -16,24 +14,6 @@ public class VmTool implements VmToolMXBean { */ public final static String JNI_LIBRARY_NAME = "ArthasJniLibrary"; - private static String libName = null; - static { - if (OSUtils.isMac()) { - libName = "libArthasJniLibrary-x64.dylib"; - } - if (OSUtils.isLinux()) { - libName = "libArthasJniLibrary-x64.so"; - if (OSUtils.isArm32()) { - libName = "libArthasJniLibrary-arm.so"; - } else if (OSUtils.isArm64()) { - libName = "libArthasJniLibrary-aarch64.so"; - } - } - if (OSUtils.isWindows()) { - libName = "libArthasJniLibrary-x64.dll"; - } - } - private static VmTool instance; private VmTool() { @@ -58,10 +38,6 @@ public static synchronized VmTool getInstance(String libPath) { return instance; } - public static String detectLibName() { - return libName; - } - /** * 检测jni-lib是否正常,如果正常,应该输出OK */ diff --git a/arthas-vmtool/src/main/java/arthas/package-info.java b/arthas-vmtool/src/main/java/arthas/package-info.java new file mode 100644 index 00000000000..ba203103de5 --- /dev/null +++ b/arthas-vmtool/src/main/java/arthas/package-info.java @@ -0,0 +1,6 @@ +/** + *
+ * 修改后要同步到 spy/src/main/java 。
+ * 
+ */ +package arthas; diff --git a/arthas-vmtool/src/test/java/arthas/VmToolTest.java b/arthas-vmtool/src/test/java/arthas/VmToolTest.java index 223a853b582..381a3eaefd9 100644 --- a/arthas-vmtool/src/test/java/arthas/VmToolTest.java +++ b/arthas-vmtool/src/test/java/arthas/VmToolTest.java @@ -2,6 +2,8 @@ import org.junit.Test; +import com.taobao.arthas.common.VmToolUtils; + import java.io.File; import java.lang.ref.WeakReference; import java.util.ArrayList; @@ -25,7 +27,7 @@ public void test01() { String path = VmTool.class.getProtectionDomain().getCodeSource().getLocation().getPath(); System.err.println(path); - String libPath = new File(path, VmTool.detectLibName()).getAbsolutePath(); + String libPath = new File(path, VmToolUtils.detectLibName()).getAbsolutePath(); VmTool vmtool = VmTool.getInstance(libPath); //调用native方法,获取已加载的类,不包括小类型(如int) diff --git a/common/src/main/java/com/taobao/arthas/common/VmToolUtils.java b/common/src/main/java/com/taobao/arthas/common/VmToolUtils.java new file mode 100644 index 00000000000..7967b415eef --- /dev/null +++ b/common/src/main/java/com/taobao/arthas/common/VmToolUtils.java @@ -0,0 +1,30 @@ +package com.taobao.arthas.common; + +/** + * + * @author hengyunabc 2021-04-27 + * + */ +public class VmToolUtils { + private static String libName = null; + static { + if (OSUtils.isMac()) { + libName = "libArthasJniLibrary-x64.dylib"; + } + if (OSUtils.isLinux()) { + libName = "libArthasJniLibrary-x64.so"; + if (OSUtils.isArm32()) { + libName = "libArthasJniLibrary-arm.so"; + } else if (OSUtils.isArm64()) { + libName = "libArthasJniLibrary-aarch64.so"; + } + } + if (OSUtils.isWindows()) { + libName = "libArthasJniLibrary-x64.dll"; + } + } + + public static String detectLibName() { + return libName; + } +} diff --git a/core/src/main/java/com/taobao/arthas/core/command/BuiltinCommandPack.java b/core/src/main/java/com/taobao/arthas/core/command/BuiltinCommandPack.java index 1e9f91953a0..0bdcd36eb43 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/BuiltinCommandPack.java +++ b/core/src/main/java/com/taobao/arthas/core/command/BuiltinCommandPack.java @@ -44,6 +44,7 @@ import com.taobao.arthas.core.command.monitor200.ThreadCommand; import com.taobao.arthas.core.command.monitor200.TimeTunnelCommand; import com.taobao.arthas.core.command.monitor200.TraceCommand; +import com.taobao.arthas.core.command.monitor200.VmToolCommand; import com.taobao.arthas.core.command.monitor200.WatchCommand; import com.taobao.arthas.core.shell.command.Command; import com.taobao.arthas.core.shell.command.CommandResolver; @@ -113,6 +114,7 @@ private static void initCommands() { commands.add(Command.create(GrepCommand.class)); commands.add(Command.create(TeeCommand.class)); commands.add(Command.create(ProfilerCommand.class)); + commands.add(Command.create(VmToolCommand.class)); commands.add(Command.create(ShutdownCommand.class)); commands.add(Command.create(StopCommand.class)); } diff --git a/core/src/main/java/com/taobao/arthas/core/command/monitor200/VmToolCommand.java b/core/src/main/java/com/taobao/arthas/core/command/monitor200/VmToolCommand.java new file mode 100644 index 00000000000..744bf2c048f --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/monitor200/VmToolCommand.java @@ -0,0 +1,261 @@ +package com.taobao.arthas.core.command.monitor200; + +import java.io.File; +import java.lang.instrument.Instrumentation; +import java.security.CodeSource; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import com.alibaba.arthas.deps.org.slf4j.Logger; +import com.alibaba.arthas.deps.org.slf4j.LoggerFactory; +import com.taobao.arthas.common.VmToolUtils; +import com.taobao.arthas.core.command.Constants; +import com.taobao.arthas.core.command.express.Express; +import com.taobao.arthas.core.command.express.ExpressException; +import com.taobao.arthas.core.command.express.ExpressFactory; +import com.taobao.arthas.core.command.model.ClassLoaderVO; +import com.taobao.arthas.core.command.model.SearchClassModel; +import com.taobao.arthas.core.shell.cli.Completion; +import com.taobao.arthas.core.shell.cli.CompletionUtils; +import com.taobao.arthas.core.shell.cli.OptionCompleteHandler; +import com.taobao.arthas.core.shell.command.AnnotatedCommand; +import com.taobao.arthas.core.shell.command.CommandProcess; +import com.taobao.arthas.core.util.ClassLoaderUtils; +import com.taobao.arthas.core.util.ClassUtils; +import com.taobao.arthas.core.util.SearchUtils; +import com.taobao.arthas.core.view.ObjectView; +import com.taobao.middleware.cli.annotations.DefaultValue; +import com.taobao.middleware.cli.annotations.Description; +import com.taobao.middleware.cli.annotations.Name; +import com.taobao.middleware.cli.annotations.Option; +import com.taobao.middleware.cli.annotations.Summary; + +import arthas.VmTool; + +/** + * + * @author hengyunabc 2021-04-27 + * + */ +//@formatter:off +@Name("vmtool") +@Summary("jvm tool") +@Description(Constants.EXAMPLE + + " vmtool --action getInstances --className demo.MathGame\n" + + " vmtool --action getInstances --className demo.MathGame --express 'instances.size()'\n" + + Constants.WIKI + Constants.WIKI_HOME + "vmtool") +//@formatter:on +public class VmToolCommand extends AnnotatedCommand { + private static final Logger logger = LoggerFactory.getLogger(VmToolCommand.class); + + private VmToolAction action; + private String className; + private String express; + + private String hashCode = null; + private String classLoaderClass; + /** + * default value 2 + */ + private int expand; + + private static String libPath; + private static VmTool vmTool = null; + + static { + String libName = VmToolUtils.detectLibName(); + if (libName != null) { + CodeSource codeSource = VmToolCommand.class.getProtectionDomain().getCodeSource(); + if (codeSource != null) { + try { + File bootJarPath = new File(codeSource.getLocation().toURI().getSchemeSpecificPart()); + File soFile = new File(bootJarPath.getParentFile(), "lib" + File.separator + libName); + if (soFile.exists()) { + libPath = soFile.getAbsolutePath(); + } + } catch (Throwable e) { + logger.error("can not find VmTool so", e); + } + } + } + + } + + @Option(shortName = "a", longName = "action", required = true) + @Description("Action to execute") + public void setAction(VmToolAction action) { + this.action = action; + } + + @Option(longName = "className") + @Description("The class name") + public void setClassName(String className) { + this.className = className; + } + + @Option(shortName = "x", longName = "expand") + @Description("Expand level of object (2 by default)") + @DefaultValue("2") + public void setExpand(int expand) { + this.expand = expand; + } + + @Option(shortName = "c", longName = "classloader") + @Description("The hash code of the special class's classLoader") + public void setHashCode(String hashCode) { + this.hashCode = hashCode; + } + + @Option(longName = "classLoaderClass") + @Description("The class name of the special class's classLoader.") + public void setClassLoaderClass(String classLoaderClass) { + this.classLoaderClass = classLoaderClass; + } + + @Option(longName = "express", required = false) + @Description("The ognl expression, default valueis `instances`.") + public void setExpress(String express) { + this.express = express; + } + + public enum VmToolAction { + getInstances, load + } + + @Override + public void process(final CommandProcess process) { + try { + Instrumentation inst = process.session().getInstrumentation(); + + if (VmToolAction.getInstances.equals(action)) { + ClassLoader classLoader = ClassLoader.getSystemClassLoader(); + if (hashCode == null && classLoaderClass != null) { + List matchedClassLoaders = ClassLoaderUtils.getClassLoaderByClassName(inst, + classLoaderClass); + if (matchedClassLoaders.size() == 1) { + classLoader = matchedClassLoaders.get(0); + hashCode = Integer.toHexString(matchedClassLoaders.get(0).hashCode()); + } else if (matchedClassLoaders.size() > 1) { + Collection classLoaderVOList = ClassUtils + .createClassLoaderVOList(matchedClassLoaders); + SearchClassModel searchclassModel = new SearchClassModel().setClassLoaderClass(classLoaderClass) + .setMatchedClassLoaders(classLoaderVOList); + process.appendResult(searchclassModel); + process.end(-1, + "Found more than one classloader by class name, please specify classloader with '-c '"); + return; + } else { + process.end(-1, "Can not find classloader by class name: " + classLoaderClass + "."); + return; + } + } + + List> matchedClasses = new ArrayList>( + SearchUtils.searchClass(inst, className, false, hashCode)); + int matchedClassSize = matchedClasses.size(); + if (matchedClassSize == 0) { + process.end(-1, "Can not find class by class name: " + className + "."); + return; + } else if (matchedClassSize > 1) { + process.end(-1, "Found more than one class: " + matchedClasses + "."); + return; + } else { + ArrayList instances = vmToolInstance().getInstances(matchedClasses.get(0)); + Object value = instances; + if (express != null) { + Express unpooledExpress = ExpressFactory.unpooledExpress(classLoader); + try { + value = unpooledExpress.bind(new InstancesWrapper(instances)).get(express); + } catch (ExpressException e) { + logger.warn("ognl: failed execute express: " + express, e); + process.end(-1, "Failed to execute ognl, exception message: " + e.getMessage() + + ", please check $HOME/logs/arthas/arthas.log for more details. "); + } + } + + process.write(new ObjectView(value, this.expand).draw()); + process.end(); + } + } + + process.end(); + } catch (Throwable e) { + logger.error("vmtool error", e); + process.end(1, "vmtool error: " + e.getMessage()); + } + } + + static class InstancesWrapper { + Object instances; + + public InstancesWrapper(Object instances) { + this.instances = instances; + } + + public Object getInstances() { + return instances; + } + + public void setInstances(Object instances) { + this.instances = instances; + } + } + + private VmTool vmToolInstance() { + if (vmTool != null) { + return vmTool; + } else { + vmTool = VmTool.getInstance(libPath); + } + return vmTool; + } + + private Set actions() { + Set values = new HashSet(); + for (VmToolAction action : VmToolAction.values()) { + values.add(action.toString()); + } + return values; + } + + @Override + public void complete(Completion completion) { + List handlers = new ArrayList(); + + handlers.add(new OptionCompleteHandler() { + + @Override + public boolean matchName(String token) { + return "-a".equals(token) || "--action".equals(token); + } + + @Override + public boolean complete(Completion completion) { + return CompletionUtils.complete(completion, actions()); + } + + }); + + handlers.add(new OptionCompleteHandler() { + @Override + public boolean matchName(String token) { + return "--className".equals(token); + } + + @Override + public boolean complete(Completion completion) { + return CompletionUtils.completeClassName(completion); + } + }); + + if (CompletionUtils.completeOptions(completion, handlers)) { + return; + } + + super.complete(completion); + } + +} \ No newline at end of file diff --git a/core/src/main/java/com/taobao/arthas/core/util/ClassUtils.java b/core/src/main/java/com/taobao/arthas/core/util/ClassUtils.java index 0d6ca77ae8a..800f1596036 100644 --- a/core/src/main/java/com/taobao/arthas/core/util/ClassUtils.java +++ b/core/src/main/java/com/taobao/arthas/core/util/ClassUtils.java @@ -181,7 +181,7 @@ public static String[] getClassNameList(Class[] classes) { return list.toArray(new String[0]); } - public static List createClassVOList(Set> matchedClasses) { + public static List createClassVOList(Collection> matchedClasses) { List classVOs = new ArrayList(matchedClasses.size()); for (Class aClass : matchedClasses) { ClassVO classVO = createSimpleClassInfo(aClass); diff --git a/lib/libArthasJniLibrary-x64.dll b/lib/libArthasJniLibrary-x64.dll new file mode 100644 index 00000000000..b9c983aeecd Binary files /dev/null and b/lib/libArthasJniLibrary-x64.dll differ diff --git a/lib/libArthasJniLibrary-x64.dylib b/lib/libArthasJniLibrary-x64.dylib new file mode 100644 index 00000000000..8ab0de58637 Binary files /dev/null and b/lib/libArthasJniLibrary-x64.dylib differ diff --git a/lib/libArthasJniLibrary-x64.so b/lib/libArthasJniLibrary-x64.so new file mode 100644 index 00000000000..b24706f3b19 Binary files /dev/null and b/lib/libArthasJniLibrary-x64.so differ diff --git a/packaging/src/main/assembly/assembly.xml b/packaging/src/main/assembly/assembly.xml index f3e8ad630a8..121523e787f 100644 --- a/packaging/src/main/assembly/assembly.xml +++ b/packaging/src/main/assembly/assembly.xml @@ -63,5 +63,8 @@ ../async-profiler + + ../lib + diff --git a/pom.xml b/pom.xml index 81ab30d75a9..207b72484ed 100644 --- a/pom.xml +++ b/pom.xml @@ -57,8 +57,8 @@ math-game - spy common + spy arthas-vmtool tunnel-common tunnel-client diff --git a/spy/pom.xml b/spy/pom.xml index 39722bc4da7..b0ff9b41161 100644 --- a/spy/pom.xml +++ b/spy/pom.xml @@ -10,6 +10,16 @@ arthas-spy arthas-spy + + + com.taobao.arthas + arthas-common + ${project.version} + provided + true + + + arthas-spy diff --git a/spy/src/main/java/arthas/VmTool.java b/spy/src/main/java/arthas/VmTool.java new file mode 100644 index 00000000000..6f7cc0a2a57 --- /dev/null +++ b/spy/src/main/java/arthas/VmTool.java @@ -0,0 +1,109 @@ +package arthas; + +import java.util.ArrayList; + +/** + * @author ZhangZiCheng 2021-02-12 + * @author hengyunabc 2021-04-26 + * @since 3.5.1 + */ +public class VmTool implements VmToolMXBean { + + /** + * 不要修改jni-lib的名称 + */ + public final static String JNI_LIBRARY_NAME = "ArthasJniLibrary"; + + private static VmTool instance; + + private VmTool() { + } + + public static VmTool getInstance() { + return getInstance(null); + } + + public static synchronized VmTool getInstance(String libPath) { + if (instance != null) { + return instance; + } + + if (libPath == null) { + System.loadLibrary(JNI_LIBRARY_NAME); + } else { + System.load(libPath); + } + + instance = new VmTool(); + return instance; + } + + /** + * 检测jni-lib是否正常,如果正常,应该输出OK + */ + private static native String check0(); + + /** + * 获取某个class在jvm中当前所有存活实例 + */ + private static native ArrayList getInstances0(Class klass); + + /** + * 统计某个class在jvm中当前所有存活实例的总占用内存,单位:Byte + */ + private static native long sumInstanceSize0(Class klass); + + /** + * 获取某个实例的占用内存,单位:Byte + */ + private static native long getInstanceSize0(Object instance); + + /** + * 统计某个class在jvm中当前所有存活实例的总个数 + */ + private static native long countInstances0(Class klass); + + /** + * 获取所有已加载的类 + */ + private static native ArrayList> getAllLoadedClasses0(); + + /** + * 包括小类型(如int) + */ + @SuppressWarnings("all") + public static ArrayList getAllClasses() { + return getInstances0(Class.class); + } + + @Override + public String check() { + return check0(); + } + + @Override + public ArrayList getInstances(Class klass) { + return getInstances0(klass); + } + + @Override + public long sumInstanceSize(Class klass) { + return sumInstanceSize0(klass); + } + + @Override + public long getInstanceSize(Object instance) { + return getInstanceSize0(instance); + } + + @Override + public long countInstances(Class klass) { + return countInstances0(klass); + } + + @Override + public ArrayList> getAllLoadedClasses() { + return getAllLoadedClasses0(); + } + +} diff --git a/spy/src/main/java/arthas/VmToolMXBean.java b/spy/src/main/java/arthas/VmToolMXBean.java new file mode 100644 index 00000000000..e70a17e7a92 --- /dev/null +++ b/spy/src/main/java/arthas/VmToolMXBean.java @@ -0,0 +1,48 @@ +package arthas; + +import java.util.ArrayList; + +/** + * VmTool interface for JMX server. How to register VmTool MBean: + * + *
+ * {@code
+ *     ManagementFactory.getPlatformMBeanServer().registerMBean(
+ *             VmTool.getInstance(),
+ *             new ObjectName("arthas:type=VmTool")
+ *     );
+ * }
+ * 
+ * @author hengyunabc 2021-04-26 + */ +public interface VmToolMXBean { + /** + * 检测jni-lib是否正常,如果正常,应该输出OK + */ + public String check(); + + /** + * 获取某个class在jvm中当前所有存活实例 + */ + public ArrayList getInstances(Class klass); + + /** + * 统计某个class在jvm中当前所有存活实例的总占用内存,单位:Byte + */ + public long sumInstanceSize(Class klass); + + /** + * 获取某个实例的占用内存,单位:Byte + */ + public long getInstanceSize(Object instance); + + /** + * 统计某个class在jvm中当前所有存活实例的总个数 + */ + public long countInstances(Class klass); + + /** + * 获取所有已加载的类 + */ + public ArrayList> getAllLoadedClasses(); +} diff --git a/spy/src/main/java/arthas/package-info.java b/spy/src/main/java/arthas/package-info.java new file mode 100644 index 00000000000..8762944ed98 --- /dev/null +++ b/spy/src/main/java/arthas/package-info.java @@ -0,0 +1,8 @@ +/** + *
+ * copy from arthas-vmtool/src/main/java 。
+ * 因为动态链接库只能被加载一次,只能使用一份代码。放在spy jar里保证只有一份。
+ * TODO 当arthas本身版本升级时,已append 到bootstrap classloader的spy jar不能升级,VmTool的接口可以会调用失败。
+ * 
+ */ +package arthas;