1. 类加载的三个阶段
- 三个阶段:
- 加载: 查找并加载类的二进制数据。
- 链接:
- 验证:确保被加载类的正确性。
- 准备:为类的静态变量分配内存,并将静态变量初始化为默认值。
- 解析:将类中符号引用变为直接引用。
- 初始化:为类的静态变量,赋予正确的初始值。
2. java 类主动使用
- 在java规范中, 只有类被首次主动使用的时候,才会被加载。
- 主动使用的情形:
- new 直接使用
- 访问某个类或是接口的静态变量,或者对该静态变量进行赋值操作。
- 调用了静态方法。
- 反射某个类。
- 初始化一个子类。
- 启动类;eg:java HelloWorld
- 特殊的情形:
- 使用子类去调用父类的静态变量,只会初始化父类,而不会初始化子类。
- new Obj[]: 定义数组也不会初始化类。
- 访问被final修饰的属性也不会被初始化。
- public static final long salary = 1000L; ====> 不会被初始化。 因为编译时就能算出结果。
- public static final int random = new Random().nextInt(100); ===> 会被初始化。 需要到运行时才能得到random产生的值。
- 除此之外,其余的都是被动调用,不会导致类的初始化。
- 栈中存放的是对象的引用,类的数据结构存放在本地方法区中,堆中存放类的数据以及对类数据结构的指向句柄。
3. 类初始化
- 静态语句块只能访问以及修改之前的静态变量;之后定义的静态变量,只能修改,不能访问。
public class Demo1 {
public static int x = 10;
static { x = 20;
System.out.println("x: " + x);
y = 30;
}
public static int y = 20;
}
class Test {
public static void main(String[] args) {
System.out.println(Demo1.x);
System.out.println(Demo1.y);
}
}
- 静态代码块,JVM已经保证了线程的安全性。
4. 类加载器以及自定义ClassLoader
- 自定义ClassLoader
public class MyClassLoader extends ClassLoader {
private final static String DEFAULT_DIR = "E:\\classLoader\\";
private String dir = DEFAULT_DIR;
private String classLoaderName;
public MyClassLoader() { super();
}
public MyClassLoader(String classLoaderName) {
super();
this.classLoaderName = classLoaderName;
}
public MyClassLoader(ClassLoader parent, String classLoaderName) {
super(parent);
this.classLoaderName = classLoaderName;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
String classPath = name.replace(".", "/");
File classFile = new File(dir, classPath + ".class");
System.out.println("ClassFileName: " + classFile.getAbsolutePath());
if (!classFile.exists()) {
throw new ClassNotFoundException("The class " + name + " not found in " + dir);
}
byte[] classBytes = loadClassBytes(classFile);
if (classBytes == null || classBytes.length == 0) {
throw new ClassNotFoundException("Load class file " + name + " failed");
}
return this.defineClass(name, classBytes, 0, classBytes.length);
}
private byte[] loadClassBytes(File classFile) {
try (ByteArrayOutputStream bais = new ByteArrayOutputStream();
FileInputStream fis = new FileInputStream(classFile)) {
byte[] buffer = new byte[1];
while (fis.read(buffer) != -1) {
bais.write(buffer);
}
bais.flush();
return bais.toByteArray();
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
}
public class ClassLoaderObject {
static {
System.out.println("Init ClassLoaderObject!");
}
public String hello() {
System.out.println("Print Hello world!");
return "Hello World!";
}
}
public class ClassLoaderTest {
public static void main(String[] args) throws ClassNotFoundException {
MyClassLoader classLoader = new MyClassLoader("MyClassLoader");
Class<?> loadClass = classLoader.loadClass("com.alex.classLoader.ClassLoaderObject");
System.out.println(loadClass);
System.out.println(loadClass.getClassLoader());
}
}
5. 打破父委托机制
- 自定义ClassLoader,并且重写loadClass方法。先使用我们自定义的classLoader加载class;若自定义的classLoader无法加载class,则使用系统classLoader,也就是使用父委托机制。但是对于
@Override
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
Class<?> clazz = null;
if (name.startsWith("java.")) {
try {
ClassLoader system = ClassLoader.getSystemClassLoader();
clazz = system.loadClass(name);
if (clazz != null) {
if (resolve) {
resolveClass(clazz);
}
return clazz;
}
} catch (Exception e) {
e.printStackTrace();
}
}
try {
clazz = findClass(name);
} catch (Exception e) {
e.printStackTrace();
}
if (clazz == null && getParent() != null) {
getParent().loadClass(name);
}
return clazz;
}
- 使用自定义的java.lang.String类。 结论:打破父委托机制,但是无法加载java.lang.*类,因为java对于java.lang包的类有安全校验, 不允许开发者定义一样的类。 eg:自定义一个java.lang.String是无法加载到classLoader中的;只能是使用jdk自带的。
@Override
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
Class<?> clazz = null;
try {
clazz = findClass(name);
} catch (Exception e) {
e.printStackTrace();
}
if (clazz == null && getParent() != null) {
getParent().loadClass(name);
}
return clazz;
}
6. 类的卸载和classLoader卸载
- JVM中只有class满足以下三个条件才能被GC.
- 该类所有的实例都已经被GC。
- 加载该类的classLoader已经被GC。
- 该类的java.lang.Class在任何地方都没有被引用。
7. 线程上下文
- 这里使用Mysql作为案列,进行分析;
Thread.currentThread().getContextClassLoader();可以得到当前线程中所处的ClassLoader;也可以设置当前线程的上下文,以及其中的classLoader
Thread.currentThread().getContextClassLoader();
Class.forName("com.jdbc.mysql");
Connection contection = DriverManager.getConnection("");