大土土的年轮
Image(008)
享受所承受的一切...
最新评论
 
迟迟/2008-07-03
人说怀才就像是怀孕....
迟迟/2008-07-03
明天就回来了。。。....
访客/2008-07-03
明天就回来了。。。....
心上秋/2008-06-26
不错哦...业余得....
雪茄/2008-06-26
这都让你抓拍到了,....
穷山沟小女友/2008-06-26
俺娘说咧,小那谁 ....
jeans88/2008-06-23
....
jeans88/2008-06-23
....
jeans88/2008-06-18
最新日志:日本性感....
朵朵/2008-06-17
听了 
感觉不错  ....
朵朵/2008-06-17
呵呵  
从内蒙到沈....
唐小蛮。/2008-06-16

我最近也在看追风筝....
访客/2008-06-14
when ever....
urs/2008-06-14
你得承认,我其实比....
saken2003/2008-05-25
好友回访

IMG_0040

最新日志
 
工作时间(2008-07-03)
上报了(2008-06-26)
获奖感言(2008-06-25)
第十一届中国国际照相机械影像...(2008-06-24)
第十一届中国国际照相机械影像...(2008-06-23)
第十一届中国国际照相机械影像...(2008-06-22)
每一个IT从业人员都应该具有...(2008-06-19)
婚礼(2008-06-14)
东西(2008-06-11)
小皮在一郎 [照片](2008-05-28)
瘸了之后(2008-05-26)
中国,加油(2008-05-21)
地动山摇(2008-05-15)
奥运在手中(2008-05-07)
每个人都有一个私密的世界(2008-05-06)
土土兄弟们
 
你是第 个土土兄
 
2008.04.25 10:25:00 
 HEAP原来就是天赋点 

事件:
系统频繁发生主机挂起现象。日志频繁报出一些错误,JMS消息传递引擎重起,有线程hung住,有时候还有outofmemory的错误。简单说一下系统配置。OS是HP-UX,中间件是WEBSPHERE,数据库用的是DB2。
针对以上错误。和IBM的工程师进行了下分析,首先是JMS消息引擎重起的问题,我们系统JMS使用的地方很多,而在websphere上消息存储器使用的是一个开源的数据库cludespace,这个数据库处理并发能力不是很强,我们怀疑是由于业务高峰期JMS并发量比较大,而导致消息传递引擎频繁重起,所以决定把数据存储器从cludespace向db2上进行切换。
在我们切换完之后,观察一天。然而在晚上17点左右又发生主机挂起现象,这次没有发现消息引擎重起的日志,但是还是有线程被hung住了,于是我们就做了kill -3 查看下被hung线程在做什么,发现是在做一些读数据库的操作,而且hung的位置是过了JDBC DRIVER 在网络层下做socket.read0上。
我们怀疑数据库上这个时候可能存在死锁,于是我们加强监控在应用发生挂起时db2锁的问题。而且调整websphere数据源的一个参数,把事物隔离级别从默认的RR(可重复读取),调整成RC(授权读取),然后继续观察。

名词解释:
 数据库肯定是要被广大客户所共享访问的,那么在数据库操作过程中很可能出现以下几种不确定情况。
●   更新丢失(Lost update):两个事务都同时更新一行数据,但是第二个事务却中途失败退出,导致对数据的两个修改都失效了。这是因为系统没有执行任何的锁操作,因此并发事务并没有被隔离开来。
●   脏读取(Dirty Reads):一个事务开始读取了某行数据,但是另外一个事务已经更新了此数据但没有能够及时提交。这是相当危险的,因为很可能所有的操作都被回滚。
●   不可重复读取(Non-repeatable Reads):一个事务对同一行数据重复读取两次,但是却得到了不同的结果。例如,在两次读取的中途,有另外一个事务对该行数据进行了修改,并提交。
●   两次更新问题(Second lost updates problem):无法重复读取的特例。有两个并发事务同时读取同一行数据,然后其中一个对它进行修改提交,而另一个也进行了修改提交。这就会造成第一次写操作失效。
●   虚读(Phantom Reads):事务在操作过程中进行两次查询,第二次查询的结果包含了第一次查询中未出现的数据(这里并不要求两次查询的SQL语句相同)。这是因为在两次查询过程中有另外一个事务插入数据造成的。
 数据库的隔离级别
       为了避免上面出现的几种情况,在标准SQL规范中,定义了4个事务隔离级别,不同的隔离级别对事务的处理不同。
