运行时数据区
- 程序计数器(线程私有)
- 一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。如果线程正在执行的是一个java方法,记录的是正在执行的虚拟机字节码指令的地址;如果执行的是native方法,这个计数器值则为空。注意:此内存区域是唯一 一个在java虚拟机规范中没有规定任何OutOfMemoryError情况的区域
- java虚拟机栈(线程私有)
- 虚拟机栈描述的是java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧(stack frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
- 局部变量表存放了编译期可知的各种基本数据类型、对象引用和returnAddress类型(指向了一条字节码指令的地址) 注意:其中64位长度的long和double类型的数据会占用2个局部变量空间(slot),其余数据只占用一个。
- 栈区域的两种异常:StackOverflowError 和 OutOfMemoryError异常
- 本地方法栈(线程私有)
- 本地方法栈是虚拟机使用到的native方法服务
- java堆
- 堆是虚拟机管理的内存中最大的一块,java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建
- java堆细分:新生代和老年代。细致一点Eden空间、from survivor空间、To survivor空间等。从内存分配的角度来看,线程共享的java堆中可能划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB)
- -Xmx和-Xms设置虚拟机堆内存是否可扩展
- 方法区(别名Non-Heap 非堆)
- 用于存储已被虚拟机加载的类信息、常量、静态变量、及时编译器编译后的代码等数据表。
- 运行时常量池是方法区的一部分。class文件中除了有类的版本、字段、方法、接口等描述信息,还有一项信息是常量池,用于存放编译器生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。
- 直接内存
- 在jdk1.4中新加入了nio类,引入了一种基于通道与缓冲区的i/o方式,它可以使用native函数库直接分配堆外内存,然后通过一个存储在java堆中directByteBuffer对象作为这块内存的引用进行操作。这样可以在一些场景中显著提高性能,因为避免了在java堆和native堆中来回复制数据。
- 本机直接内存的分配不受java堆大小的限制,但是如果在设置 -Xmx等参数信息时,忽略掉直接内存,可能导致总的内存大于了物理内存限制,从而导致动态扩展时出现OutOfMemoryError异常。
几种常见的OutOfMemoryError
- java堆溢出
- java.lang.OutOfMemoryError : java heap space
- 当出现堆溢出时一般手段是先通过内存映像分析工具(如:eclipse memory analyzer)对dump出来的堆转存快照进行分析。首先要清楚是内存泄漏还是内存溢出
- 虚拟机栈和本地方法栈溢出
- 栈容量只由 -Xss 参数设定
- 关于虚拟机栈和本地方法栈,在java虚拟机规范中描述了两种异常:
- 如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowerror
- 如果虚拟机在扩展栈时无法申请到足够的空间,则抛出OutOfMemoryError
- 方法区和运行时常量池溢出
- VM Args:-XX:PermSize = 10M -XX:MaxPermSize=10M
- java.lang.OutOfMemoryError : PermGen space
- 本机直接内存溢出
- DirectMemory容量可以通过-XX : MaxDirectMemorySize指定,如果不指定,则默认与java堆最大值一样
- 由directMemory导致的内存溢出,一个明显的特征是在Heap Dump文件中不会看见明显的异常,如果发现OOM之后dump文件很小,而程序中又直接或间接使用了NIO,则可以考虑是不是这方面问题
内存分配和回收策略
对象优先在Eden分配
大多数情况,对象在新生代Eden去分配。当Eden区没有足够空间进行分配时,虚拟机将发起一次Minor GC。
大对象直接进入老年代
所谓大对象是指—需要大量连续内存空间的java对象(最典型的大对象就是那种很长的字符串以及数组)。虚拟机提供了一个 -XX:PretenureSizeThreshold参数,令大于这个设置值的对象直接在老年代分配。这样做的目的是避免在Eden区及两个survivor区之间发生大量的内存复制。
长期存活的对象将进入老年代
在Eden中新生的对象,经历第一次Minor GC后存在,且能被survivor容纳,将被移动到survivor中,并年龄设为1。每经过一次,年龄+1,直到达到阈值(默认15,可以通过-XX:MaxTenuringThreshold设置)
动态年龄判断
如果survivor空间中相同年龄所有对象大小总和大于survivor空间的一半,年龄大于或者等于该年龄的对象直接进入老年代,无需等年龄到达阈值。
垃圾收集算法
- 标记-清除算法
- 复制算法
- 标记-整理算法
- 分代收集算法
虚拟机性能监控与故障处理工具
jdk的命令行工具
- jps:虚拟机进程状况工具
- 可以列出正在运行的虚拟机进程,并显示虚拟机执行主类(Main Class,main()函数所在的类)名称以及这些进程的本地虚拟机唯一id(Local Virtual Machine Identifier , LVMID)
- -q : 只输出LVMID,省略主类的名称
- -m : 输出虚拟机进程启动时传递给主类main()函数的参数
- -l : 输出主类的全名,如果进程执行的是jar包,输出jar的路径
- -v : 输出虚拟机启动时jvm参数
- jstat:虚拟机统计信息监视工具
- JVM Statistics Monitoring Tool是用于监视虚拟机各种运行状态信息的命令行工具,他可以显示本地或者远程虚拟机进程中的类转载、内存、垃圾收集、jit编译等运行数据,在服务器上它是运行期定位虚拟机性能问题的首选工具
- -gc : 监视java堆,包括Eden区,两个survivor区,老年代、永久代等的容量、已用空间、gc时间合计等信息
- -class : 监视类装载、卸载数量、总空间以及类装载所耗费的时间
- -gcutil : 监视内容与-gc基本相同,但输出主要关注已使用空间占总空间的百分比
- jinfo:java配置信息工具
- Configuration Info for Java 的作用是实时地查看和调整虚拟机各项参数。
- jmap:java内存映射工具
- Memory Map for Java命令用于生成堆转储快照(一般称为heapdump或dump文件)。
- -dump:生成java堆转储快照
- -dump:[live,]format=b,file= live子参数说明是否只dump出存活的对象
- -heap:显示java堆详细信息,如使用哪种回收器、参数配置、分代状况等
- -histo:显示堆中对象统计信息,包括类、实例数量、合计容量
- -F:当虚拟机进程对-dump选项没有响应时,可使用这个选项强制生成dump快照。
- jhat:虚拟机堆转储快照分析工具
- sun JDK提供了jhat(JVM Heap Analysis Tool)命令与jmap搭配使用,来分析生成的堆转储快照。jhat内置了一个微型的HTTP/HTML服务器,生成dump文件的分析结果后,可以在浏览器查看。
- 注意:一般不要在生产服务器上用该命令分析dump文件
- 分析工具是一个耗时而且消耗硬件资源的过程
- jhat的分析功能相对来说比较简陋
- jstack:java堆栈跟踪工具
- Stack Trace for Java 命令用于生成虚拟机当前时刻的线程快照(一般称为threaddump或者Javacore文件)
- -l :除堆栈外,显示关于锁的附加信息
- -m :如果调用本地方法的话,可以显示C/C++的堆栈
- -F :当正常输出的请求不被响应时,强制输出线程堆栈