(四)调优案例分析和实战

调优案例分析和实战

案例分析

高性能硬件上的程序部署策略

案例:一个15万PV/天左右的在线文档类型网站最近更换了硬件系统,新的硬件为4个CPU、16GB物理内存,操作系统为64位CentOS 5.4 ,Resin作为Web服务器。整个服务器暂时没有部署别的应用,所有硬件资源都可以提供给这访问量并不算太大的网站使用。管理员为了尽量利用硬件资源选用了64位的JDK1.5,并通过-Xmx和-Xms参数将Java堆固定在12GB。使用一段时间后发现使用效果并不理想,网站经常不定期出现长时间失去响应的情况。

在高性能硬件上部署程序有哪些方式?

  • 通过64位JDK来使用大内存
  • 使用若干个32位虚拟机建立逻辑集群来利用硬件资源

64位JDK和32位JDK的区别?

32位的jdk内存大小最大为4G

使用64位JDK管理大内存的优势和问题?

优势:能够使用的内存空间更多了

问题:

  • 内存太大,回收导致的长时间停顿
  • 无法产生堆转储快照(太大了)
  • 相同程序在64位JDK消耗的内存一般比32位JDK大 ,这是由于指针膨胀,以及数据类型对齐补白等因素导致的。

对于用户交互性强、对停顿时间敏感的系统,可以给Java虚拟机分配超大堆的前提是有把握把应用程序的Full GC频率控制得足够低,至少要低到不会影响用户使用,譬如十几个小时乃至一天才出现一次Full GC。

如何控制好GC的低频率?

控制Full GC频率的关键是看应用中绝大多数对象能否符合“朝生夕灭”的原则,即大多数对象的生存时间不应太长,尤其是不能有成批量的、长生存时间的大对象产生,这样才能保障老年代空间的稳定。

什么是session同步?为什么需要进行session同步?

session是存储在服务器的访问信息
不同服务器之间可能会不同,如果不同步,由于负载均衡可能导致下次访问失效

session同步的几种方式?

  • 进程间通信传递session:比如不同的服务器中建立socket链接,然后用socket来将相互拥有的session数据进行传递。早期的tomcat使用的方式
  • 全局缓存或者使用数据库做session管理

什么是亲合式的集群?

  • 亲和性可以实现就近部署,增强网络能力实现通信上的就近路由,减少网络的损耗。
  • 反亲和性主要是出于高可靠性考虑,尽量分散实例,某个节点故障的时候,对应用的影响只是 N 分之一或者只是一个实例。

亲和性集群可以使均衡器按一定的规则算法将一个固定的用户请求永远分配到固定的一个集群节点进行处理,因此不需要进行session同步。

使用若干个32位虚拟机建立逻辑集群来利用硬件资源的优势和问题?

具体做法是在一台物理机器上启动多个应用服务器进程 ,每个服务器进程分配不同端口,然后在前端搭建一个负载均衡器,以反向代理的方式来分配访问请求。考虑到在一台物理机器上建立逻辑集群的目的仅仅是为了尽可能利用硬件资源,并不需要关心状态保留、热转移之类的高可用性需求,也不需要保证每个虚拟机进程有绝对准确的均衡负载,因此使用无Session复制的亲合式集群是一个相当不错的选择。

集群间同步导致的内存溢出

案例:有一个基于B/S的MIS系 统 ,硬件为两台2个CPU、8GB内存的HP小型机,服务器是WebLogic 9.2 ,每台机器启动了3个WebLogic实 例 ,构成一个6个节点的亲合式集群。由于是亲合式集群,节点之间没有进行Sessurn同步,但是有一些需求要实现部分数据在各个节点间共享。开始这些数据存放在数据库中,但由于读写频繁竞争很激烈,性能影响较大,后面 使用JBossCache构建了 一个全局缓存。全局缓存启用后,服务正常使用了一段较长的时间, 但最近却不定期地出现了多次的内存溢出问题。

什么是JBossCache和JGroups?

JBossCache是一种全局缓存
JGroups是JBossCache实现集群节点通讯的协议

JGroups通讯方式导致的内存溢出问题?

由于JGroups的重发机制,每份发出的数据都会有在本地内存有一份短暂的保留。与此同时。MIS的服务端中有一个负责安全校验的全局Filter,每鈑接收到请求时,均会更新一次最后操作时间,并且将这个时间同步到所有的节点去,使得一个用户在一段时间内不能在多台机器上登录。在服务使用过程中,往往一个页面会产生数次乃至数十次的请求,因此这个过滤器导致集群各个节点之间网络交互非常频繁。当网络情况不能满足传输要求时,重发数据在内存中不断堆积,很快就产生了内存溢出。

堆外内存导致的溢出错误

案例:一个学校的小型项目:基于B/S的电子考试系统,为了实现客户端能实时地从服务器端接收考试数据 , 系统使用了逆向AJAX技术(也称为Comet或者Server Side Push) ,选用CometD 1.1.1作为服务端推送框架,服务器是Jetty 7.1 .4 ,硬件为一台普通PC机 , Core i5 CPU , 4GB内存,运行32位Windows操作系统。

什么时候对DirectMemory进行回收?

