JVM内存模型详解

在进行jvm调优之前需要做的对jvm模型和对gc回收有一定了解,这一博客主要是对JVM内存模型做一个总结,为后面的gc回收和jvm调优做准备。
java8的jvm内存模型:
JVM内存模型详解
在永久代移除后,字符串常量池也不再放在永久代了,留在了堆里。运行时常量池移动到元空间里,毕竟它是装静态变量、字节码等信息的,有它的地方才称得上方法区。方法区只是逻辑上的一种概念,永久代指物理上的堆内存的一块空间,这块实际的空间完成了方法区存储字节码、静态变量、常量的功能等等。因此也可把新的原数据区看成新的方法区。
JVM内存模型详解

一、程序计数器

程序计数器(Program Counter Register)是一块较小的内存空间,属于线程私有。
Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个内核同时只会执行一条线程中的指令。为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。

异常:
此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。

二、本地方法栈

本地方法栈(Native MethodStacks),属于线程私有。它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。本地方法栈则是为虚拟机使用到的Native 方法服务。虚拟机规范中对本地方法栈中的方法使用的语言、使用方式与数据结构并没有强制规定,因此具体的虚拟机可以自由实现它。

异常:
本地方法栈区域也会抛出的异常为StackOverflowError和OutOfMemoryError。

三、java虚拟机栈

Java虚拟机栈(Java Virtual Machine Stacks)属于线程私有,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程,虚拟机栈为虚拟机执行Java 方法(也就是字节码)服务。

异常:
(1)如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError 异常;
(2)如果虚拟机栈可以动态扩展(当前大部分的 Java 虚拟机都可动态扩展,只不过 Java 虚拟机规范中也允许固定长度的虚拟机栈),当扩展时无法申请到足够的内存时会抛出 OutOfMemoryError 异常。

-Xss 每个线程的Stack大小
java8默认为1M,一般小的应用, 如果栈不是很深, 应该是128k够用的 大的应用建议使用256k。这个选项对性能影响比较大,需要严格的测试。

四、直接内存
直接内存 并不是虚拟机运行时数据区的一部分,也不是 java 虚拟机规范中定义的内存区域。
在 jdk1.4 中加入了 NIO 类,引入了一种基于通道(Channel)与缓冲区(Buffer)的 I/O 方式,他可以使用 native 函数库直接分配堆外内存,然后通过一个存储在 java 堆里面的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在 java 堆中和 native 堆中来回复制数据。直接内存的分配不会受到 java 堆大小的限制,但是,既然是内存,则肯定还是会受到本机总内存(包括 ram 及 swap 区或者分页文件)的大小及处理器寻址空间的限制。

异常:
当会忽略掉 直接内存 ,使得各个内存区域的总和大于物理内存限制,会导致导致动态扩展时出现 OutOfMemoryError 异常。

五、元空间

元空间(Metaspace),元空间是java8废弃永久代(PermGen),而新增部分。
由于以下这些原因去掉了永久代。
(1)类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出。

(2)永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。

即方便分配管理,因为直接内存空间比较充足;便于回收,因为永久代本来回收垃圾的事件发生概率很低,直接从JVM中拿出可以提高回收效率。

-XX:MetaspaceSize=N 元空间的初始大小
元空间的默认初始大小是20.75MB
-XX:MaxMetaspaceSize=N 元空间的最大值
对于64位JVM来说,默认的元空间的最大值是无限。

建议
MetaspaceSize和MaxMetaspaceSize设置一样大;
具体设置多大,建议稳定运行一段时间后通过jstat -gc pid确认且这个值大一些,对于大部分项目256m即可。

四、堆
Java堆(Java Heap)是Java虚拟机所管理的内存中最大的一块,也是jvm调优的重点。堆大小 = 新生代 + 老年代。
JVM内存模型详解

-Xms :初始堆大小
物理内存的1/64(<1GB) 默认(MinHeapFreeRatio参数可以调整)空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制.
-Xmx :最大堆大小
物理内存的1/4(<1GB) 默认(MaxHeapFreeRatio参数可以调整)空余堆内存大于70%时,JVM会减少堆直到 -Xms的最小限制
-Xmn :年轻代大小
*注意:*此处的大小是(eden+ 2 survivor space).与jmap -heap中显示的New gen是不同的。
整个堆大小=年轻代大小 + 年老代大小 +元空间大小.
增大年轻代后,将会减小年老代大小.此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。
-XX :NewRatio=n 新生代与老生代(new/old generation)的大小比例
新生代与老生代(new/old generation)的大小比例(Ratio). 默认值为 2
-XX:SurvivorRatio=n eden/survivor 空间大小的比例(Ratio)
eden/survivor 空间大小的比例(Ratio). 默认值为 8.
-XX:newSize:表示新生代初始内存的大小
应该小于 -Xms的值;