java虚拟机在执行的指令不需要加载_java虚拟机

一、JVM是什么?

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

二、JVM的作用?

JVM有自己完善的硬件架构,如处理器、堆栈、寄存器等,还具有相应的指令系统。引入Java语言虚拟机后,Java语言在不同平台上运行时不需要重新编译。Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码字节码,就可以在多种平台上不加修改地运行。Java虚拟机在执行字节码时,把字节码解释成具体平台上的机器指令执行。这就是Java的能够“一次编译,到处运行”的原因。

三、从进程的角度解释JVM

我们知道,虚拟机是运行在操作系统之中的,那么什么东西才能在操作系统中运行呢?当然是进程,因为进程是操作系统中的执行单位。可以这样理解,当它在运行的时候,它就是一个操作系统中的进程实例,当它没有在运行时(作为可执行文件存放于文件系统中),可以把它叫做程序。

对命令行比较熟悉的同学,都知道其实一个命令对应一个可执行的二进制文件,当敲下这个命令并且回车后,就会创建一个进程,加载对应的可执行文件到进程的地址空间中,并且执行其中的指令。

四、Java版HelloWord程序的编译和执行形式:

①: 首先编写源文件HelloWord.java :

1

2

3

4

5public class HelloWorld{

public static void main(String[] args){

System.out.println("HelloWorld");

}

}

②: 编译Java版的HelloWorld程序:$ javac HelloWorld.java

③: 运行Java版的HelloWorld程序:$ java -classpath . HelloWorld

4.1 从上面的过程可以看到, 我们在运行Java版的HelloWorld程序的时候, 敲入的命令并不是 ./HelloWorld.class 。 因为class文件并不是可以直接被操作系统识别的二进制可执行文件 。 我们敲入的是java这个命令。 这个命令说明, 我们首先启动的是一个叫做java的程序, 这个java程序在运行起来之后就是一个JVM进程实例.

4.2 上面的命令执行流程是这样的:

java命令首先启动虚拟机进程,虚拟机进程成功启动后,读取参数“HelloWorld”,把他作为初始类加载到内存,对这个类进行初始化和动态链接,然后从这个类的main方法开始执行。

也就是说我们的.class文件不是直接被系统加载后直接在cpu上执行的,而是被一个叫做虚拟机的进程托管的。首先必须虚拟机进程启动就绪,然后由虚拟机中的类加载器加载必要的class文件,包括jdk中的基础类(如String和Object等),然后由虚拟机进程解释class字节码指令,把这些字节码指令翻译成本机cpu能够识别的指令,才能在cpu上运行.

4.3 从这个层面上来看,在执行一个所谓的java程序的时候,真真正正在执行的是一个叫做Java虚拟机的进程,而不是我们写的一个个的class文件。这个叫做虚拟机的进程处理一些底层的操作,比如内存的分配和释放等等。我们编写的class文件只是虚拟机进程执行时需要的“原料”。这些“原料”在运行时被加载到虚拟机中,被虚拟机解释执行,以控制虚拟机实现我们java代码中所定义的一些相对高层的操作,比如创建一个文件等,可以将class文件中的信息看做对虚拟机的控制信息,也就是一种虚拟指令。

五、JVM体系结构简介:

534153d41473a0891f4b170ceb914d23.png

根据上图表达的内容,我们编译之后的class文件是作为Java虚拟机的原料被输入到Java虚拟机的内部的,那么具体由谁来做这一部分工作呢?其实在Java虚拟机内部,有一个叫做类加载器的子系统,这个子系统用来在运行时根据需要加载类。注意上面一句话中的“根据需要”四个字。在Java虚拟机执行过程中,只有他需要一个类的时候,才会调用类加载器来加载这个类,并不会在开始运行时加载所有的类。就像一个人,只有饿的时候才去吃饭,而不是一次把一年的饭都吃到肚子里。一般来说,虚拟机加载类的时机,在第一次使用一个新的类的时候。

由虚拟机加载的类,被加载到Java虚拟机内存中之后,虚拟机会读取并执行它里面存在的字节码指令。虚拟机中执行字节码指令的部分叫做执行引擎。就像一个人,不是把饭吃下去就完事了,还要进行消化,执行引擎就相当于人的肠胃系统。在执行的过程中还会把各个class文件动态的连接起来。

Java虚拟机会进行自动内存管理。具体说来就是自动释放没有用的对象,而不需要程序员编写代码来释放分配的内存。这部分工作由垃圾收集子系统负责。

一个Java虚拟机实例在运行过程中有三个子系统来保障它的正常运行,分别是类加载器子系统, 执行引擎子系统和垃圾收集子系统。 如下图所示:

608f83078107104bb891915ca3888700.png

虚拟机的运行,必须加载class文件,并且执行class文件中的字节码指令。它做这么多事情,必须需要自己的空间。就像人吃下去的东西首先要放在胃中。虚拟机也需要空间来存放个中数据。首先,加载的字节码,需要一个单独的内存空间来存放;一个线程的执行,也需要内存空间来维护方法的调用关系,存放方法中的数据和中间计算结果;在执行的过程中,无法避免的要创建对象,创建的对象需要一个专门的内存空间来存放。

