1.类加载器
两个不同jvm里面不能共享数据,即时访问的是同一类的静态属性, jvm是通过main来区分的
同一个jvm的所有线程、所有变量都处于同一个进程里,都是用该jvm的内存区
不同jvm之间是相互透明的,即时访问的是类变量
加载连接初始化 统称为类加载或者类初始化
由jvm提供的加载器我们称谓类加载器动态
1.自定义类加载器: 一般重写findClass(string)方法,而不是覆盖loadClass()方法,可以避免覆盖默认类加载器的 父类委托和缓冲机制;核心方法: defineClass(byte[])
类加载器的二进制数据来源:
1.本地
2.jar
3.网络
4.动态编译一个java源文件,并加载
如果一个类被加载到内存,同一类将不会再被加载了
2.同一类:全限定类名(包名和类名)+其类加载器作为唯一标识,即如果加载器不同在jvm中也不是同一个类
3.类加载器的层次结构:
1.引导类加载器(bootStrap class loader)
2.扩展类加载器 extension calssloader
3.应用程序类加载器
4.自定义类加载器
根类加载器 负责基础类库 可以通过java - Xbootclasspath\a:D:/mysql.jar MainClass ->指定根类加载器加载
注意:不是ClassLoader的子类,JVM自身实现,由C/C++语言编写
引导类加载器(bootStrap class loader): 使用原生代码来实现c++,并不继承java.lang.ClassLoader;使用来 启动 lib/rt..jar文件 java的核心包
扩展类加载器 加载ext下面的jar包 java书写继承java.lang.classLoader
应用程序类加载器 classpath下的类加载 java书写继承java.lang.classLoader
自定义类加载器: 继承java.lang.classLoader
组合模式 持有引用
ClassLoader.getSystemClassLoader();
ClassLoader.getSystemClassLoader().parent();
ClassLoader.getSystemClassLoader().parent().parent();->null 应该是引导类加载器 但是 由原生代码实现 我们获取不到
system.getProperty("java.class.path");->获得path路径
4.类加载机制:
全盘委托: 一个类加载器负责加载整个class,以及相关类,除非显示调用另一个加载器加载
父类委托:
类缓存:标准的java类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,他将维持加载(缓存)一段时间,不用反复加载,不过jvm垃圾回收器可以回收这些class 对象,使用类加载器(层次机构)树状结构
缓存机制:这也是为什么修改后要重启jvm的原因
类加载之间的父子关系并不是类继承上的父子关系,指的是加载器实例的关系,子类实例持有父类实例的引用
JVM的根类加载器并不是java是书写的,而且由于程序通常无须访问根类加载器,因此扩展类父类加载器为null
大部分采用代理模式
系统采用(3个)采用 双亲委托机制
代理模式:交给其他加载器来加载指定的类
双亲委托机制(一种特殊的代理类): 父类加载器优先加载,不管是否能加载都交给父亲(递归 ,如果父类不能加载则再回来)
很安全的运行(保证),不会出现子类中重写(优先加载父类加载内容的情况)
保证用户不能定义jav。lang.object类的情况
并不是所有的类加载都会采用双亲位图机制
tomcat服务器采用代理模式,但是他会尝试加载这个类(子类优先)
委托机制 :是通过代码来控制的 if else 控制
5.类加载器举例:
URLClassLoad:
URLClassLoad 是ClassLoad的子类,既可以从本地文件中加载字节码文件,也可以从远程服务器上加载字节码文件;URL构造器传入一个uRL数组
URL可以以file: 为前缀本地文件,也可以 以http:互联网http 协议 ftp:互联网 ftp
6.查看对象信息:
由于许多对像在运行时出现两种类型,编译时类型,运行时类型
通常的解决办法:
1. 预先知道将要转变的类型通过instanceof判断
2.编译时无法知道,但是使用反射
获取 Class 的三种方法:
1.通过forname(string)
2.类.Class2()// 获取
3.对象.getClass()
7.双亲委托机制的优势:
如果定义了一个java.lang 包定义string类 并不会被加载(没有任何作用在java中,双亲委派机制),定义不出来
在服务器中不被采用,考虑到安全性,但是性能较差
classLoader 中的LoadClass(string);首先负责加载指定名称的类,在已加载类中寻找,如果没找到,在从parent进行加载,不能加载的话会抛出异常。
如果改变类的加载顺序可以重新覆盖此方法
2.类加载过程
类加载器并不是等到"首次使用"的实用功能才来加载类,java虚拟机规范运行允许系统预先加载某些类
1.类加载器加载过程:
加载
连接: 在加载后形成一个class对象,负责把二进制数据合并到JRE中
初始化: 类先查找父类是否被初始化,然后根据循序依次初始化
初始化的时机:
1.创建类实例
2.调用方法(包含静态)
3.调用类变量
4.反射创建 class对象
5.创建子类实例
6.java exe 运行某个主类
调用宏变量不会引起类的初始化
注:宏变量是在编译时就确定下来 ,没有类变量的出现
ClassLoader 类的loadClass()方法来加载某个类的时候,不会执行该类的初始化
Class.forName()静态方法会强制初始化该类
2.JVM 运行和类加载全过程:
javac 编译 ->.class
java 加载 和运行 (重点)
加载:
过程: (加载 链接 初始化) 使用 卸载
链接: 验证-》准备-》解析
加载:将class 文件的字节码内容加载到内存中,并将这些(静态数据)转换成(方法区)中的与运行时数据结构,在堆内存中生成一个代表这个类的java。lang。class对象,作为方法去类数据的访问接口
这个过程需要类加载器的参与
字节码来源很多:网络、数据库、硬盘...
连接:
链接: 将java类的二进制代码合并到jvm的运行状态之中的过程
验证: 且包加载的信息符合jvm规范,内有安全方面的问题;
准备: 为类变量开辟控件,并赋默认值,在方法区中进行分配、
解析:将虚拟机中(常量池)里面符号引用转为直接引用的过程-》符号 编程 直接地址或者间接地址
初始化:调用类构造器CINIT里调用方法
1.初始化块静态(合并 静态变量和初始化块)
2.父类静态初始化
3.虚拟机会保证类clinit()方法会在多线程环境下保持同步
静态变量 静态域是一回事
将静态域和静态初始化 ==类构造器
类加载 :
主动引用:(一定会发生类的初始化)
1.new 对象
2.调用 静态属性
3.通过反射调用类
4运行类时,一定会加载当前运行类
被动引用:
1.调用fianl static 并不会加载类
2.数组定义类引用A【】 a =new A【10】;
3.B extends A
B.A【静态变量】 A被初始化,但是B没有,找初始化的本质;子类访问父类的静态变量,子类不会初始化
3.spring框架
spring 框架就是采用根据配置文件的方法来配置对象,但是使用xml,以属性文件可以配置的信息太有限了,通常创建文件不必使用反射,因为性能相对较低,实际上只有程序需要动态创建某个类的对戏那个时才考虑使用反射,通常在开发通用性比较广的框架、基础平台时可能会大量使用反射
1.调用方法:
method 对象里面包含一个invoke(obj,obj ...)
spring 就是通过这种方法将成员变量以及依赖对象都发在配置文件中管理的,从而实现更好的解耦,这也是spring框架的IoC的秘密
2.根据获得class 根据api获得类的信息( 类的名字 方法 构造器)
a.通过setAccessible 来取消权限检查,通过反射可访问private成员
b.Class对象的getFields 或者getField() 可以获取全部成员或者指定成员变量
c.使用getType()方法只能获取普通类型的成员变量,对获得增加了泛型的成员变量,应该使用getGeneticType()方法来获取其类型,并将获得的 Type 类型强制转换为paramerizedType,paramerizedType 提供两个方法:getRawType(),getActualTypeArguments()
d.Type 是一个关于类型的接口
e.java.lang.reflect 下还有一个Array类,用它来动态的创建数组
newInstance(类型,length)
使用反射生成JDK动态代理
Proxy 提供动态代理类 和代理对象的静态方法
Class getProxyClass(ClassLoader,interface(Class))
newProxyInstanc(Classloader,interfece,class,InvocationHandler)->替代对象都会执行Invoke 方法
f.clazz.getName(); 包名+ 类名
claszz.getSimpleName();类名
属性 clazz.getFilds() Field 只能获取public 的属性名
clazz.getField(String )
getDeclaredFields()获得所有的属性
g.方法
field -》 method 即可
getMethod()只能获得public声明的
Method=getDeclaredMethods();
Method=getDeclaredMethod(methodname, 可变数组 返回值类 String.class)->因为重载的存在
h.构造器
getConstructor()//public
getDeclaredConstructors();
3.动态代理
动态代理可以非常好的实现解耦;使用指定目标对象生成动态代理->AOP代理:可以代替目标对象,包含目标对象的全部方法;Aop代理中的方法和执行目标的方法有些差别,他可以在之前或之后插入一些通用处理
4.案例
a.通过动态调用构造器构造对象:
Class<?>-->支持泛型
(User)clazz.newInstance();//其实调用了user 的无参构造器
若无参构造器出现:InstantionException 不能初始化异常
Consturctor<user> =claz.getDeclaredConstructors(int.class,int.class);
c.newInstance(1,2);
b.通过反射api调用普通方法
不使用.点
Method method = clazz.getdeclarmehod("方法名",string.class)
method.invoke(user,"jkjkj")==user.setUnamne(jdkjkjk);
通过反射得到方法再调用而不是直接通过反射得到对象 在.出方法的好处:
将 方法名和参数都作为参数传入都具有动态性
c.通过反射api操作属性
FIeld f = clazz.getDeclardField("unname");
f.set(object,value)//给那个对象涉属性
运行会出现异常 :我们不能直接操作private 方法
但是 反射中setAccessible更改了这一限制
filed.setAccessible(true); 打开私有访问限制,不用做安全检查直接可以访问
在method也存在,即可以访问私有方法
得到某个属性的值可以通过
field.get(user);
user.getUname();
d.反射操作泛型
java采用泛型擦除的机制引入泛型,java中的泛型仅仅是给编译器javac使用的,确保数据安全性和免去强制类型转换的麻烦 。但是一旦编译完成,所有和泛型有关的类型全部擦除,为了通过反射操作这些类型来迎合实际开发的需要,java新增ParameterizedType/GeneicArrayType/TypeVariable/wildcardType 代表不能被归一到class类中的类型 但和原始类型齐名的类型
type[] =method m .getGenericParametrTypes();//得到带泛型的参数类型;
for(type x:type){
if(x instanceof ParamertizedType){
((ParamertizedType)x).getactulatypeArguments();//得到参数中泛型的类型
}
}
5.反射机制的性能问题:
setAccessible 启用和禁用安全检查的开关-true 取消安全检查 -flase 进行安全检查
禁止安全检查,可以提高反射的运行速度
反射的使用使的程序的安全性能降低
普通方法调用 和 安全检查方法调用 和禁止安全检查方法调用
1:30:4的关系