我们所写的代码就相当于:
一组 *.java 文件 + 一组资源文件(多么媒体文件、配置文件)+ 相关存储中的数据(MySQL、Redis...)
我们现在的程序数据放在硬盘中,要让运行时电脑运行我们的程序,实际就是让该电脑的CPU 运行我们程序中的指令数据。
但CPU 无法直接和硬盘(IO设备)做直接的数据交换
CPU 只能和内存中的数据打交道,现在数据又放在硬盘中
所以我们应该先把数据从硬盘中读取到内存中。
这个过程是以类为单位(某个*.class 文件)进行读取的
一次一个类文件的加载,按需进行加载(用到哪个加载哪个)
这个过程就被称为 类的加载(Class Load)。这个加载过程也得依赖某些指令来执行,这些指令(程序)就被称为类的加载器(ClassLoader)。
这些指令是属于 JVM 程序的一部分。换言之, JVM 中有进行必要的类的加载的职责。
javac.exe 编译器是 A 电脑上进行的,靠着 JVM 的规范来约束
java.exe JVM(运行期)是 B 电脑上进行
用于给 Java 开发人员使用的小包裹
用于给一般用户运行好别人写好的 Java 程序(以 *.class 为代表的数据文件,可以是*.jar / *.war...)的一组环境
开发人员也会用到 JRE (不可能光开发,不去运行测试)
class file 格式一直在更新
javac.exe 5编译的类能不能用java.exe 17运行
1.8 :让编译器按照1.8(Java8)的标准检查 java 文件语法
1.8 :编译出来的类文件里,写明是 1.8(Java8)版本
常量池(很多常量)、类的基本属性、方法(构造、静态构造代码块)、签名 + 指令(语句)、字符串(字面量)、数字、符号
类文件的数据 = 类的信息 + 常量池 + 方法(方法的信息 + 指令(字节码形式))
ClassLoader 要加载一个类,主要就是要加载这些数据到内存中。
大前提:用到了一个类,但是这个类还不在内存中,就需要加载(如果已经在内存中,就没必要进行第二次加载)
什么叫用到:
class Main { psvm() { ... } }
java Main
以 Main 类作为主类启动 JVM 。需要加载 Main 类,因为用到了 Main 类的静态方法(main 方法)
Object类 也需要加载,因为 Object 是 Main 的父类
类、接口、枚举、注解都在这块称为“类”
根据要加载的类名,找到对应的类文件( *.class)
验证类文件合法,没有错误(还得考虑安全问题,有可能写恶意的类也加载进来)
解析数据(按照规范格式)
类里面用到很多用字符串字面量写死的数据,比如“java/lang/Object”
但实际程序(JVM)执行中,需要的java.lang.Object 在内存中对应的数据
所以要把 com.lingqi.demo.Main 和 java.lang.Object 根据字面量“链接”起来
将类放到内存的指定位置后,进行类里的必要数据的初始化(主要是静态属性)
执行类的初始化工作
站在Java开发者的角度:我们的代码中的哪些东西是在这个阶段执行的?静态属性的初始化(赋值操作)、静态代码块 static { ... }
- class Main {
- static int a = 10; // = 10 是赋值的操作,是类的初始化阶段要做的
-
- static int b = callStaticMethod(); // = callStaticMethod() 操作,是类的初始化阶段要做的
- // 导致这个阶段,会去调用 callStaticMethod 方法
-
- static { // 静态代码块/静态构造代码块
- System.out.println("hello"); // 这里的所有语句,是类的初始化阶段要做的
- }
-
- static int callStaticMethod() {
- return 2; // 这个方法会被执行,只是因为 b = callStaticMethod() 导致的
- }
-
- static int someStaticMethod() {
- return 1; // 这个方法不会被执行
- }
-
- public static void main(String[] args) {
- String s = "hello";
- System.out.println(s);
- }
- }
一定是先执行父类的初始化完成之后,才会进行子类的初始化。
俗称 :Main
权威类名:com.lingqi.demo.Main。更深一层就是 JVM 进行类的加载时,保证一个类只会在内存中存在一份(粗略地可以当成类只被加载一次(类是可以被卸载的))
JVM 内部:类加载器 + 权威类名,确定一个类是否存在
不同的类,由不同的类加载(因为类和类的地位的不平等)
Boostrap ClassLoader(启动类加载器):加载 SE 下的标准类(java.lang.String、java.util.List、java.io.InputStream)
Extenttion ClassLoader(扩展类加载器):加载 SE 下的扩展类
Application ClassLoader(应用类加载器):我们写类、我们通过 maven 或者其他工具引入的第三方类
举个例子:
如果有人提个要求,说让我加载一个类,那么我们怎么知道要加载的这个类的.class文件放在哪里呢?(比如说 rt.jar)
ClassLoader 肯定输入 JRE,所以知道自己被安装在哪
C:\Program Files\Java\jdk1.8.0_131\jre
所以去固定位置去找C:\Program Files\Java\jdk1.8.0_131\jre\lib\rt.jar
对于应用类加载器Application ClassLoader来说:
必须告诉它去哪里找,通过 ClassPath 变量告诉。java-classpath=路径,或者放在环境变量 ClassPath 中。 路径1/路径2/路径3。 ClassLoader 先去路径1下找,没找到再去 2 下,没找到再去 3,最后没找到,就会报异常(ClassNotFound,NoClassDef)
JVM 会管理一片内存(这片内存,JVM 是哪弄来的?JVM 是普通进程,从OS 申请过来的)
内存分的区域:
不同的JVM 实现中,可以有自己的实现。比如我们现在使用的都是 Oracle 提供的 Hotspot JVM。
除了上述的区域,还有一部分直接管理的内存,一般称为原生内存
类中的数据:类的信息、方法、常量池数据。
类的信息和方法组织成一个东西,叫做类信息,常量池其实也是类的信息。逻辑上,类信息被存到了方法区。常量池存在了运行时的常量池中,它也是类,实际上逻辑上也可以理解为,这也是方法区的一部分。
那么,我们简单理解就是,整个类的数据,都被放在了方法区之中了(基本信息、方法(所有的方法)、常量数据、静态属性)。但是这个阶段是不讨论具体的JVM 实现的。不同版本的 JVM 的具体实现实际上是不同的。所以深入探讨的时候,还需要根据实际情况来具体的分析。
1.类文件放在哪里?
硬盘中,以文件的形式出现最为常见
2.类文件是怎么来的
经过编译器,将 java 源文件编译而来的
3.类文件中的主要数据有?
按照规范,主要有 基本信息(静态属性)、方法(方法信息、指令(字节码))、常量
4.为什么进行类的加载?
按照冯诺依玛体系,CPU 无法直接读取硬盘中的数据,需要先加载到内存中
5.为什么类文件要按需加载,并且以类为单位加载?
相对来说,节省内存空间,实现方便
6.类名是什么
①权威类名 = 包名 + 类名 ② 类加载器 + 权威类名
7.什么时候会去加载类(什么时候用到了一个类)?
实例化对象、访问静态属性、调用静态方法、子类调用到父类
8.类在内存中只会存在一份
正确
9.类的加载过程
加载、链接、初始化
10.类的初始化时会执行我们的哪些代码?
①属性的初始化赋值 ②静态构造代码块 父类的初始化一定在子类之前完成 按照书写顺序
11.类被加载到内存的什么位置?
逻辑上,放在方法区。但不同 JVM 的实现,可以有进一步讨论空间
12.默认的类加载器有哪些?
启动类加载器、扩展类加载器、应用类加载器
13.加载时,ClassLoader 怎么知道一个类对应的类文件所在?
启动、扩展类加载器根据固定位置找。应用类加载器,根据class path 的路径依次查找
14.如果加载时,一个类不存在,会出现异常(ClassNotFound、NoClassDef...)
默认的三个类加载器之间遵守一个规范:
1.三个类加载器之间存在
2.Application ClassLoader 需要加载类的时候,先委派给双亲去加载
如果 parent 加载成功这个类了,你就不用加载了;否则自己再去加载
3.目的是放置加载进来用户写好的恶意代码
前提:一个类(A类)用到了其他类(B C D类),则其他的这些类(B C D )的加载动作,默认是由当时加载 A类的加载器来加载。
比如说com.lingqi.demo.Main 用到了 java.lang.String 类
- public static void main(String[] args){
- String s = "hello";
- System.out.println(s);
- }
但是我还定义了一个自己的String 类
那么我用的时候用的是 rt.jar 下的java.lang.String 还是我自己定义的?
那么双亲委派机制是如何解决这个问题的
1.com.lingqi.demo.Main 类被哪个类加载?—— ApplicationClassLoader 去加载
2.Main 用到了 java.lang.String ,则默认让 ApplicationClassLoader 去加载。
3.如果没有双亲委派,加载的就是我们自己写的 java.lang.String
4.如果有双亲委派,则 ApplicationClassLoader 优先让 BootStrapClassLoader 去加载,能加载到是 rt.jar 下的。所以不会加载我们自己写的。
这就是双亲委派的意义。