zoukankan      html  css  js  c++  java
  • 深入了解Java ClassLoader、Bytecode 、ASM、cglib (I)

    http://hi.baidu.com/maoshenmusic/blog/item/5e65dc2419baa6044c088d1a.html

    一、Java ClassLoader

    1,什么是ClassLoader 
    与 C 或 C++ 编写的程序不同,Java 程序并不是一个可执行文件,而是由许多独立的类文件组成,每一个文件对应于一个 Java 类。 
    此外,这些类文件并非立即全部都装入内存,而是根据程序需要装入内存。ClassLoader 是 JVM 中将类装入内存的那部分。 
    而且,Java ClassLoader 就是用 Java 语言编写的。这意味着创建您自己的 ClassLoader 非常容易,不必了解 JVM 的微小细节。

    2,一些重要的方法 
    A)loadClass 
    ClassLoader.loadClass() 是ClassLoader的入口点。该方法的定义为:Class loadClass( String name, boolean resolve ); 
    name:JVM 需要的类的名称,如 Foo 或 java.lang.Object。 
    resolve:参数告诉方法是否需要解析类。

    B)defineClass 
    defineClass方法是ClassLoader的主要诀窍。该方法接受由原始字节组成的数组并把它转换成Class对象。

    C)findSystemClass 
    findSystemClass方法从本地文件系统中寻找类文件,如果存在,就使用defineClass将原始字节转换成Class对象,以将该文件转换成类。

    D)resolveClass 
    可以不完全地(不带解析)装入类,也可以完全地(带解析)装入类。当编写我们自己的loadClass时可以调用resolveClass,这取决于loadClass的resolve参数的值。

    E)findLoadedClass 
    findLoadedClass充当一个缓存:当请求loadClass装入类时,它调用该方法来查看ClassLoader是否已装入这个类,这样可以避免重新装入已存在类所造成的麻烦。

    3,Java2中ClassLoader的变动 
    1)loadClass的缺省实现 
    在Java2中loadClass的实现嵌入了大多数查找类的一般方法,并使您通过覆盖findClass方法来定制它,在适当的时候findClass会调用loadClass。 
    这种方式的好处是可能不一定要覆盖loadClass,只要覆盖findClass就行了,这减少了工作量。

    2)新方法:findClass 
    loadClass的缺省实现调用这个新方法。

    3)新方法:getSystemClassLoader 
    如果覆盖findClass或loadClass,getSystemClassLoader让我们以实际ClassLoader对象来访问系统ClassLoader,而不是固定的从findSystemClass 调用它。

    4)新方法:getParent 
    为了将类请求委托给父ClassLoader,这个新方法允许ClassLoader获取它的父ClassLoader。

    4,定制ClassLoader 
    其实我们或多或少都使用过定制的ClassLoader,因为Applet查看器中就包含一个定制的ClassLoader。 
    它不在本地文件系统中寻找类,而是访问远程服务器上的 Web 站点,经过 HTTP 装入原始的字节码文件,并把它们转换成JVM 内的类。 
    Applet查看器中的ClassLoader还可以做其它事情:它们支持安全性以及使不同的Applet在不同的页面上运行而互不干扰。 
    我们将写一个自己的ClassLoader实现示例,它将实现如下步骤,这也是ClassLoader的工作原理: 
    # 调用 findLoadedClass 来查看是否存在已装入的类。 
    # 如果没有,那么采用那种特殊的神奇方式来获取原始字节。 
    # 如果已有原始字节,调用defineClass将它们转换成Class对象。 
    # 如果没有原始字节,然后调用findSystemClass查看是否从本地文件系统获取类。 
    # 如果resolve参数是true,那么调用resolveClass解析Class对象。 
    # 如果还没有类,返回ClassNotFoundException。 
    # 否则,将类返回给调用程序。 
    话不多说,看看代码先: 
    FileClassLoader.java:

    代码
     
    1. import java.io.ByteArrayOutputStream;   
    2. import java.io.File;   
    3. import java.io.FileInputStream;   
    4. import java.io.IOException;   
    5.   
    6. public class FileClassLoader extends ClassLoader {   
    7.   public Class findClass(String name) {   
    8.     byte[] data = loadClassData(name);   
    9.     return defineClass(name, data, 0, data.length);   
    10.    }   
    11.      
    12.   private byte[] loadClassData(String name) {   
    13.      FileInputStream fis = null;   
    14.     byte[] data = null;   
    15.     try {   
    16.        fis = new FileInputStream(new File("D:\\project\\test\\" + name + ".class"));   
    17.        ByteArrayOutputStream baos = new ByteArrayOutputStream();   
    18.       int ch = 0;   
    19.       while ((ch = fis.read()) != -1) {   
    20.          baos.write(ch);   
    21.        }   
    22.        data = baos.toByteArray();   
    23.      } catch (IOException e) {   
    24.        e.printStackTrace();   
    25.      }   
    26.     return data;   
    27.    }   
    28. }   

    MyApp.java: 
    代码
     
    1. public class MyApp {   
    2.   public static void main(String[] args) throws Exception {   
    3.      FileClassLoader loader = new FileClassLoader();   
    4.      Class objClass = loader.findClass("MyApp");   
    5.      Object obj = objClass.newInstance();   
    6.      System.out.println(objClass.getName());   
    7.      System.out.println(objClass.getClassLoader());   
    8.      System.out.println(obj);   
    9.    }   
    10. }   

    编译并运行MyApp类,结果为: 
    代码
     
    1. MyApp   
    2. FileClassLoader@757aef  
    3. MyApp@9cab16  

    二、Bytecode

    1,什么是Bytecode 
    C/C++编译器把源代码编译成汇编代码,Java编译器把Java源代码编译成字节码bytecode。 
    Java跨平台其实就是基于相同的bytecode规范做不同平台的虚拟机,我们的Java程序编译成bytecode后就可以在不同平台跑了。 
    .net框架有IL(intermediate language),汇编是C/C++程序的中间表达方式,而bytecode可以说是Java平台的中间语言。
    了解Java字节码知识对debugging、performance tuning以及做一些高级语言扩展或框架很有帮助。

    2,使用javap生成Bytecode 
    JDK自带的javap.exe文件可以反汇编Bytecode,让我们看个例子: 
    Test.java:

    代码
     
    1. public class Test {   
    2.   public static void main(String[] args) {   
    3.     int i = 10000;   
    4.      System.out.println("Hello Bytecode! Number = " + i);   
    5.    }   
    6. }   

    编译后的Test.class: 
    代码
     
    1. 漱壕   1 +   
    2.       
    3.   
    4.   
    5.   
    6.   
    7. <init> ()V Code LineNumberTable main ([Ljava/lang/String;)V   
    8. SourceFile     Test.java   
    9.    ! " java/lang/StringBuilder Hello Bytecode! Number = # $ # % & ' ( ) * Test java/lang/Object java/lang/System out Ljava/io/PrintStream; append -(Ljava/lang/String;)Ljava/lang/StringBuilder; (I)Ljava/lang/StringBuilder; toString ()Ljava/lang/String; java/io/PrintStream println (Ljava/lang/String;)V !   
    10.         
    11.         *                      >     ' < Y                      

    使用javap -c Test > Test.bytecode生成的Test.bytecode: 
    代码
     
    1. Compiled from "Test.java"  
    2. public class Test extends java.lang.Object{   
    3. public Test();   
    4.    Code:   
    5.    0:   aload_0   
    6.    1:   invokespecial   #1//Method java/lang/Object."<init>":()V   
    7.    4:  return  
    8.   
    9. public static void main(java.lang.String[]);   
    10.    Code:   
    11.    0:   sipush  10000  
    12.    3:   istore_1   
    13.    4:   getstatic   #2//Field java/lang/System.out:Ljava/io/PrintStream;   
    14.    7:  new   #3//class java/lang/StringBuilder   
    15.    10:   dup   
    16.    11:   invokespecial   #4//Method java/lang/StringBuilder."<init>":()V   
    17.    14:   ldc   #5//String Hello Bytecode! Number =   
    18.    16:   invokevirtual   #6//Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;   
    19.    19:   iload_1   
    20.    20:   invokevirtual   #7//Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;   
    21.    23:   invokevirtual   #8//Method java/lang/StringBuilder.toString:()Ljava/lang/String;   
    22.    26:   invokevirtual   #9//Method java/io/PrintStream.println:(Ljava/lang/String;)V   
    23.    29:  return  
    24.   
    25. }   


    JVM就是一个基于stack的机器,每个thread拥有一个存储着一些frames的JVM stack,每次调用一个方法时生成一个frame。 
    一个frame包括一个local variables数组(本地变量表),一个Operand LIFO stack和运行时常量池的一个引用。

    简单分析一下生成的字节码指令: 
    aload和iload指令的“a”前缀和“i”分别表示对象引用和int类型,其他还有“b”表示byte,“c”表示char,“d”表示double等等 
    我们这里的aload_0表示将把local variable table中index 0的值push到Operand stack,iload_1类似 
    invokespecial表示初始化对象,return表示返回 
    sipush表示把10000这个int值push到Operand stack 
    getstatic表示取静态域 
    invokevirtual表示调用一些实例方法 
    这些指令又称为opcode,Java一直以来只有约202個Opcode,具体请参考Java Bytecode规范。

    我们看到Test.class文件不全是二进制的指令,有些是我们可以识别的字符,这是因为有些包名、类名和常量字符串没有编译成二进制Bytecode指令。

    3,体验字节码增强的魔力 
    我们J2EE常用的Hibernate、Spring都用到了动态字节码修改来改变类的行为。 
    让我们通过看看ASM的org.objectweb.asm.MethodWriter类的部分方法来理解ASM是如何修改字节码的:

    代码
     
    1. class MethodWriter implements MethodVisitor {   
    2.   
    3.     private ByteVector code = new ByteVector();   
    4.   
    5.     public void visitIntInsn(final int opcode, final int operand) {   
    6.         // Label currentBlock = this.currentBlock;   
    7.         if (currentBlock != null) {   
    8.             if (compute == FRAMES) {   
    9.                  currentBlock.frame.execute(opcode, operand, nullnull);   
    10.              } else if (opcode != Opcodes.NEWARRAY) {   
    11.                 // updates current and max stack sizes only for NEWARRAY   
    12.                 // (stack size variation = 0 for BIPUSH or SIPUSH)   
    13.                 int size = stackSize + 1;   
    14.                 if (size > maxStackSize) {   
    15.                      maxStackSize = size;   
    16.                  }   
    17.                  stackSize = size;   
    18.              }   
    19.          }   
    20.         // adds the instruction to the bytecode of the method   
    21.         if (opcode == Opcodes.SIPUSH) {   
    22.              code.put12(opcode, operand);   
    23.          } else { // BIPUSH or NEWARRAY   
    24.              code.put11(opcode, operand);   
    25.          }   
    26.      }   
    27.   
    28.     public void visitMethodInsn(   
    29.         final int opcode,   
    30.         final String owner,   
    31.         final String name,   
    32.         final String desc)   
    33.      {   
    34.         boolean itf = opcode == Opcodes.INVOKEINTERFACE;   
    35.          Item i = cw.newMethodItem(owner, name, desc, itf);   
    36.         int argSize = i.intVal;   
    37.         // Label currentBlock = this.currentBlock;   
    38.         if (currentBlock != null) {   
    39.             if (compute == FRAMES) {   
    40.                  currentBlock.frame.execute(opcode, 0, cw, i);   
    41.              } else {   
    42.                 /*
    43.                   * computes the stack size variation. In order not to recompute
    44.                   * several times this variation for the same Item, we use the
    45.                   * intVal field of this item to store this variation, once it
    46.                   * has been computed. More precisely this intVal field stores
    47.                   * the sizes of the arguments and of the return value
    48.                   * corresponding to desc.
    49.                   */  
    50.                 if (argSize == 0) {   
    51.                     // the above sizes have not been computed yet,   
    52.                     // so we compute them...   
    53.                      argSize = getArgumentsAndReturnSizes(desc);   
    54.                     // ... and we save them in order   
    55.                     // not to recompute them in the future   
    56.                      i.intVal = argSize;   
    57.                  }   
    58.                 int size;   
    59.                 if (opcode == Opcodes.INVOKESTATIC) {   
    60.                      size = stackSize - (argSize >> 2) + (argSize & 0x03) + 1;   
    61.                  } else {   
    62.                      size = stackSize - (argSize >> 2) + (argSize & 0x03);   
    63.                  }   
    64.                 // updates current and max stack sizes   
    65.                 if (size > maxStackSize) {   
    66.                      maxStackSize = size;   
    67.                  }   
    68.                  stackSize = size;   
    69.              }   
    70.          }   
    71.         // adds the instruction to the bytecode of the method   
    72.         if (itf) {   
    73.             if (argSize == 0) {   
    74.                  argSize = getArgumentsAndReturnSizes(desc);   
    75.                  i.intVal = argSize;   
    76.              }   
    77.              code.put12(Opcodes.INVOKEINTERFACE, i.index).put11(argSize >> 20);   
    78.          } else {   
    79.              code.put12(opcode, i.index);   
    80.          }   
    81.      }   
    82. }   

    通过注释我们可以大概理解visitIntInsn和visitMethodInsn方法的意思。 
    比如visitIntInsn先计算stack的size,然后根据opcode来判断是SIPUSH指令还是BIPUSH or NEWARRAY指令,并相应的调用字节码修改相关的方法。
  • 相关阅读:
    Autofac ASP.NET Web API (Beta) Integration
    An Autofac Lifetime Primer
    Web api help page error CS0012: Type "System.Collections.Generic.Dictionary'2错误
    c++ 全局变量初始化的一点总结
    C++中extern关键字用法小结
    为什么多线程读写 shared_ptr 要加锁?
    CentOS7 安装Chrome
    在CentOS 7中使用VS Code编译调试C++项目
    am335x hid-multitouch.c
    implicit declaration of function 'copy_from_user'
  • 原文地址:https://www.cnblogs.com/balaamwe/p/2391995.html
Copyright © 2011-2022 走看看