(一)Java类的生命周期 与 类加载器

(一)Java类的生命周期 与 类加载器

 

 

1、简介

      Java 虚拟机为 Java  程序提供运行时环境, 其中一项重要的任务就是管理类和对象的生命周期。类的生命周期从类被加载、连接、初始化开始,到类被卸载结束。当类处于生命周期中时,它的二进制数据位于方法区内,在方法区还会有一个相应的描述这个类的Class对象,只有当类处于生命周期中时,Java程序才能使用它,比如调用类的静态属性和方法,或者创建类的实例。ClassLoader只负责class文件的加载,而是否能够运行则由 Execution Engine 来决定。

 

2、一个类被加载到内存并供我们使用,  需要经历如下三个阶段:

  1. 加载:  查找并加载类的二进制数据(把类的 .class文件读取到内存中),把他存放到运行时数据区的方法区内。所有的类都是在对其第一次使用时,动态加载到JVM中的(懒加载)。加载时机:当程序创建第一个对类的静态成员的引用时,就会加载这个类。使用new创建类对象的时候也会被当作对类的静态成员的引用。因此java程序程序在它开始运行之前并非被完全加载,其各个类都是在必需时才加载的。
  2. 连接:  包括验证(确保被加载的类的正确性)、准备(为类的静态变量分配内存,并将其初始化,  并且如果必需的话,将常量池中的符号引用转化为直接引用。)和 解析类(把类中的 符号引用 转换成 直接引用)的二进制数据  。
  3. 初始化: 每个类或接口被 java 程序主动使用的时候才会初始化,   用于执行该类的静态初始器和静态初始块。如果类存在直接的父类,并且父类还没有被初始化,那就先初始化父类。

 

 

3、类的初始化时机

  • 创建类的实例。
  • 调用类的静态方法。
  • 访问某个类或接口的静态变量, 或者对静态变量赋值。
  • 调用Java API中某些反射方法,比如:Class.forName("Test")。 (调用ClassLoader类的loadClass()方法加载一个类不会导致初始化。)
  • 初始化一个类的子类。

 

4、类加载器

      类加载器用来把类加载到Java 虚拟机中。从JDK1.2开始,类的加载采用父亲委托机制,这种机制能更好的保证Java平台的安全。除了Java 虚拟机自带的根类加载器外(没有父加载器), 其余的类加载器都只有一个父类加载器,当Java 程序请求load1去加载Sample 类时, load1首先委托父加载器去加载,若父加载器能加载, 则由父加载器加载, 否则由load1本身去加载。

Java 虚拟机自带的几种类加载器:

  •   根(Bootstrap)类加载器: 加载虚拟机的核心类库, 如 java.lang.*。 由虚拟机实现, 没有继承 java.lang.ClassLoader类。
  •   扩展(Extension)类加载器:   它的父加载器是根类加载器。 它负责从: java.ext.dirs 系统属性所指定的类库或从 JDK安装的目录:jre\lib\ext子目录下加载类库。如果把用户创建的Jar文件放在这个目录下,也会自动有扩展类加载器加载。扩展类加载器是纯Java类加载器, 是java.lang.ClassLoader的子类。
  •   系统(System)类加载器:   也称为应用类加载器, 它的父加载器是扩展类加载器。它从环境变量 classpass 或系统属性:java.class.path 所制定的目录中加载类。他是用户自定义类加载器的默认父加载器,系统类加载器是纯Java类, 是java.lang.ClassLoader的子类。

 

 5、类的卸载

        当 Sample类被加载、连接和初始化后, 它的生命周期就开始了,当代表Sample类的 Class 对象不再被引用时, 即不可触及, Class对象的生命周期就会结束。Sample类在方法区的数据也会被卸载。从而结束Sample 类的生命周期。前面介绍的 Java 虚拟机自带的类加载器所加载的类,在虚拟机的生命周期中始终不会被卸载。Java 虚拟机会始终引用这些类加载器,而这些类加载会始终引用它们所加载的类的Class对象, 因此这些类始终是可及的。

 

6、java.lang.Class 类对象

        每一个类都有一个Class对象,每当编译一个新类就产生一个Class对象(Class对象对应着java.lang.Class类),基本类型 (boolean, byte, char, short, int, long, float, and double)有Class对象,数组有Class对象,就连关键字void也有Class对象(void.class)。 每个类的实例运行时的类信息就是用Class对象表示的。它包含了与类有关的信息。其实我们的实例对象就通过Class对象来创建的。
