您的位置:首页 > 职场人生

黑马程序员基础加强---类加载器

2013-12-12 21:40 495 查看

类加载器

----------- android培训java培训、java学习型技术博客、期待与您交流! ------------

1.作用

类加载器负责加载 Java类的字节代码到 Java虚拟机中。

2.类的加载

当程序主动使用某个类的时候,如果该类还未被加载到内存中,则系统会通过加载、连接、初始化这三个步骤来对该类进行初始化。如果没有意外,JVM会连续完成这三个步骤:可以将这三个步骤称之类的加载。

2.1类加载

指类的class文件读入内存,并为之创建一个java.lang.Class对象,即,当程序使用任何类时,系统都会为之建立一个java.lang.Class对象。

2.2 类的连接

当类被加载之后,系统会为之生成一个对应的Class对象,接着会进入连接阶段,连接阶段会负责把类的二进制数据合并到JRE中,类的连接可分为三个阶段。

1)验证:验证阶段用于检验被加载的类是否有正确的内部结构,并和其他类协调一致。

2)准备:来准备阶段负责为类的静态属性分配内存,并设置为默认初始值。

3)解析:将类的二进制数据中的符号引用替换成直接引用。

2.3 类的初始化

在类的初始化阶段,虚拟机负责对类进行初始化,主要就是对静态属性进行初始化。在Java类中对静态属性指定初始值有两种方式。

1) 声明静态属性时指定初始值;

2) 使用静态代码块为静态属性指定初始值,JVM会按这些语句在程序中排列顺序依次执行它们。

2.3.1 JVM初始化一个类包含如下几个步骤

1)假如这个类还没有被加载和连接,则程序先加载并连接这个类;

2)假如这个类的直接父类还没有被初始化,则先初始化其直接父类;

3)假如类中有初始化语句,则系统依次执行这些初始化语句。

2.3.2 类的初始化时机

当Java程序首次通过下面6种方式来使用某个类或接口的时候,系统就会初始化该类或者接口。

1)创建类的实例,为某个类创建实例的方式包括:使用new关键字来创建实例,通过反射来创建实例,通过反序列化的方式来创建实例;

2)调用某个类的静态方法;

3)访问某个类或接口的静态属性,或者为该静态属性赋值;

4)使用反射方式来强制创建某个类或接口对应的java.lang.Class对象;

5)初始化某个类的子类,当初始化某个类的子类时,该子类的父类都会被初始化;

6)初始化使用java.exe命令来运行某个主类,当运行某个主类时,程序会先初始化该主类。

3.类加载器

Java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,每个类加载负责加载特定位置的类:BookStrap,ExtClassLoader,AppClassLoader

类加载器也是Java类,因为其他是Java类的加载器本身也要被类加载器加载,显然必须有第一个类加载器不是java类,这正是BootStrap。--C++写的二进制代码。

public class ClassLoaderTest02 {
public static void main(String[] args) {
//该程序的类加载器为AppClassLoader加载器
System.out.println(ClassLoaderTest02.class.getClassLoader().getClass()
.getName());
//System类的加载器为根加载器,因为其为C++编写的,所以java方法得不到它的名字
System.out.println(System.class.getClassLoader());
//看看ClassLoaderTest02的类加载器是什么
ClassLoader cl = ClassLoaderTest02.class.getClassLoader();
//若其不为空
while(cl != null){
//输出其类加载器的类的名字
System.out.println(cl.getClass().getName());
//得到其父类加载器
cl = cl.getParent();
}
//输出空值
System.out.println(cl);
}
}
输出:
sun.misc.Launcher$AppClassLoader
null
sun.misc.Launcher$AppClassLoader
sun.misc.Launcher$ExtClassLoader
null


由上例可知:类加载器的递推结果是null,因为其根源为BootStrap,其非java类,所以其没有名字。

当JVM启动时,会形成由3个类加载器组成的初始化类加载器层次结构。

如下图所示。



3.1 三个类加载器

1) Bootstrap :根类加载器,它负责加载Java的核心类,它并不是java.lang.ClassLoader的子类,而是由JVM自身实现的。

