类加载 反射

类的生命周期
加载
连接
验证
准备
解析
初始化
使用
卸载
结束生命周期
当程序主动使用某个类的时候 如果该类还没有被加载到内存中 jvm会通过类加载**(加载连接初始化 三个步骤)**对该类进行初始化 类型(指定义的类 接口 或者枚举 称为类型 而不涉及对象 )的加载连接初始化都是在程序运行期间完成的 (就是当你点击run的时候 类才开始加载 从磁盘加载到内存中 )
当jvm预料到某个类将要被使用的时候 就预先加载它 加载的时候发现这个类有错误 也不会报错 而是等到真正使用的时候才报错
加载 验证准备初始化 和卸载这五个阶段的顺序是固定的 但解析可以在初始化后再开始

加载 (最终产品 :位与堆区的class对象 由加载器完成 )

加载阶段是指将类的.class文件中的二进制数据从磁盘读到内存中 将其放到运行时数据区的方法区内 然后在堆区创建一个class对象 用来封装类在方法区内的数据结构 并提供了访问方法区数据结构的接口

在这里插入图片描述
编写一个新的Java类 jvm就会帮我们编译成class对象 放在同名的.class文件中(堆中) 当new一个对象的时候 jvm检查此类是否已经有class对象 如果没有 把class对象装在内存中 要是有的话 生成文件实例对象
总结:
加载阶段简单来说就是:
.class文件(二进制数据)——>读取到内存——>数据放进方法区——>堆中创建对应Class对象——>并提供访问方法区的接口
二进制数据源的来源:
从本地系统中直接加载
通过网络下载.class文件
从zip jar 等归档文件中加载.class文件
从专用数据库中 提取.class文件
将java源文件动态编译成.class文件

验证

文件格式验证 :验证字节流是否符合Class文件格式的规范;例如:是否以 0xCAFEBABE开头、主次版本号是否在当前虚拟机的处理范围之内、常量池中的常量是否有不被支持的类型。
元数据验证 :对字节码描述的信息进行语义分析(注意:对比javac编译阶段的语义分析),以保证其描述的信息符合Java语言规范的要求;例如:这个类是否有父类,除了 java.lang.Object之外。
字节码验证 :通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。
符号引用验证确保解析动作能正确执行。

准备

jvm为类变量(static修饰的变量)分配内存并进行初始化 (初始化的时候 如果是final修饰的变量 就赋值 否则 设为默认值 )

解析

解析阶段是虚拟机将常量池内的符号引用替换为直接引用(直接指向目标的指针)的过程

初始化

java程序对类的使用方式分为主动使用和被动使用 只有当类的首次主动使用才会引发类的初始化 (为变量赋值之类的 )
创建类的实例 new一个对象
访问某个类或接口的静态变量 或者对该静态变量赋值 (在编译期就把结果放在常量池的静态字段除外 )
调用类的静态方法
反射
初始化某个类的子类 父类也会被初始化
标注@Test 和Main的类会被首先初始化

使用

卸载

结束生命周期

执行System,exit()方法
程序正常执行结束
程序在执行过程中 遇到了异常或错误 而异常终止
操作系统出现错误 而导致java虚拟机进程终止

注意:接口在初始化的时候 并不要求父类接口全部完成初始化 用到的时候才执行初始化

双亲委派模型

如果一个类加载器收到了类加载的请求 它首先不会自己去尝试加载这个类 而是把请求委托给父加载器去完成 依次向上 所有的类加载器请求最终都应该被传递到顶层的启动类加载器中 只有当父类加载器在它的搜索范围中没有找到所需要的类时 即无法完成该加载 子加载器才会自己去尝试加载该类

1、当AppClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。

 

2、当 ExtClassLoader加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader去完成。
 

3、如果 BootStrapClassLoader加载失败(例如在 $JAVA_HOME/jre/lib里未查找到该class),会使用 ExtClassLoader来尝试加载;
 

4、若ExtClassLoader也加载失败,则会使用 AppClassLoader来加载,如果 AppClassLoader也加载失败,则会报出异常 ClassNotFoundException。

启动类加载器(BootstrapClassLoader): 加载Java运行过程中的核心类库 也就是jdk常见的类: Object String List

**扩展类加载器(ExtensionClassLoader ) **: 加载jdk\jre\lib\ext目录中 或者由系统变量指定的路径的所有类库 如Javax.开头的类
应用程序类加载器 加载CLASSPATH变量指定路径下的类 即自己在项目工程中编写的类
线程上下文加载器

在这里插入图片描述
在这里插入图片描述
双亲委派模型的优势 Java类随着它的类加载器一起具备了一种带有优先层级关系 通过这种层级关系 可以避免类的重复加载 当父类已经加载了该类时 就没有必要子加载器再加载一次
其次是考虑到安全因素 Java核心api中定义的类型不会被随意替换 假设通过网络传递 一个名为java.lang.Integer的类 通过双亲委派传递到启动类加载器 而启动类加载器在核心Java api发现这个名字的类 发现该类已经被加载 并不会重新加载网络传递过来的Java.lang.Integer 而直接返回已加载过的Integer.class 这样便可以防止核心api库被随意篡改

