在谈运行时数据区之前我们先来看看几个概念:
1、JVM
在我们工作和学习中JVM估计都已经听出老茧来了,但是大家有没有真正了解过什么是JVM,它是干嘛用的?
JVM(Java Virtual Machine),顾名思义就是Java虚拟机,首先它是虚拟机,其次它用于执行Java字节码,将Java字节码翻译成计算机能运行的机器语言,所以Java是一种跨平台的语言。那么JVM就只是单纯的执行Java字节码吗?答案是No,我们程序的内存、数据等都是由JVM来管理的,所以我们编写Java代码无需分配释放内存。
2、JDK
JDK(Java Development Kit),Java开发工具包,从字面含义我们就能大概猜到,他是Java官方提供的一些便于我们开发的工具包,包括Java类库,JVM调试工具等。
3、JRE
JRE(Java Runtime Environment),即Java运行时环境,我们安装好jdk后,就会发现jre被安装在jdk的目录下,也即它是jdk的一部分。Java程序要运行必须依赖JRE,他包含了JVM的标准实现和核心类库。
了解了以上概念,我们队Java虚拟机有了一个大概的认识,接下来就是重点:运行时数据区。
看过《深入理解Java虚拟机的同学》都应该知道,Java运行时数据区包含方法区、堆、程序计数器、虚拟机栈、本地方法栈,那么这些内容都是干嘛的,他们之间又是什么关系呢?要了解这些我们先通过下图来认识一下JVM的体系结构。
通过一句话可以简单的描述,JVM首先通过类加载器将class文件加载到虚拟机运行时数据区,再由JVM执行引擎执行字节码程序,最后得出程序结果。
下面我们就来详细看看运行时数据区。方法区
从字面上看,感觉好像这里存储的就是一些方法,但其实不只是方法,它存储的是类信息、静态变量、常量等数据,类信息一般包括一些属性、方法等。因此由上图我们也能看出,严格意义上来说,类加载时将类信息加载进了运行时数据区中的方法区了。
堆
Java的所有对象都存放到堆里面,而且区别于C++,Java的对象也只能存放在堆里面。当我们new了一对象后,虚拟机就会在堆里面开辟一个内存空间来存放当前创建的对象。这里就会有一个疑问,为什么JVM将静态变量或者常量不放到堆里面,而方法方法区呢?大家有没有想过这个问题。
其实道理很简单,JVM每创建一个对象,都会在堆里面分配一个内存空间,而我们的静态变量和常量在对象之间是共享的,如果放到堆里面,那么没创建一个对象,我们都要维护所有类对象,而这样的开销无疑是巨大的。而如果放到方法区,只需要将对象指向方法区对应的常量或静态变量就行了。虚拟机栈
在介绍虚拟机栈之前,我们先来了解一下什么是栈。我们都知道栈的原则是FIFO(先入先出),而Java栈是由栈帧组成的,一个栈可以包含多个栈帧,栈的大小是固定的,栈帧的大小是不固定的。栈帧又是由局部变量表、操作数栈和数据区组成。请看下图:
JVM会对虚拟机栈进行两种操作:压栈和出栈。比如:当我们调用一个方法时,会先进行压栈,当方法调用完成后,则出栈。
虚拟机栈存放的是局部变量(局部变量表),对象的引用(局部变量表),jvm定义的基本数据类型的值(操作数栈),动态链接(数据区)和方法返回地址(数据区)。我们经常听大神说一个方法的参数最好不要太多,不了解jvm的结构之前是无法知道为什么,当我们了解java虚拟机栈后,这个问题就能解答。如果一个方法的参数过多,必然会导致jvm进行不断的压栈,压一次栈,栈帧就会大一点,而栈的大小是固定的,这样必然会导致程序性能问题。那如果一个方法参数必须这么多,怎么办呢?我们可以放到一个对象引用里面,比如:
public void test(Params p){}
我们将需要的参数放到Params类里面,这样只会进行一次压栈,而栈里面只存放了对象的引用,Params的对象是存储在堆里面的,不会对栈产生影响。
本地方法栈
通过其字面,就能知道,本地方法栈其实是存储的java本地方法,了解过JNI的同学应该都知道Java和C/C++的调用就是通过关键字native进行的,那这些方法就是在本地方法栈运行的。
程序计数器
程序计数器指向当前线程运行过程中的指令(指针地址)。jvm在执行当前线程时,会记录当前线程执行的地址,以便执行另一个线程后,可以记录执行当前线程。
结论
运行时数据区包含两大块:数据和指令,其中数据包括方法区和堆,指令包含虚拟机栈、本地方法栈和程序计数器。而指令是线程安全的,数据是线程不安全的。
了解Java虚拟机的运行时数据区也有助于我们进行代码优化,写出更高质量的代码