●   未授权读取(Read Uncommitted):允许脏读取,但不允许更新丢失。如果一个事务已经开始写数据,则另外一个数据则不允许同时进行写操作,但允许其他事务读此行数据。该隔离级别可以通过“排他写锁”实现。
●   授权读取(Read Committed):允许不可重复读取,但不允许脏读取。这可以通过“瞬间共享读锁”和“排他写锁”实现。读取数据的事务允许其他事务继续访问该行数据,但是未提交的写事务将会禁止其他事务访问该行。
●   可重复读取(Repeatable Read):禁止不可重复读取和脏读取,但是有时可能出现幻影数据。这可以通过“共享读锁”和“排他写锁”实现。读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务。
●   序列化(Serializable):提供严格的事务隔离。它要求事务序列化执行,事务只能一个接着一个地执行,但不能并发执行。如果仅仅通过“行级锁”是无法实现事务序列化的,必须通过其他机制保证新插入的数据不会被刚执行查询操作的事务访问到。

   继续观察,应用还是发生挂起现象,而且大多挂起的时候,db2上没有死锁或者锁等待,这样我们不得不继续分析log,来查看还有什么可疑的问题,并且把日志发给HP和IBM800来寻求支持,HP方很快给出来一些方案,让我们修改OS的一个参数,将OS的参数maxfiles_lim参数调整,从原来的8196调整到16392,这个参数的意思就是一个进程能够使用的最大文件数,但是,这个似乎并不是问题的真正原因,挂起现象依旧每天发生,这时IBM800也给出了反馈,通过分析native_stdout.log发现我们的javaheap峰值最高上升到过1.5G,希望我们检查下我们的javaheap的占用情况。
   我们暂时将javaheap的maxSize调到了1.7G。想起到个治标不治本的作用,调整完之后观察,白天很安静。到了晚上19:30左右,又发生挂起现象了,而且连续几天,都是白天没事,到晚上19:30分左右挂起。这时候,日志报的错误就很固定了,有线程hung住,还有就是oom error , can not create native thread ,这时候IBM800那边发生了一点变化,又原来的男工程师,换成了一位女工程师,这位女工程师很热心,每天都主动打电话来询问情况。
   根据她的分析,应该问题就是出在native heap那块,按照她的建议,首先我们调整了os的一个参数,增加了was进程使用native heap的大小,而且她提到了2个已知的was在hp-ux上运行的bug,一个是socket的,一个是线程池的,这2个问题都会产生一些内存泄露,虽然不知道是不是问题的罪魁祸首,但是还是fix了比较好,socket的bug,她给我提供了一个补丁来修复,线程池的bug,就是在hp-ux上跑websphere,我们webcontainer的线程设置是5-50(初始和max),这样,当系统不繁忙的时候,线程会降下来,这时候就会有内存泄露,这个是websphere的一个bug,她建议我们初始线程池大小和max的一致,这样虽然牺牲了一些性能,但是不会有内存泄露的问题,她还给我发了一些关系was性能相关的文档及监控工具的使用,有几篇关于jvm参数的文档,上面建议,sun的jvm javaheap初始值和最大值设置成一样,hp-ux使用的其实也就是sun的jvm,于是我就更改了下jvm的参数,将java heap的初始值和最大值都设置成了1.5G,并且把permsize设置成了384MB(原来是512MB),设置成这些值是用过hp的一个gc分析工具hpjtune的分析之后设置的。
    更改之后,系统尚未发生线埕挂起现象或者是down机。

