From 8d88d2bff5dd0c73ef6fedad097c50c378fd8b3e Mon Sep 17 00:00:00 2001 From: gongdewei Date: Thu, 6 Aug 2020 11:48:41 +0800 Subject: [PATCH] classloader command support the classLoaderClass option (#1360) --- .../core/command/klass100/OgnlCommand.java | 43 +++++++++++++++---- .../arthas/core/command/model/OgnlModel.java | 24 +++++++++++ .../core/command/view/ClassLoaderView.java | 7 +-- .../arthas/core/command/view/OgnlView.java | 14 ++++-- .../arthas/core/util/ClassLoaderUtils.java | 22 ++++++++++ .../taobao/arthas/core/util/ClassUtils.java | 8 ++++ site/src/site/sphinx/en/ognl.md | 33 ++++++++++++++ site/src/site/sphinx/ognl.md | 33 ++++++++++++++ 8 files changed, 169 insertions(+), 15 deletions(-) diff --git a/core/src/main/java/com/taobao/arthas/core/command/klass100/OgnlCommand.java b/core/src/main/java/com/taobao/arthas/core/command/klass100/OgnlCommand.java index c1cbaa14c..61a49e856 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/klass100/OgnlCommand.java +++ b/core/src/main/java/com/taobao/arthas/core/command/klass100/OgnlCommand.java @@ -1,6 +1,8 @@ package com.taobao.arthas.core.command.klass100; import java.lang.instrument.Instrumentation; +import java.util.Collection; +import java.util.List; import com.alibaba.arthas.deps.org.slf4j.Logger; import com.alibaba.arthas.deps.org.slf4j.LoggerFactory; @@ -8,10 +10,12 @@ 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.OgnlModel; 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.middleware.cli.annotations.Argument; import com.taobao.middleware.cli.annotations.Description; import com.taobao.middleware.cli.annotations.Name; @@ -37,8 +41,8 @@ public class OgnlCommand extends AnnotatedCommand { private static final Logger logger = LoggerFactory.getLogger(OgnlCommand.class); private String express; - private String hashCode; + private String classLoaderClass; private int expand = 1; @Argument(argName = "express", index = 0, required = true) @@ -53,6 +57,12 @@ public class OgnlCommand extends AnnotatedCommand { 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(shortName = "x", longName = "expand") @Description("Expand level of object (1 by default).") public void setExpand(Integer expand) { @@ -63,15 +73,30 @@ public class OgnlCommand extends AnnotatedCommand { public void process(CommandProcess process) { Instrumentation inst = process.session().getInstrumentation(); ClassLoader classLoader = null; - if (hashCode == null) { - classLoader = ClassLoader.getSystemClassLoader(); - } else { + if (hashCode != null) { classLoader = ClassLoaderUtils.getClassLoader(inst, hashCode); - } - - if (classLoader == null) { - process.end(-1, "Can not find classloader with hashCode: " + hashCode + "."); - return; + if (classLoader == null) { + process.end(-1, "Can not find classloader with hashCode: " + hashCode + "."); + return; + } + } else if (classLoaderClass != null) { + List matchedClassLoaders = ClassLoaderUtils.getClassLoaderByClassName(inst, classLoaderClass); + if (matchedClassLoaders.size() == 1) { + classLoader = matchedClassLoaders.get(0); + } else if (matchedClassLoaders.size() > 1) { + Collection classLoaderVOList = ClassUtils.createClassLoaderVOList(matchedClassLoaders); + OgnlModel ognlModel = new OgnlModel() + .setClassLoaderClass(classLoaderClass) + .setMatchedClassLoaders(classLoaderVOList); + process.appendResult(ognlModel); + 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; + } + } else { + classLoader = ClassLoader.getSystemClassLoader(); } Express unpooledExpress = ExpressFactory.unpooledExpress(classLoader); diff --git a/core/src/main/java/com/taobao/arthas/core/command/model/OgnlModel.java b/core/src/main/java/com/taobao/arthas/core/command/model/OgnlModel.java index d90f79e0d..50cdbc4b3 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/model/OgnlModel.java +++ b/core/src/main/java/com/taobao/arthas/core/command/model/OgnlModel.java @@ -1,12 +1,18 @@ package com.taobao.arthas.core.command.model; +import java.util.Collection; + /** + * Data model of OgnlCommand * @author gongdewei 2020/4/29 */ public class OgnlModel extends ResultModel { private Object value; private int expand = 1; + private Collection matchedClassLoaders; + private String classLoaderClass; + @Override public String getType() { @@ -30,4 +36,22 @@ public class OgnlModel extends ResultModel { this.expand = expand; return this; } + + public String getClassLoaderClass() { + return classLoaderClass; + } + + public OgnlModel setClassLoaderClass(String classLoaderClass) { + this.classLoaderClass = classLoaderClass; + return this; + } + + public Collection getMatchedClassLoaders() { + return matchedClassLoaders; + } + + public OgnlModel setMatchedClassLoaders(Collection matchedClassLoaders) { + this.matchedClassLoaders = matchedClassLoaders; + return this; + } } diff --git a/core/src/main/java/com/taobao/arthas/core/command/view/ClassLoaderView.java b/core/src/main/java/com/taobao/arthas/core/command/view/ClassLoaderView.java index 8a14a252f..359ca8629 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/view/ClassLoaderView.java +++ b/core/src/main/java/com/taobao/arthas/core/command/view/ClassLoaderView.java @@ -11,6 +11,7 @@ import com.taobao.text.Decoration; import com.taobao.text.ui.*; import com.taobao.text.util.RenderUtil; +import java.util.Collection; import java.util.List; import java.util.Map; @@ -57,7 +58,7 @@ public class ClassLoaderView extends ResultView { return table; } - private void drawClassLoaders(CommandProcess process, List classLoaders, Boolean isTree) { + public static void drawClassLoaders(CommandProcess process, Collection classLoaders, boolean isTree) { Element element = isTree ? renderTree(classLoaders) : renderTable(classLoaders); process.write(RenderUtil.render(element, process.width())) .write(com.taobao.arthas.core.util.Constants.EMPTY_STRING); @@ -107,7 +108,7 @@ public class ClassLoaderView extends ResultView { } // 统计所有的ClassLoader的信息 - private static TableElement renderTable(List classLoaderInfos) { + private static TableElement renderTable(Collection classLoaderInfos) { TableElement table = new TableElement().leftCellPadding(1).rightCellPadding(1); table.add(new RowElement().style(Decoration.bold.bold()).add("name", "loadedCount", "hash", "parent")); for (ClassLoaderVO classLoaderVO : classLoaderInfos) { @@ -117,7 +118,7 @@ public class ClassLoaderView extends ResultView { } // 以树状列出ClassLoader的继承结构 - private static Element renderTree(List classLoaderInfos) { + private static Element renderTree(Collection classLoaderInfos) { TreeElement root = new TreeElement(); for (ClassLoaderVO classLoader : classLoaderInfos) { TreeElement child = new TreeElement(classLoader.getName()); diff --git a/core/src/main/java/com/taobao/arthas/core/command/view/OgnlView.java b/core/src/main/java/com/taobao/arthas/core/command/view/OgnlView.java index 509e95197..176627201 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/view/OgnlView.java +++ b/core/src/main/java/com/taobao/arthas/core/command/view/OgnlView.java @@ -6,13 +6,21 @@ import com.taobao.arthas.core.util.StringUtils; import com.taobao.arthas.core.view.ObjectView; /** + * Term view of OgnlCommand * @author gongdewei 2020/4/29 */ public class OgnlView extends ResultView { @Override - public void draw(CommandProcess process, OgnlModel result) { - int expand = result.getExpand(); - Object value = result.getValue(); + public void draw(CommandProcess process, OgnlModel model) { + if (model.getMatchedClassLoaders() != null) { + process.write("Matched classloaders: \n"); + ClassLoaderView.drawClassLoaders(process, model.getMatchedClassLoaders(), false); + process.write("\n"); + return; + } + + int expand = model.getExpand(); + Object value = model.getValue(); String resultStr = StringUtils.objectToString(expand >= 0 ? new ObjectView(value, expand).draw() : value); process.write(resultStr).write("\n"); } diff --git a/core/src/main/java/com/taobao/arthas/core/util/ClassLoaderUtils.java b/core/src/main/java/com/taobao/arthas/core/util/ClassLoaderUtils.java index 0f65b16a7..65c6b8dd4 100644 --- a/core/src/main/java/com/taobao/arthas/core/util/ClassLoaderUtils.java +++ b/core/src/main/java/com/taobao/arthas/core/util/ClassLoaderUtils.java @@ -1,7 +1,9 @@ package com.taobao.arthas.core.util; import java.lang.instrument.Instrumentation; +import java.util.ArrayList; import java.util.HashSet; +import java.util.List; import java.util.Set; /** @@ -39,6 +41,26 @@ public class ClassLoaderUtils { return null; } + /** + * 通过类名查找classloader + * @param inst + * @param classLoaderClassName + * @return + */ + public static List getClassLoaderByClassName(Instrumentation inst, String classLoaderClassName) { + if (classLoaderClassName == null || classLoaderClassName.isEmpty()) { + return null; + } + Set classLoaderSet = getAllClassLoader(inst); + List matchClassLoaders = new ArrayList(); + for (ClassLoader classLoader : classLoaderSet) { + if (classLoader.getClass().getName().equals(classLoaderClassName)) { + matchClassLoaders.add(classLoader); + } + } + return matchClassLoaders; + } + public static String classLoaderHash(ClassLoader classLoader) { int hashCode = 0; if (classLoader == null) { 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 b31ee56c0..ea4f18df0 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 @@ -199,6 +199,14 @@ public class ClassUtils { return classLoaderVO; } + public static List createClassLoaderVOList(Collection classLoaders) { + List classLoaderVOList = new ArrayList(); + for (ClassLoader classLoader : classLoaders) { + classLoaderVOList.add(createClassLoaderVO(classLoader)); + } + return classLoaderVOList; + } + public static String classLoaderHash(Class clazz) { if (clazz == null || clazz.getClassLoader() == null) { return "null"; diff --git a/site/src/site/sphinx/en/ognl.md b/site/src/site/sphinx/en/ognl.md index 81a80e7d3..389bbfaea 100644 --- a/site/src/site/sphinx/en/ognl.md +++ b/site/src/site/sphinx/en/ognl.md @@ -11,6 +11,7 @@ Since 3.0.5. |---:|:---| |*express*|expression to be executed| |`[c:]`| The hashcode of the ClassLoader that executes the expression, default ClassLoader is SystemClassLoader. | +|`[classLoaderClass:]`| The class name of the ClassLoader that executes the expression. | |[x]|Expand level of object (1 by default).| @@ -49,6 +50,38 @@ $ ognl '@demo.MathGame@random' ] ``` + +Specify ClassLoader by hashcode: + +```bash +$ classloader -t ++-BootstrapClassLoader ++-jdk.internal.loader.ClassLoaders$PlatformClassLoader@301ec38b + +-com.taobao.arthas.agent.ArthasClassloader@472067c7 + +-jdk.internal.loader.ClassLoaders$AppClassLoader@4b85612c + +-org.springframework.boot.loader.LaunchedURLClassLoader@7f9a81e8 + +$ ognl -c 7f9a81e8 @org.springframework.boot.SpringApplication@logger +@Slf4jLocationAwareLog[ + FQCN=@String[org.apache.commons.logging.LogAdapter$Slf4jLocationAwareLog], + name=@String[org.springframework.boot.SpringApplication], + logger=@Logger[Logger[org.springframework.boot.SpringApplication]], +] +$ +``` +Note that the hashcode changes, you need to check the current ClassLoader information first, and extract the hashcode corresponding to the ClassLoader. + +For ClassLoader with only unique instance, it can be specified by class name, which is more convenient to use: + +```bash +$ ognl --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoader @org.springframework.boot.SpringApplication@logger +@Slf4jLocationAwareLog[ + FQCN=@String[org.apache.commons.logging.LogAdapter$Slf4jLocationAwareLog], + name=@String[org.springframework.boot.SpringApplication], + logger=@Logger[Logger[org.springframework.boot.SpringApplication]], +] +``` + Execute a multi-line expression, and return a list: ```bash diff --git a/site/src/site/sphinx/ognl.md b/site/src/site/sphinx/ognl.md index 41e558def..4ae97fc2e 100644 --- a/site/src/site/sphinx/ognl.md +++ b/site/src/site/sphinx/ognl.md @@ -11,6 +11,7 @@ ognl |---:|:---| |*express*|执行的表达式| |`[c:]`|执行表达式的 ClassLoader 的 hashcode,默认值是SystemClassLoader| +|`[classLoaderClass:]`|指定执行表达式的 ClassLoader 的 class name| |[x]|结果对象的展开层次,默认值1| @@ -50,6 +51,38 @@ $ ognl '@demo.MathGame@random' ] ``` +通过hashcode指定ClassLoader: + +```bash +$ classloader -t ++-BootstrapClassLoader ++-jdk.internal.loader.ClassLoaders$PlatformClassLoader@301ec38b + +-com.taobao.arthas.agent.ArthasClassloader@472067c7 + +-jdk.internal.loader.ClassLoaders$AppClassLoader@4b85612c + +-org.springframework.boot.loader.LaunchedURLClassLoader@7f9a81e8 + +$ ognl -c 7f9a81e8 @org.springframework.boot.SpringApplication@logger +@Slf4jLocationAwareLog[ + FQCN=@String[org.apache.commons.logging.LogAdapter$Slf4jLocationAwareLog], + name=@String[org.springframework.boot.SpringApplication], + logger=@Logger[Logger[org.springframework.boot.SpringApplication]], +] +$ +``` +注意hashcode是变化的,需要先查看当前的ClassLoader信息,提取对应ClassLoader的hashcode。 + +对于只有唯一实例的ClassLoader可以通过class name指定,使用起来更加方便: + +```bash +$ ognl --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoader @org.springframework.boot.SpringApplication@logger +@Slf4jLocationAwareLog[ + FQCN=@String[org.apache.commons.logging.LogAdapter$Slf4jLocationAwareLog], + name=@String[org.springframework.boot.SpringApplication], + logger=@Logger[Logger[org.springframework.boot.SpringApplication]], +] +``` + + 执行多行表达式,赋值给临时变量,返回一个List: ```bash