Java基础 之 类的加载与对象初始化

2013/03/05 Java

类的加载

基本原理

所有类都由类装载器载入,载入内存中的类对应一个 java.lang.Class 实例。 已被加载的类由该类的类加载器实例与该类的全路径名的组合标识。设有 packagename.A Class ,分别被类加载器 CL1 和 CL2 加载,则系统中有两个不同的 java.lang.Class 实例: <CL1, packagename.A> 和 <CL2, packagename.A> 。

Java类加载器

存在一个 Bootstrap Loader (以下简称为 BL ),由 C++ 写成,负责在虚拟机启动后一次性加载 Java 基础类库中的所有类。其他的类装载器由 Java 写成,都是 java.lang.ClassLoader 的子类。除 BL 之外的所有类装载器都有一个 parent 属性,指向其父装载器。查阅 java.lang.ClassLoader 的源码,不难发现类装载器的委托装载方式。如果用户没有使用自定义类加载器,那么默认使用系统类加载器SystemClassLoader(SystemClassLoader)

Java 加载器的委派模型:

                                            ---------------
                                           | Bootstrap     |
                                           | Class Loader  |  null
                                             ---------------
                                                   
                                            -----parent----
                                           | Extension     |
                             ----------   | Class Loader  |
                             |              ---------------
                             |                     
                  -----parent----           ------parent-----
                 |    URL        |         |  AppClassLoader|
                 |   Class Loader|         | Class Loader   |
                  ---------------           -----------------
                                                   
                                             ------parent-----
                                           |      System        |
                                           | Class Loader     |
                                             -----------------
                              ----------------------|-------------------------
                                 ------parent----            -----parent-----
                                 |   User-def    |          |    User-def    |
                                 | Class Loader  |          | Class Loader   |
                                 -----------------           -----------------

同时,上图还说明了类加载器在Java语言中发挥的很重要的2点作用:

1. 类加载器的委派模型:

假设AppClassLoader需要加载一个类,它会首先委托其父加载器ExtClassLoader来加载此 类,ExtClassLoader也会递归性的委托其父加载器(而不是Super,不是继承关系)Bootstrap Loader来加载此类,如果Bootstrap Loader在sun.boot.class.path下找到被加载类时即加载,如果无法找到时再依次由子类加载器去加载。查找顺序是:cache→parent→self .委派模型是针对Java安全而 设计的,这也印证了Java语言的设计初衷:面向网络的编程语言。

但是 我们通过 getParent 方法获取当前加载器的加载器时,如果当前加载器是 ExtClassLoader 通过getParent 会得到null,并不意味着他没有parent,而是这个parent就是由C++编写的bootstrap class loader,他并不是classloader的子类,也就无法使用getParent()方法获得返回了。。Initiating class loader是指那些直接被程序要求加载某类的加载器,而defining class loader就是真正加载了某类的加载器。

2. 命名空间

由同一个类加载器所加载的类只能引用该加载器和其父加载器所加载的其他类,这也是类加载器的命令空间的作用。

理解 委派加载的原因:

为什么要设计的这么复杂呢?其中一个重要原因就是安全性。比如在Applet中,如果编写了一个java.lang.String类并具有破坏性。假如不采用这种委托机制,就会将这个具有破坏性的String加载到了用户机器上,导致破坏用户安全。但采用这种委托机制则不会出现这种情况。因为要加载 java.lang.String类时,系统最终会由Bootstrap进行加载,这个具有破坏性的String永远没有机会加载。

类的加载的三大步骤:

  • 装载
  • 连接
  • 初始化
装载

这个过程就是把一个类型的二进制数据解析为方法区中的内部数据结构,并在堆上建立一个 java.lang.Class 对象的过程。

连接
      验证:确定类型符合java语言的语义;
      准备:创建Java类中的静态域(静态域存在常量区),并将这些域的值设为默认值。
      解析:在类型的常量池中寻找类,接口,字段和方法的符合引用把这些符号引用替换成直接引用的过程。 ###### 初始化 初始化静态变量、静态代码块。(这里的初始化是指 类的初始化,只初始化静态变量;非静态变量的初始化是属于对象初始化,是在类被使用时通过构造函数进行初始化的。类初始化并不会在类加载到内存中后自动完成,是需要类主动触发的)

