JVM 运行时数据分区
根据 Java 虚拟机规范 Run-Time Data Areas 章节的描述,Java 虚拟机的运行时数据划分如下:
-
程序计数器/pc寄存器-PC(program counter) Register
线程私有。 程序计数器包含线程当前正在执行的Java虚拟机指令的地址。 如果线程当前正在执行的方法是本地方法,则Java虚拟机的pc寄存器的值未定义。 -
Java 虚拟机栈
每个Java虚拟机线程都有一个私有Java虚拟机栈,与该线程同时创建。Java 虚拟机栈中保存的数据结构是栈帧——每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。与 Java 虚拟堆栈相关的异常有两个:- StackOverflowError:
如果线程运行过程中压栈超过了Java虚拟机栈允许的最大深度(如递归条件不当,导致无限递归调用),则Java虚拟机将引发StackOverflowError。 - OutOfMemoryError:
如果没有足够的内存来扩展Java虚拟机堆栈;或者没有足够的内存来为新线程创建初始Java虚拟机堆栈,则Java虚拟机机器抛出OutOfMemoryError。
- StackOverflowError:
-
本地方法栈-Native Method Stacks
线程私有。和 Java 虚拟机栈类似,Java虚拟机的一种实现,用传统的堆栈实现方式来支持本地方法(非 Java 语言编写的方法)调用。通常在创建每个线程时为每个线程分配本地方法栈。 -
方法区
方法区是在虚拟机启动时创建的,所有虚拟机线程共享。它存储每个类的结构,如运行时常量池,字段和方法数据,以及方法和构造函数的代码,包括用于类和实例初始化以及接口初始化的特殊方法。与方法区相关的异常:- OutOfMemoryError:
如果方法区内存无法满足分配请求,则Java虚拟机将引发OutOfMemoryError。 - 运行时常量池
方法区的一部分。用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后,进入方法区的运行时常量池中存放。运行时常量池还具有动态性的特征——常量不一定只有编译期才能产生,运行期间也可能将新的常量放入池中,这种特性用得比较多的便是String类的intern()方法。与运行时常量池有关的异常:- OutOfMemoryError
创建类或接口时,如果构造运行时常量池所需的内存超过Java虚拟机的方法区中可用的内存,则Java虚拟机将引发OutOfMemoryError。
- OutOfMemoryError
- OutOfMemoryError:
-
堆
堆在虚拟机启动时创建,所有虚拟机线程共享。堆是运行时数据区,分配所有类实例和数组的内存,也是 GC 进行垃圾回收的主要区域。与堆有关的异常:- OutOfMemoryError:
如果申请的堆内存大于系统可以提供的堆,则Java虚拟机将引发OutOfMemoryError。
为了更好地回收内存,堆内存还有更细致的划分:
- 年轻代
- Eden
- From Survivor
- To Survivor
- 老年代
- OutOfMemoryError:
垃圾回收算法
标记-清除算法
分为两个阶段:
- 从 GC Root 节点出发,根据可达性分析,将内存对象标记为存活对象或垃圾对象;
- 将所有垃圾对象清除
优点:简单,不需要移动对象。
缺点:容易产生内存碎片,提高了内存回收频率
复制算法
将内存一分为二,每次只使用其中一半,内存回收时,将存活内存复制到另一半中,然后清除当前这一半的所有内存。
优点:不会产生垃圾碎片。
缺点:可用内存减少为一半,对象存活率高时,会频繁复制。
标记-压缩算法
分为两个阶段:
- 从 GC Root 节点出发,根据可达性分析,将内存对象标记为存活对象或垃圾对象;
- 将存活对象复制到内存的一端,然后将端边界以外的内存全部清除。
优点:不会产生内存碎片,也不用讲内存分块。
缺点:所谓压缩操作,还是会复制对象。
常见 JVM 参数选项
详细参数描述请参考:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html#CBBIJCHG
JVM 支持分为好几个类别的许多参数选项,详情参考上面的文档。常用的参数类型有:
- 以
-X
开头的:非标准选项。是特定于Java HotSpot虚拟机的通用选项,因此不能保证所有JVM实现都支持它们,并且它们可能会发生变化。 - 以
-XX
开头的:高级运行时选项。开发人员选项,用于调整Java HotSpot虚拟机操作的特定区域。使用的方式有如下3种:- -XX:+<option> 启用option参数;
- -XX:-<option> 禁用option参数;
- -XX:<option>=<value> 将option参数的值设置为value。
堆内存相关参数
参数 | 含义 | 示例 |
---|---|---|
-Xms | 堆的初始内存大小 | -Xms10m 初始化堆大小为10M |
-Xmx | 堆的最大内存 | -Xmx20m 堆占用的最大内存为20M |
-Xmn | 设置新生代内存的大小 | -Xmn10m 设置新生代内存的大小为10M |
-XX:SurvivorRatio | 设置年轻代中 Eden 区域和 Survivor 区域的比例 | |
-XX:+PrintGCDetails | 打印 GC 的详细 log 信息 | |
-XX:HeapGrowthLimit | ||
-XX:HeapMaxFree | 单次 Heap 内存调整的最大值 | |
-XX:HeapMinFree | 单次 Heap 内存调整的最小值 | |
-XX:HeapTargetUtilization | 设置理想的堆内存利用率, |
关于 Android 中的 largeHeap 选项
在 Android 中,如果我们希望应用运行时能使用更多的内存,可以在 Manifest 文件的 application 节点中,添加一个 android:largeHeap="true"
的配置。那添加了这个配置后,能比普通应用多用多少内存呢?我们可以通过以下两种方式得到答案:
- 运行时代码获取(Kotlin)
val am = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
// 普通应用可以使用的最大内存,单位:MB
val memory = am.memoryClass
// largeHeap 应用可以使用的最大内存,单位:MB
val largeMemory = am.largeMemoryClass
- 通过 adb 命令查看 DVM/ART 虚拟机 heap 相关的系统属性
$ adb shell getprop | grep vm.heap
[dalvik.vm.heapgrowthlimit]: [384m] # 相当于 JVM -XX:HeapGrowthLimit。=am.memoryClass
[dalvik.vm.heapmaxfree]: [8m] # 相当于 JVM -XX:HeapMaxFree 选项
[dalvik.vm.heapminfree]: [512k] # 相当于 JVM -XX:HeapMinFree 选项
[dalvik.vm.heapsize]: [512m] # 相当于 JVM -Xmx 选项。=am.largeMemoryClass
[dalvik.vm.heapstartsize]: [8m] # 相当于 JVM -Xms 选项
[dalvik.vm.heaptargetutilization]: [0.75] # 相当于 JVM -XX:HeapTargetUtilization 选项,用于设置理想的堆内存利用率