JVM内存模型与性能调优

来源:互联网 发布:安装postgis linux篇 编辑:程序博客网 时间:2024/05/22 00:14

jvm内存模型与内存错误

Java虚拟机管理的内存将包含以下几个运行时数据区域:程序计数器、方法区,栈区,堆区,本地方法栈。

(1)程序计数器是一块比较小的内存空间,它可以看做是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来读取下一条需要执行的字节码指令。每个线程都有一个独立的程序计数器,以保存线程的状态。各线程的计数器之间互不影响,独立存储,我们称这类内存为“线程私有”的内存。如果线程执行的是一个java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址。如果执行的Native方法,这个计数器值则为空。
此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。

(2)虚拟机栈也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是java方法执行的内存模型:每个方法执行的时候会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧从入栈到出栈的过程。
在Java虚拟机规范中,对这个区域规定了两种异常状况:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;如果虚拟机栈动态扩展无法申请到足够的内存,就会抛出OutOfMemoryError异常。

(3)本地方法栈与虚拟机栈所发挥的作用是类似的,只不过本地方法区为Native方法服务。与虚拟机栈一样,本地方法栈区域也会抛出StackOverflowError和OutOfMemoryError异常。

(4)Java堆一般是虚拟机管理的内存中最大的一块,被所有线程共享的一块内存区域,在虚拟机启动时创建。该内存区存放对象实例。Java堆是垃圾收集器管理的主要区域。从内存回收的角度看,由于现在的GC基本都采用分代收集算法,所以在java堆中还可以细分为:新生代和老年代。从内存分配的角度看,java堆中可能划分出多个线程私有的分配缓存区(Thread Local Allocation Buffer)。
如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。

(5)方法区与java堆一样,是各个线程共享的内存区域,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。运行时常量池是方法区的一部分。类文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放在编译器生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。既然运行时常量池是方法区的一部分,自然受到方法区内存的限制,当常量池无法再申请到内存是会抛出OutOfMemoryError异常。
永久代Perm Space也即是方法区,它是jvm规范的实现。当动态加载的类较多(如jsp页面较多时),容易出现内存溢出。在jdk8中已移除。
直接内存不是虚拟机运行时数据区的一部分,也不是java虚拟机规范中定义的内存区域,但是这部分内存也被频繁地使用,而且也可能导致OutOfMemoryError异常出现。
在jdk1.4中新加入NIO类,引入了一种基于通道(Channel)和缓冲区(Buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在java堆中的DirectByteBuffer对象作为这块内存的引用进行操作,这样能在一些场合显著提高性能,因为避免了在java堆和native堆中来回复制数据。本机直接内存的分配不会受到java堆大小的限制,但是既然是内存肯定会受到本机总内存大小及处理器寻址空间的限制。在配置虚拟机参数时,会根据实际内存设置-Xmx等参数信息,但经常忽略直接内存,使得各个内存区域综合大于物理内存限制。从而导致动态扩展时出现OutOfMemoryError异常。
在jdk1.8之后,虚拟机中去掉了永久代即Perm Space,而改为MetaSpace,元空间不在虚拟机中,而是直接使用本地内存。


jvm监控工具

JDK命令行工具
JDK的bin目录下有许多虚拟机监控工具,大部分都异常小巧,因为这些命令行工具大部分都是tools.jar类库的一层薄包装而已。JKD开发团队选择用java代码来实现这些监控工具是有原因的:当应用环境部署到生产环境后,无论采用直接接触物理服务器还是远程Telnet到服务器上都可能会受到限制。借助tools.jar类库里面的接口,我们可以直接在应用程序中实现功能强大的监控分析性能。命令行监控工具介绍如下:
(1) jps,显示指定系统内所有的虚拟机进程。
(2) jstat,用于收集虚拟机各方面的运行数据。
(3) jinfo,显示虚拟机配置信息。
(4) jmap,生成虚拟机的内存转储快照。
(5) jhat,内存转储快照分析工具,可以将堆中的对象以html的形式显示出来,包括对象的数量,大小等等,并支持对象查询语言。
(6) jstack,显示虚拟机的线程快照。

可视化工具
JDK除了提供命令行工具外,还有两个功能强大的可视化工具,jConsole和jVisualVM。其中后者称为Oracle主力推动的多合一故障处理工具,并且已经从JDK中分离出来称为可独立发展的开源项目。

性能调优简介

大部分人都会通过一些虚拟机参数或者命令(比如配置堆内存大小,配置垃圾回收器等)来做简单的调优,但实际上性能调优是一个复杂的事情,要想做全面的调优需要对硬件软件各方面进行综合考虑。这里介绍一下垃圾回收器对应用的影响。

垃圾回收分为轻量级垃圾回收(Minor GC,Major GC)和Full GC。轻量级垃圾回收负责回收新生代垃圾对象,一般运行时间较短。而Full GC会在老生代满了之后执行,会花费较长时间。尤其当堆内存分配过大时,Full GC可能会执行较长时间。而在GC执行的时候,程序是停顿的,给用户的感觉就是无响应。因此必须控制Full GC执行的频率,将Full GC频率降低到不会影响用户使用。

在大多数网站形式的应用中,主要对象的生存周期都应该是请求级或者页面级的,会话级和全局级的长生命对象很少。故Full GC基本很少会出现。这样使用超大堆内存时,网站相应速度才会比较有保证。

使用超大堆内存面临以下问题:
(1) 内存回收导致的长时间停顿。
(2) 要保证程序足够稳定,因为这种应用要是产生堆溢出几乎无法产生堆转储文件(因为要产生十几GB乃至更大的Dump文件),哪怕产生了快照也几乎无法进行分析。
因此建立逻辑集群来利用硬件资源是一种常用的方式。

大堆一直是一个充满争议的领域。G1( Garbage first)回收器在JDK 7update 4中首次引入,它的设计目标是能更好地支持大于4GB的堆,并希望在将来替代CMS。相比于CMS回收器,G1回收器时延更低,但是吞吐量性能不如CMS。

参考书籍:《深入理解java虚拟机》

0 0
原创粉丝点击