欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

JVM总结(一)

程序员文章站 2022-09-17 09:12:33
什么是JVMJVM是可运行Java代码的假想计算机,包括一套字节码指令集,一个寄存器,一个栈,一个垃圾回收,堆,一个存储方法域JVM的作用想要运行一个Java代码,需要具备JRE环境。而JRE中,包括Java虚拟机及Java的核心类库。Java程序员通常安装的JDK,则已经包括了JRE,还附带了常用的开发和诊断工具。在Java语言中,最重要的莫过于Java虚拟机。为什么需要有Java虚拟机呢?Java 作为一门高级程序语言,它的语法非常复杂,抽象程度也很高。因此,直接在硬件上运行这种复杂的程序并不...

什么是JVM

JVM是可运行Java代码的假想计算机,包括一套字节码指令集,一个寄存器,一个栈,一个垃圾回收,堆,一个存储方法域

JVM的作用

想要运行一个Java代码,需要具备JRE环境。而JRE中,包括Java虚拟机及Java的核心类库。Java程序员通常安装的JDK,则已经包括了JRE,还附带了常用的开发和诊断工具。
在Java语言中,最重要的莫过于Java虚拟机。为什么需要有Java虚拟机呢?
Java 作为一门高级程序语言,它的语法非常复杂,抽象程度也很高。因此,直接在硬件上运行这种复杂的程序并不现实。所以呢,在运行 Java 程序之前,我们需要对其进行一番转换。
转换的过程为通过编译器将 Java 程序转换成该虚拟机所能识别的指令序列,也称 Java 字节码。Java虚拟机会将字节码,即class文件加载到JVM中。由JVM进行解释和执行。除了 Java 外,Scala、Clojure、Groovy,以及时下热门的 Kotlin,这些语言都可以运行在 Java 虚拟机之上
说说jdk,jvm,jre之间的关系
jdk:Java的开发工具包。JDK是提供给Java开发人员使用的,其中包含了java的开发工具,也包括了JRE。所以安装了JDK,就不用在单独安装JRE了。
其中的开发工具:编译工具(javac.exe) 打包工具(jar.exe)等。
jre:Java运行环境。包括Java虚拟机(JVM Java Virtual Machine)和Java程序所需的核心类库等,如果想要运行一个开发好的Java程序,计算机中只需要安装JRE即可。
JVM总结(一)
三者间的关系
jdk=jre+开发工具集(包括javac.exe,jar.exe)
jre=JVM+java的核心类库
JVM总结(一)
JVM总结(一)
由上图可以看出,JVM是运行在操作系统上的,与硬件无直接交互

常见的JVM

我们通常用到的jvm是HotSpot JVM:Sun JDK和OpenJDK中所带的虚拟机,也是目前使用范围最广的Java虚拟机。最初由一家名为“Longview Technologies”的小公司设计,后被Sun公司收购。

JVM体系概述

JVM总结(一)
从上图中可以看出JVM把运行时内存区分为五部分:方法区、堆、Java栈(Java虚拟机将栈栈细分为面向Java方法的Java方法栈和面向本地方法的本地方法栈)、本地方法区、程序计数器。
执行 Java 代码首先需要使用类加载器将它编译而成的 class 文件加载到 Java 虚拟机中。加载后的 Java 类会被存放于方法区(Method Area)中。实际运行时,虚拟机会执行方法区内的代码。
Java 虚拟机将栈细分为面向 Java 方法的 Java 方法栈,面向本地方法(用 C++ 写的 native 方法)的本地方法栈,以及存放各个线程执行位置的 PC 寄存器(程序计数器)。
在运行过程中,每当调用进入一个 Java 方法,Java 虚拟机会在当前线程的 Java 方法栈中生成一个栈帧(栈的一片区域),用以存放局部变量以及字节码的操作数。这个栈帧的大小是提前计算好的,而且 Java 虚拟机不要求栈帧在内存空间里连续分布。当退出当前执行的方法时,不管是正常返回还是异常返回,Java 虚拟机均会弹出当前线程的当前栈帧,并将之舍弃。

类加载器

JVM总结(一)
类加载器,即ClassLoader,它负责加载class文件,class文件在文件开头有特定的文件标示,并且ClassLoader只负责class文件的加载,至于它是否可以运行,则由Execution Engine决定。

类加载器分类

