类的加载
当我们第一次使用某一个类时,会将此类的class文件读取到内存,并将此类
的所有信息存储到一个Class对象中
类的加载时机
1.创建类的实例
package com.gls;
/**
* @author gls
* @date 2022年01月25日 15:54
*/
public class Demo03 {
public static void main(String[] args) {
Student stu;
}
}
class Student{
static {
System.out.println("我是Student类的静态代码块,我被执行了.......");
}
}
代码运行截图:
这里我们可以看出,如果Student类被加载了,那么它的静态代码块也一定会被执行一次,但是结果并不是这样,这是因为我们虽然在main函数中定义了Student类型的变量,但是我们并没有去使用它,所以不会被加载
package com.gls;
/**
* @author gls
* @date 2022年01月25日 15:54
*/
public class Demo03 {
public static void main(String[] args) {
new Student();
}
}
class Student{
static {
System.out.println("我是Student类的静态代码块,我被执行了.......");
}
}
这里我们因为创建了Student类的实例对象,所以Student类的静态代码块被执行了
2.类的静态变量,或者为静态变量赋值
package com.gls;
/**
* @author gls
* @date 2022年01月25日 15:54
*/
public class Demo03 {
public static void main(String[] args) {
//new Student();
Student.str="hello world";
}
}
class Student{
static String str;
static {
System.out.println("我是Student类的静态代码块,我被执行了.......");
}
}
3.类的静态方法
package com.gls;
import java.lang.reflect.Method;
/**
* @author gls
* @date 2022年01月25日 15:54
*/
public class Demo03 {
public static void main(String[] args) {
//new Student();
//Student.str="hello world";
Student.Method1();
}
}
class Student{
static String str;
static void Method1(){
System.out.println("我被调用啦........");
}
static {
System.out.println("我是Student类的静态代码块,我被执行了.......");
}
}
4.使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
package com.gls;
import java.lang.reflect.Method;
/**
* @author gls
* @date 2022年01月25日 15:54
*/
public class Demo03 {
public static void main(String[] args) throws ClassNotFoundException {
//new Student();
//Student.str="hello world";
//Student.Method1();
Class<?> stu = Class.forName("com.gls.Student");
}
}
class Student{
// static String str;
// static void Method1(){
// System.out.println("我被调用啦........");
// }
static {
System.out.println("我是Student类的静态代码块,我被执行了.......");
}
}
5.初始化某个类的子类
package com.gls;
import java.lang.reflect.Method;
/**
* @author gls
* @date 2022年01月25日 15:54
*/
public class Demo03 {
public static void main(String[] args) throws ClassNotFoundException {
//new Student();
//Student.str="hello world";
//Student.Method1();
//Class<?> stu = Class.forName("com.gls.Student");
new SmallStudent();
}
}
class Student {
// static String str;
// static void Method1(){
// System.out.println("我被调用啦........");
// }
static {
System.out.println("我是Student类的静态代码块,我被执行了.......");
}
}
class SmallStudent extends Student{
}
- 直接使用java.exe命令来运行某个主类
使用javac Hello.java命令编译产生字节码文件和java Hello.java命令加载字节码文件
注意:以上六种情况的任何一种,都可以导致JVM将一个类加载到方法区
类加载器
类加载器:是负责将磁盘上的某个class文件读取到内存并生成Class的对象
Java中有三种类加载器,它们分别用于加载不同种类的class:
- 启动类加载器(Bootstrap ClassLoader):用于加载系统类库<JAVA_HOME>\bin目录下的class,例如:rt.jar。
- 扩展类加载器(Extension ClassLoader):用于加载扩展类库<JAVA_HOME>\lib\ext目录下的class。
- 应用程序类加载器(Application ClassLoader):用于加载我们自定义类的加载器。
package com.gls;
/**
* @author gls
* @date 2022年01月25日 16:45
*/
public class Test {
public static void main(String[] args) {
/*
类加载器:
概述:是负责将磁盘上的某个class文件读取到内存并生成Class的对象。
如何获取类加载器:类的字节码对象.getClassLoader()
*/
// 获取Test类的类加载器
ClassLoader c1 = Test.class.getClassLoader();
System.out.println(c1);// AppClassLoader
// 获取Student类的类加载器
ClassLoader c2 = Student.class.getClassLoader();
System.out.println(c2);// AppClassLoader
// 获取String类的类加载器
ClassLoader c3 = String.class.getClassLoader();
System.out.println(c3);// null
//API中说明:一些实现可能使用null来表示引导类加载器。 如果此类由引导类加载器加载,则此方法将在此类实现中返回null
System.out.println("====================委派机制=================");
System.out.println(c1.getParent());// PlatformClassLoader
System.out.println(c1.getParent().getParent());// null
}
}
补充:什么是双亲委派机制
- 双亲委派机制是指一个类在收到类加载请求后不会尝试自己加载这个类,而是把该类加载请求向上委派给其父类加载器去完成,其父类加载器在接收到该类加载请求后又会将其委派给自己的父类,以此类推,这样所有的类加载请求都被向上委派到启动类加载器中。若父类加载器在接收到类加载请求后发现自己也无法加载该类(通常原因是该类的Class文件在父类的类加载路径中不存在),则父类会将该信息反馈给子类并向下委派子类加载器加载该类,直到该类被成功加载,若找不到该类,则JVM会抛出ClassNotFoud异常
- 工作原理:
准备验证
- 新建一个java.lang包,创建一个String类
package java.lang;
/**
* @author gls
* @date 2022年01月25日 16:54
*/
public class String {
static{
System.out.println("我是自定义类String,我被加载了");
}
}
- 新建一个测试类StringTest
package com.gls;
/**
* @author gls
* @date 2022年01月25日 16:58
*/
public class StringTest {
public static void main(String[] args) {
java.lang.String string = new java.lang.String();
System.out.println("hello world.......");
}
}
可以看到我们自己定义的String类并没有被加载,这里就有个问题出来了,为什么JVM没有加载我们自定义的String类?
执行过程:我们自己写的String类应该由系统类加载器加载,根据双亲委派机制原理,类的加载器会向上委派给自己的父类加载,请求最终将到达顶层的启动类加载器,到达启动类加载器后,启动类加载器会检查当前类属于哪个包下的类,检查出String类属于java.lang包下的类,启动类加载器会接管String类的加载,不会再向下委托,此时启动类加载器会接管String类的加载
- 测试StringTest类
package com.gls;
/**
* @author gls
* @date 2022年01月25日 16:58
*/
public class StringTest {
public static void main(String[] args) {
java.lang.String string = new java.lang.String();
System.out.println("hello world.......");
StringTest stringTest = new StringTest();
System.out.println("====================================");
System.out.println(stringTest.getClass().getClassLoader());
}
}
代码运行结果:
这时我们可以看出我们自定义的StringTest类是由系统类加载器加载的
- 测试自定义String类
package com.gls;
/**
* @author gls
* @date 2022年01月25日 16:58
*/
public class StringTest {
public static void main(String[] args) {
java.lang.String string = new java.lang.String();
System.out.println("hello world.......");
StringTest stringTest = new StringTest();
System.out.println("====================================");
System.out.println(stringTest.getClass().getClassLoader());
}
}
代码运行截图:
为什么代码执行会报错,我想我们应该都知道了。String类最终会被引导类加载器加载,而核心api里面的String类没有main方法,所以代码会出现错误