目录
1.类加载概要分析
1.1. 类加载概述
所谓类加载就是将类从磁盘或网络读到 JVM 内存,然后交给执行引擎执行的过程。
1.2. JAVA 类生命周期分析
![](https://img-blog.csdnimg.cn/direct/6529e8ad152446c6addc28f525b4161b.png)
2.类加载进阶分析
2.1. 加载分析(loading)
2.1.1. 加载基本步骤分析
![](https://img-blog.csdnimg.cn/direct/e51313970dfb477792cdbc1196b4f1f9.png)
2.1.2. 加载路径分析
2.1.3. 加载方式及时机分析
1)隐式加载
2)显式加载
class ClassA {
static {
System.out.println("ClassA");
}
}
public class TestClassLoader02 {
public static void main(String[] args) throws Exception {
// ClassLoader loader=TestClassLoader02.class.getClassLoader();
// loader.loadClass("cgb.java.jvm.loader.ClassA");
Class.forName("cgb.java.jvm.loader.ClassA");
// Class.forName("cgb.java.jvm.loader.ClassA", true, loader);
}
}
2.2. 连接分析 (linking)
2.2.1. 验证(Verification)
2.2.2. 准备(Preparation)
假设一个类变量的定义为:public static int value = 3;那么变量 value 在准备阶段过后的初始值为 0,而不是 3,把 value 赋值为 3 的 动作将在初始化阶段才会执行。
假设上面的类变量 value 被定义为: public static final int value = 3;编译时 Javac 将会为 value 生成 ConstantValue 属性,在准备阶段虚拟机就会 根据ConstantValue 的设置将 value 赋值为 3
2.2.3. 解析(Resolution)
说明:相同的符号引用不同 JVM 机器上对应的直接引用可能不同,直接引用一般 对应已加载到内存中的一个具体对象。
2.3. 初始化分析(Initialization)
1)声明类变量时指定初始值。
2)使用静态代码块为类变量指定初始值
class A{ public static int a=10; static { System.out.println("A.a="+a); } } class B extends A{ static { System.out.println("B"); } }
public class TestClassLoader03 { public static void main(String[] args) { System.out.println(B.a); } }
当通过 B 对象访问 A 类的 a 属性时不会执行 B 类的静态代码块
成员变量的初始化过程分析(该过程不属于initialization过程)
比如:
Object o = new Object();
private int a=7;
分为两步执行,
第一步 给o对象或者 a申请内存空间,此时它们都还没有赋值,此时是默认值。 o=null , a =0;
第二步:调用构造方法,调用完构造方法后,会给o赋值初始值,a=7;
3.类加载器应用分析
3.1. 类加载器概要分析
3.1.1. 类加载器简介
1) getParent() 返回类加载器的父类加载器2) loadClass(String name) 加载名称为 name 的类 .3) findClass(String name) 查找名称为 name 的类 .4) findLoadedClass(String name) 查找名称为 name 的已经被加载过的类5) defineClass(String name, byte[] b, int off, int len) 把字节 数组 b 中的内容转换成 Java 类。
3.1.2. 类加载器的层次架构
![](https://img-blog.csdnimg.cn/direct/768492d86dca4693ba3962da9ae4b3a7.png)
![](https://img-blog.csdnimg.cn/direct/dfd3c8037ab44a008ac27fa3760c9bbb.png)
public static void doMethod01() {
ClassLoader loader=ClassLoader.getSystemClassLoader();
System.out.println(loader);
System.out.println(loader.getParent());
System.out.println(loader.getParent().getParent());
}
public static void doMethod02() {
// 获取一个类的classLoader
ClassLoader loader =Thread.currentThread().getContextClassLoader();
System.out.println(loader);
System.out.println(loader.getParent());
System.out.println(loader.getParent().getParent());
}
3.1.3 为什么要采用双亲委派
答:主要是为了安全。
反证法:
比如任意给定一个类,如果自定义的类加载器都能load 到内存。我现在给以class。叫Java.lang.String 交给自定义的classLoader 加载到内存里。然后将执行结果,我打包成一个类库发给客户。比如,客户在输入密码时,会将密码存成java.lang.String 类型对象。 因为这个是我写的。这块的代码中处理一个发送密码给自己的逻辑。这样凡是使用Java,并且用过我的类库的代码,密码都会发给我了。这就是严重的安全问题。
双亲委派模型就不会!!!
当类加载过程发现是 java.lang.String时,直接就一层层向上委派,到BootStrapLoader时,从它的缓存中加载出来了。不会出现被自定义classLoader篡改的场景。
次要原因:节约资源,不用重复加载。
3.1.4 不同类加载器的加载范围
bootStrapClassLoader加载, jdk 安装路径,jre/lib/底下的rt.jar包等
extensinonClassLoader加载,jdk安装路径, jre/lib/ext底下的jar包
appClassLoader加载,项目的 classes路径底下的class文件
证明:
3.2. 自定义类加载器
3.2.1. 准备工作
在指定包中创建一个自己写的类,例如:
package pkg;
public class Search {
static {
System.out.println("search static");
}
public Search() {
System.out.println("search constructor");
}
}
3.2.2. 基于 ClassLoader 创建
代码实现:
class MyClassLoader01 extends ClassLoader {
private String baseDir;
public MyClassLoader01(String baseDir) {
this.baseDir=baseDir;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException
{
byte[] classData = loadClassBytes(name);
if (classData == null) {
throw new ClassNotFoundException();
} else {
return defineClass(name, classData, 0, classData.length);
}
}
/**自己定义*/
private byte[] loadClassBytes(String className) {//pkg.Search
String fileName =baseDir+className.replace('.', File.separatorChar)
+ ".class";
System.out.println("fileName="+fileName);
InputStream ins=null;
try {
ins= new FileInputStream(fileName);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int bufferSize = 1024;
byte[] buffer = new byte[bufferSize];
int length = 0;
while ((length = ins.read(buffer)) != -1) {
baos.write(buffer, 0, length);
}
return baos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(e);
}finally {
if(ins!=null)try{ins.close();}catch(Exception e) {}
}
}
}
public class TestMyClassLoader01 {
public static void main(String[] args) throws Exception{
String baseDir="F:\\WORKSPACE\\";
MyClassLoader01 classLoader = new MyClassLoader01(baseDir);
//此类不要和当前类放相同目录结构中
String pkgCls="pkg.Search";
Class<?> testClass = classLoader.loadClass(pkgCls);
Object object = testClass.newInstance();
System.out.println(object.getClass());
System.out.println(object.getClass().getClassLoader());
}
}
3.2.3. 基于 URLClassLoader 创建
class MyClassLoader02 extends URLClassLoader {
public MyClassLoader02(URL[] urls) {
super(urls,null);// 指定父加载器为 null
}
}
public class TestMyClassLoader02 {
public static void main(String[] args)throws Exception {
File file=new File("f:\\workspace\\");
//File to URI
URI uri=file.toURI();
URL[] urls={uri.toURL()};
ClassLoader classLoader = new MyClassLoader02(urls);
Class<?> cls = classLoader.loadClass("pkg.Search");
System.out.println(classLoader);
Object obj = cls.newInstance();
System.out.println(obj);
}
}
3.3. 基于类加载器实现热替换
class MyClassLoader03 extends ClassLoader {
private String basedir; // 需要该类加载器直接加载的类文件的基目录
private HashSet<String> loadClasses; // 需要由该类加载器直接加载的类名
public MyClassLoader03(String basedir, String[] classes)
throws IOException {
// 指定父类加载器为 null,打破双亲委派原则
super(null);
this.basedir = basedir;
loadClasses = new HashSet<String>();
customLoadClass(classes);
}
// 获取所有文件完整路径及类名,刷入缓存
private void customLoadClass(String[] classes) throws IOException {
for (String classStr : classes) {
loadDirectly(classStr);
loadClasses.add(classStr);
}
}
// 拼接文件路径及文件名
private void loadDirectly(String name) throws IOException {
StringBuilder sb = new StringBuilder(basedir);
String classname = name.replace('.', File.separatorChar) + ".class";
sb.append(File.separator).append(classname);
File classF = new File(sb.toString());
instantiateClass(name,new FileInputStream(classF),
classF.length());
}
// 读取并加载类
private void instantiateClass(String name, InputStream fin, long len)
throws IOException {
byte[] raw = new byte[(int) len];
齐雷 qilei@tedu.cn 1-14
fin.read(raw);
fin.close();
defineClass(name, raw, 0, raw.length);
}
@Override
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
Class<?> cls;
// 判断是否已加载(在名字空间中寻找指定的类是否已存在)
cls = findLoadedClass(name);
if(!this.loadClasses.contains(name) && cls == null)
cls = getSystemClassLoader().loadClass(name);
if (cls == null)throw new ClassNotFoundException(name);
if (resolve)resolveClass(cls);
return cls;
}
}
public class TestMyClassLoader03 {
public static void main(String[] args) throws Exception {
MyClassLoader03 loader=new MyClassLoader03("f:\\workspace\\",
new String[] {"pkg.Search"});
Class<?> cls = loader.loadClass("pkg.Search");
System.out.println(cls.getClassLoader());
Object search = cls.newInstance();
System.out.println(search);
Thread.sleep(20000);
loader=new MyClassLoader03("f:\\workspace\\",
new String[] {"pkg.Search"});
cls=loader.loadClass("pkg.Search");
System.out.println(cls.getClassLoader());
search = cls.newInstance();
System.out.println(search);
}
}