Class类没有公共的构造方法,Class对象是在类加载的时候由Java虚拟机通过调用类加载器中的 defineClass 方法自动构造的,因此不能显式地声明一个Class对象。         

       一个类的实例总是引用代表这个类的 Class 对象,在 Object 类中定义了 getClass()方法, 这个方法返回代表对象所属类的Class对象的引用。此外所有的Java类都有一个静态属性 class, 它引用代表这个类的class对象。

6.2、获取Class对象的三种方式:

       1、Class.forName(“类的全限定名”)。

       2、实例对象.getClass()。

       3、类名.class (类字面常量)。

但是在包装类中有个一个字段TYPE,TYPE字段是一个引用,指向对应的基本数据类型的Class对象,如下所示,左右两边相互等价:

int.class.equals(Integer.TYPE)
boolean.class.equals(Boolean.TYPE)

 

例如:

 // c1 引用代表Study类的Class对象
Class c1 = Study.class; 

// c2 引用代表Study类的Class对象
Class c2 = new Study().getClass() 

//  c3 引用代表Study类的Class对象
 Class c3 = Class.forName("com.dw.study.Study")

c1= c2 = c3 = true

       一旦类被加载了到了内存中,那么不论通过哪种方式获得该类的Class对象,它们返回的都是指向同一个java堆地址上的Class引用。jvm不会创建两个相同类型的Class对象。但是对于任意一个Class对象,都需要由它的类加载器和这个类本身一同确定其在就Java虚拟机中的唯一性,也就是说,即使两个Class对象来源于同一个Class文件,只要加载它们的类加载器不同,那这两个Class对象就必定不相等。这里的“相等”包括了代表类的Class对象的equals()、isAssignableFrom()、isInstance()等方法的返回结果,也包括了使用instanceof关键字对对象所属关系的判定结果。所以在java虚拟机中使用双亲委派模型来组织类加载器之间的关系,来保证Class对象的唯一性。

 

6.3、获取Class对象的三种方式的区别:

       类名.class 来创建对Class对象的引用时,不会自动地初始化该Class对象,但是如果使用Class.forName() 来产生引用,就会立即进行了初始化。如果是实例对象的引用(c2), Class 已经被初始化,则直接获取Class对象的引用。 

测试如下:

 public class A {
        // 同时被static 与 final 修饰的成员在类被编译时,就已经被初始化,放入常量池
        static final String s1 = "A_s1";
        static  String s2 = "A_s2";
        static {
            System.out.println("Loading A");
        }
    }

    public class B {
        static String s1 = "B_s1";
        static {
            System.out.println("Loading B");
        }
    }

    public class ClassTest {

        public static void main(String[] args) throws ClassNotFoundException {
            System.out.println("-------Star A-------");
            Class a = A.class;
            System.out.println("------------");
            System.out.println(A.s1);
            System.out.println("------------");
            System.out.println(A.s2);
            System.out.println("------start B------");
            Class cat = Class.forName("com.example.classReflec.B");
            System.out.println("--------------------");
            System.out.println(B.s1);
            System.out.println("finish main");
        }
    }

打印结果如下:

     如果一个字段同时被static和final修饰,我们称为”编译时常量“,就像A类中的A_s1字段那样,那么在调用这个字段的时候是不会对A类进行初始化的。因为被static和final修饰的字段,在编译期就把结果放入了常量池中了。但是,如果只是将一个域设置为 staticfinal 的,还不足以确保这种行为,就如调用B的B_s1字段后,会强制B进行类的初始化,因为B_s1字段不是一个编译时常量。

 

7、泛型Class引用

      Class引用表示的就是它所指向的对象的确切类型,而该对象便是Class类的一个对象。在JavaSE5中,允许你对Class引用所指向的Class对象的类型进行限定,也就是说你可以对Class对象使用泛型语法。通过泛型语法,可以让编译器强制指向额外的类型检查:

public final class Class<T> implements java.io.Serializable,
                              GenericDeclaration,
                              Type,
                              AnnotatedElement {}

7.1、虽然int.class和Integer.class指向的不是同一个Class对象引用,但是它们是基本类型和包装类的关系,int可以自动包装为Integer,所以编译器可以编译通过。

Class<Integer> c1 = int.class;
   c1=Integer.class;
   //c1=Double.class; 编译报错

7.2、泛型中的类型可以持有其子类的引用吗?不行:虽然Integer继承自Number,但是编译器无法编译通过。

Class<Number> c1 = Integer.class;  //编译报错

7.3、为了使用泛化的Class引用放松限制,我们还可以使用通配符,它是Java泛型的一部分。通配符的符合是”?“,表示“任何事物“:

