千家信息网

JVM是什么意思

发表于:2026-11-01 作者:千家信息网编辑
千家信息网最后更新 2026年11月01日,小编给大家分享一下JVM是什么意思,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!一、JVM介绍在介绍JVM之前,先看一下.
千家信息网最后更新 2026年11月01日JVM是什么意思

小编给大家分享一下JVM是什么意思,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!


一、JVM介绍

在介绍JVM之前,先看一下.java文件从编码到执行的过程:

整个过程是,x.java文件需要编译成x.class文件,通过类加载器加载到内存中,然后通过解释器或者即时编译器进行解释和编译,最后交给执行引擎执行,执行引擎操作OS硬件。

从类加载器到执行引擎这块内容就是JVM。

JVM是一个跨语言的平台。从上面的图中可以看到,实际上JVM上运行的不是.java文件,而是.class文件。这就引出一个观点,JVM是一个跨语言的平台,他不仅仅能跑java程序,只要这种编程语言能编译成JVM可识别的.class文件都可以在上面运行。

所以除了java以外,能在JVM上运行的语言有很多,比如JRuby、Groovy、Scala、Kotlin等等。

从本质上讲JVM就是一台通过软件虚拟的计算机,它有它自身的指令集,有它自身的操作系统。

所以Oracle给JVM定了一套JVM规范,Oracle公司也给出了他的实现。基本上是目前最多人使用的java虚拟机实现,叫做Hotspot。使用java -version可以查看:

一些体量较大,有一定规模的公司,也会开发自己的JVM虚拟机,比如淘宝的TaobaoVM、IBM公司的J9-IBM、微软的MicrosoftVM等等。

二、JDK、JRE、JVM

JVM应该很清楚了,是运行.class文件的虚拟机。JRE则是运行时环境,包括JVM和java核心类库,没有核心的类库是跑不起来的。

JDK则包括JRE和一些开发使用的工具集。

所以总的关系是JDK > JRE > JVM。

三、Class加载过程

类加载是JVM工作的一个很重要的过程,我们知道.class是存在在硬盘上的一个文件,如何加载到内存工作的呢,面试中也经常问这个问题。所以你要和其他程序员拉开差距,体现差异化,这个问题要搞懂。

类加载的过程实际上分为三大步:Loading(加载)、Linking(连接)、Initlalizing(初始化)。

其中第二步Linking又分为三小步:Verification(验证)、Preparation(准备)、Resolution(解析)。

3.1 Loading

Loading是把.class字节码文件加载到内存中,并将这些数据转换成方法区中的运行时数据,在堆中生成一个java.lang.Class类对象代表这个类,作为方法区这些类型数据的访问入口。

3.2 Linking

Linking简单来说,就是把原始的类定义的信息合并到JVM运行状态之中。分为三小步进行。

3.2.1 Verification

验证加载的类信息是否符合class文件的标准,防止恶意信息或者不符合规范的字节信息。是JVM虚拟机运行安全的重要保障。

3.2.2 Preparation

创建类或者接口中的静态变量,并初始化静态变量赋默认值。赋默认值不是赋初始值,比如static int i = 5,这一步只是把i赋值为0,而不是赋值为5。赋值为5是在后面的步骤。

3.2.3 Resolution

把class文件常量池里面用到的符号引用转换成直接内存地址,直接可以访问到的内容。

3.3 Initlalizing

这一步真正去执行类初始化clinit()(类构造器)的代码逻辑,包括静态字段赋值的动作,以及执行类定义中的静态代码块内(static{})的逻辑。当初始化一个类时,发现父类还没有进行过初始化,则先初始化父类。虚拟机会保证一个类的clinit()方法在多线程环境中被正确加锁和同步。

四、类加载器

上面就是类加载的整个过程。而最后一步Initlalizing是通过类加载器加载类。类加载器这里我单独讲一下,因为这是一个重点。

Java中的类加载器由上到下分为:

  • Bootstrap ClassLoader(启动类加载器)
  • ExtClassLoader(扩展类加载器)
  • AppClassLoader(应用程序类加载器)

从类图,可以看到ExtClassLoader和AppClassLoader都是ClassLoader的子类。

