Jvm使用要求问题诊断和性能优化 v10文档格式.docx
- 文档编号:16765545
- 上传时间:2022-11-25
- 格式:DOCX
- 页数:16
- 大小:170.75KB
Jvm使用要求问题诊断和性能优化 v10文档格式.docx
《Jvm使用要求问题诊断和性能优化 v10文档格式.docx》由会员分享,可在线阅读,更多相关《Jvm使用要求问题诊断和性能优化 v10文档格式.docx(16页珍藏版)》请在冰豆网上搜索。
JVM版本升级策略5
理解相关JVM原理以实现优化5
了解JVMspecification中的JVM架构5
Runtimedataarea组件6
Java程序执行的大致流程7
JVM中heap内存管理10
常见的jvm参数配置11
常见Java应用错误及解决办法14
一些实用的工具15
总结:
16
主要的参考资料17
本文将要讨论的内容适用的场合主要集中在服务端综合JAVA应用。
在大部分情况下使用java应用都不需要提出特别的要求,且出问题的概率都不大,所以本文讨论的内容适用的场景可能只有5%,但在出现问题的情况下它能起到的作用将是关键的。
引出本文的原因是:
SGMEAI平台使用的是JAVA平台,在一次重大的事故(4台生产环境服务器全部crash掉了)的情况下通过研究学习和网上搜索资料得出的。
通常大家都知道JAVA是一门程序语言,但是JAVA更是一个平台,它最值得称道的地方是提供了一个庞大的为各个层面的解决方案。
本文要讨论的是java平台相关的一点内容。
JAVA平台其实更类似于一个统一的操作系统。
因为我们都知道,JAVA编写和编译出来的代码都是是跨操作系统的,那么运行java代码的jvm呢?
它是不跨平台的!
对不同的操作系统,其JVM不同,这就引出了本文要说的第一个话题:
JAVA平台使用对操作系统的要求。
另,对JVM来说,通常我们都是直接使它的默认启动参数,大部分情况就是改个最大heapsize参数。
这样做在大部分情况已经可以了,但是应用是会变化的,应用的规模会变大,它的应用类型会有所调整,最终我们会发现,JVM出问题了,比如:
OutOfMemoryError、StackOverflowError、CoreDump了。
第二个话题:
jvm问题出现的时候,怎么通过相关监控工具诊断问题,或定位jvm自身bug以便升级,或调整启动参数以实现优化。
JVM平台使用对操作系统的要求
对于操作系统来说,JVM平台就是一个普通软件,对应的称之为JDK或者JRE,这里要讲述的并不是说安装JDK或则JRE需要多少磁盘空间或则说CPU高低的一般要求,而是JVM需要的某些额外的要求。
JVM平台在各个平台上的需求是多样性的,特别是向IBM和HP上的JVM还是由IBM和HP提供,其他主要由SUN公司提供,差异还是存在不少的。
。
本文做出了一个对操作系统概要级别的阐述,具体还要查相关资料。
操作系统patch的要求
对于操作系统来说,JVM平台确实是一个普通软件,但是它对操作系统的patch是有明确要求的。
这些要求跟下面2个因素相关:
1.JVM的具体版本;
如:
32位或者64位的Java1.3、Java1.4、Java5、Java6等
2.操作系统的具体版本;
如:
HP-UX11.11withtheDec'
04QualityPackinstalled
举例来说,对于HP-UX上的jvm来说,需要的patch需要从HP的官方网站上获取具体清单(11.11withtheDec'
04QualityPackinstalled为例,它需要的Patch清单包括:
●PHSS_30049或者对应的最新替代patch;
●PHKL_32927或者对应的最新替代patch;
●PHKL_34534或者对应的最新替代patch;
●PHKL_39133或者对应的最新替代patch;
其他操作系统平台的所需的patch信息上对应的官方网站查询即可。
操作系统内核参数的要求
操作系统有很多内核参数对应控制着很多内容,如何调整这些参数是跟应用规模和特性有很大关系的,例如:
每个进程运行打开的最大文件数、每个进程运行创建的最大线程数、系统上运行的所有进程打开的文件数和进程出错后产生的core文件大小限制等等。
如果不合理调整某些内核参数的话可能会导致Server进程启动不了或者运行一段时间后莫名其妙地退出。
下面以HP-UX为例(资料收集基本来至),对一些java应用会涉及的常用参数进行说明:
nproc(2048):
系统允许打开的最大进程数,建议该参数设置2048以上。
nkthread(6000):
系统中所有可用内核线程数。
一般一个稍微大点的Java应用同时开启的线程都在1000以上,建议该参数设置6000以上。
max_thread_proc(3000):
每个进程允许打开的最大线程数。
默认值是64,这对于java应用来说太小了。
建议该参数设置3000以上,该参数的最大值上限是nkthread参数。
nfile(65536):
系统所有进程允许打开的最大文件数目,Java应用需要加载大量的class文件,如果是网站服务器的话需要加载的资源文件更多,建议该参数设置65535以上。
maxfiles(2*1024):
每个进程允许打开的最大文件数目,该参数是软限制,建议该参数设置2048以上。
maxfiles_lim(2*1024):
每个进程允许打开的最大文件数目,该参数是硬限制,建议该参数设置4096以上。
ncallout(6000):
当程序打开的socket较多时,会产生很多I/O等待,该参数控制超时等待数目,建议该参数设置6000以上。
maxdsiz(2147483648):
该参数表示数据段最大值限制,32位用户进程的三个基本的组成部分之一。
对应的全局变量、静态变量及malloc()函数申请的内存都收该参数限制。
建议该参数设置1.8G以上。
maxdsiz_64bit(4294967296):
该参数表示数据段最大值限制,64位用户进程的三个基本的组成部分之一。
建议该参数设置4G以上。
JVM平台本身的相关内容
所谓和本身相关的内容指的是,已经基本上脱离了操作系统层面去讨论的内容。
在遵循JVM规范的基础上,有JVM版本升级、JVM参数设定、JVM常见问题诊断、JVM调优等等。
涉及的内容可能会和具体环境有点关系,但主要差异是在JVM的具体实现上的小差异(因为Sun只制定了jvm规范,但并没有硬性要求实现方法)。
JVM版本升级策略
简单的来说,JVM版本分为大版本和小版本。
大版本就是指Java1.3、Java1.4、Java5、Java6等等,小版本就就具体到1.5.0.15/1.5.0.19/1.6.0.06(hp-ux)、1.4.2_08/1.6.0_20(win)等等。
最后两位数字可以称之为小版本,前面的数字有变化说明有新的特性加入,而小版本更新则主要是bug的修复;
而且对于server端应用来说,升级一个大版本是不可取的,甚至有可能导致服务器程序出错。
所以结论就是:
如果没有报出莫名奇妙的错误,JVM不升级;
如果有报出错误或新的小版本bug修复说明中有体现将来应用涉及的特性和内容,则在系统维护时间窗口允许的情况下,应该尽可能升级到最新的小版本,因为小版本主要是bug的修复和性能的提升,且最新的小版本稳定性都是最好的。
理解相关JVM原理以实现优化
了解JVMspecification中的JVM架构
主要包括两个子系统和两个组件:
Classloader(类装载器)子系统,Executionengine(执行引擎)子系统;
Runtimedataarea(运行时数据区域)组件,Nativeinterface(本地接口)组件。
Classloader子系统的作用:
根据给定的带包名的完整类名(如java.lang.Object)来装载class文件的内容到Runtimedataarea中的methodarea(方法区域)。
Javsa程序员可以extendsjava.lang.ClassLoader类来写自己的Classloader。
Executionengine子系统的作用:
执行classes中的指令。
任何JVMspecification实现(JDK)的核心是Executionengine,换句话说:
Sun的JDK和IBM的JDK好坏主要取决于他们各自实现的Executionengine的好坏。
每个运行中的线程都有一个Executionengine的实例。
Nativeinterface组件:
与nativelibraries交互,是其它编程语言交互的接口。
例如:
C/C++语言
Runtimedataarea组件:
这个组件就是JVM中的内存。
主要包括五个部分:
Heap(堆),MethodArea(方法区域),JavaStack(java的栈),Programcounterregister(程序计数器),Nativemethodstack(本地方法栈)。
Heap和MethodArea是被所有线程的共享使用的;
而Javastack,Programcounterregister和Nativemethodstack是以线程为粒度的,每个线程独自拥有。
Runtimedataarea组件
Heap
Java程序在运行时创建的所有类实例(对象)或数组都放在同一个堆中。
而一个Java虚拟机实例中只存在一个堆空间,因此所有线程都将共享这个堆。
每一个java程序独占一个JVM实例,因而每个java程序都有它自己的堆空间,它们不会彼此干扰。
但是同一java程序的多个线程都共享着同一个堆空间,就得考虑多线程访问对象(堆数据)的同步问题。
这部分会在下面的内存部分有更详细的阐述。
Methodarea
在Java虚拟机中,被装载的class的信息存储在Methodarea的内存中。
当虚拟机装载某个类型时,它使用类装载器定位相应的class文件,然后读入这个class文件内容并把它传输到虚拟机中。
紧接着虚拟机提取其中的类型信息,并将这些信息存储到方法区。
该类型中的类(静态)变量同样也存储在方法区中。
与Heap一样,methodarea是多线程共享的,因此要考虑多线程访问的同步问题。
比如,假设同时两个线程都企图访问一个名为Lava的类,而这个类还没有内装载入虚拟机,那么,这时应该只有一个线程去装载它,而另一个线程则只能等待。
这个方法内存区需要多说一句,因为在sun的jvm规范中并没有硬性规定它应该在内存区什么地方实现,但是鉴于其不需要固定大小,对class也需要也会出现动态加载和卸载的要求,后期的jvm采用了堆内存分代技术后实现都将该区作为heap的PermanentGeneration,也即是内存的永久保存区域。
Javastack
Javastack以帧为单位保存线程的运行状态。
虚拟机只会直接对Javastack执行两种操作:
以帧为单位的压栈或出栈。
每当线程调用一个方法的时候,就对当前状态作为一个帧保存到javastack中(压栈);
当一个方法调用返回时,从javastack弹出一个帧(出栈)。
栈的大小是有一定的限制,这个可能出现StackOverFlow问题。
下面的常见问题解决中会说明和Javastack相关的出错问题。
Programcounterregister
每个运行中的Java程序,每一个线程都有它自己的PC寄存器,也是该线程启动时创建的。
PC寄存器的内容总是指向下一条将被执行指令的“地址”,这里的“地址”可以是一个本地指针,也可以是在方法字节码中相对应于该方法起始指令的偏移量。
如果该线程正在执行一个本地方法,那么此时pc寄存器的值是”undefined”。
Nativemethodstack
对于一个运行中的Java程序而言,它还能会用到一些跟本地方法相关的数据区。
当某个线程调用一个本地方法时,它就进入了一个全新的并且不再受虚拟机限制的世界。
本地方法可以通过本地方法接口来访问虚拟机的运行时数据区,不止与此,它还可以做任何它想做的事情。
比如,可以调用寄存器,或在操作系统中分配内存等。
总之,本地方法具有和JVM相同的能力和权限。
Java程序执行的大致流程
要理解java程序执行的大致流程,光看上面的jvm架构规范其实是很难看懂的,因为上面定义的是规范,从进程的角度来看上面的内容就更像是一个示意图了。
要深入理解java程序的执行还是需要从进程的角度进行深入分析,首先在jvm架构中已经知道,Jvm是运行在操作系统上,它是操作系统的一个进程。
然后我们知道jvm本身是由c/c++编写,换句话来说,当java可执行程序被运行起来的时候,系统而言不过是启动了一个c/c++程序,操作系统会像对待所有其他c/c++程序一样,对其分配独立的虚拟内存地址空间,包括建立众多的数据表来维护它的代码段、数据段、堆和堆栈段。
为了更好地了解进程地址空间技术和堆栈段、堆、BBS段、数据段、代码段等相关概念,附进程的虚拟地址空间示意图(堆栈、堆、数据段、代码段)。
然后我们马上要意识到,jvm进程会按照通常C/C++的流程把自己本身实现jvm的代码加载到代码段,涉及到的全局变量和静态变量等等加载到数据段以及利用堆栈等等内容,此时理解jvm进程可以都不用考虑还有一堆java的class存在。
接着可以再看看jvm架构当中定义的子系统和组件了,他们如何处理用户编写的javaclass程序呢?
如何分配内存给他们呢?
只要你符合了jvm的架构规范,你会发现其实你怎么处理他们都行,所以到了今天大家会发现,jvm有很多实现版本,特别是各个操作系统厂商都有各自实现的jvm,实现也有所差异。
有了上面这些铺垫,我相信理解下面的java程序执行流程会容易很多。
但是要注意:
下面描述的流程涉及的所有子系统、运行时数据区和Java虚拟机规范定义的内容部行为都是抽象的。
设计者不会把实际实现按照和规范中定义的抽象元素完全对应的方式来组织。
抽象的内部组件和行为不过是一个词汇表,根据他可以定义任何Java虚拟机实现所必须的外部行为。
换句话说,一个实现内部可能是任何样子的,只要它外部看起来如同Java虚拟机那样工作即可。
Java程序执行大致流程:
●当java可执行程序被运行起来的时候,在其他一些进程相关的初始化工作完成以后,Classloader将编译好的.class文件装载到Runtimedataarea中的methodarea(在分代技术后还有个名称是持久代permanentspace)。
使用的内存在早期jvm实现中有的是使用了进程的代码段空间,但是后来java1.2之后的版本都是使用了进程堆的空间,因为发现其特性和要求更适合在进程堆中实现。
同时也会根据启动要求初始化好一个上文提到的javaheap,使用的内存空间从进程堆中申请。
●当其中的方法要被调用时候,也就说要建立一个新线程时候,Executionengine会分配一个Executionengine的实例,同时拥有一个线程自己的PC寄存器。
●同时JVM给这个线程创建一个Java线程栈。
通过这里就可以了解到-Xss参数该如何设置了,例如默认是Java线程栈是512k的话,如果线程是1000,那么就光是javastack就会花掉512M内存,如果是32位jvm的话内存马上吃紧。
这里要注意的是具体javastack的内存从何处申请是取决于操作系统如何支持多线程实现的。
可以从进程地址空间的堆栈段申请,但其实也可以从进程堆上申请。
●线程需要new对象的话就去javeheap空间中申请内存空间。
内存部分关于对象在javaheap中的活动是如何运作的在下文当中有更详细的阐述。
●如果线程还调用了本地方法的话,对应还会给它创建本地方法栈(如果本地方法是c语言编写的话就创建一个c栈),如果本地方法还使用了malloc或者new方法申请内存的话也是向进程地址空间申请的(进程堆内),所以能申请到的内存大小还是有限制,就是本地方法能申请到的的内存肯定小于java进程能访问的最大内存减去jvm其他部分消耗的内存。
要注意,本地方法可能申请内存或其他原因出错,一旦出错的话整个java进程就退出了。
从上面我们了解到的重要信息有:
●一个JVM进程被启动了以后,它会从操作系统那里获得进程所需的相关资源,包括虚拟地址空间等等资源。
●通常说的java支持的最大内存概念原来是进程级别的,比如说32位是2G/3G/4G,这绝对不说说javaheap能通过设置-mx设置到该最大内存值,也绝对不应该设置为接近该最大值。
●内存大的几块在逻辑上主要由javaheap、javastack、nativestack和nativeheap等等,这些限制都是在进程地址空间以内,需要合理通过启动参数来配置它们的大小。
JVM中heap内存管理
为什么要把heap内存管理专门拿来说呢?
因为JVM中其他地方的内存程序控制不了,你不能随便决定将你的数据存到任何地方,只有heap这块内存程序可以申请。
而且要的时候只管拿,用完之后随地乱丢,居然不用做内存管理,其实也就实现了一半控制,当然了,另外一半控制没人愿意去控制。
这听起来几乎不靠谱。
怎么办?
那就是理解jvm到底是使用了什么方法来管理这些内存的,不知这背后的故事你就不能彻底搞清楚它到底干了些什么。
但当你一旦了解了以后,你会发现事情原来很靠谱。
万一遇到问题的话我们就能知道到底哪个地方的“谱”错了。
下面以Sun公司的JVM规范内存部分实现进行说明,其他的公司的夜类似实现。
比如HP-UX的JVM实现只有一些名词上的区别。
SunJVM实现中:
Runtimedataarea(JVM内存)五个部分中的JavaStack,Programcounterregister,Nativemethodstack三部分和规范中的描述基本一致;
但对Heap和MethodArea进行了自己独特的实现。
这个实现和SunJVM的Garbagecollector(垃圾回收)机制有关。
垃圾分代回收算法(GenerationalCollecting):
基于对对象生命周期分析后得出的垃圾回收算法。
把对象分为年青代、年老代、持久代,对不同生命周期的对象使用不同的算法(上述方式中的一个)进行回收。
现在的垃圾回收器(从J2SE1.2开始)都是使用此算法的。
下图是Java堆中内存分代情况:
1.Young(年轻代)JVMspecification中的Heap的一部份
年轻代分三个区。
一个Eden区,两个Survivor区。
大部分对象在Eden区中生成(少数极大的对象直接在Tenured区生产)。
当Eden区满时,还存活的对象将被复制到两个Survivor区的一个SS#0然后清空Eden区准备接受新对象;
当这个Survivor区SS#0满时,此区的存活对象和Eden区存活的对象将被复制到另外一个Survivor区SS#1;
每次对象从Eden区移到SS区或者SS区直接互相移到的时候,对象的移动次数将会增加1,当移到次数超过n次以后对象就会被移到“年老区(Tenured)”。
需要注意,移动次数n由下文中提到的参数-XX:
MaxTenuringThreshold=控制,Survivor的两个区是对称的,没先后关系,所以同一个区中可能同时存在从Eden复制过来的活对象和从前一个Survivor复制过来的活对象,而复制到年老区的只有从Survivor区过来的对象。
而且,Survivor区总有一个是空的。
年轻代中执行的对象复制和移到就称之为minorGC。
2.Tenured(年老代)JVMspecification中的Heap的一部份
年老代存放从年轻代存活的对象。
一般来说年老代存放的都是生命期较长的对象。
在年老代空间不足的时候就会发生majorGC,也称fullGC,这是一个对象压缩过程,会导致程序停滞,如果是网站服务的话将会出现网站等待。
所需的时间也远远大于minorGC。
3.Perm(持久代)JVMspecification中的Methodarea
用于存放静态文件,如今Java类、方法等。
持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如Hibernate等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。
持久代大小通过-XX:
MaxPermSize=进行设置。
另外,持久代空间不足也会导致fullGC。
从JVM的heap内存管理应当了解到的重要信息有:
●调整持久代的大小,太小会导致JVM出错或者fullGC,太大则浪费内存,甚至影响到其他内存代的频繁GC操作。
●年轻代和年老代需要保持一个适合你实际应用的比例,这个比例可以通过监控工具通过一段时间的观察得来,从而减少GC操作。
●年轻代本身还分为1个Eden和2个Survivor区,应当根据日志输出观察结果或使用监控工具获得应用特点,从而调整Eden和Survivor的大小比例。
常见的jvm参数配置
(以下参数配置资料主要来至网上搜索结果)
1:
heapsize
a:
-Xmx<
n>
指定jvm的最大heap大小,如:
-Xmx=2g
b:
-Xms<
指定jvm的最小heap大小,如:
-Xms=2g,高并发应用,建议和-Xmx一样,防止因为内存收缩/突然增大带来的性能影响。
c:
-Xmn<
指定jvm中NewGeneration的大小,如:
-Xmn256m。
这个参数很影响性能,如果你的程序需要比较多的临时内存,建议设置到512M,如果用的少,尽量降低这个数值,一般来说128/256足以使用了。
d:
-XX:
PermSize=<
指定jvm中PermGeneration的最小值,如:
-XX:
PermSize=32m。
这个参数需要看你的实际情况,。
可以通过jmap命令看看到底需要多少。
e:
Ma
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Jvm使用要求问题诊断和性能优化 v10 Jvm 使用 要求 问题 诊断 性能 优化