a9ac7bddec937cdfff7fd56c5eeba83d.png

5.1 Java虚拟机 运行时数据区:

8064f504e355278781d46e5fffe9354f.png

5.1.1程序计数器:

①: 是一块较小的内存空间, 当前线程执行的字节码的行号指示器(记录正在执行的虚拟机字节码指令的地址(如果正在执行的是本地方法则为空)。)

②: 指向下一条需要执行的指令

③: 每条线程都需要一个独立的程序计数器,彼此互不干扰(线程私有的内存)

5.1.2Java虚拟机栈:

①: 线程私有的,生命周期与线程相同

②: 描述java方法执行的内存模型,每个方法执行时都会创建一个栈帧,用来存放局部变量表,操作数栈,常量池引用等信息.

③: 每个方法从调用到执行完成 就对应着栈帧在虚拟机栈帧中入栈和出栈.

④: 两种异常:

StackOverFlow异常 : 线程所请求的栈深度大于虚拟机允许的则抛出

OutOffMemoryError异常: 扩展时无法申请到足够的内存则抛出

⑤: 我们常说的栈 和堆 中的栈就是Java虚拟机栈,更具体来说是Java虚拟机栈中的局部变量表部分.

⑥: 局部变量表:

存放编译期可知的各种基本数据类型,对象引用类型

所需要的内存空间在编译期完成分配, 在方法运行期间不会改变其大小

64位长度的long 和 double 类型数据会占用2个局部变量空间(Slot) ,其余占一个.

f8ee7dca71908d6d75caaba23eb227b1.png

⑦: 可以通过 -Xss 这个虚拟机参数来指定每个线程的 Java 虚拟机栈内存大小:java -Xss512M HackTheJava

5.1.3本地方法栈:

本地方法栈与 Java 虚拟机栈类似,它们之间的区别只不过是本地方法栈为本地方法服务。

本地方法一般是用其它语言(C、C++ 或汇编语言等)编写的,并且被编译为基于本机硬件和操作系统的程序,对待这些方法需要特别处理。

上面三个区域随线程而生, 随线程而灭;

5.1.4Java堆( Java Heap) : 也称GC堆(Garbage Collected Heap )

①: 是Java虚拟机管理的内存中最大的一块

②: 被所有线程共享,的一块内存空间, 在虚拟机启动时候创建,

③: 唯一目的: 存放对象实例, 几乎所有的对象实例 和 数组 都在这里这里分配内存.

④: 是垃圾收集器管理的主要区域 ,所以也称GC堆

⑤: 堆不需要连续内存,并且可以动态增加其内存,增加失败会抛出 OutOfMemoryError 异常。可以通过 -Xms 和 -Xmx 这两个虚拟机参数来指定一个程序的堆内存大小,第一个参数设置初始值,第二个参数设置最大值。

java -Xms1M -Xmx2M HackTheJava

⑥: 在 Java 中,堆被划分成两个不同的区域:新生代 ( Young )、老年代 ( Old )。新生代 ( Young ) 又被划分为三个区域:Eden、From Survivor、To Survivor。

9981a27a48cf09dc2b2a51adb018682f.png

默认的,Edem : from : to = 8 : 1 : 1 ( 可以通过参数 –XX:SurvivorRatio 来设定 )即: Eden = 8/10 的新生代空间大小,from = to = 1/10 的新生代空间大小。 JVM 每次只会使用 Eden 和其中的一块 Survivor 区域来为对象服务,所以无论什么时候,总是有一块 Survivor 区域是空闲着的。因此,新生代实际可用的内存空间为 9/10 ( 即90% )的新生代空间。

5.1.5方法区(永久代):

①: 被所有线程共享,的一块内存空间

②: 用来存储Class的相关信息如已经被Java虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。

③: 和堆一样不需要连续的内存,并且可以动态扩展,动态扩展失败一样会抛出 OutOfMemoryError 异常。

④: 对这块区域进行垃圾回收的主要目标是对常量池的回收和对类的卸载,但是一般比较难实现。

⑤: HotSpot 虚拟机把它当成永久代来进行垃圾回收。但很难确定永久代的大小,因为它受到很多因素影响,并且每次 Full GC 之后永久代的大小都会改变,所以经常会抛出 OutOfMemoryError 异常。为了更容易管理方法区,从 JDK 1.8 开始,移除永久代,并把方法区移至元空间,它位于本地内存中,而不是虚拟机内存中。

5.1.5运行时常量池

①: 运行时常量池是方法区的一部分。

②: .Class 文件中的常量池(编译器生成的字面量和符号引用)会在类加载后被放入这个区域。

③: 除了在编译期生成的常量,还允许动态生成,例如 String 类的 intern()。

④: 常量池(constant pool)指的是在编译期被确定,并被保存在已编译的.class文件中的一些数据。它包括了关于类、方法、接口等中的常量,也包括字符串常量。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值