5、JVM--调优案例分析
5.1、案例分析
5.1.1、高性能硬件上的程序部署策略
假如一个15w/天左右的在线文档类型网站再准备更换硬件系统
新的硬件为4个CPU、16GB物理内存,操作系统为64为Cento是
Resin作为Web服务器
整个服务器暂时没有部署别的应用,所有的硬件资源都可以提供给这个访问量不大的网站使用
管理员未来尽量利用硬件资源选用64位JDK1.5
并且通过-Xmx和-Xms参数将java堆固定再12GB
使用一段时间后发现效果不理想,网站不定期出现长时间失去响应的情况
监控服务器运行状况发现失去响应是由GC停顿导致的
虚拟机运行再Server模式,默认使用吞吐量有限收集器回收12GB的堆
一次Gull GC的停顿高达14秒
由于程序设计关系,访问这些文档时需要把文档从磁盘提取到内存,导致内存中出现很多有文档序列化产生的大对象
这些大对象很多已经进入了老年代
没有再Minor GC中清理掉
这种情况下即使有12GB的堆,内存也很快被消耗殆尽
由此导致每隔十几分钟出现十几秒停顿。
程序部署上的主要问题是过大的堆内存进行回收时带来的长时间停顿
硬件升级前使用32位系统1。5GB的堆
用户只感觉到网站比较缓慢,但是不会发生明显的停顿
为了考虑升级硬件以提升程序效能,如果缩小给java堆分配内存,那么再硬件上的投资就是显得浪费
再高性能硬件上部署程序,有两种方式:
1、通过64位JDK来使用大内存
2、使用若干个32位虚拟机简历逻辑集群来利用硬件资源
1、通过64位JDK来使用大内存
使用第一种部署方式:对用户交互性强、对停顿时间敏感的系统
可以给Java虚拟机分配超大堆的前提下是有把握应用程序的Full GC频率控制的足够低
至少要低到不会影响用户的使用
如十几小时乃至一天出现一次Full GC,这样可以通过再深夜执行定时任务的方式触发FUll GC
甚至自动重启应用服务器来保持内存可用空间再一个稳定水平
控制Full GC频率的关键看应用中绝大多数对象能否符合“朝生夕灭”的原则
大多数对象的生存空间不长,尤其是不能有成批量、长生存时间的大对象产生,这样才能保障老年代空间的稳定
再大多数网站形式的应用里,主要对象的生存周期都应该是请求级或者页面级的
会话级和全局级的长生命对象很少,只要代码写得合适,应当都能实现再超大堆
中正常使用而没有Full GC,
使用超大内存堆时,网站响应速度才会比较有保证
初次之外还需要考虑:
1、内存回收导致的长时间卡顿
2、64位的JDK的性能测试结果普遍低于32位JDK
为了保证程序足够稳定,这种应用要是产生堆溢出几乎就无法产生堆存储快照(会产生大量的Dump文件)
哪怕产生了快照也无法进行分析
相同程序在64位JDK消耗的内存一般比32位JDK大
这是由于指针膨胀,以及数据类型对齐补白等因素导致
2、使用若干个32位虚拟机建立逻辑集群来利用硬件资源
使用若干个32位虚拟机建立逻辑集群来利用硬件资源
具体做法是再一台物理机上启动多个应用服务器进程,每个服务器进程分配不同的端口
然后再前端搭建一个负载均衡,以反向代理方式来分配访问请求
考虑再一台服务器集器上建立逻辑集群的目的仅仅是为了尽可能利用硬件资源
并不把要关心状态保留、热转移指类的高可用性需求,也不需要保证每个虚拟机进程有
绝对的负载均衡,因此使用无Session复制的亲合式方式集群是一个相当不错的选择
仅仅需要保证集群具备亲和性,也是均衡器按一定的规则算法将一个固定的用户请求
永远分配到固定的一个集群节点进行处理即可。
可能会遇到的问题:
1、尽量避免节点竞争全局资源,最典型的就是磁盘竞争,各个节点之间如果同时访问某个磁盘
问价的话,很容易导致IO异常
2、很难最高效率地利用某些资源池(连接池),一般都是再各个节点建立独立的连接池,这样有可能
导致一些节点满了而另外一些节点仍有较多的空余
3、各个节点仍然不可避免的受到32位的内存限制,再32位windows平台中每个进程都只能使用2GB的内存
考虑到堆以外的内存开销,堆一般最多只能开到1.5GB。
4、大量使用本地缓存的应用,再逻辑集群中会造成较大的内存浪费,因为每一个节点上都有一份缓存
这时候可以考虑把本地缓存改为集中式缓存
5.2.2、集群间同部导致的内存溢出
如:一个基于B/S的MIS系统,硬件为两台2个CPU、8GB内存的HP小型机
服务器是WebLogic9.2,每个机器启动了3个WebLogic实例,构成一个6个节点的亲合式集群
由于式亲合式集群,节点之间没有进行session同部,但是要实现部分数据再各个节点间共享
开始将这些数据存放在数据库中,由于读写频繁竞争很激烈,性能影响比较大
后面使用JBossCache构建一个全局缓存
全局缓存启用后,服务正常使用了一段时间,但最近却不定期的出现了很多次内存溢出的问题
在内存溢异常不出现的时候,服务内存回收状况一直正常,每次内存回收后都能恢复
到一个稳定的可用的空间,开始换衣是程序某些不正常的代码路径中存在内存泄露
但管理员反映最近程序并未更新、升级过、也没有进行什么特别操作
只好让服务带着:-XX:+HeapDumpOnOutOfMemoryError参数运行一段时间
再最近一次溢出之后,管理员发回了heapdump文件,发现里面存在大量的org.jgroups.protocols.pbcast.NAKACK对象
JBossCache是基于自家的JGroups进行集群间的数据通信,JGroups使用协议栈的方式来实现收发数据
的各种所需要特性自由组合数据包接收和发送时都要经过每层协议栈的up()和down()方法
其中的NAKACK栈用于保障各个包的有效顺序及重发
JBossCache协议栈:
https://blog.csdn.net/yangyan19870319/article/details/7631145
https://my.oschina.net/u/2338362/blog/469594
由于信息有传输失败需要重发的可能性,再确定所有注册再GMS(Group Membership Service)的节点
都受到了正确的信息前,发送的信息必须再内存中保留
此MIS服务端中有一个负责安全检验的全局Filter,每当接收到请求时,均会更新一次最后操作时间
并且将这个时间同步到所有节点去,使得一个用户再一段时间不能再多台机器上登陆
再服务使用过程中,往往一个页面会产生数次乃至数十次的请求
因此这个过滤器导致集群各个节点之间网络交互频繁
当网络情况不呢满足用户传输需求时,重发数据再内存中不断堆积,很快就产生了内存溢出
这个案例中的问题,既有JBossCache的缺陷,也有MIS系统实现方式上缺陷。
JBossCache官方的maillist中讨论过很多次类似的内存溢出异常问题,据说后续版本也有了改
进。而更重要的缺陷是这一类被集群共享的数据要使用类似JBossCache这种集群缓存来同步
的话,可以允许读操作频繁,因为数据在本地内存有一份副本,读取的动作不会耗费多少资
源,但不应当有过于频繁的写操作,那样会带来很大的网络同步的开销
5.2.3、堆外内存导致的溢出错误
如:
一个学校的小型项目基于B/S的电子考试系统,为了实现客户端能够实时的从服务器毒啊接收考试数据
系统使用了逆向AJAX技术,选用CometD1.1.1作为服务器推送控件,服务器时Jetty7.1.4,
用剑为一台普通PC机,Core i5 CPU,4GB内存,运行32位Windows操作系统
测试期间发现服务器不定时抛出内存溢出异常,服务器不一定每次都会出现异常,但假如正式考试时
崩溃一次,此时整个系统就会瘫痪,网站管理员尝试把堆开到最大,而32位系统最多到1.6GB就无法再进行加大了
而且开大了基本也没有效果,抛出内存异常更加频繁
加入-XX:+HeapDumpOnOutOfMemoryError也没有反应,抛出异常时什么文件都没有产生
实践经验角度:
除了Java堆和永久代之外还会有其他区域占用较多的内存,这里所有的内存总和受到操作系统进程最大的限制
1、Direct Memory:
可通过-XX:MaxDirectMemortSize调整大小,内存不足时抛出OutOfMemoryError或者OutOfMemoryError:Direct buffer memory
2、线程堆栈
可以通过-Xss调正大小,内存不足时抛出StackOverflowError或者OutOfMemoryError:unable to create new native thread
3、Socket缓存区
每个Socket连接都Receive和Send两个缓存区,分别大约37kb和25kb内存
连接多的话这块内存占用也比较可观,若无法分配,可能会抛出:IoException:Too many open files异常
4、JNI代码
如果代码使用JNI调用本地库,那本地库使用的内存也不在堆中
5、虚拟机和GC
虚拟机、GC的代码执行也要消耗一定的内存
5.2.4、外部命令导致系统缓慢
案例:
一个数字校园应用系统,运行再一台4个CPU的Solaris 10操作系统上,中间件位GlassFish服务器。
系统在做大并发压力测试的时候,发现请求响应时间比较慢,通过操作系统的mpstat工具发现CPU的使用率很高
并且系统占用绝大多数的CPU资源的程序并不是应用系统本身
这不是一个正常的现象,通常情况下用户应用cpu占用率应该占主要地位,才能说明系统时正常工作的
通过Solaris 10的Dtrace脚本可以查看当前情况下那些系统调用花费了最多的CPU资源
Dtrace运行后发现最快消耗CPU资源竟然是“fork”系统调用
“fork”系统调用是Linux用来产生新进程的,再java虚拟机中,用户编写的java代码最多只有线程的概念,不应当有进程产生
这是个非常异常的现象,通过系统的开发人员,最终找到了答案:
每个用户请求处理都需要执行一个外部的shell脚本来获取系统的一些信息
执行 这个shell脚本是通过Java的Runtime.getRuntime().exec()方法来调用的
这种方式可以达到目的,但是它在Java虚拟机中是非常消耗资源的操作
即使外部命令本身能很快执行完毕,频繁调用时创建进程的开销也是非常可观
Java虚拟机执行这个命令的过程是:
首先克隆一个和当前虚拟机拥有一样的环境变量的进程
再用这个新的进程区执行外部的命令
最后退出这个进程
如果频繁执行这个操作,系统会消耗很大,不仅是CPU,内存负担也很重
5.2.5、服务器JVM进程崩溃
如:
一个基于B/S的MIS系统,硬件位两台2个CPU、8GB内存的HP系统,服务器是WebLogic9.2
正常运行一段时间之后,最近发现再运行期间频繁出现集群节点的虚拟机进程自动关闭的现象
留下了一个hs_err_pidXXXX.log文件后,进程就消失了,两台物理机器的每个节点都出现过进程崩溃的现象
由于MIS系统的用户多,待办事项变化很快,为了不被OA系统速度拖累,使用了异步的
方式调用Web服务,但由于两边服务速度的完全不对等,时间越长就累积了越多Web服务没
有调用完成,导致在等待的线程和Socket连接越来越多,最终在超过虚拟机的承受能力后使
得虚拟机进程崩溃。解决方法:通知OA门户方修复无法使用的集成接口,并将异步调用改
为生产者/消费者模式的消息队列实现后,系统恢复正常。
5.2.6、不恰当数据结构导致的内存占用过大
有一个后台RPC服务器,使用64位虚拟机,内存配置为-Xms4g-Xmx8g-Xmn1g,
使用ParNew+CMS的收集器组合。平时对外服务的Minor GC时间约在30毫秒以内,完全可以
接受。但业务上需要每10分钟加载一个约80MB的数据文件到内存进行数据分析,这些数据
会在内存中形成超过100万个HashMap<Long,Long>Entry,在这段时间里面Minor GC就会造
成超过500毫秒的停顿,对于这个停顿时间就接受不了了
如果不修改程序,仅从GC调优的角度去解决这个问题,可以考虑将Survivor空间去掉
(加入参数-XX:SurvivorRatio=65536、-XX:MaxTenuringThreshold=0或者-XX:
+AlwaysTenure),让新生代中存活的对象在第一次Minor GC后立即进入老年代,等到Major
GC的时候再清理它们。这种措施可以治标,但也有很大副作用,治本的方案需要修改程
序,因为这里的问题产生的根本原因是用HashMap<Long,Long>结构来存储数据文件空间效
率太低。
空间效率
在HashMap<Long,Long>结构中,只有Key和Value所存放
的两个长整型数据是有效数据,共16B(2×8B)。这两个长整型数据包装成java.lang.Long对
象之后,就分别具有8B的MarkWord、8B的Klass指针,在加8B存储数据的long值。在这两个
Long对象组成Map.Entry之后,又多了16B的对象头,然后一个8B的next字段和4B的int型的
hash字段,为了对齐,还必须添加4B的空白填充,最后还有HashMap中对这个Entry的8B的引
用,这样增加两个长整型数字,实际耗费的内存为
(Long(24B)×2)+Entry(32B)+HashMap Ref(8B)=88B,空间效率为16B/88B=18%,
实在太低了。
5.2.7、由Windows虚拟机内存导致的长时间卡顿
有一个带心跳检测功能的GUI桌面程序,每15秒会发送一次心跳检测信号,如果
对方30秒以内都没有信号返回,那就认为和对方程序的连接已经断开。程序上线后发现心跳
检测有误报的概率,查询日志发现误报的原因是程序会偶尔出现间隔约一分钟左右的时间完
全无日志输出,处于停顿状态
因为是桌面程序,所需的内存并不大(-Xmx256m),所以开始并没有想到是GC导致的程序停顿
但是加入参数-XX:+PrintGCApplicationStoppedTime-XX:+PrintGCDateStampsXloggc:gclog.log后,
从GC日志文件中确认了停顿确实是由GC导致的,大部分GC时间都控
制在100毫秒以内,但偶尔就会出现一次接近1分钟的GC。
除GC日志之外,还观察到这个GUI程序内存变化的一个特点,当它最小化的时候,资源
管理中显示的占用内存大幅度减小,但是虚拟内存则没有变化,因此怀疑程序在最小化时它
的工作内存被自动交换到磁盘的页面文件之中了,这样发生GC时就有可能因为恢复页面文
件的操作而导致不正常的GC停顿。
在MSDN上查证后确认了这种猜想,因此,在Java的GUI程序中要避免这种现象,可以
加入参数“-Dsun.awt.keepWorkingSetOnMinimize=true”来解决。这个参数在许多AWT的程序上
都有应用,例如JDK自带的Visual VM,用于保证程序在恢复最小化时能够立即响应。在这个
案例中加入该参数后,问题得到解决。
5、JVM--调优案例分析的更多相关文章
- 《深入理解Java虚拟机》-----第5章 jvm调优案例分析与实战
案例分析 高性能硬件上的程序部署策略 例 如 ,一个15万PV/天左右的在线文档类型网站最近更换了硬件系统,新的硬件为4个CPU.16GB物理内存,操作系统为64位CentOS 5.4 , Resin ...
- Java之JVM调优案例分析与实战(1) - 高性能硬件上的程序部署策略
本JVM系列均来源于<深入理解Java虚拟机>一书中,版权归该书作者所有. 环境:一个15万PV/天左右的在线文档类型网站最近更换了硬件系统,新系统硬件为4个CPU.16GB物理内存.OS ...
- Java之JVM调优案例分析与实战(5) - 服务器JVM进程奔溃
环境:一个基于B/S的MIS系统,硬件为2个CPU.8GB内存的HP系统,服务器是WebLogic9.2(就是第二个案例中的那个系统).正常运行一段时间后,最近发现在运行期间频繁出现集群节点的虚拟机进 ...
- Java之JVM调优案例分析与实战(4) - 外部命令导致系统缓慢
环境:这是一个来自网络的案例:一个数字校园应用系统,运行在一台4个CPU的Solaris 10操作系统上,中间件为ClassFish服务器.系统在进行大并发压力测试的时候,发现请求响应时间比较慢,通过 ...
- Java之JVM调优案例分析与实战(3) - 堆外内存导致的溢出错误
环境:基于B\S的点子考试系统,为了发现客户端能实时地从服务端接收考试数据,系统使用了逆向AJAX技术(也称Comet或Server Side Push),选用CometD1.1.1作为服务端推送框架 ...
- Java之JVM调优案例分析与实战(2) - 集群间同步导致的内存溢出
环境:一个基于B/S的MIS系统,硬件为两台2个CPU.8GB内存的HP小型机,服务器是WebLogic 9.2,每台机器启动了3个WebLogic实例,构成一个6个节点的亲合式集群. 说明:由于是亲 ...
- 《深入理解Java虚拟机》调优案例分析与实战
上节学习回顾 在上一节当中,主要学习了Sun JDK的一些命令行和可视化性能监控工具的具体使用,但性能分析的重点还是在解决问题的思路上面,没有好的思路,再好的工具也无补于事. 本节学习重点 在书本上本 ...
- 【JVM.4】调优案例分析与实战
之前已经介绍过处理Java虚拟机内存问题的知识与工具,在处理实际项目的问题时,除了知识与工具外,经验同样是一个很重要的因素.本章会介绍一些具有代表性的案例. 本章的内容推荐还是原文全篇看完的好,实在不 ...
- 记一次Java调优案例分析
上周,一同学给我发来,他们那里的案例 一看就是新生代产生过多对象,肯定是批量或者循环操作导致的,导致新生代一直在进行回收导致. 如果是老生代出现这样的问题,大部分情况下是列表或者集合导致的. 因此我们 ...
- JVM调优参数、方法、工具以及案例总结
这种文章挺难写的,一是JVM参数巨多,二是内容枯燥乏味,但是想理解JVM调优又是没法避开的环节,本文主要用来总结梳理便于以后翻阅,主要围绕四个大的方面展开,分别是JVM调优参数.JVM调优方法(流程) ...
随机推荐
- 插入sql返回主键id
<insert id="insertSelective" parameterType="com.xxx.model.XDetail" useGenerat ...
- WCF使用net.tcp绑定的配置实例
<system.serviceModel> <bindings> <basicHttpBinding> <!--默认http绑定的配置,这里提高了最大传输信息 ...
- jQuery Grid入门指南
上周需要把一个项目中的普通table改成使用jQuery插件形式的表格,找到了jqgrid这个插件,本以为找个demo,查查api就能解决,没想到还是费了一番的功夫,在这里记录总结一下. 本文实现的内 ...
- python 字符串居中
下面的代码可以让字符串居中,左对齐和右对齐,字符串长度设置为50,居中后左右补充空格,右对齐会在左侧补充空格 string1 = "Now I am here." print( s ...
- BZOJ4566: [Haoi2016]找相同字符(后缀自动机)
题意 题目链接 Sol 直接在SAM上乱搞 枚举前缀,用SAM统计可以匹配的后缀,具体在匹配的时候维护和当前节点能匹配的最大值 然后再把parent树上的点的贡献也统计上,这部分可以爆跳parent树 ...
- 新电脑装不了win7?来试试我的方法!
好久没写日记了,今天稍有时间来写个有关于硬件的技术贴. 前段时间换了个惠普暗影精灵二代,它的cpu代数如图所示: 用了几天系统自带win10,不同浏览器字体模糊的问题是个问题,故而想装 ...
- Django Redis存储session会话
通常redis都是用来保存session.短信验证码.图片验证码等数据. 在django上使用redis,先要安装一个包: pip install django-redis==4.8.0(我用的dja ...
- centos7.2+php7.2+nginx1.12.0+mysql5.7配置
一. 源码安装php7.2 选择需要的php版本 从 php官网: http://cn2.php.net/downloads.php 选择需要的php版本,选择.tar.gz 的下载包,点击进入,选择 ...
- 【Java】解析xml
xml: <?xml version="1.0" encoding="GB2312"?> <RESULT> <VALUE> ...
- WCF3.5 SP1 参考源码索引
http://www.projky.com/dotnet/WCF3.5SP1/Microsoft/InfoCards/AccessibilityApplicationManager.cs.htmlht ...