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。
当我们编写完一个程序后,这个程序是如何运行起来的呢,这里简述一下大概的流程:
- java程序先被javac编译器编译成.class文件(字节码文件)
- JVM运行字节码文件
- 经过类加载子系统将字节码文件加载到系统中
- 在运行时数据区分配好相应的资源
- 执行引擎做相应处理
类加载器子系统
一个字节码文件要被加载到系统中需要三个阶段:
加载阶段
该阶段的加载器分为三种,分别是引导类加载器,扩展类加载器,系统类加载器
三者的底层级别依次递增,即引导类加载器是扩展类加载器的父类加载器,扩展类加载器是系统类加载器的父类加载器。
引导类加载器
- 这个类加载器是用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工作的时候需要不停的去切换各个线程,当我们其他线程执行好了后就得回来执行当前的线程,所以就得直到之前执行到哪了,从程序计数器去获取到指定的地址,从那开始执行,避免重复工作。这也是为什么程序计数器是线程私有的,这样才能保证每个线程在进行切换时能够继续上次的工作往下执行,不会线程之间互相干扰。
今天先到这
堆
方法区/元空间
执行引擎
标签:Java,字节,虚拟机,入门,线程,JVM,请点,加载 来源: https://blog.csdn.net/fighting32/article/details/114916774