虚拟机自带的类加载器
1、启动类加载器(Bootstrap):主要负责加载jre中的最为基础、最为重要的类。如JAVAHOME/jre/lib/rt.jarXbootclasspathC++javajava使null2ExtensionJavaJRElib/extjarjava.ext.dirsJAVA_HOME/jre/lib/rt.jar等,以及由虚拟机参数 -Xbootclasspath 指定的类。由于它由C++代码实现,没有对应的java对象,因此在java中,尝试获取此类时,只能使用null来指代。 2、扩展类加载器(Extension),由Java代码实现,用于加载相对次要、但又通用的类,比如存放在 JRE 的 lib/ext 目录下 jar 包中的类,以及由系统变量 java.ext.dirs 指定的类。如JAVA_HOME/jre/lib/ext/*.jar。
3、应用程序类加载器(AppClassLoader),由Java代码实现, 它负责加载应用程序路径下的类。(这里的应用程序路径,便是指虚拟机参数 -cp/-classpath、系统变量 java.class.path 或环境变量 CLASSPATH 所指定的路径。)默认情况下,应用程序中包含的类便是由应用类加载器加载的。
除了BootStrap Class Loader,其他的类加载器,都是Java.lang.ClassLoader的子类。其他的类加载器都由加载sum.misc.Launcher类后得到。
JVM总结(一)

类的加载过程

JVM总结(一)
又上图可以看出分为三个阶段:
1、加载
加载,是指查找字节流,并且据此创建类的过程。
类加载器负责类的加载,除了BootStrap ClassLoader,其他的类加载器都由Java代码实现,因此需要先使用BootStrap ClassLoader将其他的类加载器加载到java虚拟机。再由这些类加载器加载其他类。类的加载需要遵守双亲委派模型

双亲委派模型:每当一个类加载器接收到加载请求时,它会先将请求转发给父类加载器。在父类加载器没有找到所请求的类的情况下,该类加载器才会尝试去加载。
优势:
①这采用双亲委派模式的是好处是Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。
②其次是考虑到安全因素,防止java核心api中定义类型不会被用户恶意替换和篡改,从而引发错误。

package java.lang;
public class String {
	public String() {
		System.out.println("自己伪造的String");
	}
	public static void main(String[] args) {
		System.out.println("自己伪造的String的main()");
	}
}

JVM总结(一)
注意:在 Java 虚拟机中,类的唯一性是由类加载器实例以及类的全名一同确定的。即便是同一串字节流,经由不同的类加载器加载,也会得到两个不同的类。在大型应用中,我们往往借助这一特性,来运行同一个类的不同版本。
加载的类在JVM中创建相应的类结构,类结构会存储在元空间(1.8之前称之为方法区)。
类将.class文件加载至元空间后,会在堆中创建一个Java.lang.Class对象,用来封装类位于方法区内的数据结构,该Class对象是在加载类的过程中创建的,每个类都对应有一个Class类型的对象,Class类的构造方法是私有的,只有JVM能够创建。因此Class对象是反射的入口,使用该对象就可以获得目标类所关联的.class文件中具体的数据结构。
类加载的最终产物就是位于堆中的Class对象,该对象封装了类在方法区中的数据结构,并且向用户提供了访问方法区数据结构的接口,即Java反射的接口。
2、链接(验证、准备、解析)
加载(将字节码文件读入到虚拟机)后的类,如果没有链接,那么是暂时不能使用的。链接可分为验证、准备以及解析三个阶段。
验证指确保被加载类能够满足Java虚拟机的约束条件,防止字节码文件损坏或者被恶意注入。
准备阶段主要是在元空间(方法区)为加载类的静态字段分配内存,设置变量的初始化值。
解析阶段则主要将符号引用解析为实际的引用。如果符号引用指向一个未被加载的类,或者未被加载类的字段或方法,那么解析将触发这个类的加载。
3、初始化
类的初始化只有一次,类的初始化是不同于对象的初始化。
类加载的最后一步是初始化,如果直接赋值的静态字段被 final 所修饰,并且它的类型是基本类型或字符串时,该字段便会被 Java 编译器标记成常量值(ConstantValue)。初始化会为类的常量赋值,以及执行静态代码块中的方法。
当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
类在初始化时,会由编译器自动收集类中的所有变量的赋值语句和静态代码块,将这些合并为一个方法,由上往下执行。
Java 虚拟机会通过加锁来确保类的初始化过程(clinit)仅被执行一次。
类的初始化会在以下情况下触发:
*当虚拟机启动时,初始化用户指定的主类;
*当遇到用以新建目标类实例的 new 指令时,初始化 new 指令的目标类;
*当遇到调用静态方法的指令时,初始化该静态方法所在的类;
*当遇到访问静态字段的指令时,初始化该静态字段所在的类;
*子类的初始化会触发父类的初始化;
*如果一个接口定义了 default 方法,那么直接实现或者间接实现该接口的类的初始化,会触发该接口的初始化;
*使用反射 API 对某个类进行反射调用时,初始化这个类;

public class Singleton {
	private Singleton() {}
		private static class LazyHolder { 
			    static final Singleton INSTANCE = new Singleton();
		}
	public static Singleton getInstance() {
			    return LazyHolder.INSTANCE;
		}
}

/**
这是一个单例延时初始化的案例,只有调用Singleton.getInstance 时,程序才会访问 LazyHolder.INSTANCE,才会触发对 LazyHolder 的初始化(对应第 4 种情况),继而新建一个 Singleton 的实例。
由于类初始化是线程安全的,并且仅被执行一次,因此程序可以确保多线程环境下有且仅有一个 Singleton 实例。
**/

本文地址:https://blog.csdn.net/weixin_41731982/article/details/107695677

相关标签: 学末总结 JVM