下面是在利用JDK的Instrument來編寫調試工具的時候出現的一些問題總結
1、java.io.Console 類的讀取操作將會阻塞掉寫入操作,造成寫入操作不能異步進行。
原因是該類中加入了讀寫鎖。代碼如下:
public String readLine(String fmt, Object ... args) {
String line = null;
synchronized (writeLock) {
synchronized(readLock) {
if (fmt.length() != 0)
pw.format(fmt, args);
try {
char[] ca = readline(false);
if (ca != null)
line = new String(ca);
} catch (IOException x) {
throw new IOError(x);
}
}
}
return line;
}
2、Class.getSimpleName 方法在scala 下有可能拋出異常
例如:
Exception in thread "agent thread" java.lang.InternalError: Malformed class name
at java.lang.Class.getSimpleName(Class.java:1133)
at cn.zhxing.trace.agent.instrument.ClassFilter.match(ClassFilter.java:41)
at cn.zhxing.trace.util.InstrumentUtil.findMatchClassAndMethods(InstrumentUtil.java:111)
at cn.zhxing.trace.agent.command.LoaderCommand.run(LoaderCommand.java:40)
at cn.zhxing.trace.agent.Client.listen(Client.java:43)
at cn.zhxing.trace.agent.Main$1.run(Main.java:62)
at java.lang.Thread.run(Thread.java:662)
經過查詢發現該類爲:scala.collection.SeqLike$$anonfun$occCounts$1,這類有個特徵是連續有兩個$符號,正是這個情況導致。網上也有類似的錯誤:http://www.scala-lang.org/node/7691
3、asm.jar使用中有可能出現ClassNotFoundException 的異常(在使用instrument的時候容易出現)
例如下面的方法,調用accept的時候拋出
ClassReader reader = new ClassReader(classfileBuffer);
ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_FRAMES);
reader.accept(vistor, ClassReader.SKIP_FRAMES);//error
異常堆棧部分如下:
TraceTransformer:reader.acceptjava.lang.RuntimeException: java.lang.ClassNotFoundException: exceptions/ServiceException
at org.objectweb.asm.ClassWriter.getCommonSuperClass(Unknown Source)
at org.objectweb.asm.ClassWriter.a(Unknown Source)
at org.objectweb.asm.Frame.a(Unknown Source)
at org.objectweb.asm.Frame.a(Unknown Source)
at org.objectweb.asm.MethodWriter.visitMaxs(Unknown Source)
at org.objectweb.asm.commons.LocalVariablesSorter.visitMaxs(Unknown Source)
at cn.zhxing.trace.agent.instrument.MethodInstrument.visitMaxs(MethodInstrument.java:137)
at org.objectweb.asm.ClassReader.accept(Unknown Source)
at org.objectweb.asm.ClassReader.accept(Unknown Source)
仔細分析了asm的代碼發現,代碼如下:
protected String getCommonSuperClass(final String type1, final String type2)
{
Class<?> c, d;
ClassLoader classLoader = getClass().getClassLoader();
try {
//這裏可以看出classloader是直接在getClass().getClassLoader();中獲取的本地classloader,如果該class不在該classloader中時就會出現異常
c = Class.forName(type1.replace('/', '.'), false, classLoader);
d = Class.forName(type2.replace('/', '.'), false, classLoader);
} catch (Exception e) {
throw new RuntimeException(e.toString());
}
if (c.isAssignableFrom(d)) {
return type1;
}
if (d.isAssignableFrom(c)) {
return type2;
}
if (c.isInterface() || d.isInterface()) {
return "java/lang/Object";
} else {
do {
c = c.getSuperclass();
} while (!c.isAssignableFrom(d));
return c.getName().replace('.', '/');
}
}
修改如下:
新建一個新的Class 繼承ClassWriter,重寫getCommonSuperClass 方法,如下:
public class TraceClassWriter extends ClassWriter {
//省略其他代碼
public TraceClassWriter(ClassReader classReader, int flags, ClassLoader loader) {
super(classReader, flags);
this.loader = loader;
}
protected String getCommonSuperClass(final String type1, final String type2) {
Class c, d;
try {
c = Class.forName(type1.replace('/', '.'), true, loader);
d = Class.forName(type2.replace('/', '.'), true, loader);
} catch (Exception e) {
logger.error(e, "type1=%s,type2=%s,loader=%s", type1, type2, loader);
throw new RuntimeException(e.toString());
}
if (c.isAssignableFrom(d)) {
return type1;
}
if (d.isAssignableFrom(c)) {
return type2;
}
if (c.isInterface() || d.isInterface()) {
return "java/lang/Object";
} else {
do {
c = c.getSuperclass();
} while (!c.isAssignableFrom(d));
return c.getName().replace('.', '/');
}
}
}
使用的時候是這樣:
ClassReader reader = new ClassReader(classfileBuffer);
//由外部傳入classloader
ClassWriter writer = new TraceClassWriter(reader, ClassWriter.COMPUTE_FRAMES,loader);
reader.accept(vistor, ClassReader.SKIP_FRAMES);
類似錯誤也可看:http://www.avaje.org/topic-180.html