理论:
OOM(Out of Memory)。在很多开发人员的开发过程中,或多或少的都会遇到这类问题,这类问题定位比较困难,往往需要根据经验来判断可能出现问题的代码。原因主要是两个:对象没有被释放(多种情况引起,往往是比较隐蔽的引用导致被Hold而无法被回收)。另一种就是真的Memory不够用了,需要增加JVM的Heap来满足应用程序的需求。在Native Heap不够的时候也会发生,同时JVM Heap和Native Heap存在着相互影响和平衡的关系。

OOM
       在其他语言类似于C,Delphi等等由于内存都是由自己分配和管理,因此内存泄露的问题比较常见,同时也是很头痛的一件事情。而Java的对象生命周期管理都是JVM来做的,简化了开发人员的非业务逻辑的处理,但是这种自动管理回收机制也是基于一些规则的,而违背了这些规则的时候,就会造成所谓的“Memory Leak”。
 
OOM(Java Heap)
       错误提示:java.lang.OutOfMemoryError。
这类OOM是由于JVM分配的给应用的Heap Memory已经被耗尽,可能是因为应用在高负荷的情况下的却需要很大的内存,因此可以通过修改JVM参数来增加Java Heap Memory(不过也不能无限制增加,后面那种OOM有可能就是因为这个原因而产生)。另一种情况是因为应用程序使用对象或者资源没有释放,导致内存消耗持续增加,最后出现OOM,这类问题引起的原因往往是应用已不需要的对象还被其他有效对象所引用,那么就无法释放,可能是业务代码逻辑造成的(异常处理不够例如IO等资源),也可能是对于第三方开源项目中资源释放了解不够导致使用以后资源没有释放(例如JDBC的ResultSet等)。
       几个容易出现问题的场景:
       1.应用的缓存或者Collection:如果应用要缓存Java对象或者是在一个Collection中保存对象,那么就要确定是否会有大量的对象存入,要做保护,以防止在大数据量下大量内存被消耗,同时要保证Cache的大小不会无限制增加。
       2.生命周期较长的对象:尽量简短对象的生命周期,现在采用对象的创建释放代价已经很低,同时作了很好的优化,要比创建一个对象长期反复使用要好。如果能够设置超时的情景下,尽量设置超时。
       3.类似于JDBC的Connection Pool,在使用Pool中的对象以后需要释放并返回,不然就会造成Pool的不断增大,在其他Pool中使用也是一样。同样ResultSet,IO这类资源的释放都需要注意。
 
       解决的方法就是查找错误或者是增加Java Heap Memory。      
 
 
OOM(Native Heap)
错误提示:requested XXXX bytes for ChunkPool::allocate. Out of swap space。
       Native Heap Memory是JVM内部使用的Memory,这部分的Memory可以通过JDK提供的JNI的方式去访问,这部分Memory效率很高,但是管理需要自己去做,如果没有把握最好不要使用,以防出现内存泄露问题。JVM 使用Native Heap Memory用来优化代码载入(JTI代码生成),临时对象空间申请,以及JVM内部的一些操作。这次同事在压力测试中遇到的问题就是这类OOM,也就是这类Memory耗尽。同样这类OOM产生的问题也是分成正常使用耗尽和无释放资源耗尽两类。无释放资源耗尽很多时候不是程序员自身的原因,可能是引用的第三方包的缺陷,要确定这类问题,就需要去观察Native Heap Memory的增长和使用情况,在服务器应用起来以后,运行一段时间后JVM对于Native Heap Memory的使用会达到一个稳定的阶段,此时可以看看什么操作对于Native Heap Memory操作频繁,而且使得Native Heap Memory增长,对于Native Heap Memory的情况我还没有找到办法去检测,现在能够看到的就是为JVM启动时候增加-verbose:jni参数来观察对于Native Heap Memory的操作。另一种情况就是正常消耗Native Heap Memory,对于Native Heap Memory的使用主要取决于JVM代码生成,线程创建,用于优化的临时代码和对象产生。当正常耗尽Native Heap Memory时,那么就需要增加Native Heap Memory,此时就会和我们前面提到增加java Heap Memory的情况出现矛盾。
 
