java内存管理模拟_Java:JVM内存管理(二)

Java:JVM内存管理(二)

JVM知识版本:3

作者:陈小默

声明:禁止商业,禁止转载在第一节中Java:JVM内存管理(一)中,我们介绍了JVM的内存结构,在介绍JVM如何管理内存之前,我们先要接触JVM的体系结构与工作方式。

六、JVM体系结构

在我们初学Java的时候一定听说过Java这么介绍自己:Java是一款面向对象的跨平台编程语言。当我们学习过Java之后,我们就会对其面向对象思想有深刻的理解,但是我们仍然不明白Java到底是如何实现一次编译到处运行的呢?

6.1 模拟计算机

JVM的全称是Java Virtual Machine(Java虚拟机),它通过模拟一台计算机来达到一台计算机所拥有的功能。那么一台真正的计算机应该具有什么样的功能呢?[1]指令集:计算机能够识别的机器语言的命令集合

计算单元:能够识别并且控制指令执行的功能模块

寻址方式:地址的位数、最小地址和最大地址寻址范围、以及地址的运行规则

寄存器定义:包括操作数寄存器、变址寄存器、控制寄存器等定义、数量和使用方式

存储单元:能够存储操作数和保存操作结构的单元、如内核缓存、内存和磁盘等

6.1.1 指令集

指令集是指能够操作CPU和控制计算机系统的一系列指令的集合,由于每一个型号的CPU作用不尽相同,所以厂商都会为自己的设备单独定义一套指令集。目前市面上主流的两套指令集从体系结构上划分位精简指令集(RISC,Reduced Instruction Set Computing)和复杂指令集(CISC,Complex Instruction Set Computing),这也就造成了像C/C++这样的直接操作硬件的语言编译出的可执行文件在其他平台上并不能正常运行的原因。

6.1.2 指令集与汇编

指令集是可以直接被计算机识别的机器码,也就是说它必须以二进制形式存放在计算机中,而汇编语言是机器码的助记符。也就是说,我们编写的每一条汇编指令,都是与机器码一一对应的。

6.1.3 指令集与CPU架构

不同的CPU架构的寄存器和段不相同,自然无法使用相同的指令集,但是现在不同的芯片厂商往往会兼容不同的指令集以吸引客户。

6.2 JVM的跨平台特性

Java的跨平台特性正是基于JVM的规范,JVM和实体机一样有一套自己的指令集,这个指令集能够被JVM解析执行。这个指令我们称之为JVM的字节码指令,凡是符合JVM规范的class字节码文件都可以被JVM执行。

于是Java的跨平台特性就此产生,我们在任何平台上编写编译的代码都是符合JVM虚拟机规范的,而不同平台上的JVM会进行二次翻译,也就是将字节码指令翻译为JVM所在的计算机的机器码。

6.3 JVM体系结构Java语言并不是只能运行在JVM之上,只要实现了相应的编译器Java语言就可以运行在任何平台之上(比如J++),也可以被编译为本地代码直接运行在操作系统之上,比如,Linux上的GCJ(GNU Compiler for Java)就可以把Java语言编译为本地代码直接执行。同样的,JVM上也不是只能执行Java语言,只要实现了适当的编译器,将其他语言编译为JVM上的字节码,就可以在JVM上运行。比如,JRuby,Jython以及Groovy等其他JVM语言,都会通过相应的编译器或是解释器转化为.class,然后在JVM上运行。由于JVM并不关心.class文件是由Java、JRuby、Jython等转化而来,只要这个文件结构正确并能通过class文件校验。因此,由于.class文件屏蔽了Java、JRuby等上层语言的差异,所以Java、Kotlin、Groovy等可以相互调用。[2]

0_1323760255zmkL.gif

JVM的基本结构由4部分组成:类加载器,在JVM启动时或者在类运行时将需要的class文件加载进JVM

执行引擎,执行引擎的任务是负责执行class文件中包含的字节码指令文件,相当于实际机器上的CPU

内存区,将内存划分成若干区以模拟真实计算机的存储、记录和调度模块,如实际计算机上各种功能的寄存器或者PC指针记录器

本地方法调用,调用C/C++实现本地方法的代码返回结果

七、JVM的工作机制计算机只接受机器指令,其他高级语言必先经过编译器编译成计算机指令才能被计算机正确的执行,所以从高级语言到机器语言之间必须有个翻译的过程。

7.1 JVM引擎的架构设计

每当我们创建一个新的线程的时候,JVM会为这个线程创建一个栈,同时为这个栈分配一个PC寄存器,并且这个PC寄存器会指向这个线程中的第一行可执行代码。每当调用一个新的方法的时候会在这个栈上创建一个新的栈帧数据结构,这个栈帧会保留这个方法的一些元信息,比如方法的局部变量、一些用来支持常量池的解析、正常方法的返回以及异常处理机制等。

