Java虚拟机简称 JVM(java Virtual machine)
这里面有三个基本的名词需要进行解析:
首先,第一个问题,java是什么?
我们都知道java是一种语言,准确来说java是一种编程语言。
那有人就会问,什么是编程?
编程,编程。编就是编写。程就是程式。java是一种可以用来编写程式的语言。
那什么是程式?
我们可以对比初中学过方程式,方程式是可以得到未知数结果的算式。经过分析,我们知道,java其实就是一种可以编写通过某种指定语法的算式按照某种次序进行运算可以得到结果的语言。
那么由谁来进行运算呢?
就是我们今天的主角,JVM。
当然JVM不能直接运行Java,而是需要Java文件编译成字节码,才能被JVM进行处理
其次,第二个问题,虚拟什么东西?
简单的说,其实是虚拟CPU。概括的说,JVM是模拟CPU进行工作。
但是不同的是CPU执行的是最底层的机器码指令,而JVM执行的是字节码指令。
再者,第三个问题,机是什么?
机,就是计算机。
最终,用一句话来描述,JVM就是读取字节码进行运算的模拟计算机。
JVM中有几大模块,其中比较重要的模块有3个:
模块 | 组件 | 功能 |
---|---|---|
加载模块 | 类装载子系统 | 主要将class文件从磁盘加载到存储模块 |
存储模块 | 运行时数据区 | 存放控制模块所需要的数据。(在操作系统内存中) |
控制模块 | 字节码执行引擎 | 控制程序流转,对存储模块进行读写操作 |
我们知道,CPU进行运算的时候,是需要把数据从磁盘加载到内存中。
同理,如果JVM想要运算,或者说运行程序。也需要将class文件加载到内存中。
这事谁干的?
这个过程就是由加载模块来完成的。
加载模块也就是类装载子系统。
它的核心,我们称之为加载器,准确来说,应该称之为类加载器。
我们按照类加载器加载内容的不同,我们将其划分为不同的品种:
表1.2 类加载器功能表格
类型 | 功能 | 比如 |
---|---|---|
启动类加载器 | 加载核心的java类库的类 | java.util.ArrayList |
扩展类加载器 | 加载扩展java类库的类 | com.sun.javafx.binding.Logging |
应用程序/系统类加载器 | 加载我们自己写的类 | com.cqq.classloaderdemo.ClassLoaderTest |
每个加载器的功能都是一样的。只是把不同的类库下,运行程序时所需要的类的信息,加载到存储模块。
以下代码可以查看不同的类加载器:
package com.cqq.classloaderdemo;
import com.sun.javafx.binding.Logging;
import java.util.ArrayList;
public class ClassLoaderTest {
public static void main(String[] args) {
//Classloader of this class:sun.misc.Launcher$AppClassLoader@18b4aac2
System.out.println( ClassLoaderTest.class.getClassLoader());
//Classloader of Logging:sun.misc.Launcher$ExtClassLoader@1540e19d
System.out.println( Logging.class.getClassLoader());
//null
System.out.println( ArrayList.class.getClassLoader());
}
}
为啥,ArrayList的加载器是null,不要在意这个细节,因为启动类加载器不是用java实现的。而是由C或C++语言来实现的。所以看不到
那么,当JVM要加载一个类的时候,加载模块到底是如何协调这些类加载器工作的呢?
我们知道java代码,是从main函数直接运行的。
那我们先放一段java代码
package com.cqq.classloaderdemo;
public class Student {
}
package com.cqq.classloaderdemo;
import java.util.ArrayList;
public class MainDemo {
public static void main(String[] args) {
Student student = new Student();
ArrayList<String>list = new ArrayList<>();
System.out.println(list);
}
}
在这段代码中,我们从main函数直接运行。
当我们启动MainDemo的时候。因为MainDemo是我们自己写的类。
我们根据表1.2得知,它是交给系统类加载器去进行加载的。
突然,我们发现,该类在运行过程中引用到了Student类。
既然用到了,那么也要把它加载到存储模块。
那么问题来了,Student 这个类,应该交给哪个加载器去加载呢?
当然是系统类加载器,因为Student 也是我们写的呀。
这个时候,我们发现一般情况下,类加载器去加载的某个类的时候,如果该类使用到别的类,其他类往往也需要由这个类加载器去进行加载。
那么干脆,我们把这作为一条默认规定吧!
这就是全盘负责委托机制。
指当一个ClassLoader装载一个类时,除非显示地使用另一个ClassLoader,则该类所依赖及引用的类也由这个ClassLoader载入.
好,程序继续执行。
这个时候,我们又发现引用到了ArrayList这个类。
系统类加载器 找不到ArrayList这个类。
因为ArrayList是核心的java类库的类,根据表格1.2,JVM会使用启动类加载器加载这个类。
那么问题来了,如果我们自己写了一个ArrayList类,并且自己创建了一个java.util包,将ArrayList放置于该包下。
这样我们的ArrayList,与系统核心类的ArrayList的全限定名就完全一样了。
换句话说,系统类加载器扫我们自己的代码时,可以加载到这个类,而启动类加载器扫核心程序的时候也可以加载到核心类库的ArrayList。
那么ArrayList到底应该由谁来加载呢?
事实上,如果发生冲突,我们应该要让系统核心类库的类要优先于我们自己写的代码的类。
即让启动类加载器去加载。
JVM如何实现这一点的呢?
为了解决这个问题。JVM采用了双亲委托机制
双亲委托机制概念:
当某个类加载器需要加载某个
.class
文件时,它首先把这个任务委托给他的上级类加载器,递归这个操作,如果上级的类加载器没有加载,自己才会去加载这个类。
什么意思?
首先JVM规定两点:
启动类加载器是扩展类加载器的上级加载器
扩展类加载器是系统类加载器的上级加载器
拿刚才那个程序进行讲解。
首先,MainDemo是我们自己写的类,所以是由系统类加载器进行加载的。
根据全盘负责委托机制,该类中引用到的所有类,都应该由系统类加载器首先来进行加载。
那加载流程怎么样的,比如,Student类被系统类加载器进行加载的时候
再根据双亲委派机制,系统类加载器先不去查找,而是先委托给上级去加载,如果加载失败,再去查找并加载。
系统类加载器的上级是扩展类加载器,
扩展类加载器,它也是先不去查找,而是先将类委托给它的上级去加载,如果失败,再去查找并加载。
扩展类加载器的上级是启动类加载器。
启动类加载器是最上次的加载器,它没有上级,直接去查找,找到就加载了,并层层返回,找不到就是没找到。
如果启动类加载器没找到,那么扩展类加载器再去查找。
如果扩展类加载器没找到,那么就由系统类加载器去加载。
Student不是系统类库,也不是扩展类库,所以启动类加载器和扩展类加载器显然找不到
最终由系统类加载器去查找,Student被加载到存储模块中。
通过这种方式,可以避免刚才的那个问题,因为即使我们写了java.util.ArrayList。
但是系统类加载器在加载之前,委托给的上级的上级已经找到并加载了。避免意外发生。
那么,类加载器究竟被加载到JVM的哪里去呢,如何被组织存放的呢?
可以移步到深入浅出 Java虚拟机(2)之运行时数据区