所以如果要自定义一个类加载器,可以继承ClassLoader抽象类,重写里面的方法。重写什么方法后面再讲。

五、双亲委派机制

讲完类加载器,这些类加载器是怎么工作的呢。对于双亲委派机制可能多多少少有听过,没听过也没关系,我正要讲。

上面说过有Bootstrap,ExtClassLoader,AppClassLoader三个类加载器。工作机制如下:

加载类的逻辑是怎么样的呢,核心代码是可以在JDK源码中找到的,在抽象类ClassLoader类的loadClass(),有兴趣可以源码看看:

protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
//如果上层有类加载器,递归向上,往上层的类加载器寻找
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
//如果上层的都找不到相应的class
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
//自己去加载
c = findClass(name);

// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}

其实整个逻辑已经很清晰了,为了更好理解,我这里画张图给给大家,更好理解一点:

看到这里,应该都清楚了双亲委派机制的流程了。重点来了,为什么要使用双亲委派机制呢?

如果面试官问这个问题,一定要答出关键字:安全性。

反证法来辩证。假设不采用双亲委派机制,那我可以自定义一个类加载器,然后我写一个java.lang.String类用自定义的类加载器加载进去,原来java本身又有一个java.lang.String类,那么类的唯一性就没法保证,就不就给虚拟机的安全带来的隐患了吗。所以要保证一个类只能由同一个类加载器加载,才能保证系统类的的安全。

六、自定义类加载器

自定义类加载器,上面讲过可以有样学样,自定义一个类继承ClassLoader抽象类。重写哪个方法呢?loadClass()方法是加载类的方法,重写这个不就行了?

如果重写loadClass()那证明有思考过,但是不太对,因为重写loadClass()会破坏了双亲委派机制的逻辑。应该重写loadClass()方法里的findClass()方法。

findClass()方法才是自定义类加载器加载类的方法。

那findClass()方法源码是怎么样的呢?

明显这个方法是给子类重写用的,权限修饰符也是protected,如果不重写,那就会抛出找不到类的异常。如果学过设计模式的同学,应该看得出来这里用了模板模式的设计模式。所以我们自定义类加载器重写此方法即可。开始动手!

创建CustomerClassLoader类,继承ClassLoader抽象类的findClass()方法。

public class CustomerClassLoader extends ClassLoader {
//class文件在磁盘中的路径
private String path;
//通过构造器初始化class文件的路径
public CustomerClassLoader(String path) {
this.path = path;
}

/**
* 加载类
*
* @param name 类的全路径
* @return Class
* @author Ye hongzhi
*/
@Override
protected Class findClass(String name) throws ClassNotFoundException {
Class clazz = null;
//获取class文件,转成字节码数组
byte[] data = getData();
if (data != null) {
//将class的字节码数组转换成Class类的实例
clazz = defineClass(name, data, 0, data.length);
}
//返回Class对象
return clazz;
}

private byte[] getData() {
File file = new File(path);
if (file.exists()) {
try (FileInputStream in = new FileInputStream(file);
ByteArrayOutputStream out = new ByteArrayOutputStream();) {
byte[] buffer = new byte[1024];
int size;
while ((size = in.read(buffer)) != -1) {
out.write(buffer, 0, size);
}
return out.toByteArray();
} catch (IOException e) {
e.printStackTrace();
return null;
}
} else {
return null;
}
}
}

这样就完成了,接下来测试一下,定义一个Hello类。

public class Hello {
public void say() {
System.out.println("hello.......java");
}
}

使用javac命令编译成class文件,如下图:

最后写个main方法运行测试一把:

public class Main {
public static void main(String[] args) throws Exception {
String path = "D:\\mall\\core\\src\\main\\java\\io\\github\\yehongzhi\\classloader\\Hello.class";
CustomerClassLoader classLoader = new CustomerClassLoader(path);
Class clazz = classLoader.findClass("io.github.yehongzhi.classloader.Hello");
System.out.println("使用类加载器:" + clazz.getClassLoader());
Method method = clazz.getDeclaredMethod("say");
Object obj = clazz.newInstance();
method.invoke(obj);
}
}