2) ExtensionClassLoader:扩展灯加载器,它负责加载JRE的扩展路径中的JAR包中的类。

3) System ClassLoader:系统类加载器,它负责在JVM启动时加载来自java命令的-classpath选项、Java.class.path系统属性,或者CLASSPATH环境所制定的JAR包和类路径。

4. 类加载机制

JVM的类加载机制主要有如下三种机制:

1)全盘负责:指当一个类加载负责加载某个Class时,该Class所依赖的和引用的其他Class也将有类加载器负责载入,除非显式使用另外一个类加载器来载入;

2)父类委托:指先让父类加载器视图加载该Class,只有在父类加载器无法加载该类时才尝试从自己类路径中加载该类;

3)缓存机制:指所有加载过的Class都会被缓存,当程序中需要使用某个Class时候,类加载器先从缓存区中搜寻该Class,只有当缓存去中不存在该Class对象时候,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓存区中。这就是为什么修改了Class后,必须重新启动JVM,程序所做的修改才能生效。

如下代码可看到JVM的各种类加载器

public class ClassLoaderThree {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
// 获得系统类加载器
ClassLoader systemLoader = ClassLoader.getSystemClassLoader();
System.out.println("系统类加载器为:" + systemLoader);
// 获得系统类加载器的父类加载器,得到扩展类加载器
ClassLoader extensionLoader = systemLoader.getParent();
System.out.println("扩展类加载器:" + extensionLoader);
System.out
.println("扩展类加载器的加载路径:" + System.getProperty("java.ext.dirs"));
System.out.println("扩展类加载器的父类是:" + extensionLoader.getParent());
}
}
输出结果:
系统类加载器为:sun.misc.Launcher$AppClassLoader@19821f
扩展类加载器:sun.misc.Launcher$ExtClassLoader@addbf1
扩展类加载器的加载路径:C:\Program Files\Java\jdk1.6.0_10\jre\lib\ext;C:\WINDOWS\Sun\Java\lib\ext
扩展类加载器的父类是:null


注:扩展类加载器的父类是null,这是因为根类加载器并不是java类,并没有继承ClassLoader抽象类,所以扩展类加载器的getParent()方法返回的是null,但是事实上扩展类加载器的父类加载器是根类加载器。

5.类加载器的委托机制(双亲委派机制)

5.1 概念

就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载

5.2类加载器委托机制(双亲委派机制)简述

1.当需要某个类加载器要加载指定的类的时候,并不是立刻到自己的管辖范围去加载指定的字节码文件,而是将这个指定的类传递给父类,由父类尝试去到自己的加载范围去加载这个类。就这样,一级级地向上递推到类加载器的最高父类:JVM的内嵌类加载器---Bootstrap类加载器。

2.Bootstrap类加载器首先在自己的管辖范围jre/lib/rt.jar中找到了指定类名对应的字节码文件。

1)如果找到相应的字节码文件就直接加载进来,结束本次对指定类的加载。

2)如果没找到,Bootstrap按照原路返回到ExtClassLoader,将这个要加载的指定类型交给ExtClassLoader去加载。

3)如果还没找到,就这样一级级向子类类加载器递推。如果在中间的某一级的类加载器找到了指定的类的字节码文件,就结束这次对指定类的加载。

3. 如果退回到最初要加载类的类加载器的时候,并且在这个类加载器本身的管辖范围内还是没有找到这个指定的类的字节码文件,就会直接抛出ClassNotFoundException这个异常。

总结:由某个子类加载器层层向上推进到Bootstrap类加载器加载。若最高父类类加载器无法加载指定的类,又开始层层向下递推到子类加载器进行加载。如果退回到发起者的类加载器还是没有办法加载指定的类,就抛出ClassNotFoundException这个异常

6. 自定义类加载机制

JVM中除了根类加载器之外的所有类加载器都是ClassLoader的子类实例,可以继承并实现其方法

6.1 ClassLoader

|--Java.lang.Object
|--java.lang.ClassLoader

