类加载机制
1.类的加载,连接和初始化
java源文件–>编译器–>字节码文件–>jvm–>机器码
1.类的加载
当程序使用到某一个类时,如果这个类还没有被加载,则系统会通过,加载,连接和初始化的步骤来对该类进行初始化.类加载指的时将.class文件读入内存,并创建一个java.lang.Class对象.类加载器通常时由JVM来提供的.可以从不同来源的类加载二进制文件.
- 本地文件系统加载class文件
- JAR包加载class文件
- 通过网络加载class文件
- 把一个Java源文件动态编译,并执行加载
2.类的连接
当类被加载后,系统会生成一个Class对象,接着将会进入连接阶段,连接负责把类的二进制数据合并到jre中,类连接分为三个阶段
- 验证:验证阶段用于检验被加载的类是否正确的内部结构,并和其他类协调一致。
- 准备:类准备阶段则负责为类的变量分配内存,并设置默认初始值。
- 解析:将类的二进制数据中的符号引用替换成直接引用。
3.类的初始化
在类的初始化阶段,虚拟机负责对类进行初始化,主要就是对类变量进行初始化,在java类中对类变量指定初始值有两种方式。
-
声明变量时指定初始值
-
使用静态初始化块为类变量指定初始值
加载顺序实例:
父类
@Getter @Setter public class Parent { //父类的静态代码块 static { System.out.println("这是父类静态代码块!"); } //父类的非静态代码块 { System.out.println("这是父类非静态代码块!"); } //父类的属性 private String name = "张三"; private int age; //父类的构造器 public Parent(){ this.name = "李四"; } }
子类
@Setter @Getter public class Chidren extends Parent { //子类的静态代码块 static{ System.out.println("这是子类的静态代码块!"); } //子类的非静态代码块 { System.out.println("这是子类的非静态代码块!"); } //子类的属性 private double height = 172.5; //子类的构造器 public Chidren(){ this.height = 175.8; } }
测试
/** * 本测试测试类初始化加载顺序 */ @Test public void testClass(){ Chidren chidren = new Chidren(); System.out.println(chidren.getHeight()); }
结果
字节码文件:
父类的.class文件
// Decompiled by Jad v1.5.8e2. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://kpdus.tripod.com/jad.html
// Decompiler options: packimports(3) fieldsfirst ansi space
// Source File Name: Parent.java
package com.jdbc.review;
import java.io.PrintStream;
public class Parent
{
private String name;
private int age;
public Parent()
{
System.out.println("这是父类非静态代码块!");
name = "张三";
name = "李四";
}
public String getName()
{
return name;
}
public int getAge()
{
return age;
}
public void setName(String name)
{
this.name = name;
}
public void setAge(int age)
{
this.age = age;
}
static
{
System.out.println("这是父类静态代码块!");
}
}
子类的.class文件
// Decompiled by Jad v1.5.8e2. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://kpdus.tripod.com/jad.html
// Decompiler options: packimports(3) fieldsfirst ansi space
// Source File Name: Chidren.java
package com.jdbc.review;
import java.io.PrintStream;
// Referenced classes of package com.jdbc.review:
// Parent
public class Chidren extends Parent
{
private double height;
public Chidren()
{
System.out.println("这是子类的非静态代码块!");
height = 172.5D;
height = 175.80000000000001D;
}
public void setHeight(double height)
{
this.height = height;
}
public double getHeight()
{
return height;
}
static
{
System.out.println("这是子类的静态代码块!");
}
}
由上面的字节码文件不难发现:
1.首先编译器把非静态代码块中的代码放到了构造器的最上面.
2.先加载父类的静态代码块然后是子类的,然后其他的和创建对象的过程没有变.
2.类的加载器
2 类加载器
2.1 类加载器简介
类加载器负责将.calss文件加载到内存中,并为之生成对应的java.lang.Class对象。一旦一个类被载入JVM中,同一个类就不会被再次载入了。一个类的全限定类名和其类加载器作为其唯一标识。当JVM启动时,会形成由三个类加载器组成的初始类加载器。
Bootstrap ClassLoader:
1.根类加载器,它负责加载Java的核心类,他非常特殊,它并不是java.lang.ClassLoader的子类,而是JVM自身实现的。
Extension ClassLoader:
2.扩展类加载器,它负责加载JRE的扩展目录(jre/lib/ext)
System ClassLoader:
3.系统类加载器,它负责加载来自java命令的-classpath选项,java.class.path系统属性,或者CLASSPATH环境变量所指定的JAR包和类路径。
2.2 类加载机制
JVM的类加载机制主要有如下三种
- 全盘负责:
所谓全盘负责,就是当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显式使用另外一个类加载器来载入。 - 父类委托:
所谓父类委托,则是先让parent(父)类加载器视图加载该Class,只有在父类加器无法加载该类时才尝试从自己的路径中加载该类。 - 缓存机制:
缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓冲区中搜索该Class,只有当缓存区中不存在该Class对象时,系统才会读取该类对应的二进制数据,并将起转成Class对象,存入缓冲区中。这就是为什么修改了Class后,必须重新启动JVM,程序所做的修改才会生效的原因。