jvm内存结构
组成
1. 程序计数器
是最小的一块内存区域,它可以看作是当前线程所执行的字节码的行号计数器。在虚拟机的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程回复等基础功能都需要依赖这个计数器来完成。
- 线程私有:各线程都有一个独立的程序计数器,他们之间独立存储、互不影响。
- 执行Java方法时,计数器记录正在执行的虚拟机字节码指令地址;Native方法时,计数器值为空(Undefined)
2. 虚拟机栈
描述Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
- 局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、char、shor、int、float、long、double)、对象引用和returnAddress类型(指向了一条字节码指令的地址)。
- 局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在栈帧中分配的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。
- 64位长度的long和double类型的数据占用2个局部变量空间(Slot),其余数据类型只占用1个。
- 当线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常。(如:缺少终止条件的递归函数)
- 当虚拟机拓展时无法申请到足够的内存,抛出OutOfMemoryError。
3. 本地方法栈
与虚拟机栈作用类似,区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用的Native方法服务。
- 部分虚拟机将虚拟机栈与本地方法栈合二为一。(如:Sun HotSpot虚拟机)
4. 堆(GC堆)
是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。几乎所有的对象实例都在这里分配内存。(直接内存:通过调用Native方法直接分配堆外内存)
- 通过-Xms(最小值)和-Xmx(最大值)参数设置大小。
-Xms为JVM启动时申请的最小内存,默认为操作系统物理内存的1/64但小于1G,-Xmx为JVM可申请的最大内存,默认为物理内存的1/4但小于1G,默认当空余堆内存小于40%时,JVM会增大Heap到-Xmx指定的大小,可通过-XX:MinHeapFreeRation=来指定这个比列;当空余堆内存大于70%时,JVM会减小heap的大小到-Xms指定的大小,可通过XX:MaxHeapFreeRation=来指定这个比列,对于运行系统,为避免在运行时频繁调整Heap的大小,通常-Xms与-Xmx的值设成一样。 - 如果堆中没有内存完成实例分配,且堆也无法再拓展时,将会抛出OutOfMemoryError异常
- 线程共享
- 由于现在收集器都是采用分代收集算法,堆被划分为新生代和老年代。新生代主要存储新创建的对象和尚未进入老年代的对象。老年代存储经过多次新生代GC(Minor GC)任然存活的对象。
新生代: 程序新创建的对象都是从新生代分配内存,新生代由Eden Space和两块相同大小的Survivor Space(通常又称S0和S1或From和To)构成,可通过-Xmn参数来指定新生代的大小,也可以通过-XX:SurvivorRation来调整Eden Space及Survivor Space的大小。
老年代: 用于存放经过多次新生代GC任然存活的对象,例如缓存对象,新建的对象也有可能直接进入老年代,主要有两种情况:①.大对象,可通过启动参数设置-XX:PretenureSizeThreshold=1024(单位为字节,默认为0)来代表超过多大时就不在新生代分配,而是直接在老年代分配。②.大的数组对象,且数组中无引用外部对象。 老年代所占的内存大小为-Xmx对应的值减去-Xmn对应的值。
GC开始前对象只存于Eden空间和From Survivor空间,To Survivor空间是空的。GC时,Eden区中所有存活对象复制到To Survivor,年龄记为一岁,。From Survivor中存活对象计算年龄决定是否移至老年代。经过这次GC后,From和To空间角色交换,即原先的From变为To,To变为From。
堆内存分配策略
- 对象优先在Eden空间分配
- 大对象直接进入老年代
- 长期存活的对象将进入老年代。(默认15岁,每活过一个GC加一岁)
5. 方法区(Non-Heap 非堆)
用于存储被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。运行时常量池也是方法区的一部分。
- 最初设计时使用的是永久代(Perm Gen)实现方法区,jdk1.8以后永久代被彻底移除。
- Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有用于存放编译期生成的各种字面量和符号引用的常量池,这部分内容在类加载后进入方法区的运行时常量池。
- 默认最小值为16MB,最大值为64MB,可通过-XX:PermSize 和 -XX:MaxPermSize 参数限制方法区的大小
6. 直接内存
直接内存并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域。在JDK 1.4中新加入了NIO类,引入了一种基于通道与缓冲区的I/O方式,它可以使用Native函数库直接分配堆外内存。这样能避免在Java堆和Native堆中来回复制数据,在一些场景中显著提高性能。
总览图
参阅
《深入理解Java虚拟机:JVM高级特性与最佳实践》
Java虚拟机的内存组成以及堆内存介绍
JVM内存结构 VS Java内存模型 VS Java对象模型
一张图看懂JVM(升级版)