- 注:环境JDK8、HotSpot
一、JVM内存架构
- JVM内存由堆、方法区、程序计数器(PC寄存器)、虚拟机栈(Java栈)、本地方法栈五个部分组成;构成模型图如下所示:
1. 方法区(Method Area)
存放类的元数据信息;
包括类信息、运行时常量池、字符串常量池。类信息又包括了类的版本、字段、方法、接口和父类等信息。JVM在执行某个类的时候,必须经过加载、连接、初始化,而连接又包括验证、准备、解析三个阶段。
在加载类的时候,JVM会先加载class文件,而在class文件中除了有类的版本、字段、方法和接口等描述信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期间生成的各种字面量和符号引用。如下图所示:
- 注:在HotSpot中该存储区对应着永久区,JDK8后永久区被堆外内存的元空间取代,故在JDK8后方法区对应的是元空间。
2. 堆(Heap)
用于存放程序运行时所需的类对象与数据。
堆内存是JVM中最大的一块内存空间,几乎所有的类对象与数组都被分配到堆内存中。
由于堆内存是对象存放区域,故也是GC重点区域。如下图所示:堆内存分为新生代与老年代,新生代又分为Eden与两个一样大小的Surivivor分别为From 和 To。
为什么堆要用分代方式区分堆内存呢?
分代的好处主要是为了方便针对不同区域采用不同的GC算法进行垃圾回收,毕竟不同的GC算法优缺点各不同。
例如对于朝生夕死的就放在新生代,在新生代采用频繁的高效GC算法释放出更多内存空间。年轻代中为啥会有两个一样大小的Survivor,其实是为了方便使用GC复制算法进行垃圾回收。针对GC算法在GC算法中会详细解说,这里就不详细展开了。
Java8为什么使用元空间替代永久代,这样做有什么好处呢?
元空间存储class与Meta。其实在HotSpot中元空间对应的就是方法区。
官方给出的解释是:
移除永久代是为了融合HotSpot JVM与JRockit VM而做出的努力,因为JRockit没有永久代,所以不需要配置永久代。
永久代内存经常不够用或发生内存溢出,爆出异常java.lang.OutOfMemoryError: PermGen,这是因为在JDK1.7版本中,指定的PermGen区大小为8M,由于PermGen中类的元数据信息在每次FullGC的时候都可能被收集,回收率都偏低,成绩很难令人满意;
还有,为PermGen分配多大的空间很难确定,PermSize的大小依赖于很多因素,比如, JVM加载的class总数、常量池的大小和方法的大小等。
3. 程序计数器(PC Regiser)
用来记录下一条需要执行的计算机指令。运行速度最快的存储区域。
每一个线程都有自己独立的程序计数器,CPU需要通过获取存储在这里的指令地址来执行指令,因此也叫指令寄存器。在虚拟机的概念模型中,字节码解释器的工作就是通过改变程序计数器的值来选择下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都要依赖这个计数器来完成。
4. 虚拟机栈(VM Stack)
存放方法运行时所需的数据、部分结果,并参与方法的调用和返回操作。
虚拟机栈为线程私有的内存空间,它伴随着Java的线程而创建。生存周期需要预先设定,当创建一个线程时,会在虚拟机栈时申请一个栈桢,用来保存方法的局部变量、操作数栈、动态链接方法和返回地址等信息。虚拟机栈有一个很重要的特性就是存储在栈内的数据在该线程内是可以共享的。
5. 本地方法栈(Native Method Stack)
本地方法栈跟虚拟机栈功能类似,区别是虚拟机栈用于管理Java函数的调用,而本地方法栈用来管理本地方法的调用,本地方法栈是由C语言实现的。
二、JVM 简单参数说明
如下为常见的配置参数
XX: +PrintGCDetails
-XX:SurvivorRatio=8
-XX:MaxTenurinqThreshold=15
-Xms40м
-Xmx40м
-Xmn20м
JVM常用参数图如下:
参考:
周明耀《深入理解JVM & G1 GC》