只能等待老年代满了后Full GC,然后“顺便地”帮它清理掉内存的废弃对象。

上面案例造成溢出的原因?

这台服务器使用的32位 Windows平台的限制是2GB,其中划了1.6GB给Java堆,而Direct Memory内存并不算入1.6GB的堆之内,因此它最大也只能在剩余的0.4GB空间中分出一部分。

还有哪些容易造成堆外内存溢出的区域?

  • Direct Memory:可通过-XX:MaxDirectMemorySize调整大小,内存不足时拋出OutOfMemoryError或者OutOfMemoryError:Direct buffer memory。
  • 线程堆栈:可通过-Xss调整大小,内存不足时拋出StackOverflowError (纵向无法分配, 即无法分配新的栈帧)或者OutOfMemoryError : unable to create new native thread (横向无法分配 ,即无法建立新的线程)。
  • Socket缓存区:每个Socket连接都Receive和Send两个缓存区,分别占大约37KB和25KB内存,连接多的话这块内存占用也比较可观。如果无法分配,则可能会拋出IOException : Too many open files异常。
  • JNI代码:如果代码中使用JNI调用本地库,那本地库使用的内存也不在堆中。
  • 虚拟机和GC:虚拟机、GC的代码执行也要消耗一定的内存。

外部命令导致系统缓慢

案例:一个数字校园应用系统,运行在一台4个CPU的Solaris 10操作系统上,中间件为GlassFish服务器。系统在做大并发压力测试的时候,发现请求响应时间比较慢 ,通过操作系统的mpstat工具发现CPU使用率很高 ,并且系统占用绝大多数的CPU资源的程序并不是应用系统本身。

为什么外部命令会导致系统缓慢?

每个用户请求的处理都需要执行一个外部shell脚本来获得系统的一些信息。实际上也是这个脚本导致每次都回去fork一个新进程,吃了大量资源。
外部命令不属于java内存的管理范畴,所以无法监控到。

解决方案:建议去掉这个Shell脚本执行的语句,改为使用Java的API去获取这些信息后,系统很快恢复了正常。

服务器JVM进程崩溃

案例:一个基于B/S的MIS系统,硬件为两台2个CRJ、8GB内存的HP系统,服务器是 WebLogic 9.2。正常运行一段时间后,最近发现在运行期间频繁出现集群节点的虚拟机进程自动关闭的现象,留下了一个hs_err_pid/#/#/#.log文件后,进程就消失了,两台物理机器里的每个节点都出现过进程崩溃的现象。从系统日志中可以看出,每个节点的虚拟机进程在崩溃前不久,都发生过大量相同的异常:SocketException

为什么服务器中过多异步http调用会导致服务器崩溃?

过多异步http调用导致在等待的线程和Socket连接越来越多,最终在超过虚拟机的承受能力后使得虚拟机进程崩溃。

解决方法:通知OA门户方修复无法使用的集成接口,并将异步调用改为生产者/消费者模式的消息队列实现后,系统恢复正常。

不恰当的数据结构导致内存占用过大

案例:有一个后台RPC服务器,使用64位虚拟机,内存配置为-Xms4g-Xmx8g-Xmnlg,使用ParNew+CMS的收集器组合。平时对外服务的Minor GC时间约在30毫秒以内,完全可以接受。但业务上需要每10分钟加载一个约80MB的数据文件到内存进行数据分析,这些数据会在内存中形成超过100万个HashMap<Long,Long>Entry,在这段时间里面Minor GC就会造成超过500毫秒的停顿,对于这个停顿时间就接受不了了。

使用一个100W大小的haspMap会导致的GC问题?

ParNew收集器使用的是复制算法,这个算法的高效是建立在大部分对象都“朝生夕灭”的特性上的,如果存活对象过多,把这些对象复制到Survivor并维持这些对象引用的正确就成为一个沉重的负担,因此导致GC暂停时间明显变长。

解决方案:加入参数-XX:SurvivorRatio=65536、-XX:MaxTenuringThreshold=0或者-XX:+AlwaysTenure,让新生代中存活的对象在第一次Minor GC后立即进入老年代,等到Major GC的时候再清理它们。这个方案治标不治本。

hashMap的空间效率?

在HashMap<Long,Long>结构中,只有Key和Value所存放的两个长整型数据是有效数据,共16B( 2x8B )。

这两个长整型数据包装成java.lang.Long对象之后,就分别具有8B的MarkWord、8B的Klass指针,在加8B存储数据的long值。在这两个Long对贏组成Map.Entry之后,又多了16B的对象头,然后一个8B的next字段和4B的int型的hash字段,为了对齐,还必须添加4B的空白填充,最后还有HashMap中对这个Entry的8B的引用,这样增加两个长整型数字,实际耗费的内存为(Long(24B)x2)+Entry(32B)+HashMap Ref(8B)=88B,空间效率为16B/88B=18%,实在太低了。

Eclipse运行速度调优

本文标题:(四)调优案例分析和实战

文章作者:Sun

发布时间:2019年10月17日 - 23:10

最后更新:2019年11月04日 - 14:11

原始链接:https://sunyi720.github.io/2019/10/17/JVM/(四)调优案例分析和实战/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。