类加载器是负责加载类的对象。ClassLoader 类是一个抽象类。如果给定类的二进制名称,那么类加载器会试图查找或生成构成类定义的数据。一般策略是将名称转换为某个文件名,然后从文件系统读取该名称的“类文件”。

JVM中除了根类加载器之外所有的家族其他成员都是ClassLoader的子类实例,可以继承该类的方法,其中有三个方法比较常用。

6.2 类加载器的常用方法



6.3 自定义的类加载器

6.3.1 必要步骤

要创建用户自己的类加载器,只需要扩展java.lang.ClassLoader类,然后覆盖它的findClass(String name)方法即可,该方法根据参数指定类的名字,返回对应的Class对象的引用。

findClass
protected Class<?> findClass(String name)
throws ClassNotFoundException


使用指定的二进制名称查找类。此方法应该被类加载器的实现重写,该实现按照委托模型来加载类。在通过父类加载器检查所请求的类后,此方法将被 loadClass 方法调用。默认实现抛出一个 ClassNotFoundException。

参数说明:

name - 类的文件路径

返回:得到的 Class 对象

抛出:

ClassNotFoundException- 如果无法找到类。

6.3.2 代码实例

被自定义类加载器加载的类

package com.ping.ClassLoader;
import java.util.Date;
//该类继承自Date,也没有别的意思,只是因为一会儿的过程中,该类的Class文件会乱码,JVM无法正常加载
public class ClassLoaderAttachment02  extends Date{
//该类只重写了toString方法,因为一会儿输出该类对象就会显示“HelloWorld”
public String toString(){
return "HelloWorld";
}
}


自定义类加载器

package com.ping.ClassLoader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class MyClassLoader extends ClassLoader {
/**
* @param args
*/
//这些与类加载器无关,不过之前用了这个来将类的字符码加密了
public static void main(String[] args) {
// TODO Auto-generated method stub
//把args[0]的参数赋给srcPath
String srcPath = args[0];
//保存的文件夹名
String destPath = args[1];
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream(srcPath);
//要保存的文件的文件名
String FileName = srcPath.substring(srcPath.lastIndexOf(File.separatorChar)+1);
//要保存的文件的绝对路径
destPath = destPath + File.separatorChar +FileName;
//保存为输出流
fos = new FileOutputStream(destPath);
//加密
cypher(fis,fos);
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
fis.close();
fos.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//加密解密的方法
public static void cypher(InputStream ips , OutputStream ops){
int b = -1;
try {
//将其里面的内容全部异或,所以用非自定义加载器会乱码
while((b = ips.read()) != -1){
ops.write(b ^ 0xff);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private String classDir;
@Override
protected Class<?> findClass(String name){
try {
//得到文件名
String classFileName = classDir + File.separatorChar + name.substring(name.lastIndexOf('.')+1)  + ".class";
//将其转化为输入流
FileInputStream fis = new FileInputStream(classFileName);
//用缓冲输出流接收
ByteArrayOutputStream bos = new ByteArrayOutputStream();
//解码
cypher(fis,bos);
//接收完关闭
fis.close();
//将缓冲输出流的内容保存为数组
byte [] by = bos.toByteArray();
bos.close();
//用该方法加载类
return defineClass(by, 0, by.length);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

try {
//若不行则用父类加载
return super.findClass(name);
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
};
return null;
}
public MyClassLoader(){}
//把文件夹名传给成员变量
public MyClassLoader(String name){
this.classDir = name;
}
}


测试类

package com.ping.ClassLoader;
import java.util.Date;
public class Test {

/**
* @param args
* @throws ClassNotFoundException
*/
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
//用loadClass方法将类保存为Class文件
Class clazz = new MyClassLoader("itcastlib").loadClass("com.ping.ClassLoader.ClassLoaderAttachment02");
//实例对象
Date date = (Date)clazz.newInstance();
//输出
System.out.println(date);
}
}
输出:
HelloWorld


----------- android培训java培训、java学习型技术博客、期待与您交流! ------------
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: