本文为《深入理解Java虚拟机》一书第二章的读书记录笔记。
JVM运行时数据区包括如下图几个部分,其中有的区域是随着JVM进程的启动而存在,随着JVM进程的结束而销毁,有些区域则是依赖用户线程的启动而建立,结束而销毁。
1. 程序计数器
程序计数器(Program Counter Register):它可以看作当前线程所执行的字节码的行号指示器。它是线程私有的,不同线程有属于自己的独立的程序计数器。
- 如果线程正在执行的是一个Java方法,这个计数器记录的正在执行的虚拟机的字节码指令的地址;
- 如果正在执行的Native方法,则这个计数器值为空。
同时,程序计数器也是JVM中唯一一个没有规定任何OutOfMemoryError情况的区域。
2. 虚拟机栈
虚拟机栈(Java Virtual Machine Stack)描述的Java方法执行的内存模型:每一个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用到执行完成的过程,就是对应着一个栈帧在虚拟机中入栈到出栈的过程。
虚拟机栈也是线程私有的,它的生命周期与线程相同。
虚拟机栈有两种异常状况:
-
如果线程请求的栈深度大于JVM所允许的栈深度,就会抛出StackOverflowError
-
如果虚拟机支持动态扩展,在扩展时无法申请到足够的内存,就会抛出OutOfMemoryError
-Xss128k: 每个线程的堆栈大小为128k。
3. 本地方法栈
本地方法栈(Native Method Stack)与虚拟机栈类似,只是一个是执行Java方法,一个是执行本地方法。与虚拟机栈一样,本地方法栈也会抛出StackOverflowError和OutOfMemoryError。
4. 堆
Java堆(Java Heap)是JVM内存管理主要的区域,它是被所有线程共享的一块内存区域,随着JVM进程的启动而创建,结束而销毁。几乎所有的对象实例和数组都是在这个分配内存。
- 从基于分代收集算法的收集器来说,Java堆可以分为:新生代和老年代。其中,新生代又可以分为Eden, From Survivor,To Survivor。
- 从内存分配角度来说,线程共享的Java堆可能分配出多个线程私有的分配缓冲区(Thread Local Allocation Buffer, TLAB)
Java堆中如果没有内存完成实例分配并且无法扩展时就会出现OutOfMemoryError。
-Xmx3550m: 最大堆大小为3550m。
-Xms3550m: 设置初始堆大小为3550m。
将-Xms与-Xmx参数设置成一样可以避免Java堆的自动扩展。
-Xmn2g: 设置年轻代大小为2g。
-XX:NewRatio=4: 设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)。
-XX:SurvivorRatio=4: 设置年轻代中Eden区与Survivor区的大小比值。设置为4,则两个Survivor区与一个Eden区的比值为2:4,一个Survivor区占整个年轻代的1/6
-XX:MaxTenuringThreshold=0: 设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。
-XX:PretenureSizeThreshold=10m: 设置晋升老年代对象的大小,默认为0,设置为10M则超过10M的对象将不在Eden区分配,直接进入老年代。
5. 方法区
方法区(Method Area)是存储JVM加载的类信息、常量、静态变量和运行时常量池。常量池存放的是编译期生成的各种字面量和符号引用。它也是被所有线程共享的。
- 方法区无法满足内存分配的需求时,将抛出OutOfMemoryError
- 当常量池无法申请到内存时,将抛出OutOfMemoryError
-XX:MaxPermSize=16m: 设置持久代大小为16m (JDK 1.6及之前的版本,之后的版本移除了持久代)