// ? 表示所有类型
Class<?> c1 = int.class; c1= double.class; // 被限定为该类型,或者该类型的子类型 Class<? extends Number> c1 = Integer.class; c1 = Number.class; c1 = Double.class; // c1=String.class; 报错,不属于Number类和其子类

7.4、通配符?不仅可以与extend结合,而且还可以与super关键字相结合,表示被限定为某种类型,或该类型的任何父类型:

 Class<? super Integer> c1 = Integer.class;
        c1 = Number.class;
        c1 = Object.class;
        c1=Integer.class.getSuperclass();

 

8、Class类的方法

  1. getName():返回String形式的该类的名称。
  2. getSimpleName(): 返回源代码中给出的底层类的简称
  3. getPackage(): 返回该类的包,如果存档或基本代码中没有可用的包信息,则返回 null。
  4. forName(): 返回与带有给定字符串名的类或接口相关联的 Class 对象。
  5. forName(String name, boolean initialize,ClassLoader loader): 上一个方法的重载方法,可以指定类名称、加载时是否运行静态区块、指定类加载器。
  6. newInstance() :创建此 Class 对象所表示的类的一个新实例。如同用一个带有一个空参数列表的 new 表达式实例化该类。如果该类尚未初始化,则初始化这个类。
  7. getClassLoader():返回该Class对象对应的类的类加载器。
  8. getSuperClass():返回某子类所对应的直接父类所对应的Class对象。
  9. getModifiers(): 获取修饰符,表示该类修饰符的 int值。可以通过Modifier.toString()转成字符串形式。
  10. getComponentType() :如果当前类表示一个数组,则返回表示该数组组件的 Class 对象,否则返回 null。
  11. getConstructor(Class[]) :返回当前 Class 对象表示的类的指定的公有构造子对象。
  12. getConstructors() :返回当前 Class 对象表示的类的所有公有构造子对象数组。
  13. getDeclaredConstructor(Class[]) :返回当前 Class 对象表示的类的指定已说明的一个构造子对象。
  14. getDeclaredConstructors() :返回当前 Class 对象表示的类的所有已说明的构造子对象数组。
  15. getDeclaredField(String) :返回一个 Field 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明字段。name参数是一个String,它指定所需字段的简称。注意,此方法不反映数组类的 length 字段。
  16. getDeclaredFields() :返回 Field 对象的一个数组,这些对象反映此 Class 对象所表示的类或接口所声明的所有字段。包括公共、保护、默认(包)访问和私有字段,但不包括继承的字段。
  17. getDeclaredMethod(String, Class[]) :返回一个 Method 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明方法。name 参数是一个 String,它指定所需方法的简称,parameterTypes 参数是 Class 对象的一个数组,它按声明顺序标识该方法的形参类型。如果在某个类中声明了带有相同参数类型的多个方法,并且其中有一个方法的返回类型比其他方法的返回类型都特殊,则返回该方法;否则将从中任选一个方法。
  18. getDeclaredMethods() : 返回 Method 对象的一个数组,这些对象反映此 Class对象表示的类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。返回数组中的元素没有排序,也没有任何特定的顺序。如果该类或接口不声明任何方法,或者此 Class 对象表示一个基本类型、一个数组类或 void,则此方法返回一个长度为 0 的数组。
  19. getField(String) :返回当前 Class 对象表示的类或接口的指定的公有成员域对象。
  20. getFields() :返回当前 Class 对象表示的类或接口的所有可访问的公有域对象数组。
  21. getInterfaces() :返回当前对象表示的类或接口实现的接口。
  22. getMethod(String, Class[]) :返回当前 Class 对象表示的类或接口的指定的公有成员方法对象。
  23. getMethods() :返回当前 Class 对象表示的类或接口的所有公有成员方法对象数组,包括已声明的和从父类继承的方法。
  24. isInstance(Object) :此方法是 Java 语言 instanceof 操作的动态等价方法。
  25. isAssignableFrom(): 确定此Class对象表示的类或接口是否与指定参数表示的类或接口相同,或者是该Class类或接口的超类或超接口。如果是A.isAssignableFrom(B) 确定一个类(B)是不是继承来自于另一个父类(A),一个类(A)是不是实现了另外一个接口(B),或者两个类相同。
  26. isAnnotation(): 判定此Class对象所对应的是否是一个注释对象
  27. isArray():判定此Class对象所对应的是否是一个数组对象
  28. isInterface() :判定指定的 Class 对象是否表示一个接口类型
  29. isPrimitive() :判定指定的 Class 对象是否表示一个 Java 的基本类型。

 
 
 
 
 

 

posted @ 2018-08-16 09:44  邓维-java  阅读(286)  评论(0编辑  收藏  举报