Java虚拟机(一)Java虚拟机概念

Java的起源

但是要说清楚Java虚拟机,我想从JDK开始说起。我们想要在一个计算机上使用Java,就必须在网上下载JDK和JRE,JDK是Java的开发工具,JRE是Java的运行环境,有了这个两个就能在计算机上使用Java。有人说可能还需要配置path路径,其实也未必,之所以要配置Java_home,不是因为它必须配置了才能用,只是因为配置了path路径后,就能够在全局中访问到它,如果你没有配置,其实在使用idea的时候,也没有多大影响。

JDK起源于sun公司,这是一个B2B企业,专门为其他企业提供软件服务,但是如今的Java已经不属于sun公司了,sun被Oracle收购了,Java已经属于Oracle了,并且,这家公司还把JDK和JRE结合到了一起,下载的时候直接下载JDK就行了,而在命名上,也有了变化,以后没有1.7和1.8这样的说法了,统称JDk7和JDk8这样的命名,如今的Jdk,貌似出到了JDk13了。

什么是Java虚拟机

先简述一下:Java虚拟机把java代码,根据所在平台的不同,会自动编译成相应的字节码,在任何平台上运行。这么讲似乎有点抽象,那我们做一个比喻,比如c语言,它只能在自己的平台上运行,比如Windows,你把c语言代码拿去Linux下运行,那是行不通的,必须安装相应的运行环境,而Java虚拟机的优势在于,你只要写了Java代码,有这个虚拟机,就能在任意平台上运行,这就是所谓的跨平台性。

但是Java虚拟机并不是一个个体,而是一个种类,自Java的发展史以来,Java虚拟机有很多,而现今在使用的Java虚拟机叫做HotSpot,具体可以在cmd(命令行)中,使用Java -version进行查看。

jvm2

可以从第三行得知,这个是HotSpot虚拟机

Java虚拟机运行时的数据区域

jvm

如图所示,共分为这么几个区域:

1.程序计数器:内存空间小,线程私有。字节码解释器工作是就是通过改变这个计数器的值来选取下一条需要执行指令的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖计数器完成。这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是 Native 方法,这个计数器的值则为 (Undefined)。

(1),区别于计算机硬件的pc寄存器,两者不略有不同。计算机用pc寄存器来存放“伪指令”或地址,而相对于虚拟机,pc寄存器它表现为一块内存(一个字长,虚拟机要求字长最小为32位),虚拟机的pc寄存器的功能也是存放伪指令,更确切的说存放的是将要执行指令的地址。

(2)当虚拟机正在执行的方法是一个本地(native)方法的时候,jvm的pc寄存器存储的值是undefined。

(3)程序计数器是线程私有的,它的生命周期与线程相同,每个线程都有一个。

(4)此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。

2.Java 虚拟机栈:线程私有,生命周期和线程一致。描述的是 Java 方法执行的内存模型:每个方法在执行时都会床创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行结束,就对应着一个栈帧从虚拟机栈中入栈到出栈的过程。Java虚拟机栈。即是Java中的栈内存。

(1)线程私有的,它的生命周期与线程相同,每个线程都有一个。

(2)每个线程创建的同时会创建一个JVM栈,JVM栈中每个栈帧存放的为当前线程中局部基本类型的变量(java中定义的八种基本类型:boolean、char、byte、short、int、long、float、double;和reference (32 位以内的数据类型,具体根据JVM位数(64为还是32位)有关,因为一个solt(槽)占用32位的内存空间 )、部分的返回结果,非基本类型的对象在JVM栈上仅存放一个指向堆上的地址;

(3)每一个方法从被调用直至执行完成的过程就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

(4)栈运行原理:栈中的数据都是以栈帧(Stack Frame)的格式存在,栈帧是一个内存区块,是一个数据集,是一个有关方法和运行期数据的数据集,当一个方法A被调用时就产生了一个栈帧F1,并被压入到栈中,A方法又调用了B方法,于是产生栈帧F2也被压入栈,B方法又调用了C方法,于是产生栈帧F3也被压入栈…… 依次执行完毕后,先弹出后进……F3栈帧,再弹出F2栈帧,再弹出F1栈帧。

3.本地方法栈:区别于 Java 虚拟机栈的是,Java 虚拟机栈为虚拟机执行 Java 方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。而所谓的Native方法,就是指使用非Java语言的方法,比如C++等,整个Java虚拟机的内部实现都是由C++构成的。

(1)先解释什么是本地方法:jvm中的本地方法是指方法的修饰符是带有native的但是方法体不是用java代码写的一类方法,这类方法存在的意义当然是填补java代码不方便实现的缺陷而提出的。案例介绍将在 下面22知识点仔细介绍。

(2)作用同java虚拟机栈类似,区别是:虚拟机栈为虚拟机执行Java方法服务,而本地方法栈则是为虚拟机使用到的Native方法服务。

(3)是线程私有的,它的生命周期与线程相同,每个线程都有一个。

4..Java 堆:对于绝大多数应用来说,这块区域是 JVM 所管理的内存中最大的一块。线程共享,主要是存放对象实例和数组。内部会划分出多个线程私有的分配缓冲区。可以位于物理上不连续的空间,但是逻辑上要连续。即是所谓的堆内存的存放地点,在Java中经常会用到。

(1)是Java虚拟机所管理的内存中最大的一块。

(2)不同于上面3个,堆是jvm所有线程共享的。

(3)在虚拟机启动的时候创建。

(4)唯一目的就是存放对象实例,几乎所有的对象实例以及数组都要在这里分配内存。

(5)Java堆是垃圾收集器管理的主要区域。

(6)因此很多时候java堆也被称为“GC堆”(Garbage Collected Heap)。从内存回收的角度来看,由于现在收集器基本都采用分代收集算法,所以Java堆还可以细分为:新生代和老年代;新生代又可以分为:Eden 空间、From Survivor空间、To Survivor空间。(23知识点详细介绍)

(7)java堆是计算机物理存储上不连续的、逻辑上是连续的,也是大小可调节的(通过-Xms和-Xmx控制)。

(8)如果在堆中没有内存完成实例的分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。

5.方法区:方法区在GC中,曾经也被称之为永久代,在JDK8以及之后被称之为元空间。也可以从逻辑上推导,方法在虚拟机中一般不会被GC回收。属于共享内存区域,存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

(1)在虚拟机启动的时候创建。

(2)所有jvm线程共享。

(3)除了和堆一样不需要不连续的内存空间和可以固定大小或者可扩展外,还可以选择不实现垃圾收集。

(4)用于存放已被虚拟机加载的类信息、常量、静态变量、以及编译后的方法实现的二进制形式的机器指令集等数据。

(5)被装载的class的信息存储在Methodarea的内存中。当虚拟机装载某个类型时,它使用类装载器定位相应的class文件,然后读入这个class文件内容并把它传输到虚拟机中。

(6)运行时常量池(Runtime Constant Pool)是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。

6.运行时常量池:属于方法区一部分,用于存放编译期生成的各种字面量和符号引用。编译器和运行期(String 的 intern() )都可以将常量放入池中。内存有限,无法申请时抛出 OutOfMemoryError。

7.直接内存:非虚拟机运行时数据区的部分。可以看作为本机内存,本机内存不受堆大小的限制。