运行结果:

七、破坏双亲委派机制

看到这里,你肯定会很疑惑。上面不是才讲过双亲委派机制为了保证系统的安全性吗,为什么又要破坏双亲委派机制呢?

重温一下双亲委派机制,应该还记得,就是底层的类加载器一直委托上层的类加载器,如果上层的已经加载了,就无需加载,上层的类加载器没有加载则自己加载。这就突出了双亲委派机制的一个缺陷,就是只能子的类加载器委托父的类加载器,不能反过来用父的类加载器委托子的类加载器。

那你会问,什么情况会出现父的类加载器委托子的类加载器呢?

还真有这个场景,就是加载JDBC的数据库驱动。在JDK中有一个所有 JDBC 驱动程序需要实现的接口Java.sql.Driver。而Driver接口的实现类则是由各大数据库厂商提供。那问题就出现了,DriverManager(JDK的rt.jar包中)要加载各个实现了Driver接口的实现类,然后进行统一管理,但是DriverManager是由Bootstrap类加载器加载的,只能加载JAVA_HOME下lib目录下的文件(可以看回上面双亲委派机制的第一张图),但是实现类是服务商提供的,由AppClassLoader加载,这就需要Bootstrap(上层类加载器)委托AppClassLoader(下层类加载器),也就破坏了双亲委派机制。这只是其中一种场景,破坏双亲委派机制的例子还有很多。

那么怎么实现破坏双亲委派机制呢?

  • 最简单就是自定义类加载器,前面讲过为了不破坏双亲委派机制重写findClass()方法,所以如果我要破坏双亲委派机制,那就重写loadClass()方法,直接把双亲委派机制的逻辑给改了。在JDK1.2后不提倡重写此方法。所以提供下面这种方式。
  • 使用线程上下文件类加载器(Thread Context ClassLoader)。 这个类加载器可以通过java.lang.Thread类的setContextClassLoader()方法进行设置,如果创建线程时还未设置,它将会从父线程中继承一个;如果在应用程序的全局范围内都没有设置过,那么这个类加载器默认就是AppClassLoader类加载器。

那么刚刚说的JDBC又是采用什么方式破坏双亲委派机制的呢?

当然是采用上下文文件类加载器,还有使用了SPI机制,下面一步一步分解。

第一步,Bootstrap加载DriverManager类,在DriverManager类的静态代码块调用初始化方法。

public class DriverManager {
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
}

第二步,加载Driver接口的所有实现类,得到Driver实现类的集合,获取一个迭代器。

第三步,看ServiceLoader.load()方法。

第四步,看迭代器driversIterator。

接着一直找下去,就会看到一个很神奇的地方。

而这个常量值PREFIX则是:

private static final String PREFIX = "META-INF/services/";

所以我们可以在mysql驱动包中找到这个文件:

通过文件名找接口的实现类,这是java的SPI机制。到此为止,破案了大人!

作为暖男的我,就画张图,总结一下整个过程吧:

以上是"JVM是什么意思"这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注行业资讯频道!

机制 方法 文件 双亲 委派 运行 就是 上层 过程 接口 逻辑 安全 数据 程序 静态 保证 委托 编译 代码 内存 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 互联网的科技 没上市的科技互联网公司 互联网科技金融 未来的互联网科技 狮子座互联网科技有限公司 深圳艾佳互联网科技 居家互联网科技有限公司 昊天互联网科技有限公司 厦门千工互联网科技有限公司 电子科技大学互联网创业大赛 互联网行业科技股 互联网科技和法律行业 互联网科技大赛获奖感言 工业互联网展湾科技股份有限公司 山东有哪些互联网科技公司 卡尔迅互联网科技有限公司 昆明中奇联合互联网科技 云南互联网科技学费 长沙渺峰互联网科技有限公司 杭州融税互联网科技有限公司 保险科技 互联网保险 中关村互联网科技集团 上海挚品互联网科技 深圳市运趣互联网科技有限公司 互联网科技创新服务 爱医互联网络科技 互联网科技平台产品运营 杭州融税互联网科技有限公司 广州全民伙伴互联网科技有限公司 小牛互联网科技有限公司地址
0