目录
一、类加载过程
1.装载阶段
1.1执行过程
1.2.类加载器
1.3.双亲委派模型
1.4 类加载时机
2. 链接阶段
2.1验证阶段
2.2准备阶段
2.3解析阶段
3. 初始化阶段
二、反射
1.定义
2.用途
3.步骤
4.代码实现
一、类加载过程
1.装载阶段
1.1执行过程
以Main.java为例
其中定义了两个变量,首先利用javac Main.java进行编译生成Main.class文件
然后利用javap -verbose命令查看反汇编
其中首先由一个4字节大小的魔数来标识文件类型。
上图中的minor version为次版本号,major version表示主版本号,分别占用2字节。
Constant pool为常量池,其大小不固定,由定义的变量来决定。
1.2.类加载器
Main类经过javac编译后,生成.class文件保存下来,然后经过类加载器加载类至内存,生成java.lang.Class类的实例,这个实例就是程序访问这个类的入口,通过这个class实例的newInstance方法即可得到这个类的实例对象。
JVM中的类的加载器主要有三种:启动类加载器,拓展类加载器,应用类加载器。
- 启动类加载器(Bootstrap classLoader):又称为引导类(启动类)加载器,由C++编写,无法通过程序得到。主要负责加载JAVA中的一些核心类库,主要是位于<JAVA_HOME>/lib/rt.jar中。
- 拓展类加载器(Extension classLoader):主要加载JAVA中的一些拓展类,位于<JAVA_HOME>/lib/ext中,是启动类加载器的子类。
- 应用类加载器(System classLoader): 又称为系统类(应用类)加载器,主要用于加载CLASSPATH路径下我们自己写的类,是拓展类加载器的子类。
如果生成的class文件不在这三个类加载器路径下,它首先会由子加载器依次往父加载器去找,最后找不到就会报错ClassNotFoundException,加载失败。
如果在这些路径下,通过Class.forName("com.company.Main"),通过全路径加载进来。然后用Student.class.getClassLoader()得到它的类加载器,得到的是AppClassLoader(即系统类加载器),如果用Student.class.getClassLoader().getParent()得到的是它的父加载器ExtClassLoader(即拓展类加载器),然后用Student.class.getClassLoader().getParent().getParent()得到将会是Null,因为启动类加载器是用C++写的,无法通过程序直接得到。
例如:
输出:
TestClass为自己定义的类,使用应用加载器加载,它的父类就是扩展加载器,String类使用的Bootstrap加载器,它由c++实现,因此得到的只能是null。
从子类到父类流程是:从子类到父类查找之前是否加载过这个类,如果加载过,就返回当前类的Class对象,若没有加载过,委托给父类加载器,查找是否加载过。
若都没有加载过当前类,从启动类开始尝试加载(有没有在加载路径下),若加载失败就委托给子类加载器进行加载。
1.3.双亲委派模型
这几种类加载器的层次关系,称为类加载器的双亲委派模型。该模型要求除了顶层的启动类加载器外,其它的类加载器都要有自己的父类加载器。简单描述其过程:
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此。因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。
其优点:
(1).避免类的重复加载
如需要定义一个 System 的 Class 对象,并且只需要一份,如果不使用委托机制,而是自己加载自己的,那么类 A 打印的时候就会加载一份 System 字节码,类 B 打印的时候又会加载一份 System 字节码。而使用委托机制就可以有效的避免这个问题。
(2).安全性
如果在String类中嵌套了病毒,若没有用该模型直接加载的话,病毒就会攻击jvm,造成破坏,因此像String这种系统类都是由Bootstrap加载器去加载的,保证了其安全性。
1.4 类加载时机
- new对象时
- 调用静态变量/方法(在反汇编中,invokestatic指令专门用于调用静态方法变量的,invokespecial指令用于调用构造方法的,invokevirtual指令用于调用实例方法变量的)
- 获取当前类的Class对象
2. 链接阶段
2.1验证阶段
该阶段的任务是为了保证Class文件的字节流中包含的信息符合当前虚拟机要求,如魔数,jdk版本号,各种数据验证等,并且不会危害虚拟机自身的安全。
2.2准备阶段
为类变量分配内存并设置类变量(静态变量)初始值(静态变量的默认值)的阶段,这些变量所使用的内存都将在方法区中进行分配。
private static int a = 10;
2.3解析阶段
将符号引用解析为直接引用的过程。
例如:
str内容对应的地址被保存在磁盘上,通过磁盘上的地址去访问该内容就是符号引用,而通过堆去访问就是直接引用,解析的作用就是进行二者的转换。
3. 初始化阶段
给静态变量进行赋值操作。
二、反射
1.定义
Java的反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。
2.用途
在日常的第三方应用开发过程中,经常会遇到某个类的某个成员变量、方法或是属性是私有的或是只对系统应用开放,这时候就可以利用Java的反射机制通过反射来获取所需的私有成员或是方法。当然,也不是所有的都适合反射,有的案例通过反射得到的结果与预期不符。阅读源码发现,经过层层调用后在最终返回结果的地方对应用的权限进行了校验,对于没有权限的应用返回值是没有意义的缺省值,否则返回实际值起到保护用户的隐私目的,因此如果源码中明确进行了权限验证,而你的应用又无法获得这个权限的话,建议就不要浪费时间反射了。
3.步骤
①获取Class对象
- Class c = People.class;
- Class c = Class.forName("People");
- People p = new People(); Class c = p.getClass();
②获取当前类的构造函数
Constructor con = c.getConstructor();
③通过new对象进行构造函数生成
4.代码实现
①无参方法反射:
其中:getDeclaredConstructor在private,public,protected中查找,getConstructor在public方法中查找,私有的构造方法用getConstructor去调用会报错,并且要给constuctor对象设置访问权限。
②属性修改反射
③有参方法