jvm-运行时数据区域
09 May 2020虚拟机和Java虚拟机
所谓虚拟机就是一款软件,用来执行一系类虚拟的计算机指令。大体上,虚拟机可以分为系统虚拟机和程序虚拟机,VMware就属于系统虚拟机,他们完全是对物理计算机的仿真,提供了一个可运行完整操作系统的软件平台(比如:Windows上装WMware,WMware里面装Linux)。程序虚拟机就是专门为执行单个程序而设计的,java虚拟机(Java Virtual Machine)用来执行Java字节码指令的。我们写的.java文件先被编译成.class文件,java虚拟机加载这个.class文件并且执行文件中的字节码,这样我们写的java代码就运行起来了。
jvm架构
jvm架构主要分为三个子系统:类加载子系统、运行时数据区域和执行引擎。简单介绍。
类加载子系统(ClassLoader Subsystem)
:实现java类的动态加载功能,当java程序在运行时第一次引用某个类,类加载系统就加载、连接并且初始化这个类对应的class文件。而不是在编译期就做这些事情。运行时数据区(Runtime Data Area)
:java字节码将分配到这个区域,具体介绍如下。执行引擎(Execution Engine)
:分配至运行时数据区的的字节码将会被执行引擎执行,执行引擎读这些字节码并且一段一段地执行。
运行时数据区域
java虚拟机管理的内存是系统内存的一部分。java虚拟机在执行java程序时会把它管理的内存区域划分成:程序计数器、java虚拟机栈、本地方法栈、java堆和方法区五个部分。运行时数据区的内部结构如上图所示,其中虚拟机栈、本地方法栈、程序计数器的是线程隔离的,每个线程中的这三个区域的随着线程的启动和结束而建立和销毁。方法区和堆是线程共享的。
程序计数器
程序计数器(Program Counter Register)是每个线程的私有空间,java虚拟机会为每个java线程创建一个程序计数器。在任意时刻,java线程总是在执行一个方法,这个正在被执行的方法被称为当前方法,如果当前方法不是本地方法,则程序计数器就会指向当前正在被执行的指令,如果当前方法是本地方法,程序计数器的值为空。程序计数器是程序控制流的指示器,分支、循环、跳转、异常处理、线程回复等基础功能都要依赖这个指示器来完成。
java虚拟机栈
java虚拟机栈(Java Virtual Machine Stack)也是线程私有的,类的方法被执行的时候,java虚拟机就会为每个方法创建一个栈帧(Stack Frame)用于存储该方法的局部变量表、操作数栈、动态连接、方法出口等信息。每个一个方法被调用直到执行完毕的过程,就对应着一个栈帧在虚拟机栈中从出栈到入栈的过程。栈帧和方法是一对一的关系,虚拟机栈和方法是一堆多的关系。
每一次函数调用,都会有个对应的栈帧被压入java虚拟机栈,调用结束后这个栈帧被弹出java虚拟机栈。假如函数1中有子函数2……以此类推,对应的栈帧入栈如上图所示。由这些可以得知频繁的出栈入栈会增大时间开销,先看一段代码:
for(int i=0; i<objects.length(); i++){
..........
}
objects.length()执行一次应该花费不了多少时间,但是当objects中元素个数很多时,频繁的入栈出栈也会增大时间开销,显然这种写法是不可取的。
可以改成下面的代码:
int length = objects.length()
for(int i=0; i<length; i++){
..........
}
这么写无论objects中有多少个元素,都只执行一次入栈出栈操作。不过我已经很少使用for循环了,lambda表达式真的很好用。
栈帧的局部变量表中保存着局部变量(基本数据类型和和对象引用)。(有个问题,类的成员变量的对象引用存放在哪个区域?)
java虚拟机栈中会抛出两种异常:如果线程请求深度大于虚拟机所允许的深度,将抛出StackOverflowError异常,比如:递归调用忘了设置边界条件;局部变量保存会占用存储空间,当局部变量要占用的内存超过java虚拟机栈可分配的内存时,会抛出OutOfMemoryError异常。
本地方法栈
本地方法栈(Native Method Stacks)与虚拟机栈的作用是相似的,不同的是:虚拟机栈的是为执行用户编写的java方法服务的,本地方法栈是为执行本地方法服务的。那什么事本地方法呢?本地方法(Native Method)在java源程序中以关键字”Native“声明,不提供函数体,其实现使用C/C++在另外的文件中编写,编写的规则遵循java本地接口规范。简而言之:本地方法就是java中声明的可使用C/C++实现的方法。
java堆
java 堆(Java Heap)是java虚拟机管理的内存中最大的一块,而且这块区域被所有的线程共享。java中的对象实例几乎都在这里分配(如果是栈上分配,对象实例会分配在栈帧中)。java对也是垃圾收集器主要发挥作用的区域(至于分配策略和垃圾回收机制,后面再写吧),同样当java对中没有内存空间完成实力分配时也会抛出OutOfMemoryError异常。
方法区
方法区(Method Area)也是各个线程共享的内存区域,它用于存储已经被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。
运行时常量池(Runtime Constant Pool)是方法区的一部分。java文件编译后生成的class文件中有一项信息是常量池表(Runtime Pool Table),用于存放编译期生成的各种字面量和符号应用,这部分内容在类加载后存放到方法区的运行时常量池中。方法区如果无法满足新的内存分配需求是也会抛出OutOfMemoryError异常。