类在内存中生命周期:加载->使用->卸载
类的加载
一、加载:将 class 加载到内存中
二、连接
-
验证:
- 校验合法性/正确性 版本对不对
- cafe babe 0101010101 字节码的文件不是以
cafe babe
开头的 不是正确的字节码
-
准备:准备对应的方法区,创建Class对象,给类变量赋默认值,以及给静态的常量赋初始值
比如public static String name = “李白”;
在类加载的准备阶段 赋上null
在类初始化阶段 才变成“李白”;
比如public static final String name = “李白”;
在此阶段已经完成了赋值
不会触发初始化
-
解析:把字节码中的符号引用 替换为 对应的直接地址引用
可以提高资源查找的速度
class Person {
int age;
String name;
public void show() {
System.out.println(name.toUpperCase);
}
}
如:String
name.toUpperCase
等符号引用 变为 地址
三、类的初始化<clinit>
四、类的初始化可能会滞后:
即使 类没有完成初始化 也能够去使用部分资源
类的初始化
-
调用该类的main() 会导致 该类完成初始化
-
当创建子类对象时 会先进行父类的初始化
-
使用类中的静态资源时 会进行类的初始化
-
创建一个类的对象时 会 进行类的初始化
-
使用反射操作此类时 会进行该类的初始化
类的初始化滞后
-
当使用类中的静态常量的时候 不会触发 类的初始化
因为在类的加载阶段 完成了 对静态常量的赋值
-
当子类使用从父类继承过来的静态方法时 只会对父类完成 类的初始化 子类 不会进行初始化
class Student extends Father { } class Father { public static void show() {}; }
-
创建一个类型的数组时 不会触发对该类的初始化
Father[] father = new Father[20];
使用不同类的加载器加载数据
类的加载器:加载类的
加载器的分类:
- 引导类加载器 根加载器
- 扩展类加载器
- 应用程序加载器
- 自定义类加载器
引导类加载器 根加载器:
加载java的核心类库 jre/rt.jar
String
因为他是根 不是java实现的 所以获取它的对象的时候,常常是null
Class<String> aClass = String.class;
System.out.println(aClass.getClassLoader());//null
扩展类加载器: $ExtClassLoader
它负责加载 jre/lib/ext
扩展库
应用程序加载器:$AppClasaLoader
Person
Student
自定义类加载器:
需要自主加载的内容
加载外部特殊资源 可能有加密内容
高机密的类 需要自定义类加载器
双亲委托模式
-
先由当前类加载器 查看是否加载过这个类 如果没有加载过
-
父类加载器 会查看 是否加载过这个类 若没有,则继续向上传递
-
根加载器 没有加载过这个类 从头往回传再找一遍
ClassNotFoundException
public class Integer{
public static void main(String[] args) {
//Integer.无法使用包装类内的方法,因为已经当前类加载器已经加载过class Integer了,就使用它了
}
}a
通过组合的方式实现的
- 引导类加载器 根加载器
- 扩展类加载器
- 应用程序加载器
各把上面是视为父类加载器
class A {
}
class B {
A parent;
public void setParent(A parent) {
this.parent = parent;
}
}
组合:此时没有通过extents
关键字实现继承,而是把一个类作为一个类的属性的方式