http://www.iteye.com/topic/1119390
字节码的加载
1、写一段将目录中指定的.class文件加载到JVM的程序,并通过Class对象获取到完整类名等信息;
对于 ClassLoader 的加载机制、过程及双亲委派模型等这里就不详细介绍了,基本上属于老生常谈的东西了。不过不了解朋友的可以看一下该作者文章:
深入JVM(4):关于ClassLoader的一些知识
先定义一个 DirectoryClassLoader继承 ClassLoader,通过加载指定的目录下的 class 文件,以下贴出该类的主要代码,完整的代码请从附件中下载。
- 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));
- }
- }
OK, 接下为写个 Case 测试一把:
- 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());
- }
- }
Class: Apple - ClassLoader: it.denger.jvm.classloader.DirectoryClassLoader@3d434234
Class: Banana - ClassLoader: it.denger.jvm.classloader.DirectoryClassLoader@3d434234
Class: it.denger.Pear - ClassLoader: it.denger.jvm.classloader.DirectoryClassLoader@3d434234
Class: java.lang.String - ClassLoader: null
目录结构:

可以看出,以上目录下的"所有" class 文件均已经加载,并且所对应的 ClassLoader 都为 DirectoryClassLoader。唯独 java.lang.String 类的 ClassLoader 为 null,其原因是由于 ClassLoader 的双亲委派模型,因为 String 属于 jre 核心库,已由 BootStrap ClassLoader 加载,而 BootStrap 的加载过程是由 C 实现,所以这里自然就是空了。
其实这题还有另外一个更加简单的方法,就是通过 URLClassLoader 进行加载,示例代码如下:
- 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"));
2、一段展示代码,里面包含一个全局静态整型变量,问如果用两个ClassLoader加载此对象,执行这个整型变量++操作后结果会是怎么样的?
继续使用上题的 ClassLoader 对象,修改 Apple.java为:
- public class Apple{
- public static int count = 1;
- public static void addCount(){
- count++;
- }
- public static int getCount(){
- return count;
- }
- }
从 JVM加载 Class 的过程来说,对于同一包路径同名的 Class 是不会在同一ClassLoader进行重复加载的,当然,如果如这题所说,使用两个 ClassLoader 实例进行加载同一 Class ,这时候会产生两个 Class实例,原因是由于 ClassLoader 实例之间的相互隔离的。
以下测试用例是使用不同 ClassLoader 实例加载 Apple Class 之后并分别调用 addCount 一次:
- 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()));
- }
~
方法的执行
1、A a=new A();a.execute();和 IA a=new A();a.execute();执行有什么不同;
简单的说,如果想知道方法的执行上有什么不同,那么先看一下它们的bytecode(字节码)有什么不同吧
Java 代码如下:
- 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....");
- }
- }
- 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
- }
然而以上两个指令在到底在执行时有何不同,这得从 VM 的 Spec 说起了,你可以从以下几个链接中了解个大概:
Java字节码invokeinterface.... invokevirtual的含义和区别
invokeinterface , invokevirtual
What is the point of invokeinterface?
最后当然少不了 VM Spec 了, Sun VM Spec
2、反射的性能低的原因是?
关于这题,在 Sun 官方的 The Reflection API 明确说明了:
引用
Because reflection involves types that are dynamically resolved, certain Java virtual machine optimizations can not be performed. Consequently, reflective operations have slower performance than their non-reflective counterparts, and should be avoided in sections of code which are called frequently in performance-sensitive applications.
大致意思是说因为反射涉及到动态解析类型,以致于某些 jvm 不能够对其进行执行时的优化, 因此使用反射的性能低于非反射的性能..blabla...
上面所说的动态解析,意味着对于对象创建的过程、方法调用的过程是动态,通过动态加载字节码在 JVM 中进行执行,并且当使用 Class.forName 或 getMethod 时会执行权限校验检查以及lookup的过程,以至于这些调用操作必定将存在时间成本。
另外说的JVM不能进行优化,大致是说的是在对 Class 进行编译时候的优化(如在语义分析过程中会自动拆箱装箱处理),因为编译过程中是无法知道反射代码的真正所需要做的事情, 另外也可能无法发挥 JIT 的最大优化潜力。
值一提的是该问题是作者在09年发出,到目前为止,JDK反射提升越来越好了,从1.6版本后基本上与非反射方法调用相差无几了。当然,最好能在反射的代码进行缓存 Class 或 Method 对象,避免重复调用 getMethod 和 Class.forName,从而减少访问检查及lookup 的过程。
3、编写一段程序,动态的创建一个接口的实现,并加载到JVM中执行;(可以允许用BCEL等工具)
既然这里提到可以使用 BCEL 来实现接口的动态实现,那么就直接使用 BCEL 吧,顺便再复习一下 Builder 模式来进行代码的实现,将 Class 的创建与其具体的组成过程进行解耦。
首先定义一个 User 接口:
- package it.denger.jvm.code.bcel;
- public interface IUser {
- void jump();
- void run();
- }
- 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();
- }
- 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;
- }
- 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());
- }
- }
- 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());
- }
- }
- }
运行之后显示绿条,并在控制台输出了:
I'm jump....
I'm run....
OK, 至此已经完成本题的所有要求。虽然这里只是简单的实现两个无参、无返回值并且只是 println 输出,但是 BCEL 能做的远远不止这些,理论上来说只要你能手写出的代码基本上都能通过其 API 来动态生成字节码。P.S: 上面代码没写什么注释不过其代码看上去应该比较好懂,有什么可以提出.
~
如果对于以上我个人的分析和理解与你有什么偏差的话,希望能提出一起讨论,另外关于后面两大部分题,现在还在 writing 中,将在近期发出。