调优案例分析与实战

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

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

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

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

解决

部署方案调整为建立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堆和永生代之外,下面区域还会占用较多的内存,这里的内存之和受操作系统进程最大内存的限制。这里写图片描述

外部命令导致系统缓慢

每个用户请求的处理都需要执行一个外部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虚拟内存导致的长时间停顿

虚拟机执行子系统

这里写图片描述
java语言中的各种变量,关键字和运算符号的语义最终都是由多条字节码命令组合而成的,因此字节码命令所能提供的语义描述能力肯定会比java语言本身更加强大。

  • Class类文件的结构
    Class文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑的排列在Class文件之中,中间没有添加任何分隔符,这使得整个Class文件中存储的内容几乎都是程序运行的必要数据,没有空隙存在。当遇到需要占用8位字节以上空间的数据项时,则会按照高位在前的方式分割成若干个8位字节进行存储。
    根据java虚拟机规范的规定,Class文件格式采用一种类似于C语言结构体的伪结构来存储数据,这种伪结构中只有两种数据类型:无符号数和表。
  • 无符号数属于基本的数据类型,以u1,u2,u4,u8来分别分别代表1个字节,两个字节……,无符号数可以用来描述数字,索引引用,数量值或者按照UTF-8编码构成字符串值。
  • 表是有多个无符号数或者其他表作为数据项构成的符合数据类型,所有表都习惯性的以“-info”结尾。表用来描述有层次关系的符合结构的数据,整个Class文件本质上就是一张表。这里写图片描述
  • 魔数与Class文件的版本
    每个Class文件的头4个字节称为魔数,它唯一的作用是确定这个文件是否为一个能被虚拟机接受的Class文件。紧跟着魔数的4个字节存储的是Class文件的版本号,第5和第6个字节是次版本号。第7和第8个字节是主版本号

虚拟机类加载机制

  • 类加载的时机
    这里写图片描述
    加载,验证,准备,初始化和卸载这5个阶段的顺序是确定的,类的加载过程必须按照这种顺序按部就班地开始,而解析不一定:它在某些情况下可以在初始化阶段之后在开始,这是为了支持java语言的运行时绑定
  • 遇到new,getstatic,putstatic或invokestatic这4条字节码指令时,如果类没有进行初始化,则需要先触发器初始化。
  • 使用反射包的方法对类进行反射调用的时候,如果没有进行过初始化,则需要先触发其初始化。
  • 当初始化一个类的时候,如果发现其父亲还没有进行过初始化,则需要先触发其父类的初始化。
  • 当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值