内存动态分配和垃圾收集技术


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

问题:网站经常出现不定期长时间失去响应的情况

  • 监控服务器运行状况后发现网站响应是有GC停顿导致的,虚拟机运行在Server模式,默认使用吞吐量优先收集器,由于程序设计的问题,访问文档要把文档从磁盘 提取到内存中,导致内存中出现很多有文档序列化产生的大对象,并且这些大对象都进入老年代。内存很快被耗尽。由此出现这样的问题
  • 目前在高性能硬件部署程序,主要有两种方式 
    • 通过64位JDK来使用大内存
    • 使用若干个32位虚拟机建立逻辑集群来利用硬件资源
  • 对于用户交互性强,停顿时间敏感的系统,可以给java虚拟机分配超大堆的前提是有把握把应用程序的Full GC频率控制的足够低,比如一天才出现一次,这样可以通过在深夜执行定时任务的范式触发Full GC甚至自动重启应用服务器来保持内存可用空间在一个稳定的水平。控制Full GC频率的关键是看应用中绝大多数对象是否符合“朝生夕灭”的原则,即大多数对象的生存时间不应太长,尤其不能成批量,长生存时间的大对象产生。
  • 如果使用64位JDK来管理大内存,需考虑以下问题 
    • 内存回收导致的长时间停顿。
    • 需要保证程序足够稳定,因为这种应用要是产生堆溢出几乎就无法产生堆转储快照。哪怕产生了快照也几乎无法进行分析。
    • 相同程序在64位JDK的内存一般比32位JDK大,这是由于指针膨胀,以及数据类型对齐补白等因素导致的。
  • 使用逻辑集群需要注意的问题 
    • 尽量避免节点竞争全局的资源,最典型的就是磁盘的竞争。各个节点如果同时访问某个磁盘文件的话,很容易导致io异常
    • 很难最高效率的利用某些资源池,比如连接池。一般都是在各个节点建立自己独立的连接池。可能会出现一些节点池满了而另外择有很多空闲。
    • 各个节点仍然不可避免受到32位的内存限制。在windows平台下每个进程只能使用2GB的内存,在Liunx或nuix系统,提议提升3或4GB,但仍受这个最大值限制
    • 大量使用本地缓存的应用,在逻辑集群中会造成较大的内存浪费,因为每个逻辑节点都有一份缓存,这时候可以考虑把本地缓存改为集中式缓存。

解决

部署方案调整为建立5个32位JDK的逻辑集群,每个进程按2GB内存计算(其中堆固定为1.5GB),占用了10 GB内存。另外建立一个Apache服务作为前段均衡代理访问门户,考虑到用户对响应速度比较关心,并且文档服务的主要压力集中在磁盘和内存,cpu资源敏感度较低,因此改为CMS收集器进行垃圾回收。

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

  • 问题描述 
    一个基于B/S的MIS系统,硬件:2个CPU,8GB内存的HP小型机,服务器WebLogic9.2,每台机器启动3个WebLogic实例,构成一个6个节点的亲合式集群,因为为亲合式集群,节点之间没有进行Session同步,但是有些需求要实现部分数据在各个节点间共享,放入数据库,由于读写频繁竞争激烈,性能影响较大,后面使用JBossCache构建了一个全局缓存,但是不久后不定期出现多次内存溢出问题。

原因以及解决

由于信息传输失败需要重发的可能性,在确认收到正确的信息前,发送的信息必须保存在内存中,MIS的服务端中有一个负责安全检验的全局Filter,每当接受到请求时,均会更新一次最后操作时间并且将这个时间同步到所有节点去,使得一个用户在一段时间内不能在多台机器上操作登录,在服务使用过程中,往往一个页面会产生数次甚至数十次的请求,因此这个过滤器导致集群各个节点之间网络交互非常频繁,当网络情况不能满足传输要求时,重发数据在内存中不断堆积,很快就产生了内存溢出。

解析

这个案例,既有JBossCache的缺陷,也有MIS系统实现方式上缺陷。更重要额缺陷是这一类被集群共享的数据要使用类似JBossCache这种集群缓存来同步的话,可以允许读操作频繁,因为数据在本地内存有一份副本,读取的动作不会耗费多少资源,但不应当有过于频繁的写操作,那样会带来很大的网络同步的开销。

堆外内存导致的溢出错误

Direct Memory内存并不算堆之内,垃圾收集进行时,虚拟机虽然会对Direct Memory进行回收,但是Direct Memory却不能像新生代,老年代那样,发现空间不足了就通知收集器进行垃圾回收,它只能等待老年代满了后Full GC,然后“顺便地”帮他清理掉内存的废弃对象。否则它只能一直等到抛出内存溢出异常时,先catch掉,在catch块里面调用System.gc()。如果虚拟机打开了-XX:DisableExplicitGC开关,则不执行此方法。

  • 除了java堆和永生代之外,下面区域还会占用较多的内存,这里的内存之和受操作系统进程最大内存的限制。内存动态分配和垃圾收集技术_第1张图片

外部命令导致系统缓慢

每个用户请求的处理都需要执行一个外部shell脚本来获得系统的一些信息。执行这个shell脚本是通过java的Runtime.getRuntime().exec()方法来调用的。这种方式可以达到目的,但是它在java虚拟机中是非常消耗资源的操作,即使外部命令本身能很快执行完毕,频繁调用时创建进程的开销也是非常可观。java虚拟机执行这个命令的过程:首先克隆一个和当前虚拟机拥有一样环境变量的进程,再用这个新的进程去执行外部命令,最后再退出这个进程,如果频繁执行这个操作,系统的消耗会很大,不仅是CPU,内存负担也很重。

  • 解决 
    去掉这个shell脚本执行语句,改为java的API去获取这些信息。

服务器JVM进程崩溃

一个基于B/S的MIS系统,硬件:2个CPU,8GB内存的HP小型机,服务器WebLogic9.2,正常运行一段时间后,发现运行期间频繁出现集群节点的虚拟机进程自动关闭的现象,留下了一个hs_err_pid###.log文件后,进程就消失了。两台物理机器里的每个节点都出现过进程崩溃的现象。

  • 问题 
    系统集成的时候,两边服务速度的不对等,时间越久就积累了越多web服务没有调用完成,导致在等待的线程和Sochet连接越来越多,最终在超过虚拟机的承受能力后使得虚拟机进程崩溃。
  • 解决 
    修复无法使用的集成接口,并将一部调用改为生产着/消费者模式的消息队列实现。

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

由于Windows虚拟内存导致的长时间停顿

你可能感兴趣的:(内存动态分配和垃圾收集技术)