7.2 执行引擎的执行过程

7.2.1 Oolong介绍

Oolong是一种属于汇编的编程语言,我们之所以要介绍这门语言,是因为它能够帮助我们更好的理解class文件结构中的类信息,这些信息都是面向JVM的,也就是说只有JVM能够认识它们。所以我们先将class文件中的二进制信息转换为易读的Oolong语言,可以帮助我们理解class文件的结构。

由于资源太难找,本人打包了一个Oolong项目,提供下载CSDN:面积分Oolong汇编查看器下载,使用时将此jar包放在与待查看的class文件同级目录下,然后双击运行即可。或者用命令行对某一指定的class文件进行反汇编Oolong.jar C:\Users\cxm\Desktop\Hello.class就可以对桌面上的Hello.class文件进行反汇编。得到一个后缀位.j的文件。

7.2.2 代码分析

首先我们通过一个具体的例子来查看执行引擎的执行过程:

publicclassTest{

publicstaticvoidmain(String[]args){

inta=2;

intb=4;

intc=(a+b)*10;

}

}

以下是使用Oolong得到的反汇编代码

.sourceTest.java

.classpublicsuperTest

.superjava/lang/Object

.methodpublic()V

.limit stack1

.limit locals1

.line1

l0:aload_0

l1:invokespecial java/lang/Object/()V

l4:return

.endmethod

.methodpublicstaticmain([Ljava/lang/String;)V

.limit stack2

.limit locals4

.line3

l0:iconst_2

l1:istore_1

.line4

l2:iconst_4

l3:istore_2

.line5

l4:iload_1

l5:iload_2

l6:iadd

l7:bipush10

l9:imul

l10:istore_3

.line6

l11:return

.endmethod

我们只看方法,首先我们看到开篇第一个方法是来自Object的Init方法,这个方法用来对对象进行初始化,是隐式的。

第二个方法才是我们的main方法

.methodpublicstaticmain([Ljava/lang/String;)V

.limit stack2

.limit locals4

第一行表示这个一个方法.method其访问权限是公开的public,其存储方式的静态的static,其方法名位main,其中参数[表示这是一个数组,L表示这不是一个基本数据类型,其类型是String,返回值是Void。

第二行的 stack 2 说明该方法的操作栈的最大长度为2

第三行的 locals 4 表示该方法使用了局部变量栈的最大长度为4

.line3

l0:iconst_2

l1:istore_1

.line 3表示当前运行的代码在源文件中的第三行,该行所做的操作是,将常数2入栈,将栈顶元素移至局部变量区第一位存储

.line4

l2:iconst_4

l3:istore_2

第四行的操作是将int类型的常量4入栈,然后在将其移动到局部变量区的第二位

.line5

l4:iload_1

l5:iload_2

l6:iadd

l7:bipush10

l9:imul

l10:istore_3

第五行的操作稍显复杂,14:这里先从局部变量区中取出第一位(int 2)放到操作栈中,15:再将局部变量区中取出的第二位(int 4)压到操作栈中,16:执行加法指令(将栈中连续两次出栈的值相加再将结果压栈),17:将常量10压栈,19:执行乘法操作(将栈中连续两次出栈的值相乘并入栈)

.line6

l11:return

第六行有一个空返回值的return语句,这也说明了当我们没有显式的添加return语句时,编译器会自动帮我们加上。

7.3 JVM方法调用栈

接下来我们分析方法调用的实现,源代码如下:

publicclassTest{

publicstaticvoidmain(String[]args){

inta=2;

intb=4;

intc=add(a,b)*10;

}

publicstaticintadd(inta,intb){

returna+b;

}

}

以下是通过Oolong生成的汇编代码

.sourceTest.java

.classpublicsuperTest

.superjava/lang/Object

.methodpublic()V

.limit stack1

.limit locals1

.line1

l0:aload_0

l1:invokespecial java/lang/Object/()V

l4:return

.endmethod

.methodpublicstaticmain([Ljava/lang/String;)V

.limit stack2

.limit locals4

.line3

l0:iconst_2

l1:istore_1

.line4

l2:iconst_4

l3:istore_2

.line5

l4:iload_1

l5:iload_2

l6:invokestaticTest/add(II)I

l9:bipush10

l11:imul

l12:istore_3

.line6

l13:return

.endmethod

.methodpublicstaticadd(II)I

.limit stack2

.limit locals2

.line9

l0:iload_0

l1:iload_1

l2:iadd

l3:ireturn

.endmethod

这里我们可以看到这样一段代码

l6:invokestaticTest/add(II)I

这是一段跳转指令,调用一个Test类中方法签名为add (II)I的方法。

[1]许令波.深入分析Java Web技术内幕(修订版).电子工业出版社 ↩

[2]CSDN:JVM总结--JVM体系结构 ↩

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值