类的初始化操作 是需要触发的,并且只会执行一次,主要使用场景:

  1. 调用了类的main方法
  2. 通过反射构建了类的对象
  3. 调用了类的静态方法
  4. 访问了类的静态属性(如果直接访问父类的静态属性,那么只能导致父类被初始化,子类不会被初始化)
  5. 通过new关键字直接构建对象
  6. 在顶层Java类中执行assert语句
理解初始化:

① 类被加载完成后 只是表明这个类在内存中可以进行调用了。

② 建立的 java.lang.Class对象作用(参考); Class 没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的,因此不能显式地声明一个Class对象。 虚拟机为每种类型管理一个独一无二的Class对象。也就是说,每个类(型)都有一个Class对象。运行程序时,Java虚拟机(JVM)首先检查是否所要加载的类对应的Class对象是否已经加载。如果没有加载,JVM就会根据类名查找.class文件,并将其Class对象载入。基本的 Java 类型(boolean、byte、char、short、int、long、float 和 double)和关键字 void 也都对应一个 Class 对象。 每个数组属于被映射为 Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象。一个堆上的class实例:是连接类实例与方法区数据结构的桥梁,通过class实例,类的实例能够知道类本身的详细构成信息。一般某个类的Class对象被载入内存,它就用来创建这个类的所有对象。

完成类的加载后,被加载的类在内存中是个什么样子呢 ?

初始化

初始化 分为 类初始化 和 对象初始化(参考):

类初始化:

类初始化是需要触发,在上面已经讲到; 类初始化只会初始化类的静态变量、静态代码块。而且是按照先父类 后子类的顺序;类的初始化一定是在对象初始化之前完成(静态成员初始化先于非静态成员初始化);

对象初始化:

(原则:类的非静态变量在编译为class文件之后,都会被移到构造函数中初始化;) new 一个对象A,首先会寻找类A的构造函数,系统默认在构造函数最前面添加了super();除非显示声明; 执行super();前会为对象的成员变量在堆中分配内存,并且声明变量。(注意这里只是声明) 执行super();查找父类的构造函数,原则与2相同,一直到object对象。 然后从父类到子类依此顺序执行构造函数;

例如: A a = new A();

备注:类的成员变量,不管程序有没有显式的进行初始化,Java虚拟机都会先自动给它初始化为默认值。但是方法里面的局部变量声明之后,Java虚拟机就不会自动给它初始化为默认值,因此局部变量的使用必须先经过显式的初始化(基本数据类型作为局部变量是放在栈中)。

理解 继承关系:

子类会继承父类的所有域和方法。在一个子类被创建的时候,首先会在内存中创建一个父类对象,然后在父类对象外部放上子类独有的属性,两者合起来形成一个子类的对象。所以所谓的继承使子类拥有父类所有的属性和方法其实可以这样理解,子类对象确实拥有父类对象中所有的属性和方法,但是父类对象中的私有属性和方法,子类是无法访问到的,只是拥有,但不能使用。

理解对象在内存中的资源分配:

请参考:Java对象内存结构

理解方法重载:

初始化子类时,为什么父类中的方法被子类重载后,父类构造函数调用的这个重载方法却是子类中的那个重载方法?

请参考: Java中的继承分析 和 Java 的动态绑定

 Parent p1 = new Parent();
 Parent c1 = new Chield();

通过 p1、c1 调用的属性成员,都会自动到Parent中找(属性绑定到定义的类型),调用的方法,回到new出来的对象中找(方法绑定到对象) 原因是:在Java中,属性绑定到类型,方法绑定到对象! 备注: 如果方法是private、static、final的,或者是一个构造器,那么编译器能准确地判断应该调用那个方法,这称为静态绑定,而对其他的方法,要调用那个方法只有根据隐式参数的实际类型来决定,并且在运行时使用动态绑定。

Search

    Post Directory