MOYUN(/Java/SQL/Linux/DevOps/运维/架构/管理/敏捷/开发)

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

一、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加载器加载的,AppClassLoaderParent 加载器是 ExtClassLoader,但是ExtClassLoaderParent为 null 是怎么回事呵,朋友们留意的话,前面有提到Bootstrap Loader是用C++语言写的,依java的观点来看,逻辑上并不存在Bootstrap Loader的类实体,所以在java程序代码里试图打印出其内容时,我们就会看到输出为null  

 

posted on 2018-05-13 18:28  moyun-  阅读(448)  评论(0编辑  收藏  举报