加载:查找并加载类的二进制文件。
将class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在内存中创建一个java.lang.class对象(规范并未说明Class对象在哪里,HotSpot虚拟机将其放在了方法区中)用来封装类在方法区内的数据结构
加载class文件的方式
1.从本地系统中直接加载
2.通过网络下载.class文件
3.从zip.jar等归档文件中加载class文件
4.从专有数据库中提取class文件
5.将JAVA源文件动态编译成class文件
连接
1.验证:确保被加载类的正确性。(clas文件按照JVM规范)
2.准备:为类的静态变量分配内存,并将其初始化为默认值。
class Test(){
public static int a = 1;`
}
如上 静态变量a在准备阶段,被初始化为0,而不是1.因为int的默认值是0。
3.解析:把类中的符号引用转换为直接引用。
初始化 : 为类的静态变量赋予正确的初始值。(这边才是真正给a赋值为1的地方)
使用:
卸载:
JAVA程序对类的使用方式可分为2种
1.主动使用(
1.创建类的实例
2.访问某个类或接口的静态变量,或者对该静态变量赋值。
3.调用类的静态方法
4.反射(如Class.forName(com.test.Test)
5.初始化一个类的子类
6.Java虚拟机启动时被标明为启动类的类
7.JDK.1.7开始提供的动态语言支持:
java.lang.invoke.MethodHandle实例的解析结果REF_getStatic,REF_putStatic,REF_invokeStatic句柄对应的类没有初始化,则初始化。
)
public class Test {
public static void main(String[] args) {
System.out.printf(myChild.str);
}
}
class myParent{
public static String str = "父类加载";
static {
System.out.printf("父类正在被加载");
}
}
class myChild extends myParent{
/* public static String str2 = "子类加载";*/
static {
System.out.printf("子类正在被加载");
}
}
***上面输出的是
父类正在被加载 父类加载
并没有初始化myChild 所以 没有打印子类的静态方法。假如需要打印出子类的str,则在main方法中myChild.str2
2.被动使用(不会导致类的初始化)
常量
public class Test {
public static void main(String[] args) {
System.out.printf(myChild.str);
}
}
class myParent{
public static final String str = "父类加载";
static {
System.out.printf("父类正在被加载");
}
}
打印的是
父类加载
由于加了final修饰str,str变成了常量。
常量在编译阶段会存入到调用这个常量的方法所在的类的常量池中。本质上,调用类并没有直接引用到定义常量的类。因此并不会触发定义常量的类的初始化。这里是将常量存放到Test的常量池中,Test 和 myParent没有任何关系。myParent.class删掉也没事。
助记符
ldc表示将int,float或是String类型的常量值从常量池中推至栈顶
bipush表示将单字节(-128 - 127)的常量值推至栈顶
sipush表示将一个短整形常量值(-32768-32767)推送至栈顶
iconst_1将int类型1推送至栈顶(iconst_1 -iconst_5)