应用内存组合
       对于应用来说,可分配的内存受到OS的限制,不同的OS对进程所能访问虚拟内存地址区间直接影响对于应用内存的分配,32位的操作系统通常最大支持4G的内存寻址,而Linux一般为3G,Windows为2G。然而这些大小的内存并不会全部给JVM的Java Heap使用,它主要会分成三部分:Java Heap,Native Heap,载入资源和类库等所占用的内存。那么由此可见,Native Heap和 Java Heap大小配置是相互制约的,哪一部分分配多了都可能会影响到另外一部分的正常工作,因此如果通过命令行去配置,那么需要确切的了解应用使用情况,否则采用默认配置自动监测会更好的优化应用使用情况。
       同样要注意的就是进程的虚拟内存和机器的实际内存还是有区别的,对于机器来说实际内存以及硬盘提供的虚拟内存都是提供给机器上所有进程使用的,因此在设置JVM参数时,它的虚拟内存绝对不应该超过实际内存的大小。


  JVM优化配置
Garbage Collector Policy:
       在Jdk提供了三种GC,除了原来提供的串行GC(SerialGC)以外,还提供了两种新的GC:ParallelGC和ConcMarkSweepGC。ParallelGC采用了多线程并行管理和回收垃圾对象,提高了回收效率,提高了服务器的吞吐量,适合于多处理器的服务器。ConcMarkSweepGC采用的是并发方式来管理和回收垃圾对象,降低垃圾回收产生的响应暂停时间。这里说一下并发和并行的区别,并发指的是多个进程并行执行垃圾回收,那么可以很好的利用多处理器,而并行指的是应用程序不需要暂停可以和垃圾回收线程并发工作。串行GC适合小型应用和单处理器系统(无需多线程交互,效率比较高),后两者适合大型系统。
       使用方式就是在参数配置中增加-XX:+UseParallelGC等方式来设置。
       对于这部分的配置在网上有很多的实例可以参考,不过最终采用哪一种GC还是要根据具体的情况来分析和选择。
 
Heap:
       OOM的各种经历已经让每一个架构师开发人员看到了了解Heap的重要性。OOM已经是Heap的临界点,不得不引起注意,然而Heap对于性能的潜在影响并未被引起重视,不过和GC配置一样,在没有对使用情况作仔细分析和研究的情况下,贸然的去修改Heap配置,可能适得其反,这里就来看一下Heap的一些概念和对于性能的影响。
       我们的应用所能够得到的最大的Heap受三部分因素的制约:数据处理模型(32位或者64位操作系统),系统地虚拟内存总数和系统的物理内存总数。首先Heap的大小不能超过不同操作系统的进程寻址范围,当前大部分系统最高限度是4G,Windows通常是2G,Linux通常是3G。系统的虚拟内存也是分配的依据,首先是不能超过,然后由于操作系统支持硬盘来做部分的虚拟内存,如果设置过大,那么对于应用响应来说势必有影响。再则就是要考虑同一台服务器上运行多个Java虚拟机所消耗的资源总合也不能超过可用资源。就和前面OOM分析中的一样,其实由于OS的数据处理模型的限制,机器本身的硬件内存资源和虚拟内存资源并不一定会匹配,那么在有限的资源下如何调整好资源分配,对于应用来说尤为重要。感觉很像魔兽里面的天赋点。通过理论和实践来分配HEAP,让系统达到最佳的状态。
 
 
