头段时间,在线上发布遇到一个class not found的问题.纠结了好久..虽然有很多工具可以查看到对应载入的class.比如在启动的时候添加 -verbose 参数(等同于 -XX:+TraceClassLoading 和 ◦-XX:+TraceClassUnloading) 或者 下载一个对应类加载跟踪的agent(比如 这个jvm类跟踪器 ),或者直接用JVMTI拦截等等.但是,上面所有的方法,都需要依赖PE,然后重启应用.这个成本相对来说是比较高的(PE同学不一定允许你指定agent代理呢)..所以我就琢磨着,做一个小工具,可以直接查看当前应用载入的class类.
该工具依赖以下技术:
1 JavaAgent(JDK5以后提供),单独这个肯定不行,因为还是需要指定代理和重启
2 Java Tools API 中的 Attach API (JDK6以后才提供的).
上面两个依赖,就间接要求必须依赖tools.jar..所以如果线上环境是用JRE跑的话,需要自己依赖tools.jar.注意windows版本的tools.jar和linux版本的tools.jar是不一样的...
做法相当简单.
1 首先下载附件中的agent.然后放到你的工程classpath.该agent的源码如下,很简单
package com.taobao.ju.agent.tool;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.instrument.Instrumentation;
import java.util.ArrayList;
import java.util.List;
/**
* User: zhenghui
* Date: 13-10-29
* Time: 下午4:46
*/
public class LoadedClassAgent {
public static void agentmain(String args, Instrumentation inst)throws Exception {
System.out.println("LoadedClassAgent agentmain attach...");
if(args == null || "".equals(args)){
args = "/home/admin/load_class_agent_temp.txt";
}
System.getProperties().setProperty("monitor.conf.loadAgentFile",args);
List<String> msgList = new ArrayList<String>();
for (Class clazz :inst.getAllLoadedClasses()){
msgList.add(clazz.getName());
}
File file = new File(args);
writeToFile(msgList,file);
System.out.println("LoadedClassAgent agentmain end...");
}
private static void writeToFile(List<String> msgList,File file) throws IOException {
FileWriter fileWriter = new FileWriter(file);
for(String msg : msgList){
fileWriter.write(msg);
fileWriter.write('\r');
fileWriter.write('\n');
}
fileWriter.close();
}
}
agent需要自己打包,我是用maven打包的,所以顺便发一下对应的pom.xml. .注意看加红部分的设置..如果是自己打包,对应的META-INF的MANIFEST.MF里别忘了加这几项
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>com.taobao</groupId> <artifactId>parent</artifactId> <version>1.0.1</version> </parent> <groupId>com.taobao.ju</groupId> <artifactId>ju-agent-tool</artifactId> <version>1.0.0-SNAPSHOT</version> <packaging>jar</packaging> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <configuration> <archive> <manifest> <addDefaultImplementationEntries>true</addDefaultImplementationEntries> <addDefaultSpecificationEntries>true</addDefaultSpecificationEntries> </manifest> <manifestEntries> <Agent-Class>com.taobao.ju.agent.tool.LoadedClassAgent</Agent-Class> <Can-Redefine-Classes>true</Can-Redefine-Classes> <Can-Retransform-Classes>true</Can-Retransform-Classes> </manifestEntries> </archive> </configuration> </plugin> </plugins> </build> </project>
2 动态load 该agent.然后页面输出.下面是我的源码,注意一下,我是用webx作为mvc框架的.如果使用其他的框架,照着里面的代码自己改就OK.
package com.taobao.juwl.iserver.web.module.screen.tool;
import com.alibaba.citrus.turbine.Context;
import com.alibaba.citrus.turbine.Navigator;
import com.alibaba.citrus.turbine.TurbineRunData;
import com.alibaba.common.lang.StringUtil;
import com.google.common.collect.Lists;
import com.sun.tools.attach.VirtualMachine;
import com.taobao.ju.agent.tool.LoadedClassAgent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedReader;
import java.io.FileReader;
import java.lang.management.ManagementFactory;
import java.util.List;
import java.util.Properties;
/**
* User: zhenghui
* Date: 13-10-30
* Time: 上午10:16
*/
public class LoadedClassView {
private static final Logger log = LoggerFactory.getLogger(LoadedClassView.class);
private static final String ENV_WINDOWS = "windows";
private static final String ENV_LINUX = "linux";
public void execute(TurbineRunData turbineRunData, Navigator nav, Context context) {
try {
Properties properties = System.getProperties();
VirtualMachine virtualMachine = VirtualMachine.attach(getPid());
virtualMachine.loadAgent(getAgentJar());
Thread.sleep(1000);
virtualMachine.detach();
String fileName = (String) properties.get("monitor.conf.loadAgentFile");
if (StringUtil.isBlank(fileName)) {
return;
}
FileReader fileReader = new FileReader(fileName);
//最后的结果就放在loadedClasses里
List<String> loadedClasses = Lists.newArrayList();
BufferedReader bufferedReader = new BufferedReader(fileReader);
String s;
while ((s = bufferedReader.readLine()) != null) {
loadedClasses.add(s);
}
bufferedReader.close();
context.put("loadedClasses", loadedClasses);
} catch (Throwable e) {
log.error("LoadedClassView error", e);
}
}
private static String getPid() {
String name = ManagementFactory.getRuntimeMXBean().getName();
String pid = name.split("@")[0];
return pid;
}
/**
* 获取agent jar包所在的地址
*
* @return
*/
private static String getAgentJar() {
String path = LoadedClassAgent.class.getResource("").getFile();
if (path != null) {
path = path.replace("!/com/taobao/ju/agent/tool/", "");
//如果是windows 则去除 "file:/" 如果是linux 则去除 "file:"
if(ENV_WINDOWS.equals(getOSEnv())){
path = path.replace("file:/","");
} else {
path = path.replace("file:","");
}
}
return path;
}
/**
* 买不起mac,所以不知道mac是怎么设置的 :<
* @return
*/
private static String getOSEnv(){
String osName = System.getProperty("os.name");
if(osName.toLowerCase().contains("windows")){
return ENV_WINDOWS;
} else {
return ENV_LINUX;
}
}
}
数据获取到了,如何展现就看自己咯..我就是直接for循环打印了.哈哈
#if($!loadedClasses)
#foreach($clazz in $!loadedClasses)
$!clazz <br>
#end
#end
最后说明一下,如果说线上的应用,只有JRE,没有JDK的话,需要在classpath加一个 tools.jar..
参考资料 http://www.ibm.com/developerworks/cn/java/j-lo-jse61/index.html
最后说明一下如何用maven依赖tools.jar吧..遇到了很多问题.不过终于算解决了.首先,maven仓库中必须有windows和linux的tool.jar. 然后通过maven的profile去区别..
<profile>
<id>product</id>
<properties>
<filterFile>${basedir}/../antx.properties</filterFile>
<skipTgzPackage>false</skipTgzPackage>
<toolsVersion>1.6-linux</toolsVersion>
</properties>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
</profile>
<profile>
<id>dev_win</id>
<properties>
<filterFile>${basedir}/src/main/filter/dev-filter.properties</filterFile>
<skipTgzPackage>true</skipTgzPackage>
<toolsVersion>1.6</toolsVersion>
</properties>
<activation>
<os>
<family>windows</family>
</os>
</activation>
</profile>
默认是linux版的tools.jar.如果是windows,则依赖 windows的tools.jar.这里只指定了版本号.具体的依赖到dependency 里写