diff --git a/core/src/main/java/com/taobao/arthas/core/command/klass100/ClassLoaderCommand.java b/core/src/main/java/com/taobao/arthas/core/command/klass100/ClassLoaderCommand.java index ffab67d1d..1cb86775b 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/klass100/ClassLoaderCommand.java +++ b/core/src/main/java/com/taobao/arthas/core/command/klass100/ClassLoaderCommand.java @@ -24,6 +24,8 @@ import com.taobao.middleware.cli.annotations.Summary; import java.lang.instrument.Instrumentation; import java.net.URL; import java.net.URLClassLoader; +import java.security.CodeSource; +import java.security.ProtectionDomain; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -51,6 +53,7 @@ import java.util.TreeSet; " classloader -a\n" + " classloader -a -c 327a647b\n" + " classloader -c 659e0bfd --load demo.MathGame\n" + + " classloader -u # url statistics\n" + Constants.WIKI + Constants.WIKI_HOME + "classloader") public class ClassLoaderCommand extends AnnotatedCommand { @@ -63,6 +66,8 @@ public class ClassLoaderCommand extends AnnotatedCommand { private boolean includeReflectionClassLoader = true; private boolean listClassLoader = false; + private boolean urlStat = false; + private String loadClass = null; private volatile boolean isInterrupted = false; @@ -115,6 +120,12 @@ public class ClassLoaderCommand extends AnnotatedCommand { this.loadClass = className; } + @Option(shortName = "u", longName = "url-stat", flag = true) + @Description("Display classloader url statistics") + public void setUrlStat(boolean urlStat) { + this.urlStat = urlStat; + } + @Override public void process(CommandProcess process) { // ctrl-C support @@ -123,6 +134,15 @@ public class ClassLoaderCommand extends AnnotatedCommand { boolean classLoaderSpecified = false; Instrumentation inst = process.session().getInstrumentation(); + + if (urlStat) { + Map urlStats = this.urlStats(inst); + ClassLoaderModel model = new ClassLoaderModel(); + model.setUrlStats(urlStats); + process.appendResult(model); + process.end(); + return; + } if (hashCode != null || classLoaderClass != null) { classLoaderSpecified = true; @@ -396,6 +416,42 @@ public class ClassLoaderCommand extends AnnotatedCommand { } return urlStrs; } + + private Map urlStats(Instrumentation inst) { + Map urlStats = new HashMap(); + Map> usedUrlsMap = new HashMap>(); + for (Class clazz : inst.getAllLoadedClasses()) { + ClassLoader classLoader = clazz.getClassLoader(); + if (classLoader != null) { + ProtectionDomain protectionDomain = clazz.getProtectionDomain(); + CodeSource codeSource = protectionDomain.getCodeSource(); + if (codeSource != null) { + URL location = codeSource.getLocation(); + if (location != null) { + Set urls = usedUrlsMap.get(classLoader); + if (urls == null) { + urls = new HashSet(); + usedUrlsMap.put(classLoader, urls); + } + urls.add(location.toString()); + } + } + } + } + for (Entry> entry : usedUrlsMap.entrySet()) { + ClassLoader loader = entry.getKey(); + Set usedUrls = entry.getValue(); + List allUrls = getClassLoaderUrls(loader); + List unusedUrls = new ArrayList(); + for (String url : allUrls) { + if (!usedUrls.contains(url)) { + unusedUrls.add(url); + } + } + urlStats.put(ClassUtils.createClassLoaderVO(loader), new ClassLoaderUrlStat(usedUrls, unusedUrls)); + } + return urlStats; + } // 以树状列出ClassLoader的继承结构 private static List processClassLoaderTree(List classLoaders) { @@ -583,6 +639,36 @@ public class ClassLoaderCommand extends AnnotatedCommand { } } + public static class ClassLoaderUrlStat { + private Collection usedUrls; + private Collection unUsedUrls; + + public ClassLoaderUrlStat() { + } + + public ClassLoaderUrlStat(Collection usedUrls, Collection unUsedUrls) { + super(); + this.usedUrls = usedUrls; + this.unUsedUrls = unUsedUrls; + } + + public Collection getUsedUrls() { + return usedUrls; + } + + public void setUsedUrls(Collection usedUrls) { + this.usedUrls = usedUrls; + } + + public Collection getUnUsedUrls() { + return unUsedUrls; + } + + public void setUnUsedUrls(Collection unUsedUrls) { + this.unUsedUrls = unUsedUrls; + } + } + public static class ClassLoaderStat { private int loadedCount; private int numberOfInstance; diff --git a/core/src/main/java/com/taobao/arthas/core/command/model/ClassLoaderModel.java b/core/src/main/java/com/taobao/arthas/core/command/model/ClassLoaderModel.java index 76c439a3e..40bc02492 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/model/ClassLoaderModel.java +++ b/core/src/main/java/com/taobao/arthas/core/command/model/ClassLoaderModel.java @@ -1,6 +1,7 @@ package com.taobao.arthas.core.command.model; import com.taobao.arthas.core.command.klass100.ClassLoaderCommand.ClassLoaderStat; +import com.taobao.arthas.core.command.klass100.ClassLoaderCommand.ClassLoaderUrlStat; import java.util.List; import java.util.Map; @@ -24,6 +25,9 @@ public class ClassLoaderModel extends ResultModel { private Collection matchedClassLoaders; private String classLoaderClass; + //urls stat + private Map urlStats; + public ClassLoaderModel() { } @@ -112,4 +116,13 @@ public class ClassLoaderModel extends ResultModel { this.matchedClassLoaders = matchedClassLoaders; return this; } + + public Map getUrlStats() { + return urlStats; + } + + public void setUrlStats(Map urlStats) { + this.urlStats = urlStats; + } + } 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 d433aca60..4c59ced74 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 @@ -1,6 +1,7 @@ package com.taobao.arthas.core.command.view; import com.taobao.arthas.core.command.klass100.ClassLoaderCommand.ClassLoaderStat; +import com.taobao.arthas.core.command.klass100.ClassLoaderCommand.ClassLoaderUrlStat; import com.taobao.arthas.core.command.model.ClassDetailVO; import com.taobao.arthas.core.command.model.ClassLoaderModel; import com.taobao.arthas.core.command.model.ClassLoaderVO; @@ -14,6 +15,7 @@ import com.taobao.text.util.RenderUtil; import java.util.Collection; import java.util.List; import java.util.Map; +import java.util.Map.Entry; /** * @author gongdewei 2020/4/21 @@ -46,8 +48,39 @@ public class ClassLoaderView extends ResultView { if (result.getClassLoaderStats() != null){ drawClassLoaderStats(process, result.getClassLoaderStats()); } + if (result.getUrlStats() != null) { + drawUrlStats(process, result.getUrlStats()); + } } + private void drawUrlStats(CommandProcess process, Map urlStats) { + for (Entry entry : urlStats.entrySet()) { + ClassLoaderVO classLoaderVO = entry.getKey(); + ClassLoaderUrlStat urlStat = entry.getValue(); + + // 忽略 sun.reflect.DelegatingClassLoader 等动态ClassLoader + if (urlStat.getUsedUrls().isEmpty() && urlStat.getUnUsedUrls().isEmpty()) { + continue; + } + + TableElement table = new TableElement().leftCellPadding(1).rightCellPadding(1); + table.row(new LabelElement(classLoaderVO.getName() + ", hash:" + classLoaderVO.getHash()) + .style(Decoration.bold.bold())); + Collection usedUrls = urlStat.getUsedUrls(); + table.row(new LabelElement("Used URLs:").style(Decoration.bold.bold())); + for (String url : usedUrls) { + table.row(url); + } + Collection UnnsedUrls = urlStat.getUnUsedUrls(); + table.row(new LabelElement("Unused URLs:").style(Decoration.bold.bold())); + for (String url : UnnsedUrls) { + table.row(url); + } + process.write(RenderUtil.render(table, process.width())) + .write("\n"); + } + } + private void drawClassLoaderStats(CommandProcess process, Map classLoaderStats) { Element element = renderStat(classLoaderStats); process.write(RenderUtil.render(element, process.width()))