JVM全称Java Virtual Machine,译为Java虚拟机。JVM是Java语言的基础,是Java应用运行的平台。学过C和C++的同学都知道,开发人员可以通过代码直接对内存进行管理。但对于Java语言来说,JVM会自动对内存进行管理,并不直接把内存管理权限暴露给开发人员。
接下来,我们以HotSpot虚拟机为例,对JVM的内存管理机制进行介绍。
1.JVM内存分区
JVM内存主要分为以下五个区:方法区、堆、虚拟机栈、本地方法栈、程序计数器。这五个区也叫运行时数据区,描述java程序运行时内存的分配状况。
其中方法区和堆属于所有线程共享的内存区域,而虚拟机栈、本地方法栈和程序计数器则是线程间隔离的,每个线程有对应的虚拟机栈、本地方法栈和程序计数器。
2.方法区
方法区主要存储的是被虚拟机加载的类信息、常量、静态变量、即时编译器遍以后的代码等数据。方法区数据存储于永久代(Permanent Generation),是JVM启动过程就分配好了的,如果在运行中调用了String.intern()方法,JVM也会在永久代给此String分配内存。在JVM启动之前,可定义永久代的上限,用-XX:MaxPermSize表示。
3.堆
堆主要存储JVM启动和运行过程中创建的实例。由于程序运行过程中会进行产生大量的实例,堆所占内存一般较大,此内存区域需要进行大量的垃圾回收以及实例创建操作。堆可分为新生代和老年代,新生代又可分为Eden区、From Survivor区和To Survivor区。JVM启动之前,可对堆的大小进行限制,一般用-Xms和-Xmx参数,分别表示启动时分配的堆内存和可分配最大堆内存。如果无法获取更多的内存或者所需内存已超过Xmx的值,则抛出OutOfMemoryError异常。
4.虚拟机栈
虚拟机栈主要存储Java方法的执行信息。每个java方法执行时都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每个方法的调用直到结束,就对应着一个栈帧在虚拟机栈的入栈到出栈的过程。其中,局部变量表存放了编译期可知的各种基本数据类型(byte、boolean、char、short、int、float、double、long)、对象引用(reference)。64位的long和double数据占用2个局部变量空间(Slot),其余只占一个Slot。如果栈深度大于JVM允许的深度,则抛出StackOverflowError异常;如果虚拟机可动态扩展,但是都无法申请到足够的内存,则抛出OutOfMemoryError异常。
5.本地方法栈
本地方法栈和虚拟机栈类似,不过存储的是native方法运行的栈帧信息。
6.程序计数器
程序计数器主要存储当前线程所执行的字节码的行号指示器。如果是java方法,则计数器记录的是正在执行的虚拟机字节码指令的地址;如果执行的是native方法,则计数器值为Undefined。此区域不抛出OutOfMemoryError异常。
7.查看进程堆内存分配
如果要查看java运行时内存分配信息,可通过jmap -heap $PID命令获取,$PID可通过jcmd -l命令获取。
jmap结果如上图所示,包含堆配置信息和堆内存使用信息。更多关于Java工具的使用问题,可参考https://docs.oracle.com/en/java/javase/12/tools/index.html。