JVM知识点题目解答
- - ITeye论坛最新讨论因最近一直在学习 JVM,看到 BlueDavy 的一篇文章 JVM知识点题目,于是便激起了我去解答的兴趣. 1、写一段将目录中指定的.class文件加载到JVM的程序,并通过Class对象获取到完整类名等信息;. 对于 ClassLoader 的加载机制、过程及双亲委派模型等这里就不详细介绍了,基本上属于老生常谈的东西了.
package it.denger.jvm.classloader;
package it.denger.jvm.classloader;
public class DirectoryClassLoader extends ClassLoader {
protected File directory;
protected ClassLoader parent;
public DirectoryClassLoader(File directory, ClassLoader parent) {
// 将父 ClassLoader 传入super,用于classloader加载时向上查找
super(parent);
.....
}
public Class<?>[] findAndLoadAllClassInDirectory(){
// 获取当前目录中所有 class 文件的相对路径
String[] classPaths = getAllClassRelativePathInDirectory(directory);
List<Class<?>> classes = new ArrayList<Class<?>>(classPaths.length);
for (String classPath : classPaths){
try {
// 将所有 class 文件相对路径转换为相应的包名
// 如 it/denger/Pear.class 则转换为 it.denger.Pear
String className = convertPathToClassName(classPath);
// 调用父类的 loadClass,该方法实现通过向上 一级级的ClassLoader
// 进行查找,当所有上级全部无法找到时则会调用本ClassLoader的
// findClass 方法进行查找,也就是所谓的 “双亲委派模型”
Class<?> classz = loadClass(className);
if (classes != null){
classes.add(classz);
}
} catch (Exception e) {
e.printStackTrace();
}
}
return classes.toArray(new Class<?>[0]);
}
// 重写父类 findClass,当所有 Parent Class Loader 无法找到类时,
// 则会通过调用这里所该方法进行最后的查找
protected java.lang.Class<?> findClass(String name) throws ClassNotFoundException {
Class<?> classz = null;
// 通过 className 获取到对应的 File 对象
File classFile = new File(directory, convertClassNameToPath(name));
if (classFile.exists()) {
try {
// 重点在这里,从 class 的文件流中加载 Class 对象
classz = loadClassFromStream(name, new FileInputStream(classFile), (int)classFile.length());
} catch (Exception e) {
throw new ClassNotFoundException(String.format("Find class %s error!", name), e);
}
}
.....
return classz;
}
protected byte[] loadBytesFromStream(InputStream stream, int len) throws IOException{
byte[] streamBytes = new byte[len];
int read, off = 0;
while(len > 0 && (read = stream.read(streamBytes, off, len)) != -1 ){
off += read;
len -= read;
}
return streamBytes;
}
// 通过调用父类的 defineClass 将 字节 转为 Class 对象
protected Class<?> loadClassFromBytes(String name, byte []classBytes){
return defineClass(name, classBytes, 0, classBytes.length);
}
protected Class<?> loadClassFromStream(String name, InputStream stream, int len){
return loadClassFromBytes(name, loadBytesFromStream(stream, len));
}
}
public class DirectoryClassLoaderTest extends TestCase{
protected DirectoryClassLoader directoryClassLoader;
protected String classDirectory;
protected void setUp() throws Exception {
classDirectory = "/Users/denger/Workspaces/Java/backup/classes";
directoryClassLoader = new DirectoryClassLoader(new File(classDirectory), this.getClass().getClassLoader());
}
public void testShouldBeLoadedAllClassFileInDirectory(){
Class<?>[] classes = directoryClassLoader.findAndLoadAllClassInDirectory();
assertTrue(classes.length > 0);
for(Class<?> classz : classes){
assertNotNull(classz);
System.out.println("Class: " + classz.getName() + " - ClassLoader: " + classz.getClassLoader());
}
} 输入结果:
String classFileDirectory = "file:///Users/denger/Workspaces/Java/backup/classes/";
URLClassLoader classLoader = new URLClassLoader(new URL[]{new URL(classFileDirectory)});
System.out.println(classLoader.loadClass("it.denger.Pear")); 不过我想以上这种通过URLClassLoader的实现这并不是作者所要考察的。
public class Apple{
public static int count = 1;
public static void addCount(){
count++;
}
public static int getCount(){
return count;
}
} 实际上,当两个ClassLoader加载此Class,并分别通过实例调用 addCount 时,其 count 是2还是3取决于分别加载的这两个 Apple Class 是否相等。
public void testRepeatedLoadClass() {
DirectoryClassLoader steveDirectoryClassLoader = new DirectoryClassLoader(
new File(classDirectory), this.getClass().getClassLoader());
DirectoryClassLoader myDirectoryClassLoader = new DirectoryClassLoader(
new File(classDirectory), this.getClass().getClassLoader());
Class<?> steveApple = steveDirectoryClassLoader.loadClass("Apple");
Class<?> myApple = myDirectoryClassLoader.loadClass("Apple");
// 产生不同的 Apple Class 实例,原因是以上两个 ClassLoader 实例是相互隔离的,他们都并知道对方加载了哪些 Class
assertTrue(steveApple != myApple);
// 分别调用 addCount
steveApple.getMethod("addCount").invoke(null);
myApple.getMethod("addCount").invoke(null);
// 其 count 都为2,都只是 ++ 了自己的 count
assertTrue(Integer.parseInt(steveApple.getMethod("getCount").invoke(null).toString()) == 2);
assertTrue(Integer.parseInt(steveApple.getMethod("getCount").invoke(null).toString()) == Integer.parseInt(myApple.getMethod("getCount").invoke(null).toString()));
}
package it.denger.jvm.code;
public class MethodExecute {
public static void main(String[] args) {
A a = new A();
a.execute();
IA ia = new A();
ia.execute();
}
}
interface IA{
public void execute();
}
class A implements IA{
public void execute() {
System.out.println("A execute call....");
}
} 通过 javap -c it.denger.jvm.code.MethodExecute 命令查看代码的字节码信息,其中 main 方法的字节码如下:
public static void main(java.lang.String[]); Code: 0: new #2; //class it/denger/jvm/code/A 3: dup 4: invokespecial #3; //Method it/denger/jvm/code/A."<init>":()V 7: astore_1 8: aload_1 9: invokevirtual #4; //Method it/denger/jvm/code/A.execute:()V 12: new #2; //class it/denger/jvm/code/A 15: dup 16: invokespecial #3; //Method it/denger/jvm/code/A."<init>":()V 19: astore_2 20: aload_2 21: invokeinterface #5, 1; //InterfaceMethod it/denger/jvm/code/IA.execute:()V 26: return }对 exeucte 方法的调用主要在第 9 行和第 21行,不难看出,他们的调用所使用的指令不一样,分别为 invokevirtual(调用对象方法) 和 invokeinterface(调用接口中方法)。
package it.denger.jvm.code.bcel;
public interface IUser {
void jump();
void run();
} 然后针对该接口动态类生成定义一个 Builder 接口:
package it.denger.jvm.code.bcel;
import org.apache.bcel.generic.ClassGen;
public interface UserClassGenBuilder {
static final String USER_CLASS_NAME = "it.denger.jvm.code.bcel.IUser";
UserClassGenBuilder init(String className);
UserClassGenBuilder implementJumpMethod();
UserClassGenBuilder implementRunMethod();
ClassGen build();
}
对于 User 接口来说,具体实现可能有 Student、Staff等,以下以 Student 为例,定义 StudentClassGenBuilder,动态生成 Student User 及实现 jump 和 run 方法:
public class StudentClassGenBuilder implements UserClassGenBuilder{
protected String className;
protected ClassGen classGen;
public StudentClassGenBuilder init(String className) {
this.className = className;
classGen = new ClassGen(className, "java.lang.Object",
"<generated>", ACC_PUBLIC | ACC_SUPER,
new String[] { USER_CLASS_NAME });
classGen.addEmptyConstructor(ACC_PUBLIC);
return this;
}
public UserClassGenBuilder implementJumpMethod() {
InstructionFactory instructionFactory = new InstructionFactory(classGen);
InstructionList instructionList = new InstructionList();
ConstantPoolGen constantPool = classGen.getConstantPool();
MethodGen methodGen = new MethodGen(ACC_PUBLIC,
Type.VOID, new Type[0], new String[0],
"jump", className, instructionList, constantPool);
instructionList.append(instructionFactory.createPrintln("I'm jump...."));
instructionList.append(InstructionFactory.createReturn(Type.VOID));
methodGen.setMaxStack();
classGen.addMethod(methodGen.getMethod());
return this;
}
public UserClassGenBuilder implementRunMethod() {
InstructionFactory instructionFactory = new InstructionFactory(classGen);
InstructionList instructionList = new InstructionList();
ConstantPoolGen constantPool = classGen.getConstantPool();
MethodGen methodGen = new MethodGen(ACC_PUBLIC,
Type.VOID, new Type[0], new String[0],
"run", className, instructionList, constantPool);
instructionList.append(instructionFactory.createPrintln("I'm run...."));
instructionList.append(InstructionFactory.createReturn(Type.VOID));
methodGen.setMaxStack();
classGen.addMethod(methodGen.getMethod());
return this;
}
public ClassGen build() {
return classGen;
} 可以看出以上已经完成了两个方法的实现,并返回 ClassGen 对象(用于生成具体 Class 的对象)。但该题最终是需要将动态的实现的 Class 加载至 JVM中,并调用动态实现的方法,于是以下的 UserClassGenLoader 便产生了:
public class UserClassGenLoader {
protected UserClassGenBuilder builder;
public UserClassGenLoader(UserClassGenBuilder builder){
this.builder = builder;
}
public Class<?> loadClass(String className) throws ClassNotFoundException, IOException{
ClassGen classGen = this.builder.init(className)
.implementJumpMethod().implementRunMethod().build();
return loadClassForClassGen(classGen);
}
protected Class<?> loadClassForClassGen(ClassGen classGen) throws ClassNotFoundException, IOException{
ByteArrayOutputStream arrayOutputStream = new ByteArrayOutputStream();
classGen.getJavaClass().dump(arrayOutputStream);
byte[] classBytes = arrayOutputStream.toByteArray();
Map<String, byte[]> classByteMap = new HashMap<String, byte[]>();
classByteMap.put(classGen.getClassName(), classBytes);
ByteClassLoader byteClassLoader = new ByteClassLoader(this.getClass()
.getClassLoader(), classByteMap);
return byteClassLoader.loadClass(classGen.getClassName());
}
} 至此已经完成从动态接口实现至将其加载至 JVM 中,并获取最终的 Class 对象,最后写一个 TestCase 测试一把:
public void testShouldBeLoadStudentNewInstance() {
try {
Class<?> studentClass = new UserClassGenLoader(new StudentClassGenBuilder())
.loadClass("it.denger.jvm.code.bcel.Student");
IUser studentUser = (IUser) studentClass.newInstance();
assertNotNull(studentUser);
assertEquals(studentUser.getClass().getName(), "it.denger.jvm.code.bcel.Student");
studentUser.jump();
studentUser.run();
} catch (Exception e) {
fail(e.getMessage());
}
}
}