(一)基础概念介绍
首先看一张图:下图是Java虚拟机运行时数据区,JVM的内存模型可以分为方法区、虚拟机栈、本地方法栈、堆和程序计数器。
首先还是介绍一下基本概念
程序计数器:
程序计数器的作用可以看成是当前线程所执行的字节码的行号指示器。字节码解释器工作时就是通过改变计数器的值来选择下一条需要执行的字节码的指令。java虚拟机的多线程是通过线程轮流切换来分配处理器执行时间的方式实现的,为了线程切换之后能恢复到正确的执行位置,每个线程就需要一个独立的程序计数器。
java虚拟机栈:
java虚拟机栈线程私有,每个方法被执行的同时都会创建一个栈帧用于存放局部变量表、操作数栈、动态链接、方法出口等信息。
本地方法栈:
本地方法栈的功能和虚拟机栈类似,本地方法栈为虚拟机用到的Native方法服务,一个Native Method就是一个java调用非java代码的接口,本地方法栈也会抛出StackOverFlow和OutOfMemoryError异常
java堆
java堆可以说是java虚拟机中所管理的内存最大的一块,java堆被所有线程共享,虚拟机启动的时候创建。java堆中存放的对象实例的数组。几乎所有的对象实例以及数组都在堆上分配,java堆也是垃圾回收器管理的主要区域,java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可,这和磁盘空间很相似。当一个堆无法再扩展时,会抛出OutOfMemoryError异常。
方法区:
方法区也是线程共享的内存区域,用于存放已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据,java虚拟机对方法区的限制十分宽松,和java堆一样不需要连续的内存外,还可以选择不实现垃圾回收。
(二)Java虚拟机栈与程序计数器
虚拟机栈中保存的主要内容是栈帧,每次方法调用就会使得一个栈帧被压入到虚拟机栈。比如上一篇文章讲JVM字节码的时候,我们用到了这样一段代码:
public class Main {
public static int calculate(){
int a=1;
int b=2;
int c=(a+b)*10;
return c;
}
public static void main(String[] args) {
System.out.println(calculate());
}
}
在JVM虚拟机栈中,结构就是这样的:
局部变量表存放了编译器可知的八个基本数据类型、对象引用、和returnAddress类型(指向了一条字节码指令的地址)。
操作数栈主要用来进行一系列出入栈的数值操作。
有关局部变量表、操作数栈、程序计数器更加详细的应用可以看我的前一篇文章:两张图让你快速读懂JVM字节码指令
动态链接:动态链接是值在程序运行期间将符号引用转换为直接引用。在一段代码中,类名、常量名、修饰符、对象名等都是符号引用,而如何通过符号找到具体的引用就需要动态链接做一层转换,将符号引用转换为直接引用。比如:JVM就能通过对象名就链接到堆中真实的对象。
动态链接的工作内容和类加载过程中解析这一步是一样的,只不过解析是将一些静态方法或变量(比如main()方法、static变量)替换为指向数据所存内存的直接引用。
方法出口:当一个方法执行之后,返回的方式可能是正常执行结束返回,也可能是抛出异常的返回。无论采用哪种方式,在方法退出之后都需要回到方法调用的位置,因此这些信息就会被保存到方法出口中。
JVM模式每个线程的虚拟机栈大小是1M,也可以通过-Xss调整虚拟机栈的大小。
(三)方法区
方法区是线程共享的内存区域,用于存放已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。关于方法区的概念,很多人其实都是浑的。方法区、永久代、元空间之间的区别大家都知道吗?
其实方法区是JVM规范中的一个概念,在不同的Java虚拟机以及不同版本的Java虚拟机中,方法区都有各自的实现。以最流行的HotSpot 虚拟机为例,在JDK1.8之前,方法区的实现叫做永久代,放在JVM内存之中。到了JDK1.8,方法区的实现变成了元空间,放在直接内存中。
(四)本地方法栈
本地方法栈的作用是在Java中调用非Java代码,他的功能和虚拟机栈类似。本地方法栈的存在应该算是历史遗留问题,Java刚出来的时候C、C++是当时绝对的统治者,因此在Java中免不了会去调用这一类代码,也因此需要本地方法栈。
(五)堆
堆是JVM虚拟机中最核心的部分了,绝大部分的垃圾回收都在堆中完成。
JVM堆分为新生代和老年代,默认比例为1:2,其中新生代又分为Eden区和两个survivor区,比例为8:1:1。关于堆的大部分内容我已经放到垃圾回收部分进行讲解。
(六)总结
当真正去深入理解JVM虚拟机的时候,会发现它并不像想象中那么难,一个JVM虚拟机实际上就包含了这几样东西,最后再画张图描述一下常用JVM参数针对的位置。