类加载
- 在Java代码中,类型的加载、连接与初始化过程都是在程序运行期完成的
- 提供了更大的灵活性,增加了更多的可能性
注意 :
类加载:最常见(不是唯一)的一种情况,将已经存在的类的class文件从硬盘上面加载到内存里面
连接:确定好类与类之间的关系,并且对字节码的进行相关处理,比如验证、校验
初始化:对一些类的静态变量赋值
小知识:
1.类加载的时候一定是加载的字节码,字节码没有问题才能被虚拟机所执行,如果字节码产生问题,虚拟机就拒绝执行。
2.字节码是编译帮我们生产的,理想情况下是没有错的,但我们可以通过一些工具如16进制编辑器修改字节码文件换句话说字节码文件是可以人为操作的,因此就会存在错误的可能
类加载器
用来加载类的工具(每一个类都是由类加载器加载到内存中的)
java里的每一个类的数据结构、信息最终都被纳入到JVM的管辖范围内,换句话说每一个类都会进入JVM管理的内存当中,如何进入?由类加载器完成!
Java虚拟机与程序的生命周期
在如下几种情况下,Java虚拟机将结束生命周期
1.执行了System.exit()方法(我们在程序中调用了此方法)
2.程序正常执行结束
3.程序在执行过程中遇到了异常或错误而终止
4.由于操作系统出现错误而导致Java虚拟机进程终止
小知识:Java虚拟机本身就是一个进程
类的加载、连接、初始化
- 加载:查找并加载类的二进制数据
- 连接:
- 验证:确保被加载类的正确性(满足JVM对字节码格式的要求)
- 准备:为类的静态变量分配内存,并将其初始化为默认值
- 解析:将类与类之间的符号引用转换为直接引用 - 初始化:为类的静态变量赋予正确的初始值
注意 :
1.在连接的准备阶段,不存在类的对象(因此在连接的准备阶段强调的是静态变量,实例变量此时还没有)
2.符号引用是虚拟机在不知道内存地址时(预编译时)代表的实例,直接引用是已经有了内存地址了,这个时候的符号引用就会转化成直接引用了.(可以用指针的方式,来直接定向到特定的目标的方法或者成员变量)
在类的加载过程中的解析阶段,Java虚拟机会把类的二进制数据中的符号引用 替换为 直接引用,如Worker类中一个方法:
public void gotoWork(){
car.run(); //这段代码在Worker类中的二进制表示为符号引用
}
在Worker类的二进制数据中,包含了一个对Car类的run()方法的符号引用,它由run()方法的全名 和 相关描述符组成。在解析阶段,Java虚拟机会把这个符号引用替换为一个指针,该指针指向Car类的run()方法在方法区的内存位置,这个指针就是直接引用。
流程图:
所有的Java虚拟实现必须在每个类或接口被Java程序“首次主动使用”时才能初始化他们
类的使用与卸载
- 使用:使用这个类。比如创建对象、调用类的相关方法等
- 卸载:字节码文件加载到内存里面后,形成了自己的数据结构驻留在内存里面,可以在内存中销毁
注意 :
类一旦卸载完后,就不能使用这个类再去创建对象因为这个类型在内存里已经没有了(当然我们可以再把类重新加载到内存里面)
- Java程序对类的使用方式可分为两种:
- 主动使用(七种)
- 创建类的实例
- 访问某个类或者接口的静态变量,或者对该静态变量赋值
- 调用类的静态方法
- 反射(如:Class.forName(“com.test.Test”))
- 初始化一个类的子类(也是对父类的主动使用)
- Java虚拟机启动时被标明为启动类的类(含有main方法的类)
- JDK1.7开始提供的动态语言支持(了解即可)
- 被动使用
除了主动使用的七种情况,其它使用Java类的方式都被看作对类的被动使用,都不会导致类的初始化(该类可能加载连接也可能根本不会加载唯一明确的是被动使用是不会初始化的)
类的加载
类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在内存中创建一个java.lang.Class对象(规范并未说明Class对象位于哪里,HotSpot虚拟机将其放在了方法区内)用来封装类在方法区内的数据结构
加载.class文件的方式
- 从本地系统中直接加载(在硬盘里的.class文件)
- 通过网络下载.class文件
- 从zip,jar等归档文件中加载.class文件
- 从专有数据库中提取.class文件
- 将Java源文件动态编译为.class文件(动态代理 这个类是在运行期才生成出来的 JSP)
练习主动、被动使用
例子1:
package test;
/*
* 对MyParent1的主动使用,并未对MyChild1主动使用
*(所有的Java虚拟实现必须在每个类或接口被Java程序“首次主动使用”时才能初始化他们)
*/
public class demo1 {
public static void main(String[] args) {
System.out.println(MyChild1.str); // 初始化一个类的子类(也是对父类的主动使用)
}
}
class MyParent1{
public static String str = "hello word";
static {
System.out.println("MyPartent static block");
}
}
class MyChild1 extends MyParent1{
static {
System.out.println("MyChild static block");
}
}
/*
* 运行结果:
* MyPartent static block
* hello word
*/
例子2:
package test;
/**
* 主动使用了2个类:
* 访问某个类或者接口的静态变量,或者对该静态变量赋值
* 初始化一个类的子类(也是对父类的主动使用)
*
*/
public class demo2 {
public static void main(String[] args) {
System.out.println(MyChild2.str2);
}
}
class MyParent2{
public static String str1 = "hello word";
static {
System.out.println("MyPartent static block");
}
}
class MyChild2 extends MyParent2{
public static String str2 ="welcome";
static {
System.out.println("MyChild static block");
}
}
/*
* 运行结果:
* MyPartent static block
* MyChild static block
* welcome
*/
注意 :当一个类初始化时,要求其父类全部都已经初始化完毕(若父类还有父类就要求父类的父类全部初始化完毕才初始化父类再初始化本类 一直父上去…)