反射、类加载与垃圾回收
类加载
由类的加载器完成
类加载:将.java编译过后的.class文件中二进制代码加载到内存中去,
这一个过程,就叫类加载
类的生命周期:
类加载器加载的来源:查找并加载类的二进制数据
1、 本地文件系统加载的.class文件(我们自己写的),绝大多数的加载都是这里来的。
2、 从iar文件中加载.class文件,这种也是比较常见的.
3、 通过网络加载class文件
4、 动态加载一个Java源文件,先编译,后执行加载,
(1)验证:确保被加载的类的正确性
验证是连接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。验证阶段大致会完成4个阶段的检验动作:
文件格式验证:验证字节流是否符合Class文件格式的规范;例如:是否以0xCAFEBABE开头、主次版本号是否在当前虚拟机的处理范围之内、常量池中的常量是否有不被支持的类型。
元数据验证:对字节码描述的信息进行语义分析(注意:对比javac编译阶段的语义分析),以保证其描述的信息符合Java语言规范的要求;例如:这个类是否有父类,除了java.lang.Object之外。
字节码验证:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。
符号引用验证:确保解析动作能正确执行。
验证阶段是非常重要的,但不是必须的,它对程序运行期没有影响,如果所引用的类经过反复验证,那么可以考虑采用-Xverifynone参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间。
(2)准备:为类的静态变量分配内存,并将其初始化为默认值
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中分配。对于该阶段有以下几点需要注意:
1、这时候进行内存分配的仅包括类变量(static),而不包括实例变量,实例变量会在对象实例化时随着对象一块分配在Java堆中。
2、这里所设置的初始值通常情况下是数据类型默认的零值(如0、0L、null、false等),而不是被在Java代码中被显式地赋予的值。
假设一个类变量的定义为:public static int value = 3;
那么变量value在准备阶段过后的初始值为0,而不是3,因为这时候尚未开始执行任何Java方法,而把value赋值为3的putstatic指令是在程序编译后,存放于类构造器()方法之中的,所以把value赋值为3的动作将在初始化阶段才会执行。
• 这里还需要注意如下几点:
• 对基本数据类型来说,对于类变量(static)和全局变量,如果不显式地对其赋值而直接使用,则系统会为其赋予默认的零值,而对于局部变量来说,在使用前必须显式地为其赋值,否则编译时不通过。
• 对于同时被static和final修饰的常量,必须在声明的时候就为其显式地赋值,否则编译时不通过;而只被final修饰的常量则既可以在声明时显式地为其赋值,也可以在类初始化时显式地为其赋值,总之,在使用前必须为其显式地赋值,系统不会为其赋予默认零值。
• 对于引用数据类型reference来说,如数组引用、对象引用等,如果没有对其进行显式地赋值而直接使用,系统都会为其赋予默认的零值,即null。
• 如果在数组初始化时没有对数组中的各元素赋值,那么其中的元素将根据对应的数据类型而被赋予默认的零值。
(3)解析:把类中的符号引用转换为直接引用
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。
符号引用就是一组符号来描述目标,可以是任何字面量。
直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。
(4)初始化
初始化,为类的静态变量赋予正确的初始值,JVM负责对类进行初始化,主要对类变量进行初始化。在Java中对类变量进行初始值设定有两种方式:
①声明类变量是指定初始值
②使用静态代码块为类变量指定初始值
JVM初始化步骤
1、假如这个类还没有被加载和连接,则程序先加载并连接该类
2、假如该类的直接父类还没有被初始化,则先初始化其直接父类
3、假如类中有初始化语句,则系统依次执行这些初始化语句
类初始化时机:只有当对类的主动使用的时候才会导致类的初始化,
类的主动使用包括以下六种:
– 创建类的实例,也就是new的方式
– 访问某个类或接口的静态变量,或者对该静态变量赋值
– 调用类的静态方法
– 反射(如Class.forName(“com.shengsiyuan.Test”))
– 初始化某个类的子类,则其父类也会被初始化
– Java虚拟机启动时被标明为启动类的类(Java Test),直接使用java.exe命令来运行某个主类
卸载
•在如下几种情况下,Java虚拟机将结束生命周期
– 执行了System.exit()方法
– 程序正常执行结束
– 程序在执行过程中遇到了异常或错误而异常终止
– 由于操作系统出现错误而导致Java虚拟机进程终止
类缓存
使用标准javaSE.版本的类加载器,加载的类,类一旦被加载完成,在一定时间范围内 ,是有缓存效果的,但是也不是说就永远都不销毁,当然什么时候销毁,由垃圾回收器说了算.
类的加载器
这几种类加载器的层次关系如下图所示:
注意:这里父类加载器并不是通过继承关系来实现的,而是采用组合实现的。
站在Java开发人员的角度来看,类加载器可以大致划分为以下三类:
启动类加载器:
Bootstrap ClassLoader,负责加载存放在JDK\jre\lib(JDK代表JDK的安装目录,下同)下,或被-Xbootclasspath参数指定的路径中的,并且能被虚拟机识别的类库(如rt.jar,所有的java.*开头的类均被Bootstrap ClassLoader加载)。启动类加载器是无法被Java程序直接引用的。
扩展类加载器:
Extension ClassLoader,该加载器由sun.misc.LauncherKaTeX parse error: Expected 'EOF', got '\jre' at position 25: …oader实现,它负责加载DK\̲j̲r̲e̲\lib\ext目录中,或者由…AppClassLoader来实现,它负责加载用户类路径(ClassPath)所指定的类,开发者可以直接使用该类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
JVM类加载机制
全盘负责:当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入
父类委托:先让父类加载器试图加载该类,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类
代理加载:当一个类加载器接受到一个类的加载请求的时候,让其他的类加载器去帮助加载的过程,父类委托本身一种代理加.
不同的类加载器,加载相同的类时,产生的类的Class对象不一定是一样的。
缓存机制:缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区寻找该Class,只有缓存区不存在,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓存区。这就是为什么修改了Class后,必须重启JVM,程序的修改才会生效
反射
反射机制:
就是指:在Java中,可以在程序运行时期,动态的加载、探知和使用,
在编译期间,完全无法确定的类。
或:java在运行期去载入一个只知道“类的全限定名”的类,并且获取其
完整的数据结构[运行时数据],还可以参照这个完整的数据结构,我们拥有创建类的实例,以及访问类的属性,访问类的方法的能力, “我们把这种能力,也称之为“自省”, “自审”, “内省”
第一步:获得Class对象获得Class对象的方式主要有以下三种:
(1)方式一:如果一个类的实例已经得到,你可以使用
【Class c=对象名.getClass0: 】
例: TextFieldt= new TextField();
ClassC = tgetClass();
Classs = c.getSuperclass();
(2)方式二:如果编译器在编译期间知道类了,我们还可以使用如下的方法
Class c= JButton.class;
或者: Class c = Integer.TYPE
(3)方式三: 如果编译器连类名不知道,但是在运行期可以获得,还可以使用下面的方法
Class c= Class.forName(stra):
口 Java的反射技术是java程序的特征之一,它允许运行中的 Java程序对自身进行检查,或者说"自审" ,并能直接操作程序的内部属性。
口使用反射可以获得Java类中各个成员的名称并显示出来。简单的说,反射就是让你可以通过名称来得到对象(类,属性 ,方法)的技术。
口一句话:运行时探究和使用编译时未知的类。
对象实际上代表现实生活中的一些数据,或者是数据的表示,当然Class的对象,代表方法区中的“类的数据结构”,类的相关结构信息,也被存储在Class对象中。
Class对象,不像其他的类的对象,直接存储数据,更像镜子,可以间接的访问“方法栈”。
通过Class对象,探究类的相关信息
Class c=Class.forName(name)
探究一个类中的属性
Fields[] fields=
c.GetField()获得所有的公有的属性
c.getDeclaredField()获得所有的属性
c.getField(name)通过名字获得属性,公有的中查找
c.getDeclaredField(name)
探究一个类中的方法
Method[] methods=c.getMthods()
For(Method m:methods){
getMethods()获得公共的方法,包括继承至父类的方法
getName()方法名
getParameterType()参数列表
getReturnType()返回类型
getDeclaredMethods()获得所有方法
}
Method method=c.getMethod(“方法名”,参数1,参数2,参数3)
本方法用于通过方法名和参数列表取出公共的方法(参数可变)
Method method=c.getDeclaredMethod()
c.getName()//得到类的全路径
c.getSuperclass().getName()//得到类的超类的全路径
c.getSimpleName()//得到类的类名
探究一个类的构造器
Constructor[] cons=c.getConstructors();//获取所有公共的的构造器
c.getDeclaredConstructors();//获取私有的无参构造
Constructor<?>未知的构造器类型
c.getDeclaredConstructors(String.class,)//有参构造
使用构造,产生类的对象
类加载
Constructor<?> constructor =c.getConstructor();
Object obj=constructor.newInstance();利用构造器产生对象
Constructor<?> constructor=c.getDeclaredConstructor();//私有无参构造
Constructor.setAcceable(true)//安全管理,放开私有权限
Object obj=constructor.newInstance(“张三”,“”);有参构造
使用Class对象,操作类的属性
Field field=c.getField(“gender”);
Field.get(obj)可以得到某个属性的值
Field.set(obj,””);设置某个值
使用Class对象,操作类的方法
Method method=c.getMethod(“方法名”,“参数(String.class)”);
Method.invoke(obj,””)执行某个对象的某个方法,并传入参数
Method.setAccessible(true)
注解
JDK1.5以后提供的,一种和类,接口,数组地位平等的一种新的特性
注解可以应用在以下的这些元素中:包、类、属性、方法、形参列表
它的作用主要有3个:
- 跟comment(注释)一样,对元素进行注释说明
- 跟其他的第三方达成一定的共识,或者达成一定的“约定”如:@override注释就是我们代码和编译器达成一个共识,由此注解的元素,必须是一个重新实现的方法
- 第三个作用,注解的内容可以在程序中,被访问到,比如:完成ORM映射
注解的格式:@注解的名称(参数名)
注解的声明:
Public @interface 注解名{
}
常见的注解:JDK的内置注解、自定义注解
JDK的内置注解:@override(重写)、@Deprecated(过时、不推荐、废弃)、@SuppressWarning(”unused”)
Deprecation 使用了不赞成使用的类或方法时
Unchecked 未检查
Fallthrough switch case未写break
Finally finally子句未能完成
All 所有的警告抑制
元注解
Target注解可能出现的地方
垃圾回收
- 对象的属性赋值为null
- System.gc()