052、JVM、反射、内省

Par @Martin dans le
Tags :

JVM 结构

更多关于 JVM 内存结构参考:
1. Java 的内存结构 (Memory Structure) 和垃圾收集 (Garbage Collection) 图解
2. 我对 Java 内存的认识
3. JVM 内存区域划分 Eden Space、Survivor Space、Tenured Gen,Perm Gen 解释

  • Class File
    • *.class 文件
  • Class Loader 类加载器
  • Runtime Data Area 运行时数据区
    • Method Area 方法区 (线程共享)
      • *.class 文件被加载时会在方法区创建该类的描述符
        • 程序将根据方法区的类描述符创建类的实例
      • 类描述符只被创建一次, 当使用完后并不会从方法区移除
    • Heap 堆区 (线程共享)
      • 堆内存用来存放由 new 出来的对象, 栈中的变量指向堆内存中的对象地址
        • 堆中保存着对象的属性、方法的引用 (方法的主体还是在方法区)
      • 在堆中分配的内存, 由 JVM 的自动垃圾回收器来管理
    • Java Stack 栈区
      • 基本类型的变量和引用变量都在栈内存中分配, 当超过变量的作用域后, Java 会自动释放掉为该变量所分配的内存空间
    • Native Method Statck 本地方法栈 (=> 操作系统)
    • Program Counter Register 程序计数器
  • Execution Engine 执行引擎
  • Native Interface 本地接口
  • Native Libraries 本地类库

补充静态代码块:
使用 Class.forName(" ") 加载类时, 如果指定第二个参数为 false, 则不执行静态代码块, 等下次加载时会再执行静态代码块 (只执行静态代码块, 并不会重新加载类, 类只会被加载一次)

反射

反射: 动态访问对象的属性和方法 (通过类描述符得到指定的方法描述符, 然后通过 method.invoke(object, param) 调用方法);

  • java.lang.Class: 类类, 描述类的类, 类描述符
  • java.lang.reflect.Method: 方法类, 描述方法的类, 方法描述符
  • java.lang.reflect.Field: 字段类 (属性类), 描述字段的类, 字段描述符
  • java.lang.reflect.constructors: 构造器类, 描述构造方法的类, 构造方法描述符
  • java.lang.reflect.modifier: 修饰符类, 描述修饰符的类, 修饰符描述符

class.getDeclaredMethods 获取类中声明的方法 (不管是 Public 还是 Private, 但不包含继承的父类方法, 因为没有声明), 它的第二个参数是可变参数, 是要获取的方法的参数类型说明, 如 int.class, String.class.

class.getMethods 获取类的所有 public 方法 (包含继承的方法)

注意, 反射依然遵守方法修饰符权限的规则 (public, private, protect), 但是通过 method.setAccessible(true) 就能 invoke private or protect methods.

method.getModifiers 获取方法的修饰符, 然后通过 Modifier.isXxxx() 判断是什么类型的修饰符

class.newInstance 要求类有无参构造函数, 如果没有的话就会报异常, 解决方法是通过 class.getDeclaredConstructor 获取构造函数, 然后通过 constructor.newInstance 来实例化对象.

class.getDeclaredConstructor 可以获取到所有的构造函数, 而 class.getConstructor 只能获取到 public 的构造函数.

class.getDeclaredField 获取到类的字段描述后, 可以再通过 field.get(object) 来获取到实例的字段值, 依然遵守方法修饰符权限的规则 (public, private, protect), 但通过 method.setAccessible(true) 可以修改它的修饰符权限.

内省

Reference: 深入理解 Java: 内省 (Introspector)

通过反射的方式访问 JavaBean 的技术, 是 Java 语言对 Bean 类属性、事件的一种缺省处理方法.

例如类 A 中有属性 name, 那我们可以通过 getNamesetName 来得到其值或者设置新的值, 通过 getName/setName 来访问 name 属性, 这就是默认的规则.

Java 中提供了一套 API 用来访问某个属性的 getter/setter 方法, 通过这些 API 可以使你不需要了解这个规则, 这些 API 存放于包 java.beans 中.

一般的做法是通过类 Introspector 来获取某个对象的 BeanInfo 信息, 然后通过 BeanInfo 来获取属性的描述器 (PropertyDescriptor), 通过这个属性描述器就可以获取某个属性对应的 getter/setter 方法, 然后我们就可以通过反射机制来调用这些方法.

下面我们来看一个例子, 这个例子把某个对象的所有属性名称和值都打印出来:

importjava.beans.BeanInfo;
importjava.beans.Introspector;
importjava.beans.PropertyDescriptor;

public class IntrospectorDemo {
    String name;
    public static void main(String[]args) throws Exception {
        IntrospectorDemo demo = new IntrospectorDemo();
        demo.setName("WinterLau");

        // 如果不想把父类的属性也列出来的话, 那 getBeanInfo 的第二个参数填写父类的信息
        BeanInfo bi = Introspector.getBeanInfo(demo.getClass(), Object.class);
        PropertyDescriptor[] props = bi.getPropertyDescriptors();

        /*for (int i = 0; i < props.length; i++) {
            System.out.println(props[i].getName() + "=" + props[i].getReadMethod().invoke(demo));
        }*/
        for (PropertyDescriptor prop : props ) {
            System.out.println(prop.getName() + "=" + prop.getReadMethod().invoke(demo));
        }
    }

    public String getName(){
        retur nname;
    }

    public void setName(String name){
        this.name=name;
    }
}


另外, 如果已知属性名的话, 是可以直接通过 PropertyDescriptor 类来操作 JavaBean 的.

public class IntrospectorDemo {
    String name;
    public static void main(String[]args) throws Exception {
        IntrospectorDemo demo = new IntrospectorDemo();
        demo.setName("WinterLau");

        PropertyDescriptor propDesc = new PropertyDescriptor("name", demo);
        Method methodGetName = proDescriptor.getReadMethod();
        Object objName = methodGetName.invoke(demo);
        System.out.println("get name:" + objName.toString());
    }

    public String getName(){
        retur nname;
    }

    public void setName(String name){
        this.name=name;
    }
}