在学习jvm之前,觉得有必要先了解一下虚拟机是个什么操作。虚拟机有很多种,有常用的vmvare,我想学习linux指令,最好安装一个linux系统,但是我又不想安装双系统,怎么办,那就在vmvare中安装linux,仿佛多了一台计算机。
c,c++编译好的程序,换到另一台计算机上运行,可能会出错,而java程序,一次编译,到处运行,这是因为jvm的无比威力。
所以什么是虚拟机?
虚拟机是通过软件模拟物理机中所拥有的硬件系统功能,并且在隔离的环境中运行。
jvm?
c,c++程序是在物理机上直接编译运行,使用的是物理机的指令集,数据的表达形式等,因为每台计算机可能其数据形式不同,指令集不同,编写好的程序在特定的计算机可以运行,换到其他计算机上就运行不了。
但是java程序它是在jvm上进行编译运行,相当于在物理机和java程序之间加了一层软件,jvm能够支持不同的操作系统,不同的物理机,只要你的计算机下载了jdk。所以,java程序能够做到一次编写,到处运行,代价就是其速度不如c,c++。
jvm封装了很多底层细节,比如内存分配,垃圾回收,这些都由jvm代替程序员完成。
进入正题,因为jvm为我们提供了内存分配,相信好奇的java程序员还是会想知道底层到底是怎么实现的,更重要的是在出现内存溢出、泄露时能帮助我们定位问题。
运行时数据区
与其他语言不同,jvm在执行java程序过程中将内存划分为若干个不同的数据区域。在java程序中涉及到的数据有类信息,实例对象,静态变量,常量,实例变量,局部变量,对这些数据的创建时间,生命周期是不一样的。比如堆区是需要经常回收的区域,将实例对象存放于这一区域,方法区相当于永久代,不会轻易被回收的区域,将类信息,即时编译信息等放置此区。虚拟机栈、本地方法栈、程序计数器都是线程私有的。
如果不划分区域,完全放置一个区里会怎么样?
更复杂的垃圾回收算法,更复杂的内存分配算法,艰难的内存创建,对象定位等等
下面对每个区做一个详细的描述:
1.程序计数器
存放的是当前线程正在执行的指令地址
存在原因:确定指令执行位置,在多线程抢占到cpu资源,在控制语句返回后能定位到正确的执行位置。
说明:
- 线程私有
- 记录当前指令执行地址
- 如果执行的是native方法,程序计数器为undefined
- 是内存区域中唯一一个不会出现OutofMemoryError的区域
2.方法区
存放的是类元信息,静态变量(static),常量(final),即时编译器编译后的代码。
在jdk1.7以后就将字符串常量池移到堆区中。
说明:线程共享
3.堆区
在虚拟机启动时就创建,存放的对象实例,几乎所有的对象实例都在堆上分配,还有一部分会在栈上分配。这是垃圾回收器的主要回收区域,由于现在的垃圾回收器是基于分代收集算法,所以堆区被分为eden,survivor1,survivor2,tenured。
java堆可以是物理上不连续的,只要是逻辑上连续的即可。
4.虚拟机栈
线程私有,方法调用时都会创建一个栈帧存放到栈中,方法调用结束后,从栈中弹出栈帧。栈帧中存放的主要有局部变量表,操作数栈,动态链接,出口等信息。
局部变量表:存放编译期已知的基本数据类型,对象引用类型,returnAddress。每个条目长度为32位,如果是64位的long,double类型基本数据,就占据俩个局部变量空间。
public void test(int a,int b){
int c = 0;
c = a + b;
Object o = new Object();
}
0 reference this
1 int int a
2 int int b
3 int int c
4 reference o
操作数栈:java方法中所有的参数传递都是通过操作数栈进行的,在java中没有寄存器
public static int add(int a,int b){
int c = 0;
c = a + b;
return c;
}
0:iconst_0 //0压栈
1:istore_2 //弹出int,存放于局部变量2中,2指的是c,0指的是a,1是b
2:iload_0 //把局部变量0压栈
3:iload_1 //把局部变量1压栈
4:iadd //弹出俩个变量,求和,结果压栈
5:istore_2 //弹出结果,放于局部变量2
6:iload_2 //局部变量2压栈
7:return //返回
5.本地方法栈
与虚拟机栈类似,只不过本地方法栈服务的为本地方法