Java中的字节码:Java源代码经过虚拟机编译器编译后产生的文件(即扩展为.class的文件),它不面向任何特定的处理器,只面向虚拟机。
1.反射的定义
反射机制
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象.
什么是反射
反射就是把java类中的各种成分映射成一个个的Java对象
2.Class类
Class 类的实例表示正在运行的 Java 应用程序中的类和接口。也就是jvm中有N多的实例每个类都有该Class对象。(包括基本数据类型)
Class 没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的defineClass 方法自动构造的。也就是这不需要我们自己去处理创建,JVM已经帮我们创建好了
(具体可查看源码,或查看Java api详解)
所有类型的Class对象
Class c1 = Object.class; //类Class c2 = Comparable.class; //接口Class c3 = String.class; //一维数组Class c4 = int[][].class; //二维数组Class c5 = Override.class; //注解Class c6 = ElementType.class; //枚举Class c7 = Integer.class; //基本数据类型Class c8 = void.class; //voidClass c9 = Class.class; //Class//只要元素类型与维度一致,就是同一个Classint[] a = new int[10];int[] b = new int[100];int[][] c = new int[10][10];System.out.println(a.getClass().hashCode());System.out.println(b.getClass().hashCode());System.out.println(c.getClass().hashCode());
3.获取反射对象Class五种方式
- 通过Object类的方法getClass()继承给所有子类
- 任何数据类型都有静态的class属性
- 通过Class类的静态方法:forName(String className)
- 基本内置类型的包装类都有一个Type属性
- 通过子类Class找到父类Class
4.类的加载内存分析
加载
加载,是指Java虚拟机查找字节流(查找.class文件),并且根据字节流创建java.lang.Class对象的过程。这个过程,将类的.class文件中的二进制数据读入内存,放在运行时区域的方法区内。然后在堆中创建java.lang.Class对象,用来封装类在方法区的数据结构。
类加载阶段:
(1)Java虚拟机将.class文件读入内存,并为之创建一个Class对象。
(2)任何类被使用时系统都会为其创建一个且仅有一个Class对象。
(3)这个Class对象描述了这个类创建出来的对象的所有信息,比如有哪些构造方法,都有哪些成员方法,都有哪些成员变量等。
链接
链接包括验证、准备以及解析三个阶段。
(1)验证阶段。主要的目的是确保被加载的类(.class文件的字节流)满足Java虚拟机规范,不会造成安全错误。
(2)准备阶段。负责为类的静态成员分配内存,并设置默认初始值。
(3)解析阶段。将类的二进制数据中的符号引用替换为直接引用。
初始化
初始化,则是为标记为常量值的字段赋值的过程。换句话说,只对static修饰的变量或语句块进行初始化。
如果初始化一个类的时候,其父类尚未初始化,则优先初始化其父类。
如果同时包含多个静态变量和静态代码块,则按照自上而下的顺序依次执行。
具体说法如下:
执行类构造器<clinit>()方法的过程。类构造器<clinit>()方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器)。
当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确加锁和同步。
虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确加锁和同步。
public class test03 {public static void main(String[] args) {A a = new A();System.out.println(a.m);}/*1.加载到内存,会产生一个类对应的Class对象2.链接,结束后 m = 0;3.初始化<clinit>() {System.out.println("A类静态代码块初始化");m = 300;m = 100;}m = 100;*/
}
class A {static {System.out.println("A类静态代码块初始化");m = 300;}static int m = 100;public A() {System.out.println("A类无参构造初始化");}
}
5.分析类初始化
类的主动引用(一定会发生初始化)
-
当虚拟机启动,先初始化main方法所在的类
-
调用类的静态成员(除了final常量)和静态方法
类的被动引用(不会发生类的初始化)
- 当访问一个静态域时,只有真正声明这个域的类才会被初始化。如:当通过子类引用父类的静态变量,不会导致子类的初始化
- 通过数组定义引用,不会出发此类的初始化
- 引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中)
public class test04 {static {System.out.println("Main类被加载");}public static void main(String[] args) throws ClassNotFoundException {//1.主动引用Son son = new Son();//反射也会产生主动引用Class.forName("cn.com.reflection.Son");//不会产生类的引用的方法//子类引用父类的值System.out.println(Son.n);//数组只是开辟一个内存空间,也不会初始化Son[] a = new Son[5];//引用常量也不会初始化System.out.println(Son.M);}
}
class Father {static int n = 2;static {System.out.println("父类被加载");}
}
class Son extends Father {static {System.out.println("子类被加载");m = 300;}static int m = 100;static final int M = 1;
}
6.类加载器
类加载器作用是用来把类(class)装载进内存的。JVM规范定义了如下类型的类加载器。
系统类加载器
负责java-classpath或-d java.class.path所指的目录下的类与jar包装入工作库,是最常用的加载器。
扩展类加载器
负责jre/lib/ext或-d java.ext.dirs所指的目录下的类与jar包装入工作库
引导类加载器(根加载器)
用c++编写的,是jvm自带的类加载器,负责Java平台核心库,用来装载核心类库。该加载器无法直接获取。
各加载器关系
自定义加载器—>System Classloader—>Extension Classloader—>Bootstap Classloader(从左至右对应从底到顶)
自底向上检查类是否已装载
自顶向底尝试加载类
双亲委派机制
自顶向底尝试加载类时,会检查是否存在了该类的包,如果已存在就不会向下加载子类的包。
双亲委派机制保证了安全性。避免创建一些如Java核心类库中同样的命名。