反射

RTTI 运行时类型识别 作用是在运行时识别一个对象和类型的类的信息 每个Java类都有一个class对象 对象的类型就是该Java类 该对象保存在同名.class文件中 (编译后的字节码文件保存的就是class对象 放在Java堆中 创建时候的数据放在方法区 )
class对象是由jvm实现的 在第一次使用的时候动态加载到jvm中
**class.forname方法 **
方法返回一个对应类的class对象 当我们向获取一个类的运行时类型信息 并加以使用的时候 可以调用Class.ForName方法 获取Class对象的引用 (直接调用编译时产生在堆区的class文件)
.class方法
更简单 安全 通过字面量的方法获取Class对象的引用 不会自动初始化该类 不仅可以应用普通的类 还可以应用于接口 数组 及基本类型
反射机制 反射机制是在运行状态中 对于任意一个类 都能知道这个类的所有方法和属性 对于任意一个对象 都能调用它的任意一个方法 和属性 动态获取的信息 动态调用对象的方法的功能 反射包中 常用的类 Constructor类表示的是Class对象所表示的类的构造方法 利用它可以在运行时动态创建对象 Field表示Class对象所表示的类的成员变量 通过它可以在运行时动态修改成员变量的属性值 Mothed 可以动态调用对象的方法
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

当通过反射与一个未知类型的对象打交道时,JVM只会简单地检查这个对象,判断该对象属于那种类型,同时也应该知道,在使用反射机制创建对象前,必须确保已加载了这个类的Class对象,当然这点完全不必由我们操作,毕竟只能JVM加载,但必须确保该类的”.class”文件已存在并且JVM能够正确找到。

反射的Method.invoke前15次是调用navite code用C++实现的,后面使用java实现
原因:

Java实现的版本在初始化时需要较多时间,但长久来说性能较好;native版本正好相反,启动时相对较快,但运行时间长了之后速度就比不过Java版了。这是HotSpot的优化方式带来的性能特性,同时也是许多虚拟机的共同点:跨越native边界会对优化有阻碍作用,它就像个黑箱一样让虚拟机难以分析也将其内联,于是运行时间长了之后反而是托管版本的代码更快些。
为了权衡两个版本的性能,Sun的JDK使用了“inflation”的技巧:让Java方法在被反射调用时,开头若干次使用native版,等反射调用次数超过阈值时则生成一个专用的MethodAccessor实现类,生成其中的invoke()方法的字节码,以后对该Java方法的反射调用就会使用Java版。

#反射的作用–>实现框架的功能

框架与框架要解决的核心问题:

把框架看着是房子的话,我把房子卖给客户住(这个房子只是一个空的没有配带家具和门窗),有用户自己安装家具和门窗,房子看做框架,用户需要使用我提供的框架,把门窗和家具插入我的框架中。框架和工具类存在区别,工具类被用户的类调用,而框架则是调用用户提供的类。
框架要解决的核心问题:
问:我在写(房子)框架的时候,客户这个人可能在干其他的,还不会写程序,我写的框架程序怎么能掉用你以后写的(门窗)类呢?
**答:因为在写程序时无法知道要被调用的类名,所有无法直接new某个类的实例对象,所有需要用到反射来实现。 **

通过反射写一个小框架的应用:

在ide工具中创建一个配置文件 config.properties(内容为:className =java.util.HashSet )
然后通过字节流输入流(读进来)读取配置文件 InputStream input = new FileInputStream(配置文件路径);

创建配置文件 Properties pro = new Properties();
通配置文件加载输入流读取的内容 pro.load(input);
最后关流 input.close();
获取配置文件的key String className = pro.getProperty(“calssName”);
通过反射获取配置文件value的实例对象 Collections collection = (Collections) class.forName(“className”).newInstance();
创建几个对象(该对象重新生成了hashcode()和equals()方法)
Person p1 = new Person(10,“小明”);
Person p2 = new Person(12,“小白”);
Person p3 = new Person(12,“小白”);
collection.add(p1);collection.add(p2);collection.add(p3);

System.out.println(collection.size());//长度打印出来了为2,反射成功!!!

##使用类加载器加载这些文件

适用场景:资源文件和类文件在不在同一目录都可以
注意:getResourceAsStream里的参数要写资源文件的全限定路径,
包名+文件名且开头千万不要写"/"

使用方法:

InputStream ips = 类名.class.getClassLoader().getResourceAsStream("配置文件的路径“);

例如:InputStream ips = TestReflect2.class.getClassLoader().getResourceAsStream(“itcast/cn/Reflect/config.properties”);
注意:ClassLoader加载配置文件时,它是在classpath 的根路径下搜索,所以在填写配置文件的路径时要特别注意。bin是classpath 根路径,在配置文件钱要加上完整的包名。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值