JVM-内存模型
Java内存模型(Java Memory Model ,JMM)就是一种符合内存模型规范的,屏蔽了各种硬件和操作系统的访问差异的,保证了Java程序在各种平台下对内存的访问都能保证效果一致的机制及规范。
根据java虚拟机规范,java虚拟机管理的内存将分为下面五大区域。
程序计数器
内存空间小,线程私有。字节码解释器工作是通过改变这个计数器的值来选取下一条需要执行的指令代码、分支、循环、跳转、异常处理线程恢复等基础功能都需要依赖计数器完成。(如果线程正在执行一个Java方法,从这个计数器的值则为(undefined)。此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域)
Java虚拟机栈
线程私有,生命周期和线程一致。描述的是Java方法执行的内存模型:每个方法在执行时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用到执行结束,就对应着一个栈帧从虚拟机中入栈到出栈的过程。(局部变量表:存放了编译期可知的各种基本类型【boolean、byte、char、short、int、float、long、double】。对象引用[referencele 类型],和returnAddress类型【指向了下一条字节码指令的地址】)。
- StackOverflowError:线程请求的栈深度大于虚拟机所允许的深度。
- OutOfMemoryError:如果虚拟机栈可以动态扩展,而扩展时无法申请到足够的内存。
本地方法栈
区别Java虚拟机栈的是,Java虚拟机为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务,也会有StackOverFlowError和OutOfMemoryError异常
Java堆
对于觉大多数应用来说,这块区域是JVM所管理的内存中最大的一块。线程共享,主要存放对象实例和数组。内部会划分出多个线程私有的分配的缓冲区(Thread Local Allocation Buffer TLAB)可以位于物理上不连续的空间,但是逻辑上要连续。
- OutOfMemoryError:如果堆中没有内存完成实例分配,并且堆也无法再扩展时,抛出该异常。
方法区
属于共享内存区域,储存已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。
运行时常量池
属于方法区一部分,用于存放编译期生成的各种字面量和符号引用,编译器和运行期(String的intern())都可以将常量放入池中。内存有限,无法申请时抛出OutOfMemoryError.
直接内存
非虚拟机运行时数据区的部分
在JDK1.4中新加入NIO(New Input/Output)类,引入了一种基于通道(Channel)和缓存(Buffer)的I/O方式,它可以使用Native函数直接分配堆外内存,然后通过一个在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。可以避免在Java堆和Native堆中来回的数据耗时操作。
OutOfMemoryError:会受到本机内存限制,如果内存区域总和大于物理内存限制从而导致动态扩展时出现该异常。
jdk1.7的堆内存模型
-
Young 年轻区(代):Young区被划分为三部分,Eden区和两个大小严格相同的Survivor区,其中,Survivor区间中,某一时刻只有其中一个是被使用的,另外一个留做垃圾收集时复制对象用,在Eden区间变满的时候, GC就会将存活的对象移到空闲的Survivor区间中,根据JVM的策略,在经过几次垃圾收集后,任然存活于Survivor的对象将被移动到Tenured区间。
-
Tenured 年老区:Tenured区主要保存生命周期长的对象,一般是一些老的对象,当一些对象在Young复制转移一定的次数以后,对象就会被转移到Tenured区,一般如果系统中用了application级别的缓存,缓存中的对象往往会被转移到这一区间。
-
Perm 永久区:Perm代主要保存class,method,fifiled对象,这部份的空间一般不会溢出,除非一次性加载了很多的类,不过在涉及到热部署的应用服务器的时候,有时候会遇到java.lang.OutOfMemoryError : PermGen space 的错误,造成这个错误的很大原因就有可能是每次都重新部署,但是重新部署后,类的class没有被卸载掉,这样就造成了大量的class对象保存在了perm中,这种情况下,一般重新启动应用服务器可以解决问题。
-
Virtual区:最大内存和初始内存的差值,就是Virtual区。
jdk1.8的堆内存模型
在jdk1.8中变化最大的Perm区,用Metaspace(元数据空间)进行了替换。(Metaspace所占用的内存空间不是在虚拟机内部,而是在本地内存空间中,这也是与1.7的永久代最大的区别所在。)
废弃jdk1.7中的永久区的原因
移除永久代是为融合HotSpot JVM与 JRockit VM而做出的努力,因为JRockit没有永久代,不需要配置永久代。由于永久代内存经常不够用或发生内存泄露,爆出异常java.lang.OutOfMemoryError: PermGen。所以,将永久区废弃,而改用元空间,改为了使用本地内存空间。
通过jstat命令进行查看堆内存使用情况
- jstat命令可以查看堆内存各部分的使用量,以及加载类的数量。命令的格式如下:
- jstat [-命令选项] [vmid] [间隔时间/毫秒] [查询次数]
查看class加载统计
[root@localhost /]# jps
9906 Bootstrap
9922 Jps
[root@localhost /]# jstat -class 9906
Loaded Bytes Unloaded Bytes Time
7633 14950.6 0 0.0 7.94
Loaded | 加载class的数量 |
---|---|
Bytes | 所占用空间大小 |
Unloaded | 未加载数量 |
Bytes | 未加载占用空间 |
Time | 时间 |
查看编译统计
[root@localhost /]# jstat -compiler 9906
Compiled Failed Invalid Time FailedType FailedMethod
5425 2 0 13.97 1 org/apache/catalina/webresources/StandardRoot getResourceInternal
Compiled | 编译数量 |
---|---|
Failed | 失败数量 |
Invalid | 不可用数量 |
Time | 时间 |
FailedType | 失败类型 |
FailedMethod | 失败的方法 |
垃圾回收统计
也可以指定打印的间隔和次数,每1秒中打印一次,共打印5次
S0C | 第一个Survivor区的大小(KB) |
---|---|
S1C | 第二个Survivor区的大小(KB) |
S0U | 第一个Survivor区的使用大小(KB) |
S1U | 第二个Survivor区的使用大小(KB) |
EC | Eden区的大小(KB) |
EU | Eden区的使用大小(KB) |
OC | Old区大小(KB) |
OU | Old使用大小(KB) |
MC | 方法区大小(KB) |
MU | 方法区使用大小(KB) |
CCSC | 压缩类空间大小(KB) |
CCSU | 压缩类空间使用大小(KB) |
YGC | 年轻代垃圾回收次数 |
YGCT | 年轻代垃圾回收消耗时间 |
FGC | 老年代垃圾回收次数 |
FGCT | 老年代垃圾回收消耗时间 |
GCT | 垃圾回收消耗总时间 |