一、java加载机制
每个Java程序执行前都必须经过编译、加载、连接、和初始化这几个阶段。
加载:
查找并加载类的二级制数据。
连接:
验证、确保类被加载的正确性
准备、为类的静态变量分配内存,并为其初始化默认值
解释、把类中的符号引用转换为直接引用
初始化:
为类的静态变量赋予正确的默认值
加载是指将编译后的java类文件(也就是.class文件)中的二进制数据读入内存,并将其放在运行时数据区的方法区内,然后再堆区创建一个Java.lang.Class对象,用来封装类在方法区的数据结构,即加载后最终得到的是Class对象,并且更加值得注意的是:该Java.lang.Class对象是单实例的,无论这个类创建了多少个对象,他的Class对象时唯一的!!!!。 而 加载并获取该Class对象可以通过三种途径。Class.forName(类的全路径)、实例对象.class(属性)、实例对象getClass()
二、Class.forName、实例对象.class(属性)、实例对象getClass()的区别
A、相同点:得到的都是Java.lang.Class对象
1 package us.codecraft.clazz; 2 3 public class A { 4 5 public static void main(String[] args) throws Exception { 6 System.out.println(A.class);//class us.codecraft.clazz.A 7 A a = new A(); 8 System.out.println(a.getClass());//class us.codecraft.clazz.A 9 System.out.println(Class.forName("us.codecraft.clazz.A"));//class us.codecraft.clazz.A 10 System.out.println("====================================="); 11 System.out.println(A.class.newInstance()); 12 System.out.println(a.getClass().newInstance()); 13 System.out.println(Class.forName("us.codecraft.clazz.A").newInstance()); 14 } 15 }
B、不同点
1、Class cl=A.class; JVM将使用类A的类装载器,将类A装入内存(前提是:类A还没有装入内存),不对类A做类的初始化工作.返回类A的Class的对象
2、Class cl=对象引用a.getClass();返回引用a运行时真正所指的对象所属的类的Class的对象
3、Class.forName("类名"); JAVA人都知道.装入类A,并做类的初始化
可以看出,现在可以看出,newInstance: 弱类型。低效率。只能调用无参构造,new: 强类型。相对高效。能调用任何public构造。Class对象的newInstance()(这种用法和Java中的工厂模式有着异曲同工之妙)实际上是把new这个方式分解为两步,即首先调用Class加载方法加载某个类,然后实例化。 这样分步的好处是显而易见的。我们可以在调用class的静态加载方法forName时获得更好的灵活性,提供给了一种降耦的手段。
三、Class对象解释
Java运行时系统一直对所有的对象进行所谓的运行时类型标识。这项信息纪录了每个对象所属的类。虚拟机通常使用运行时类型信息选准正确方法去执行,用来保存这些类型信息的类是Class类,Class类封装一个对象和接口运行时的状态,当装载类时,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对象实际上描述的只是类型,而这类型未必是类或者接口。例如上面的int.class是一个Class类型的对象。由于历史原因,数组类型的getName方法会返回奇怪的名字
3、1 Class类的常用方法
1、getName():
一个Class对象描述了一个特定类的属性,Class类中最常用的方法getName以 String 的形式返回此 Class 对象所表示的实体(类、接口、数组类、基本类型或 void)名称
2:newInstance()
Class还有一个有用的方法可以为类创建一个实例,这个方法叫做newInstance()
x.getClass.newInstance(),创建了一个同x一样类型的新实例。newInstance()方法调用默认构造器(无参数构造器)初始化新建对象
3、getClassLoader():
返回该类的类加载器
4、getComponentType():
返回表示数组组件类型的 Class
5、getSuperclass() :
返回表示此 Class 所表示的实体(类、接口、基本类型或 void)的超类的 Class
6、isArray()
判定此 Class 对象是否表示一个数组类
3、2 Class的一些使用方法
1、forName和newInstance结合起来使用,可以根据存储在字符串中的类名创建对象,列如:Object obj = Class.forName(s).newInstance();
2、虚拟机为每种类型管理一个独一无二的Class对象。因此可以使用==操作符来比较类对象,列如: if(a.getClass() == A.class)
3、3 在初始化一个类,生成一个实例的时候,newInstance()方法和new关键字除了一个是方法,一个是关键字外,最主要有什么区别
1、它们的区别在于创建对象的方式不一样,前者是使用类加载机制,后者是创建一个新类
2、这主要考虑到软件的可伸缩、可扩展和可重用等软件设计思想,Java中工厂模式经常使用newInstance()方法来创建对象,因此从为什么要使用工厂模式上可以找到具体答案
3、从JVM的角度看,我们使用关键字new创建一个类的时候,这个类可以没有被加载。 但是使用newInstance()方法的时候,就必须保证 :这个类已经加载,这个类已经连接了
而完成上面两个步骤的正是Class的静态方法forName()所完成的,这个静态方法调用了启动类加载器,即加载 java API的那个加载器。
3、4 应用场景
1、加载数据库驱动的时候,
1 Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver"); 2 Connection con=DriverManager.getConnection("jdbc:sqlserver://localhost:1433;DatabaseName==JSP","jph","jph");
为什么在我们加载数据库驱动包的时候有的却没有调用newInstance( )方法呢?
即有的jdbc连接数据库的写法里是Class.forName(xxx.xx.xx);而有一些:Class.forName(xxx.xx.xx).newInstance(),为什么会有这两种写法呢?
刚才提到,Class.forName("");的作用是要求JVM查找并加载指定的类,如果在类中有静态初始化器的话,JVM必然会执行该类的静态代码段,而在JDBC规范中明确要求这个Driver类必须向DriverManager注册自己,即任何一个JDBCDriver的Driver类的代码都必须类似如下:
1 public class MyJDBCDriver implements Driver { 2 3 static { 4 try { 5 DriverManager.registerDriver(new MyJDBCDriver()); 6 } catch (SQLException e) { 7 e.printStackTrace(); 8 } 9 } 10 11 }
既然在静态初始化器的中已经进行了注册,所以我们在使用JDBC时只需要Class.forName(XXX.XXX);就可以了
ClassLoader概念
ClassLoader是用来动态的加载class文件到虚拟机中,并转换成java.lang.class类的一个实例,每个这样的实例用来表示一个java类,我们可以根据Class的实例得到该类的信息,并通过实例的newInstance()方法创建出该类的一个对象,除此之外,ClassLoader还负责加载Java应用所需的资源,如图像文件和配置文件等
ClassLoader类是一个抽象类。如果给定类的二进制名称,那么类加载器会试图查找或生成构成类定义的数据。一般策略是将名称转换为某个文件名,然后从文件系统读取该名称的“类文件”。ClassLoader类使用委托模型来搜索类和资源。每个 ClassLoader实例都有一个相关的父类加载器。需要查找类或资源时,ClassLoader实例会在试图亲自查找类或资源之前,将搜索类或资源的任务委托给其父类加载器
一个应用程序总是由n多个类组成,Java程序启动时,并不是一次把所有的类全部加载后再运行,它总是先把保证程序运行的基础类一次性加载到jvm中,其它类等到jvm用到的时候再加载,这样的好处是节省了内存的开销,因为java最早就是为嵌入式系统而设计的,内存宝贵,这是一种可以理解的机制,而用到时再加载这也是java动态性的一种体现。
顾名思义,类加载器(class loader)用来加载 Java 类到 Java 虚拟机中。一般来说,Java 虚拟机使用 Java 类的方式如下:Java 源程序(.java 文件)在经过 Java 编译器编译之后就被转换成 Java 字节代码(.class 文件)。类加载器负责读取 Java 字节代码,并转换成 java.lang.Class
类的一个实例。每个这样的实例用来表示一个 Java 类。通过此实例的 newInstance()
方法就可以创建出该类的一个对象。实际的情况可能更加复杂,比如 Java 字节代码可能是通过工具动态生成的,也可能是通过网络下载的
在程序运行期间, 通过java.lang.ClassLoader的子类动态加载class文件, 体现java动态实时类装入特性. 加载顺序是:自底向上检查类是否已经装在,有则返回,否则自顶向下尝试加载类
类装入的方式
显式 类装入:发生在使用以下方法调用装入的类的时候: cl.loadClass()(cl 是 java.lang.ClassLoader 的实例) Class.forName()(启动的类装入器是当前类定义的类装入器)
隐式类装入:发生在由于引用、实例化或继承导致装入类的时候(不是通过显式方法调用)
与显式类装入一样,如果类已经装入了,那么只是返回一个引用;否则,装入器会通过委托模型装入类
JDK默认ClassLoader
1、Bootstrp loader
Bootstrp加载器是用C++语言写的,它是在Java虚拟机启动后初始化的,它主要负责加载%JAVA_HOME%/jre/lib,-Xbootclasspath参数指定的路径以及%JAVA_HOME%/jre/classes中的类。
2、ExtClassLoader
Bootstrp loader加载ExtClassLoader,并且将ExtClassLoader的父加载器设置为Bootstrp loader.ExtClassLoader是用Java写的,具体来说就是 sun.misc.Launcher$ExtClassLoader,ExtClassLoader主要加载%JAVA_HOME%/jre/lib/ext,此路径下的所有classes目录以及java.ext.dirs系统变量指定的路径中类库。
3、AppClassLoader
Bootstrp loader加载完ExtClassLoader后,就会加载AppClassLoader,并且将AppClassLoader的父加载器指定为 ExtClassLoader。AppClassLoader也是用Java写成的,它的实现类是 sun.misc.Launcher$AppClassLoader,另外我们知道ClassLoader中有个getSystemClassLoader方法,此方法返回的正是AppclassLoader.AppClassLoader主要负责加载classpath所指定的位置的类或者是jar文档,它也是Java程序默认的类加载器。
三者之间的关系可以通过下图形象的描述
为什么要有三个类加载器,一方面是分工,各自负责各自的区块,另一方面为了实现委托模型
前面说了,java中有三个类加载器,问题就来了,碰到一个类需要加载时,它们之间是如何协调工作的,即java是如何区分一个类该由哪个类加载器来完成呢。 在这里java采用了委托模型机制,这个机制简单来讲,就是“类装载器有载入类的需求时,会先请示其Parent使用其搜索路径帮忙载入,如果Parent 找不到,那么才由自己依照自己的搜索路径搜索类”
下面代码来说明类加载的相关循序,
public class MyDemoTest { public static void main(String[] args) { ClassLoader c = MyDemoTest.class.getClassLoader(); System.out.println(c); ClassLoader c1 = c.getParent(); System.out.println(c1); ClassLoader c2 = c1.getParent(); System.out.println(c2); //打印结果如下 //sun.misc.Launcher$AppClassLoader@18b4aac2 //sun.misc.Launcher$ExtClassLoader@12a3a380 //null } }
可以看出MyDemoTest是由AppClassLoader加载器加载的,AppClassLoader的Parent
加载器是 ExtClassLoader,但是ExtClassLoader
的Parent
为 null
是怎么回事呵,朋友们留意的话,前面有提到Bootstrap Loader是用C++语言写的,依java的观点来看,逻辑上并不存在Bootstrap Loader的类实体,所以在java
程序代码里试图打印出其内容时,我们就会看到输出为null