微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

JVM入门请点进来,一起快乐学习JVM吧!

JVM

前言

以下所有内容,仅针对Hotspot虚拟机而言。

学习一门技术的时候,首当其冲,先百度一下:

JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。

Java语言的一个非常重要的特点就是与平台的无关性。而使用Java虚拟机是实现这一特点的关键。一般的高级语言如果要在不同的平台上运行,至少需要编译成不同的目标代码。而引入Java语言虚拟机后,Java语言在不同平台上运行时不需要重新编译。Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。Java虚拟机在执行字节码时,把字节码解释成具体平台上的机器指令执行。这就是Java的能够"一次编译,到处运行"的原因。

OK,百度完了之后就得开始缩句了:

  • JVM就是二进制字节码的运行环境,在运行的时候,JVM是通过字节码进行运行的,不同的JVM的字节码格式不同,相同格式的字节码文件经过编译后,可以在符合该字节码格式的JVM上直接运行,不需要再次编译。
  • JVM只关心字节码文件,非Java语言的程序只要符合JVM的规范就可以在JVM上运行。
  • JVM是在操作系统之上的,跟硬件没有直接接触JVM。

当我们编写完一个程序后,这个程序是如何运行起来的呢,这里简述一下大概的流程:

  1. java程序先被javac编译器编译成.class文件(字节码文件
  2. JVM运行字节码文件
  3. 经过类加载子系统将字节码文件加载到系统中
  4. 在运行时数据区分配好相应的资源
  5. 执行引擎做相应处理

类加载器子系统

一个字节码文件要被加载到系统中需要三个阶段:

加载阶段

该阶段的加载器分为三种,分别是引导类加载器,扩展类加载器,系统类加载器

三者的底层级别依次递增,即引导类加载器是扩展类加载器的父类加载器,扩展类加载器是系统类加载器的父类加载器。

引导类加载器

  • 这个类加载器是用C/C++编写的,主要作用是用来加载java的的核心库。
  • 负责加载<JAVA_HOME>\jre\lib\rt.ajr、resourse.jar或者是sun.boot.class.path路径下的内容
  • 这是最底层的类加载器了,他没有父类加载器。

扩展类加载器

  • java语言编写。
  • 负责加载<JAVA_HOME>\jre\lib\ext目录下的类库或者系统变量"java.ext.dirs"指定的目录下的类库。

系统类加载器

  • java语言编写。
  • 负责加载java.class.path指定路径下的类库。
  • 该类是程序中认的类加载库

双亲委派机制

可能会有小伙伴说这么多加载器,我写一个类都得用到这些加载器去加载吗?
JVM也帮我们想到了这个问题,他提供了双亲委派机制帮我们去解决,双亲委派机制的原理如下:

  • 一个类加载器收到了类的加载请求时,不会马上去加载,而是将这个请求转给它的父类加载器。
  • 如果父类加载器还有父类加载器,则再次向上委派。
  • 直到找到引导类加载器,这时候就会判断引导类加载器是否可以完成这个类的加载,可以的就加载该类返回,否则就让子加载器去发挥作用。

优点:

  • 可以避免类被重复加载。
  • 保护程序的安全,避免核心API被人恶意修改。(因为核心类如果每个加载器都可以访问到的话,就可以根据自定义加载器去对他们进行修改,所以才要从最顶层的父类加载器开始判断)

沙箱安全机制

因为有了上述的双亲委派机制,所以JVM为我们提供了沙箱安全机制。
举个例子体验下吧!

在javaTest包下自定义一个String类,添加main方法


package javaTest;

public class String {
    public static void main(String[] args) {
        System.out.println("我是自定义的String类");
    }
}

此时我们是运行不了的,因为在加载String类的时候,会因为双亲委派机制一直委派到引导类加载器进行加载,加载的过程中,rt.jar中的String类找不到main方法,所以运行失败。

加载流程

  • 通过该加载类的全类名过去定义该类信息的二进制字节流
  • 通过该字节流所代表的静态存储结构转化为方法区(下面提及)的运行时数据结构
  • 在内存中生成一个代表该类的java.lang.class的类,通过该类去调用方法区中该类的信息,作为数据的访问入口

链接阶段

链接阶段又可以细分为3个小阶段

验证

确保在上述加载阶段生成的Class对象中符合当前虚拟机要求,并且不会危害到虚拟机的安全。

准备

  • 类变量分配响应的内存并设置该类变量认初始值。
  • 这里不会为实例变量分配内存和认初始化,实例变量的产生要等对象被创建后一起分配到堆中,而类变量的话是被分配到方法区中(JDK1.8前后发生了改变,下面方法区会谈到)。
  • 加入类变量被final修饰,则直接为变量赋值,而不是认初始化。

解析

  • 将常量池中的符号引用转为直接引用

初始化阶段

  • 初始化阶段就是执行类构造器方法cinit()的过程。
  • 这里要注意的是cinit()并不是类中的构造器,这个方法是要当类中有静态代码块或者静态变量时,系统自动帮我们生成的,不用我们定义。

运行时数据区

这个区域是JVM重点关注的区域,可以说是重中之重,说他是核心也不为过。

JVM定义了在程序运行期间会使用到的运行时数据区,大体分为以下5块,这里面有的是线程独立的,随着线程生亡,有些是随着虚拟机生亡;

线程独立的数据区有虚拟机栈,本地方法栈,程序计数器;

虚拟机栈

本地方法

程序计数器

这个区域是运行时数据区最小的一个区域,只是用来记录线程的执行地址。

cpu工作的时候需要不停的去切换各个线程,当我们其他线程执行好了后就得回来执行当前的线程,所以就得直到之前执行到哪了,从程序计数器去获取到指定的地址,从那开始执行,避免重复工作。这也是为什么程序计数器是线程私有的,这样才能保证每个线程在进行切换时能够继续上次的工作往下执行,不会线程之间互相干扰。

今天先到这

方法区/元空间

执行引擎

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。

相关推荐