Java 类加载机制
Java 内存分析
jvm把class文件加载到内存,并对数据进行校验、解析和初始化,最终形成jvm可以直接使用的java类型的过程。
-
加载:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区中的运行时数据结构,在堆中生成一个代表这个类的java.lang.Class对象,作为方法区类数据的访问入口。
-
链接:将java类的二进制代码合并到jvm的运行状态之中的过程,链接过程又分为3个过程:
(1)、验证:确保加载的类信息符合jvm规范,没有安全方面的问题
(2)、准备:正式为类变量(static变量)分配内存并设置类变量初始值的阶段, 这些内存都将在方法区中进行分配
(3)、解析:虚拟机常量池内的符号引用替换为直接引用的过程。(比如String s ="aaa",转化为 s的地址指向“aaa”的地址)
-
初始化:初始化阶段是执行类构造器<clinit>()方法的过程。类构造器<clinit>()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static块)中的语句合并产生的。
当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先初始化其父类的初始化
虚拟机会保证一个类的构造器方法在多线程环境中被正确加锁和同步
当访问一个java类的静态域时,只有真正声明这个静态变量的类才会被初始化。
代码解析:
public class Demo {
public static void main(String[] args) {
A a = new A();
System.out.println(a.m);
}
}
class A{
static {
System.out.println("A 类的静态代码块初始化");
m = 300;
}
static int m = 100;
public A(){
System.out.println("A 类的无参构造器初始化");
}
}
输出结果:
Java类的生命周期
类初始化
类的主动引用(一定会发生类的初始化)
-
当虚拟机启动,先初始化main方法所在的类
-
new 一个类的对象
-
调用类的静态成员(除了 final 常量)和静态方法
-
使用 java.lang.reflect 包的方法对类进行反射调用
-
当初始化一个类,如果其父类没有被初始化,则先会初始化它的父类
类的被动引用(不会发生类的初始化)
-
当访问一个静态域时,只有真正声明这个域的类才会被初始化。如:当通过子类引用父类的静态变量,不会导致子类初始化
-
通过数组定义类引用,不会触发此类的初始化
-
引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了)
代码解析:
public class Demo {
static {
System.out.println("main被加载");
}
public static void main(String[] args) throws ClassNotFoundException {
//主动引用
//Son son = new Son();
//反射
//Class.forName("com.tx.reflection.Son");
//不会产生类的引用的方法
//System.out.println(Son.f);
//数组
//Son[] sons = new Son[5];
//常量
System.out.println(Son.m);
}
}
class Father{
static int f = 2;
static {
System.out.println("父类被加载");
}
}
class Son extends Father{
static {
System.out.println("子类被加载");
s = 3;
}
static int s = 1;
static final int m = 4;
}
Java 类加载器
类加载的作用:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区中的运行时数据结构,在堆中生成一个代表这个类的java.lang.Class对象,作为方法区类数据的访问入口。
类缓存:标准的 JavaSE 类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过 JVM 垃圾回收机制可以回收这些 Class 对象
JVM 规范定义了如下类型的类的加载器:
-
引导类加载器(Bootstrap)
用 本地代码实现的类加载器,它负责将 <JAVA_HOME>/lib 下面的核心类库 或 -Xbootclasspath 选项指定的 jar 包等 虚拟机识别的类库加载到内存中。由于启动类加载器是由 native 方法实现加载过程,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作。
-
扩展类加载器(Extension)
由 Sun 的 ExtClassLoader(sun.misc.Launcher$ExtClassLoader)实现的,它负责将 <JAVA_HOME >/lib/ext 或者由系统变量-D java.ext.dir 指定位置中的类库加载到内存中。开发者可以直接使用标准扩展类加载器。
-
系统类记载器(System)
也称应用类加载器,由 Sun 的 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的,负责将用户类路径java -classpath 或 -D java.class.path 所指的目录下的类与 jar 包加载到内存中。开发者可以直接使用系统类加载器,是最常用的加载器
Bootstrap ClassLoader :最顶层的加载类,主要加载核心类库,也就是我们环境变量下面 %JRE_HOME%\lib 下的rt.jar、resources.jar、charsets.jar和class等。另外需要注意的是可以通过启动jvm时指定-Xbootclasspath和路径来改变Bootstrap ClassLoader的加载目录。比如java -Xbootclasspath/a:path被指定的文件追加到默认的bootstrap路径中。我们可以打开我的电脑,在上面的目录下查看,看看这些jar包是不是存在于这个目录
Extention ClassLoader :扩展的类加载器,加载目录%JRE_HOME%\lib\ext目录下的jar包和class文件。还可以加载-D java.ext.dirs选项指定的目录。
Appclass Loader:也称为SystemAppClass。 加载当前应用的classpath的所有类。
这三种类加载器的加载顺序:Bootstrap ClassLoader > Extention ClassLoader > Appclass Loader
层次关系:
public static void main(String[] args) throws ClassNotFoundException {
//获取系统类的加载器
ClassLoader systemClassLoader = C