精华内容
下载资源
问答
  • asm

    千次阅读 2019-01-05 19:16:25
    ASM系列之一:初探ASM   一、什么是ASM  ASM是一个JAVA字节码分析、创建和修改的开源应用框架。在ASM中提供了诸多的API用于对类的内容进行字节码操作的方法。与传统的BCEL和SERL不同,在ASM中提供了更为优雅和...

    https://blog.csdn.net/coslay/article/details/43370985

    ASM系列之一:初探ASM

     

    一、什么是ASM

        ASM是一个JAVA字节码分析、创建和修改的开源应用框架。在ASM中提供了诸多的API用于对类的内容进行字节码操作的方法。与传统的BCEL和SERL不同,在ASM中提供了更为优雅和灵活的操作字节码的方式。目前ASM已被广泛的开源应用架构所使用,例如:Spring、Hibernate等。

    二、ASM能干什么

        分析一个类、从字节码角度创建一个类、修改一个已经被编译过的类文件

    三、ASM初探例子

        这里我们使用ASM的CoreAPI(ASM提供了两组API:Core和Tree,Core是基于访问者模式来操作类的,而Tree是基于树节点来操作类的)创建一个MyClass类,目标类如下:

    public class MyClass {  
        private String name;  
          
        public MyClass(){  
            this.name = "zhangzhuo";  
        }  
      
        public String getName() {  
            return name;  
        }  
      
        public void setName(String name) {  
            this.name = name;  
        }  
    } 

     

     这个类在构造方法中初始化了属性name,并提供了两个public方法来修改和访问name属性。

     

     接下来就要书写创建这个类的代码了,现将代码给出,然后逐步解释,代码如下:

       代码1:

    public class GenerateClass {
    
    public void generateClass() {
    
    
    //方法的栈长度和本地变量表长度用户自己计算
    
    ClassWriter classWriter = new ClassWriter(0);
    
    
    //Opcodes.V1_6指定类的版本
    
    //Opcodes.ACC_PUBLIC表示这个类是public,
    
    //“org/victorzhzh/core/classes/MyClass”类的全限定名称
    
    //第一个null位置变量定义的是泛型签名,
    
    //“java/lang/Object”这个类的父类
    
    //第二个null位子的变量定义的是这个类实现的接口
    
    classWriter.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC,
    
    "org/victorzhzh/core/classes/MyClass", null,
    
    "java/lang/Object", null);
    
    
    ClassAdapter classAdapter = new MyClassAdapter(classWriter);
    
    
    classAdapter.visitField(Opcodes.ACC_PRIVATE, "name",
    
    Type.getDescriptor(String.class), null, null);//定义name属性
    
    
    classAdapter.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null,
    
    null).visitCode();//定义构造方法
    
    
    String setMethodDesc = "(" + Type.getDescriptor(String.class) + ")V";
    
    classAdapter.visitMethod(Opcodes.ACC_PUBLIC, "setName", setMethodDesc,
    
    null, null).visitCode();//定义setName方法
    
    
    String getMethodDesc = "()" + Type.getDescriptor(String.class);
    
    classAdapter.visitMethod(Opcodes.ACC_PUBLIC, "getName", getMethodDesc,
    
    null, null).visitCode();//定义getName方法
    
    
    byte[] classFile = classWriter.toByteArray();//生成字节码
    
    
    MyClassLoader classLoader = new MyClassLoader();//定义一个类加载器
    
    Class clazz = classLoader.defineClassFromClassFile(
    
    "org.victorzhzh.core.classes.MyClass", classFile);
    
    try {//利用反射方式,访问getName
    
    Object obj = clazz.newInstance();
    
    Method method = clazz.getMethod("getName");
    
    System.out.println(obj.toString());
    
    System.out.println(method.invoke(obj, null));
    
    } catch (Exception e) {
    
    e.printStackTrace();
    
    }
    
    }
    
    
    class MyClassLoader extends ClassLoader {
    
    public Class defineClassFromClassFile(String className, byte[] classFile)
    
    throws ClassFormatError {
    
    return defineClass(className, classFile, 0, classFile.length);
    
    }
    
    }
    
    
    public static void main(String[] args) {
    
    GenerateClass generateClass = new GenerateClass();
    
    generateClass.generateClass();
    
    }
    
    }

       代码2:

    public class MyClassAdapter extends ClassAdapter {
    
    
    public MyClassAdapter(ClassVisitor cv) {
    
    super(cv);
    
    }
    
    
    @Override
    
    public MethodVisitor visitMethod(int access, String name, String desc,
    
    String signature, String[] exceptions) {
    
    MethodVisitor methodVisitor = cv.visitMethod(access, name, desc,
    
    signature, exceptions);
    
    if (name.equals("<init>")) {
    
    return new InitMethodAdapter(methodVisitor);
    
    } else if (name.equals("setName")) {
    
    return new SetMethodAdapter(methodVisitor);
    
    } else if (name.equals("getName")) {
    
    return new GetMethodAdapter(methodVisitor);
    
    } else {
    
    return super.visitMethod(access, name, desc, signature, exceptions);
    
    }
    
    }
    
    
    //这个类生成具体的构造方法字节码
    
    class InitMethodAdapter extends MethodAdapter {
    
    public InitMethodAdapter(MethodVisitor mv) {
    
    super(mv);
    
    }
    
    
    @Override
    
    public void visitCode() {
    
    mv.visitVarInsn(Opcodes.ALOAD, 0);
    
    mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object",
    
    "<init>", "()V");//调用父类的构造方法
    
    mv.visitVarInsn(Opcodes.ALOAD, 0);
    
    mv.visitLdcInsn("zhangzhuo");//将常量池中的字符串常量加载刀栈顶
    
    mv.visitFieldInsn(Opcodes.PUTFIELD,
    
    "org/victorzhzh/core/classes/MyClass", "name",
    
    Type.getDescriptor(String.class));//对name属性赋值
    
    mv.visitInsn(Opcodes.RETURN);//设置返回值
    
    mv.visitMaxs(2, 1);//设置方法的栈和本地变量表的大小
    
    }
    
    };
    
    
    //这个类生成具体的setName方法字节码
    
    class SetMethodAdapter extends MethodAdapter {
    
    public SetMethodAdapter(MethodVisitor mv) {
    
    super(mv);
    
    }
    
    
    @Override
    
    public void visitCode() {
    
    mv.visitVarInsn(Opcodes.ALOAD, 0);
    
    mv.visitVarInsn(Opcodes.ALOAD, 1);
    
    mv.visitFieldInsn(Opcodes.PUTFIELD,
    
    "org/victorzhzh/core/classes/MyClass", "name",
    
    Type.getDescriptor(String.class));
    
    mv.visitInsn(Opcodes.RETURN);
    
    mv.visitMaxs(2, 2);
    
    }
    
    
    }
    
    
    
    //这个类生成具体的getName方法字节
    
    class GetMethodAdapter extends MethodAdapter {
    
    
    public GetMethodAdapter(MethodVisitor mv) {
    
    super(mv);
    
    }
    
    
    @Override
    
    public void visitCode() {
    
    mv.visitVarInsn(Opcodes.ALOAD, 0);
    
    mv.visitFieldInsn(Opcodes.GETFIELD,
    
    "org/victorzhzh/core/classes/MyClass", "name",
    
    Type.getDescriptor(String.class));//获取name属性的值
    
    mv.visitInsn(Opcodes.ARETURN);//返回一个引用,这里是String的引用即name
    
    mv.visitMaxs(1, 1);
    
    }
    
    }
    
    }

       运行结果:

    org.victorzhzh.core.classes.MyClass@1270b73  
    zhangzhuo 


       这个例子只是简单地介绍了一下ASM如何创建一个类,接下来的几个章节,将详细介绍ASM的CoreAPI和TreeAPI中如何操作类。

    ASM系列之二:Java类的基本表述

        上一篇文章中我们看到了如何使用ASM生成一个简单的JAVA类,里面使用到了很多的基本概念,比如:方法描述、引用描述等,下面将一一介绍。

    一、类版本:

        一个Java二进制的类文件,都有一个版本,因此ASM中提供了几个常量来指定一个类的版,这些常量定义在org.objectweb.asm.Opcodes接口中,如下:

     
    1. int V1_1 = 3 << 16 | 45;

    2. int V1_2 = 0 << 16 | 46;

    3. int V1_3 = 0 << 16 | 47;

    4. int V1_4 = 0 << 16 | 48;

    5. int V1_5 = 0 << 16 | 49;

    6. int V1_6 = 0 << 16 | 50;

    7. int V1_7 = 0 << 16 | 51;

     

     二、内部名字:

         在Java二进制文件中使用的是JVM的内部名字,而不是我们所熟悉的以“.”分割的全限定名,内部名字是以“/”替代“.”的全名,例如:java.lang.String在JVM中的内部名字是java/lang/String。在ASM中可以使用org.objectweb.asm.Type类中的静态方法getInternalName(final Class c) 来获得,如下:

    public class InternalNameTransform {
    
    
    public static void main(String[] args) {
    
    System.out.println(Type.getInternalName(String.class));
    
    System.out.println(Type.getInternalName(Integer.class));
    
    System.out.println(Type.getInternalName(InternalNameTransform.class));
    
    }
    
    }

        运行结果:

    java/lang/String  
    java/lang/Integer  
    org/victorzhzh/core/structure/InternalNameTransform 

     

    三、类型描述:

        我们知道JAVA类型分为基本类型和引用类型,在JVM中对每一种类型都有与之相对应的类型描述,如下表:

    Java类型JVM中的描述
    booleanZ
    charC
    byteB
    shortS
    intI
    floatF
    longJ
    doubleD
    ObjectLjava/lang/Object;
    int[I
    Object[[Ljava/lang/Object;

        在ASM中要获得一个类的JVM内部描述,可以使用org.objectweb.asm.Type类中的getDescriptor(final Class c)方法,如下:

    public class TypeDescriptors {
    
    public static void main(String[] args) {
    
    System.out.println(Type.getDescriptor(TypeDescriptors.class));
    
    System.out.println(Type.getDescriptor(String.class));
    
    }
    
    
    }

        运行结果:

    Lorg/victorzhzh/core/structure/TypeDescriptors;  
    Ljava/lang/String;  

     

     四、方法描述:

        在Java的二进制文件中,方法的方法名和方法的描述都是存储在Constant pool中的,且在两个不同的单元里。因此,方法描述中不含有方法名,只含有参数类型和返回类型,如下:

    方法描述,在类中的方法描述,在二进制文件中的
    void a(int i,float f)(IF)V
    void a(Object o)(Ljava/lang/Object;)V
    int a(int i,String s)(ILjava/lang/String;)I
    int[] a(int[] i)([I)[I
    String a()()Ljava/lang/String;

        获取一个方法的描述可以使用org.objectweb.asm.Type.getMethodDescriptor方法,如下:

    public class MethodDescriptors {
    
    public static void main(String[] args) throws Exception {
    
    Method m = String.class.getMethod("substring", int.class);
    
    System.out.println(Type.getMethodDescriptor(m));
    
    }
    
    
    }

        运行结果:

    (I)Ljava/lang/String;  


     其实在org.objectweb.asm.Type类中提供了很多方法让我们去了解一个类,有兴趣的可以看一下它的源码,这对我们了解一个类和操作一个类还是有大帮助的。

    ASM系列之三:ASM中的访问者模式

     

        在ASM的Core API中使用的是访问者模式来实现对类的操作,主要包含如下类:

    一、ClassVisitor接口:

        在这个接口中主要提供了和类结构同名的一些方法,这些方法可以对相应的类结构进行操作。如下:

    1. public interface ClassVisitor {
      
      void visit(int version,int access,String name,String signature,String superName,String[] interfaces);
      
      void visitSource(String source, String debug);
      
      void visitOuterClass(String owner, String name, String desc);
      
      AnnotationVisitor visitAnnotation(String desc, boolean visible);
      
      void visitAttribute(Attribute attr);
      
      void visitInnerClass(String name,String outerName,String innerName,int access);
      
      FieldVisitor visitField(int access,String name,String desc,String signature,Object value);
      
      MethodVisitor visitMethod(int access,String name,String desc,String signature,String[] exceptions);
      
      void visitEnd();
      
      }
      
      

      这里定义的方法调用是有顺序的,在ClassVisitor中定义了调用的顺序和每个方法在可以出现的次数,如下:

    visit [ visitSource ] [ visitOuterClass ] ( visitAnnotation | visitAttribute )* (visitInnerClass | visitField | visitMethod )* visitEnd。

    二、ClassReader类:

        这个类会提供你要转变的类的字节数组,它的accept方法,接受一个具体的ClassVisitor,并调用实现中具体的visit,

    visitSource, visitOuterClass, visitAnnotation, visitAttribute, visitInnerClass,visitField, visitMethod和 visitEnd方法。

    三、ClassWriter类:

        这个类是ClassVisitor的一个实现类,这个类中的toByteArray方法会将最终修改的字节码以byte数组形式返回,在这个类的构造时可以指定让系统自动为我们计算栈和本地变量的大小(COMPUTE_MAXS),也可以指定系统自动为我们计算栈帧的大小(COMPUTE_FRAMES)。

    四、ClassAdapter类:

       这个类也是ClassVisitor的一个实现类,这个类可以看成是一个事件过滤器,在这个类里,它对ClassVisitor的实现都是委派给一个具体的ClassVisitor实现类,即调用那个实现类实现的方法。

    五、AnnotationVisitor接口:

       这个接口中定义了和Annotation结构想对应的方法,这些方法可以操作Annotation中的定义,如下:

    public interface AnnotationVisitor {
    
    void visit(String name, Object value);
    
    void visitEnum(String name, String desc, String value);
    
    AnnotationVisitor visitAnnotation(String name, String desc);
    
    AnnotationVisitor visitArray(String name);
    
    void visitEnd();
    
    }

     调用顺序如下:

    (visit | visitEnum | visitAnnotation | visitArray)* visitEnd

    六、FieldVisitor接口:

       这个接口定义了和属性结构相对应的方法,这些方法可以操作属性,如下:

    public interface FieldVisitor {
    
    AnnotationVisitor visitAnnotation(String desc, boolean visible);
    
    void visitAttribute(Attribute attr);
    
    void visitEnd();
    
    }

     调用顺序:

    ( visitAnnotation | visitAttribute )* visitEnd .

    七、MethodVisitor接口:

        这个接口定义了和方法结构相对应的方法,这些方法可以去操作源方法,具体的可以查看一下源码。

    八、操作流程:

       一般情况下,我们需要操作一个类时,首先是获得其二进制的字节码,即用ClassReader来读取一个类,然后需要一个能将二进制字节码写回的类,即用ClassWriter类,最后就是一个事件过滤器,即ClassAdapter。事件过滤器中的某些方法可以产生一个新的XXXVisitor对象,当我们需要修改对应的内容时只要实现自己的XXXVisitor并返回就可以了。

    九、例子:

        在这个例子中,我们将对Person类的sayName方法做出一些修改,源类:

    public class Person {
    
    private String name;
    
    
    public void sayName() {
    
    System.out.println(name);
    
    }
    
    }

      如果我们定义一个Person类然后调用其sayName()方法将会得到的是一个null,行成的二进制字节码如下:

    public void sayName();
    
    Code:
    
    Stack=2, Locals=1, Args_size=1
    
    0: getstatic #17; //Field java/lang/System.out:Ljava/io/PrintStream;
    
    3: aload_0
    
    4: getfield #23; //Field name:Ljava/lang/String;
    
    7: invokevirtual #25; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
    
    10: return
    
    }
    
    


     我们修改一下这个方法,让它输出"zhangzhuo",代码如下:

    public class GenerateNewPerson {
    
    public static void main(String[] args) throws Exception {
    
    // 使用全限定名,创建一个ClassReader对象
    
    ClassReader classReader = new ClassReader(
    
    "org.victorzhzh.core.ic.Person");
    
    // 构建一个ClassWriter对象,并设置让系统自动计算栈和本地变量大小
    
    ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
    
    
    ClassAdapter classAdapter = new GeneralClassAdapter(classWriter);
    
    
    classReader.accept(classAdapter, ClassReader.SKIP_DEBUG);
    
    
    byte[] classFile = classWriter.toByteArray();
    
    
    // 将这个类输出到原先的类文件目录下,这是原先的类文件已经被修改
    
    File file = new File(
    
    "target/classes/org/victorzhzh/core/ic/Person.class");
    
    FileOutputStream stream = new FileOutputStream(file);
    
    stream.write(classFile);
    
    stream.close();
    
    }
    
    }
    
    
    public class GeneralClassAdapter extends ClassAdapter {
    
    
    public GeneralClassAdapter(ClassVisitor cv) {
    
    super(cv);
    
    }
    
    
    @Override
    
    public MethodVisitor visitMethod(int access, String name, String desc,
    
    String signature, String[] exceptions) {
    
    MethodVisitor mv = cv.visitMethod(access, name, desc, signature,
    
    exceptions);
    
    // 当是sayName方法是做对应的修改
    
    if (name.equals("sayName")) {
    
    MethodVisitor newMv = new SayNameMethodAdapter(mv);
    
    return newMv;
    
    } else {
    
    return mv;
    
    }
    
    }
    
    
    // 定义一个自己的方法访问类
    
    class SayNameMethodAdapter extends MethodAdapter {
    
    public SayNameMethodAdapter(MethodVisitor mv) {
    
    super(mv);
    
    }
    
    
    // 在源方法前去修改方法内容,这部分的修改将加载源方法的字节码之前
    
    @Override
    
    public void visitCode() {
    
    // 记载隐含的this对象,这是每个JAVA方法都有的
    
    mv.visitVarInsn(Opcodes.ALOAD, 0);
    
    // 从常量池中加载“zhangzhuo”字符到栈顶
    
    mv.visitLdcInsn("zhangzhuo");
    
    // 将栈顶的"zhangzhuo"赋值给name属性
    
    mv.visitFieldInsn(Opcodes.PUTFIELD,
    
    Type.getInternalName(Person.class), "name",
    
    Type.getDescriptor(String.class));
    
    }
    
    
    }
    
    
    }

     这时,我们在查看一下Person的字节码:

    public void sayName();
    
    Code:
    
    Stack=2, Locals=1, Args_size=1
    
    0: aload_0
    
    1: ldc #13; //String zhangzhuo
    
    3: putfield #15; //Field name:Ljava/lang/String;
    
    =============以上是我们新增加的内容================================
    
    6: getstatic #21; //Field java/lang/System.out:Ljava/io/PrintStream;
    
    9: aload_0
    
    10: getfield #15; //Field name:Ljava/lang/String;
    
    13: invokevirtual #27; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
    
    16: return
    
    
    }

    再次调用Person对象,输出结果为:zhangzhuo

    ASM系列之四:操作类属性

     

        在上一篇文章中,我们看到了ASM中的Core API中使用的是XXXVisitor操作类中的对应部分。本文将展示如何使用ASM中的Core API对类的属性的操作。

    首先,我们定义一个原类Person,如下:

    public class Person {
    
    public String name = "zhangzhuo";
    
    public String address = "xxxxx" ;
    
    }

     这里,我们将属性定义为public类型,目的是为了我们使用反射去调用这个属性,接下来我们要为这个类添加一个int类型的属性,名字叫age。

        第一个问题,ASM的Core API允许我们在那些方法中来添加属性?

        在ASM的Core API中你要为类添加属性就必须要自己去实现ClassVisitor这个接口,这个接口中的visitInnerClass、visitField、visitMethod和visitEnd方法允许我们进行添加一个类属性操作,其余的方法是不允许的。这里我们依然使用Core API中的ClassAdapter类,我们继承这个类,定义一个去添加属性的类,ClassAdapter实现了ClassVisitor。

        第二个问题,我们要在这些方法中写什么样的代码才能添加一个属性?

        在使用ASM的Core API添加一个属性时只需要调用一句语句就可以,如下:

    1. classVisitor.visitField(Opcodes.ACC_PUBLIC, "age", Type.getDescriptor(int.class),

    2. null, null);

     第一个参数指定的是这个属性的操作权限,第二个参数是属性名,第三个参数是类型描述,第四个参数是泛型类型,第五个参数是初始化的值,这里需要注意一下的是第五个参数,这个参数只有属性为static时才有效,也就是数只有为static时,这个值才真正会赋值给我们添加的属性上,对于非static属性,它将被忽略。

    好了,让我们看一段代码,在visitEnd去添加一个名字为age的属性:

    public class Transform extends ClassAdapter {
    
    
    public Transform(ClassVisitor cv) {
    
    super(cv);
    
    }
    
    
    @Override
    
    public void visitEnd() {
    
    cv.visitField(Opcodes.ACC_PUBLIC, "age", Type.getDescriptor(int.class),
    
    null, null);
    
    }
    
    
    }

     非常简单吧,只要一句话就可以添加一个属性到我们的类中,看一下我们的测试类:

    public class TransformTest {
    
    @Test
    
    public void addAge() throws Exception {
    
    ClassReader classReader = new ClassReader(
    
    "org.victorzhzh.core.field.Person");
    
    ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
    
    ClassAdapter classAdapter = new Transform(classWriter);
    
    
    classReader.accept(classAdapter, ClassReader.SKIP_DEBUG);
    
    
    byte[] classFile = classWriter.toByteArray();
    
    
    GeneratorClassLoader classLoader = new GeneratorClassLoader();
    
    Class clazz = classLoader.defineClassFromClassFile(
    
    "org.victorzhzh.core.field.Person", classFile);
    
    Object obj = clazz.newInstance();
    
    
    System.out.println(clazz.getDeclaredField("name").get(obj));//----(1)
    
    System.out.println(clazz.getDeclaredField("age").get(obj));//----(2)
    
    }
    
    }

     在这里,如果我们的age没有被添加进去那么(2)这个地方将会报错,看一下结果:

    zhangzhuo  
    0 

     

     int类型在没有被初始化时,默认值为0,而第二行输出0,说明我们添加了一个属性age

    接下来,我们在visitField方法中在次添加age属性,如下:​​​​​​​

    public class Transform extends ClassAdapter {
    
    
    public Transform(ClassVisitor cv) {
    
    super(cv);
    
    }
    
    
    @Override
    
    public FieldVisitor visitField(int access, String name, String desc,
    
    String signature, Object value) {
    
    cv.visitField(Opcodes.ACC_PUBLIC, "age", Type.getDescriptor(int.class),
    
    null, null);
    
    return super.visitField(access, name, desc, signature, value);
    
    }
    
    
    }

     这时,我们再次运行测试类,结果如下:​​​​​​​

    java.lang.ClassFormatError: Duplicate field name&signature in class file org/victorzhzh/core/field/Person
    
    at java.lang.ClassLoader.defineClass1(Native Method)
    
    at java.lang.ClassLoader.defineClassCond(ClassLoader.java:632)
    
    at java.lang.ClassLoader.defineClass(ClassLoader.java:616)
    
    at java.lang.ClassLoader.defineClass(ClassLoader.java:466)
    
    at org.victorzhzh.common.GeneratorClassLoader.defineClassFromClassFile(GeneratorClassLoader.java:14)
    
    at org.victorzhzh.core.field.TransformTest.addAge(TransformTest.java:22)
    
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    
    at java.lang.reflect.Method.invoke(Method.java:597)
    
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
    
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
    
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
    
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
    
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:76)
    
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
    
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
    
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
    
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
    
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
    
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
    
    at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
    
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:49)
    
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
    
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
    
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
    
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)

     很奇怪,怎么会属性名重复,我们看一下原类,

    public String name = "zhangzhuo";
    
    public String address = "xxxxx" ;

     没有重复的名字,而我们添加的是age也不重复,为什么会报重复属性名错误呢?

    原因是,在我们的Transform类中的visitField方法,这个方法会在每次属性被访问时调用,而ASM在对这个类操作时会遍历到每个属性,也就是说有一个属性就会调用一次visitField方法,有两个属性就会调用两次visitField方法,所以当我们原类中有两个属性时visitField方法被调用了两次,因此创建了两个同名的age属性。

     

    从这个例子中我们可以将visitInnerClass、visitField、visitMethod和visitEnd这些方法分成两组,一组是visitInnerClass、visitField、visitMethod,这些方法有可能会被多次调用,因此在这些方法中创建属性时要注意会重复创建;另一组是visitEnd,这个方法只有在最后才会被调用且只调用一次,所以在这个方法中添加属性是唯一的,因此一般添加属性选择在这个方法里编码。

        当然这里只给出了如何创建一个属性,其实修改,删除也都一样,根据上述知识大家可以参考ASM的源码即可掌握修改删除等操作。

     

    附GeneratorClassLoader类代码

    public class GeneratorClassLoader extends ClassLoader {
    
    
    @SuppressWarnings("rawtypes")
    
    public Class defineClassFromClassFile(String className, byte[] classFile)
    
    throws ClassFormatError {
    
    return defineClass(className, classFile, 0, classFile.length);
    
    }
    
    }

    ASM系列之五:操作类方法

     

    前面我们了解了如何使用ASM的CoreAPI来操作一个类的属性,现在我们来看一下如何修改一个类方法。

    场景:假设我们有一个Person类,它当中有一个sleep方法,我们希望监控一下这个sleep方法的运行时间:

    一般我们会在代码里这样写:

    public void sleep() {
    
    long timer = System.currentTimeMillis();
    
    
    
    try {
    
    System.out.println("我要睡一会...");
    
    TimeUnit.SECONDS.sleep(2);
    
    } catch (InterruptedException e) {
    
    e.printStackTrace();
    
    }
    
    System.out.println(System.currentTimeMillis()-timer);
    
    
    
    }

     标红的两行代码是我们希望有的,但是一般不会将这样的代码和业务代码耦合在一起,所以借助asm来实现动态的植入这样两行代码,就可以使业务方法很清晰。因此我们需要能够修改方法的API,在ASM中提供了对应的API,即MethodAdapter,使用这个API我们就可以随心所欲的修改方法中的字节码,甚至可以完全重写方法,当然这样是没有必要的。下面我们来看一下如何使用这个API,代码如下:

    public class ModifyMethod extends MethodAdapter {
    
    
    public ModifyMethod(MethodVisitor mv, int access, String name, String desc) {
    
    super(mv);
    
    }
    
    
    @Override
    
    public void visitCode() {
    
    mv.visitFieldInsn(Opcodes.GETSTATIC,
    
    Type.getInternalName(Person.class), "timer", "J");
    
    mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System",
    
    "currentTimeMillis", "()J");
    
    mv.visitInsn(Opcodes.LSUB);
    
    mv.visitFieldInsn(Opcodes.PUTSTATIC,
    
    Type.getInternalName(Person.class), "timer", "J");
    
    }
    
    
    @Override
    
    public void visitInsn(int opcode) {
    
    if (opcode == Opcodes.RETURN) {
    
    mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out",
    
    "Ljava/io/PrintStream;");
    
    mv.visitFieldInsn(Opcodes.GETSTATIC,
    
    Type.getInternalName(Person.class), "timer", "J");
    
    mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System",
    
    "currentTimeMillis", "()J");
    
    mv.visitInsn(Opcodes.LADD);
    
    mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream",
    
    "println", "(J)V");
    
    }
    
    mv.visitInsn(opcode);
    
    }
    
    }

    MethodAdapter类实现了MethodVisitor接口,在MethodVisitor接口中严格地规定了每个visitXXX的访问顺序,如下:

    visitAnnotationDefault?( visitAnnotation | visitParameterAnnotation | visitAttribute )*( visitCode
    ( visitTryCatchBlock | visitLabel | visitFrame | visitXxxInsn |visitLocalVariable | visitLineNumber )*visitMaxs )?visitEnd

    首先,统一一个概念,ASM访问,这里所说的ASM访问不是指ASM代码去调用某个类的具体方法,而是指去分析某个类的某个方法的二进制字节码。

    在这里visitCode方法将会在ASM开始访问某一个方法时调用,因此这个方法一般可以用来在进入分析JVM字节码之前来新增一些字节码,visitXxxInsn是在ASM具体访问到每个指令时被调用,上面代码中我们使用的是visitInsn方法,它是ASM访问到无参数指令时调用的,这里我们判但了当前指令是否为无参数的return来在方法结束前添加一些指令。

    通过重写visitCode和visitInsn两个方法,我们就实现了具体的业务逻辑被调用前和被调用后植入监控运行时间的代码。

     

    ModifyMethod类只是对方法的修改类,那如何让外部类调用它,要通过我们上一篇中使用过的类,ClassAdapter的一个子类,在这里我们定义一个ModifyMethodClassAdapter类,代码如下:

    public class ModifyMethodClassAdapter extends ClassAdapter {
    
    
    public ModifyMethodClassAdapter(ClassVisitor cv) {
    
    super(cv);
    
    }
    
    
    @Override
    
    public MethodVisitor visitMethod(int access, String name, String desc,
    
    String signature, String[] exceptions) {
    
    if (name.equals("sleep")) {
    
    return new ModifyMethod(super.visitMethod(access, name, desc,
    
    signature, exceptions), access, name, desc);
    
    }
    
    return super.visitMethod(access, name, desc, signature, exceptions);
    
    }
    
    
    @Override
    
    public void visitEnd() {
    
    cv.visitField(Opcodes.ACC_PRIVATE + Opcodes.ACC_STATIC, "timer", "J",
    
    null, null);
    
    }
    
    
    }

     上述代码中我们使用visitEnd来添加了一个timer属性,用于记录时间,我们重写了visitMethod方法,当ASM访问的方法是sleep方法时,我们调用已经定义的ModifyMethod方法,让这个方法作为访问者,去访问对应的方法。

    这样两个类就实现了我们要的添加执行时间的业务。

    看一下测试类:

    public class ModifyMethodTest {
    
    @Test
    
    public void modiySleepMethod() throws Exception {
    
    ClassReader classReader = new ClassReader(
    
    "org.victorzhzh.common.Person");
    
    ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
    
    ClassAdapter classAdapter = new ModifyMethodClassAdapter(classWriter);
    
    classReader.accept(classAdapter, ClassReader.SKIP_DEBUG);
    
    
    byte[] classFile = classWriter.toByteArray();
    
    
    GeneratorClassLoader classLoader = new GeneratorClassLoader();
    
    @SuppressWarnings("rawtypes")
    
    Class clazz = classLoader.defineClassFromClassFile(
    
    "org.victorzhzh.common.Person", classFile);
    
    Object obj = clazz.newInstance();
    
    System.out.println(clazz.getDeclaredField("name").get(obj));
    
    clazz.getDeclaredMethod("sleep").invoke(obj);
    
    }
    
    }

     通过反射机制调用我们修改后的Person类,运行结果如下:

    zhangzhuo  
    我要睡一会...  
    2023 


    2023就是我们让sleep方法沉睡的时间,看一下我们的原始Person类:

    public class Person {
    
    public String name = "zhangzhuo";
    
    
    public void sayHello() {
    
    System.out.println("Hello World!");
    
    }
    
    
    public void sleep() {
    
    try {
    
    System.out.println("我要睡一会...");
    
    TimeUnit.SECONDS.sleep(2);//沉睡两秒
    
    } catch (InterruptedException e) {
    
    e.printStackTrace();
    
    }
    
    }
    
    }

    以上几篇文章都是关于ASM的大体介绍,ASM的功能可以说是十分强大,要学好这个东西个人有几点体会:

    第一、要熟悉Java字节码结构,及指令:因为我们在很多时候都是要写最原始的字节吗指令的,虽然ASM也为我们提供相应的简化API替我们来做这些事情,但是最基本的东西还是要了解和掌握的,这样才能使用的更好;

    第二、充分理解访问者模式有助于我们理解ASM的CoreAPI;

    第三、掌握基本的ClassVisitor、ClassAdapter、MethodVisitor、MethodAdapter、FieldVisitor、FieldWriter、ClassReader和ClassWriter这几个类对全面掌握CoreAPI可以有很大的帮助;

    第四、在掌握了CoreAPI后再去研究TreeAPI,这样更快速;

    最后,希望这几篇文章能对研究ASM的朋友有所帮助

    展开全文
  • ASM

    千次阅读 2008-01-07 10:36:00
    ASM可以条带化和镜像磁盘,因此可以在数据库备加载的情况下添加、删除磁盘、以及自动平衡IO。ASM不是一个文件系统,所以无法从操作系统访问ASM存储的文件,对于使用ASM的数据库只能用RMAN进行备份和恢复。ASM作为...

    ASM可以条带化和镜像磁盘,因此可以在数据库备加载的情况下添加、删除磁盘、以及自动平衡IO。
    ASM不是一个文件系统,所以无法从操作系统访问ASM存储的文件,对于使用ASM的数据库只能用RMAN进行备份和恢复。
    ASM作为单独的ORACLE实例实施,只有ASM运行时数据库才能访问。

    一、磁盘组

    ASM提供了三中磁盘组类型
     1 normal redundacy  支持的映像等级有 2-WAY、3-WAY、不保护 ,缺省的2-WAY
      标准冗余度要求提供双向镜像,并且要求在一个磁盘组中,至少有2个故障组(failure groups),故障组中的一个磁盘出现故障不
    不会导致磁盘故停止工作,也不会丢失数据,对查询磁盘组的对象有些性能影响,这个时候就需要管理员调整
    2  high redundancy    支持的映像等级 3-WAY
       高冗余度提供三向映像,并且在一个磁盘组至少有3个故障组。故障组中任何其中两组出现故障,对用户来说就象标准冗余一样
     3 external redundancy 不保护
       表面冗余,紧要求有一个故障组,
    在配置磁盘组时,尽量把同一类型的磁盘放到一个组里,避免不同类型的磁盘在一个组里以免影响性能
    二、文件
      写在ASM磁盘上的文件称之为ASM文件,他们的名字是由ASM自动生成的,你可以指定一个别名对一个ASM文件,你可以创建一个目录

    SQL> select name,type,total_mb,free_mb from v$asm_diskgroup;

    NAME                           TYPE     TOTAL_MB    FREE_MB
    —————————— —— ———- ———-
    DATA                           EXTERN       7248       5489
    比如说我在DATA磁盘组上创建一个BOSON目录
    alter diskgroup data add directory ‘+data/boson’;

    SQL> alter diskgroup data add directory ‘+data/boson’;

    Diskgroup altered.

    看一下我的数据文件
    SQL> select name from v$datafile;

    NAME
    ——————————————————————————–
    +DATA/rac/datafile/system.269.636854893
    +DATA/rac/datafile/undotbs1.268.636854911
    +DATA/rac/datafile/sysaux.261.636854917
    +DATA/rac/datafile/undotbs2.259.636855011
    +DATA/rac/datafile/users.258.636855023
    我门现在利用刚创建的目录给users.258.636855023创建一个别名
    SQL> alter diskgroup data add alias ‘+data/boson/users.dbf’
      2  for ‘+data/rac/datafile/users.258.636855023′;

    Diskgroup altered.
    三、模板
    ASM支持数据库所有的文件,并提供了控制文件、数据文件、在线日志文件等模板,具体的可以查看官方的资料

    四、安装

    确定所需的 ASMLib 版本
    ASMLib 以三个 Linux 程序包组成的程序包集提供:
    oracleasmlib - ASM 库
    oracleasm-support - 用于管理 ASMLib 的实用程序
    oracleasm - ASM 库的内核模块
    每个 Linux 发行套件都有其自己的 ASMLib 程序包集。在每个发行套件中,每个内核版本都有一个相应的 oracleasm 程序包。以下部分介绍如何确定所需的程序包集。
    首先,以 root 用户身份登录并运行以下命令来确定所使用的内核:
    uname -rm

    例如:
    oracle:/home/oracle # uname -rm
    2.6.16.21-0.8-default i686
    使用此信息在 OTN 上查找相应的 ASMLib 程序包:
    将 Web 浏览器指向 http://www.oracle.com/technology/global/cn/tech/linux/asmlib
    1, 选择适用于您的 Linux 版本的链接。
    2, 下载适用于您的 Linux 版本的 oracleasmlib 和 oracleasm-support 程序包
    3, 下载与您的内核相对应的 oracleasm 程序包。
    oracle:/home/oracle # ls
    .Xauthority .fonts .profile .xinitrc.template oracleasm-2.6.16.21-0.8-default-2.0.3-1.i586.rpm
    .bash_history .gnu-emacs .urlview .xtalkrc oracleasm-support-2.0.3-1.i386.rpm
    .bashrc .inputrc .viminfo 10201_database_linux32.zip oracleasmlib-2.0.2-1.i386.rpm
    .dvipsrc .kermrc .xcoralrc Documents public_html
    .emacs .mozilla .xemacs bin
    .exrc .muttrc .xim.template database
    oracle:/home/oracle # rpm -aq oracleasm*
    oracle:/home/oracle # rpm -ivh oracleasm*.rpm
    Preparing… ########################################### [100%]
    1:oracleasm-support ########################################### [ 33%]
    2:oracleasm-2.6.16.21-0.8########################################### [ 67%]
    3:oracleasmlib ########################################### [100%]
    oracle:/home/oracle #
    配置 ASMLib
    使用 ASMLib 之前,必须运行配置脚本以准备驱动程序。以 root 用户身份运行以下命令,并响应如下示例中所显示的提示。

    oracle:/home/oracle # /etc/init.d/oracleasm configure
    Configuring the Oracle ASM library driver.
    This will configure the on-boot properties of the Oracle ASM library
    driver. The following questions will determine whether the driver is
    loaded on boot and what permissions it will have. The current values
    will be shown in brackets (’[]’). Hitting <ENTER> without typing an
    answer will keep that current value. Ctrl-C will abort.
    Default user to own the driver interface []: oracle
    Default group to own the driver interface []: dba
    Start Oracle ASM library driver on boot (y/n) [n]: y
    Fix permissions of Oracle ASM disks on boot (y/n) [y]: y
    Writing Oracle ASM library driver configuration: done
    Creating /dev/oracleasm mount point: done
    Loading module “oracleasm”: done
    Mounting ASMlib driver filesystem: done
    Scanning system for ASM disks: done
    oracle:/home/oracle #

    现在,如下所示启用 ASMLib 驱动程序。
    oracle:/home/oracle # /etc/init.d/oracleasm enable
    Writing Oracle ASM library driver configuration: done
    Scanning system for ASM disks: done
    oracle:/home/oracle #
    为 ASM 配置磁盘
    接下来,告诉 ASM 驱动程序您要使用的磁盘。请注意,这些磁盘是不包含任何内容(甚至不包含分区)的空磁盘。可以将磁盘分区用于 ASM,但建议您不要这样做。
    通过以 root 用户身份运行以下命令来标记由 ASMLib 使用的磁盘:
    /etc/init.d/oracleasm createdisk DISK_NAME device_name
    (提示:DISK_NAME 应由大写字母组成。当前版本有一个错误,即如果使用小写字母,ASM 实例将无法识别磁盘。)
    例如:
    # /etc/init.d/oracleasm createdisk VOL1 /dev/sdb
    Marking disk “/dev/sdb” as an ASM disk [ OK ]

    不过我按照上面说的运行
    oracle:/home/oracle # /etc/init.d/oracleasm createdisk VOL1 /dev/sdb
    Marking disk “/dev/sdb” as an ASM disk: asmtool: Device “/dev/sdb” is not a partition
    failed
    然后我把/dev/sdb重新分区了就可以
    oracle:/home/oracle # /etc/init.d/oracleasm createdisk VOL1 /dev/sdb1
    Marking disk “/dev/sdb1″ as an ASM disk: done
    oracle:/home/oracle # /etc/init.d/oracleasm createdisk VOL2 /dev/sdb2
    Marking disk “/dev/sdb2″ as an ASM disk: done
    oracle:/home/oracle # /etc/init.d/oracleasm createdisk VOL3 /dev/sdb3
    Marking disk “/dev/sdb3″ as an ASM disk: done
    oracle:/home/oracle # /etc/init.d/oracleasm createdisk VOL4 /dev/sdb5
    Marking disk “/dev/sdb5″ as an ASM disk: done
    以下示例演示了如何列出标记为由 ASMLib 使用的所有磁盘。
    oracle:/home/oracle # /etc/init.d/oracleasm listdisks
    VOL1
    VOL2
    VOL3
    VOL4
    oracle:/home/oracle #
    既然已经安装了 ASMLib,且已将磁盘标记为可用,下面,您便可以创建一个 ASM 实例,并构建一个使用 ASM 进行磁盘存储的数据库。最容易的方法就是使用数据库配置助手 (DBCA) 来完成此操作。

    五、ASM的参数文件

    [oracle@rac1 dbs]$ cat init+ASM1.ora

    ##############################################################################
    # Copyright (c) 1991, 2001, 2002 by Oracle Corporation
    ##############################################################################
     
    ###########################################
    # Cluster Database
    ###########################################
    cluster_database=true
     
    ###########################################
    # Diagnostics and Statistics
    ###########################################
    background_dump_dest=/u01/app/oracle/admin/+ASM/bdump
    core_dump_dest=/u01/app/oracle/admin/+ASM/cdump
    user_dump_dest=/u01/app/oracle/admin/+ASM/udump
     
    ###########################################
    # Miscellaneous
    ###########################################
    instance_type=asm 必须为ASM
     
    ###########################################
    # Pools
    ###########################################
    large_pool_size=12M
     
    ###########################################
    # Security and Auditing
    ###########################################
    remote_login_passwordfile=exclusive 启用了远程管理,所以必须有密码文件
     
    asm_diskgroups=’DATA’
     
    +ASM2.instance_number=2
    +ASM1.instance_number=1

    还有一些参数比如说
    asm_power_limit,这个是设置磁盘重新平衡的速度缺省为1 可以取值0-11
    当你往ASM磁盘组添加磁盘时,修改此参数为较大值能更快让磁盘数据更块的平均分布,但是由此也待来了性能影响,
    所以在做此项操作时要估计对数据库性能的影响,选择合适的值。
    可以通过查询 v$asm_operation视图查看
    SQL> desc v$asm_operation;
     Name                                                  Null?    Type
     —————————————————– ——– ————————————
     GROUP_NUMBER                                                   NUMBER
     OPERATION                                                      VARCHAR2(5)
     STATE                                                          VARCHAR2(4)
     POWER                                                          NUMBER
     ACTUAL                                                         NUMBER
     SOFAR                                                          NUMBER
     EST_WORK                                                       NUMBER
     EST_RATE                                                       NUMBER
     EST_MINUTES                                                    NUMBER
    参数文件错误有 ORA-15021

    六、启动ASM实例

    要连接ASM实例,需要设置ORACLE_SID,比说单实例的数据库缺省的为+ASM
    集群环境为+ASMnode#

    SQL> shutdown
    ORA-15097: cannot SHUTDOWN ASM instance with connected RDBMS instance

     oerr ora 15097
    15097, 00000, “cannot SHUTDOWN ASM instance with connected RDBMS instance”
    // *Cause:  A SHUTDOWN command was issued to an ASM instance that had one or
    //          more connected RDBMS instances.
    // *Action: Connect to each RDBMS instance and shut it down, and then reissue
    //          the SHUTDOWN command to the ASM instance.  Alternatively, use the
    //          SHUTDOWN ABORT command.  Note that issuing the SHUTDOWN ABORT
    //          command to an ASM instance results in abormal termination of all
    //          RDBMS instances connected to that ASM instance.
    //
    要关闭ASM实例,首先要关闭每一个数据库实例

    % sqlplus /nolog
    SQL> CONNECT / AS sysdba
    Connected to an idle instance.

    SQL> STARTUP
    ASM instance started

    Total System Global Area   71303168 bytes
    Fixed Size                  1069292 bytes
    Variable Size              45068052 bytes
    ASM Cache                  25165824 bytes
    ASM diskgroups mounted
    CRS 根据配置管理 Oracle 的群集资源,包括资源的启动、停止、监视和故障切换。
    CSS 可管理 Oracle 群集成员并将其自身的组成员服务提供给 RAC 实例
    [oracle@rac2 ~]$ crsctl check cssd
    CSS appears healthy

    八ASM限制

    ASM imposes the following limits:

    63 disk groups in a storage system

    10,000 ASM disks in a storage system

    4 petabyte maximum storage for each ASM disk

    40 exabyte maximum storage for each storage system

    1 million files for each disk group

    Maximum files sizes as shown in the following table:

    Disk Group Type Maximum File Size
    External redundancy 35 TB
    Normal redundancy 5.8 TB
    High redundancy 3.9 TB

    八、磁盘组管理

    1、创建磁盘组

    SQL> STARTUP NOMOUNT
    SQL> CREATE DISKGROUP dgroup1 NORMAL REDUNDANCY
      2  FAILGROUP controller1 DISK
      3 ‘/devices/diska1′,
      4 ‘/devices/diska2′,
      5 ‘/devices/diska3′,
      6 ‘/devices/diska4′
      7 FAILGROUP controller2 DISK
      8 ‘/devices/diskb1′,
      9 ‘/devices/diskb2′,
     10 ‘/devices/diskb3′,
     11 ‘/devices/diskb4′;

    2、添加磁盘

    ALTER DISKGROUP dgroup1 ADD DISK
         ‘/devices/diska5′ NAME diska5,
         ‘/devices/diska6′ NAME diska6;

    没有指定故障组,所以两个磁盘分别添加到自己的故障组

    ASM中,创建一个目录
    alter diskgroup boson add directory ‘+data/backup’;
    在已经创建的目录中在创建目录
    alter diskgroup boson add directory ‘+data/backup/archivelog’;
    重命名一个目录
    alter diskgroup boson rename directory ‘+data/backup’ to ‘+data/bak’;
    删除一个目录
    alter diskgroup boson drop directory ‘+data/bak’force
    ASM文件别名的管理,相关视图v$asm_alias
    ALTER DISKGROUP dgroup1 ADD ALIAS ‘+dgroup1/mydir/second.dbf’
         FOR ‘+dgroup1/sample/datafile/mytable.342.3′;
    重命名一个别名
    ALTER DISKGROUP dgroup1 RENAME ALIAS ‘+dgroup1/mydir/datafile.dbf’
         TO ‘+dgroup1/payroll/compensation.dbf’;

    删除一个别名
    ALTER DISKGROUP dgroup1 DROP ALIAS ‘+dgroup1/payroll/compensation.dbf’;
    删除一个ASM文件
    ALTER DISKGROUP dgroup1 DROP FILE ‘+dgroup1/payroll/compensation.dbf’;
    删除一个磁盘从磁盘组中
    ALTER DISKGROUP dgroup1 DROP DISK diska5;

    ALTER DISKGROUP dgroup1 DROP DISK diska5
         ADD FAILGROUP failgrp1 DISK ‘/devices/diska9′ NAME diska9;

    修改磁盘组中的磁盘大小
    修改一个磁盘组中所有的磁盘:
    ALTER DISKGROUP dgroup1
         RESIZE DISKS IN FAILGROUP failgrp1 SIZE 100G
    手动调整字盘平衡速度
    ALTER DISKGROUP dgroup2 REBALANCE POWER 5 WAIT
    挂载所有磁盘组
    ALTER DISKGROUP ALL DISMOUNT;
    ALTER DISKGROUP dgroup1 DISMOUNT;
    检查所有磁盘组的一致性
    ALTER DISKGROUP dgroup1 CHECK ALL;
    删除磁盘组
    DROP DISKGROUP dgroup1;
    原地址: http://www.oralife.cn/html/2007/180_asm.html

    展开全文
  • ASM4使用指南 ASM GUIDE

    2020-11-17 09:56:49
    ASM4使用指南 ASM GUIDE
  • asm添加硬盘

    2017-04-21 11:10:13
    ASM
  • 汇编工具ASM汇编工具ASM汇编工具ASM汇编工具ASM汇编工具ASM
  • QT-ASM-转换器:GUI ASM到HEX以及HEX到ASM转换工具
  • ASM_人脸识别_Asm_源码

    2021-10-03 04:42:30
    基于 ASM 的人脸识别程序,包括训练部分。
  • ASM386 Assembly Language Reference
  • cglib动态代理模式jar包 cglib-2.2.jar asm-tree.jar asm-commons.jar asm.jar
  • ASM初级教程,gba arm7编程入门
  • oracleAsm oracle ASM

    2011-09-26 11:26:40
    oracle ASM oracle 自动存储
  • 很好用的ASM工具箱
  • x86asm x86 asm 编程复习 要求 视觉 C++ 2013 资源
  • ASM模拟坏块 ASM里实现修改指定的Block
  • java-asm java asm原始解析
  • oracle asm

    2013-09-04 10:55:36
    硬件asm
  • asm storage

    2014-07-25 15:39:17
    asm storage description
  • ASM Highlighter

    2017-11-01 16:39:19
    ASM Highlighter是Visual Studio 2015+的一款插件,能够在VS内显示asm代码的高亮
  • asm:随机asm文件的存储库
  • oracleasm-2.6.18-194.el5-2.0.5-1.el5.x86_64.rpm oracleasmlib-2.0.4-1.el5.x86_64.rpm oracleasm-support-2.1.7-1.el5.x86_64.rpm kmod-oracleasm-2.0.8-15.el6_9.x86_64.rpm oracleasmlib-2.0.4-1.el6.x86_64....
  • ORACLE ASM

    2011-06-21 13:19:43
    ORACLE ASM ORACLE ASM ORACLE ASM ORACLE ASM ORACLE ASM ORACLE ASM ORACLE ASM ORACLE ASM ORACLE ASM
  • struts2.3开发依赖的asm包.不然有以下报错java.lang.NoClassDefFoundError: com/opensymphony/xwork2/util/finder/ClassFinder$InfoBuildingVisitor,导入asm-3.3.jar,asm-commons-3.3,asm-tree-3.3.jar即可
  • cglib和asm

    2018-10-18 19:11:12
    cglib和asm的jar包 ,有asm-5.2,asm-3.3.1, cglib-2.2
  • asmlibrary

    2016-03-01 09:45:53
    最近在研究特征点标定的算法,这是asm部分,国人写的,效果也比较好
  • MATLAB ASM

    2012-09-26 10:40:13
    ASM工具箱,主要是ASM的主程序,对做ASM的很有帮助
  • ASM入门笔记

    2018-12-03 10:09:22
    Oracle ASM 元数据是Oracle ASM用来控制磁盘组并且元数据驻留在磁盘组中的信息
  • 揭秘oracle ASM

    2018-11-08 14:10:25
    揭秘oracle ASM,从10g到最新版本12c,非常详细的资料,深入解析oracle asm
  • ASM IDE源码

    2017-08-27 22:46:45
    ASM IDE源码
  • asm.js中实现噪声算法。 目录 目标与理念 asm-noise致力于仅使用JavaScript就能最快,最灵活地产生多维程序噪声。 使用npm: npm install asm-noise 使用unpkg CDN: < script src =" ...
  • ASM维护手册之-ASM存储迁移(包括各种文件迁移)

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 216,738
精华内容 86,695
关键字:

asm