关于Heap的几个参数设置:
       说了Heap的有限资源问题以后,就来看看如何通过配置去改变JVM对于Heap的分配。下面所说的主要是对于Java Heap的分配,那么在申请了Java Heap以后,剩下的可用资源就会被使用到Native Heap。
       Xms: java heap初始化时的大小。默认情况是机器物理内存的1/64。这个主要是根据应用启动时消耗的资源决定,分配少了申请起来会降低启动速度,分配多了也浪费。
       Xmx:java heap的最大值,默认是机器物理内存的1/4,最大也就到1G。这个值决定了最多可用的Java Heap Memory,分配过少就会在应用需要大量内存作缓存或者零时对象时出现OOM的问题,如果分配过大,那么就会产生上文提到的第二类OOM。所以如何配置还是根据运行过程中的分析和计算来确定,如果不能确定还是采用默认的配置。
       Xmn:java heap新生代的空间大小。在GC模型中,根据对象的生命周期的长短,产生了内存分代的设计:青年代(内部也分成三部分,类似于整体划分的作用,可以通过配置来设置比例),老年代,持久代。每一代的管理和回收策略都不相同,最为活跃的就是青年代,同时这部分的内存分配和管理效率也是最高。通常情况下,对于内存的申请优先在新生代中申请,当内存不够时会整理新生代,当整理以后还是不能满足申请的内存,就会向老年代移动一些生命周期较长的对象。这种整理和移动会消耗资源,同时降低系统运行响应能力,因此如果青年代设置的过小,就会频繁的整理和移动,对性能造成影响。那是否把年青代设置的越大越好,其实不然,年青代采用的是复制搜集算法,这种算法必须停止所有应用程序线程,服务器线程切换时间就会成为应用响应的瓶颈(当然永远不用收集那么就不存在这个问题)。老年代采用的是串行标记收集的方式,并发收集可以减少对于应用的影响。
       Xss:线程堆栈最大值。允许更多的虚拟内存空间地址被Java Heap使用。
 
以下是sun公司的性能优化白皮书中提到的几个例子:
 
1.对于吞吐量的调优。机器配置:4G的内存,32个线程并发能力。
java -Xmx3800m -Xms3800m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20
       -Xmx3800m -Xms3800m 配置了最大Java Heap来充分利用系统内存。
       -Xmn2g 创建足够大的青年代(可以并行被回收)充分利用系统内存,防止将短期对象复制到老年代。
    -Xss128 减少默认最大的线程栈大小,提供更多的处理虚拟内存地址空间被进程使用。
    -XX:+UseParallelGC 采用并行垃圾收集器对年青代的内存进行收集,提高效率。
    -XX:ParallelGCThreads=20 减少垃圾收集线程,默认是和服务器可支持的线程最大并发数相同,往往不需要配置到最大值。
 
2.尝试采用对老年代并行收集
java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20 -XX:+UseParallelOldGC
-Xmx3550m -Xms3550m 内存分配被减小,因为ParallelOldGC会增加对于Native Heap的需求,因此需要减小Java Heap来满足需求。
-XX:+UseParallelOldGC 采用对于老年代并发收集的策略,可以提高收集效率。
 
3.提高吞吐量,减少应用停顿时间
java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:ParallelGCThreads=20 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:SurvivorRatio=8 -XX:TargetSurvivorRatio=90 -XX:MaxTenuringThreshold=31
 
-XX:+UseConcMarkSweepGC -XX:+UseParNewGC 选择了并发标记交换收集器,它可以并发执行收集操作,降低应用停止时间,同时它也是并行处理模式,可以有效地利用多处理器的系统的多进程处理。
 
-XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=31 表示在青年代中Eden和Survivor比例,设置增加了Survivor的大小,越大的survivor空间可以允许短期对象尽量在年青代消亡。
 
-XX:TargetSurvivorRatio=90 允许90%的空间被占用,超过默认的50%,提高对于survivor的使用率。

以上这些其实都是治标不治本。重要的是程序中是否有导致OOM的CODE。
从整个事件分析和处理的过程,除去最先的websphere中JMS的BUG以及WEBSPHERE在HP-UX运行时线程降低产生的BUG外。
更重要的是java heap和native heap分配方面。一开始报OOM的错,同事增大了java heap。但反到产生了负面效果。是因为程序中有大查询。当初始化这种大查询对象的时候,又需要native heap,而这时native heap又不够用了。这时候平均分配了这两个HEAP。
目前来说,HEAP看似够用了。但还需要从程序中查找出这种大结果集,来优先程序。对于编写代码本身来说,对于访问DB资源,一定要尽可能晚地访问关键资源,尽可能快地提交工作,尽可能使用分页查询。这样才会最大程度的避免死锁以及OOM。

标签: OOM,java heap,native heap,死锁
作者 wkui_2000 评论() | 人气()  | 引用() | 推荐 | 保存日志 | 问题日志 | 收藏到网摘 | 返回首页