专业的JAVA编程教程与资源

网站首页 > java教程 正文

Java JVM常见面试题:JVM调优案例(java jvm调优工具)

temp10 2024-09-25 21:12:15 java教程 8 ℃ 0 评论

GC调优策略

降低Minor GC频率

  • Minor GC的时间分为两部分:扫描新生代和复制存活对象时间。
  • 如果堆中短期的对象很多,扩容新生代的大小(虽然会增加扫描新生代的时间,但同时也会缩短复制对象的时间,因为减少了存回对象的数量。对于老年代来说,进行Major,甚至是Full GC来说,所需要的时间也会减少。PS:扫描对象成本明显低于复制存活对象的成本)
  • 如果堆中长期存活的对象很多,则不应该增加新生代的大小,因为会增加Minor GC的时间(扫描和复制存活对象的时间,都不会减少)

降低Full GC频率

  • 减少大对象的创建
    • 会超过所设置的大对象大小的阈值,直接进入老年代
    • 即使没有超过所设置的大对象大小的阈值,在新生代空间有限的情况下,根据空间分配担保也会有大几率被分配到老年代
  • 增大堆内空间
    • 设置初始化堆内存和最大堆内存大小相同,也可以降低Full GC的频率。也会减少内存自动扩容、自动降容的成本。

选择适当的GC回收器

  • 对单次操作的响应时间有优先的要求,可以选择CMS和G1收集器
  • 对系统吞吐量有优先的要求,可以选择Parallel Scavenge收集器

案例

大内存硬件上的程序部署策略

  • 场景:一个统计/大文档系统,给HotSpot虚拟机分配2G内存,用户使用觉得网站比较慢,在运维把内存升到12G。
  • 问题:经常发生无规律长时间失去响应。
  • 原因:(Parallel Scavenge/Old收集器)过大的堆内存进行Full GC时,用户线程长时间停顿导致(之前服务的堆内存2G,在进行Full GC时,用户线程停顿时间稍短,所以用户在访问时,只是会觉得慢,但是把内存增加到12G之后,老年代在进行Full GC时,需要回收的内存变大,所需的时间就会增加,甚至会超过web服务器的响应时间,失去响应)。
  • 部署方面(单机的情况下):
    • 单独的Java虚拟机实例的缺点
      • 回收大块堆内存而导致的长时间停顿(可以考虑G1收集器的增量回收)
      • 大内存需要64位Java虚拟机,但是因为64位虚拟机的压缩指针、处理器缓存行容量的额外开销,性能会比32位虚拟机偏低。
      • 保证应用程序的足够稳定。如果发生了OOM,堆转储快照的文件太大,信息量很难进行分析。甚至无法产生堆转储快照文件。
    • 多个Java虚拟机,逻辑集群(负载均衡、反向代理)的缺点
      • 节点竞争全局的资源(磁盘IO)
      • 很难最高效率的利用连接池(分配不均)
      • 大量使用本地缓存(各个节点都需要维护自己的缓存,存在内存浪费的情况)
  • 最终总结:
    • 优化代码层次:减少大对象的产生,或者不能有批量的、长生存时间的大对象产生。
    • 虚拟机配置:监控观察大对象的大小,设置-XX:PretenureSizeThreshold的大小,来拦截进入对象老年区的机会。使其在新生代完成对象的生命周期,并改用CMS收集器。
    • 建立多虚拟机节点的逻辑集群:每个虚拟机分配2G的内存。

服务器虚拟机进程崩溃

  • 场景:基于B/S的MIS系统,双机6实例集群。
  • 问题:运行期间频繁出现集群节点的虚拟机进程自动关闭。
  • 原因:调用了第三方服务导致,由于第三方接口响应时间慢的原因,虽然使用了异步调用的方式,但是会有越来越多等待的线程和Socket连接,导致虚拟机进程崩溃
  • 最终总结:使用生产者/消费者模式的消息队列

堆外内存导致的溢出错误

  • 场景:websocket实时推送服务
  • 问题:服务器不定时抛出内存溢出。(加大内存、堆转储快照分析、甚至Eden,Survivor,老年代,方法区的GC频次与时间都无问题)
  • 原因:大量的NIO操作使用直接内存导致。
  • 扩展:除了堆和方法区,服务还会占用其他的内存
    • 直接内存,如NIO连接,可以设置-XX:MaxDirectMemorySize设置所使用的内存大小。即使在OOM的时候,也能产生堆转储快照。
    • 线程堆栈:-Xss调整大小。
    • Socket缓存区:每个Socket连接都需要Receive和Send两个缓存区。
    • JNI代码:本地库使用的内存是占用了本地方法栈和本地内存。
    • 虚拟机和垃圾收集器

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

  • 场景:一个后台RPC服务器,使用64位Java虚拟机,内存配置为-Xms4g-Xmx8g-Xmn1g,使用ParNew加CMS的收集器组合。业务上需要每10分钟加载一个约80MB的数据文件到内存进行数据分析,这些数据会在内存中形成超过100万个HashMap<Long,Long>Entry,在这段时间里面Minor GC就会造成超过500毫秒的停顿。
  • 分析:ParNew收集器使用的是复制算法,这个算法的高效是建立在大部分对象都是存活时间很短的情况下,如果存活对象过多,把这些对象复制到Survivor并维持这些对象引用,导致垃圾收集的暂停时间明显变长。
  • 总结:
    • GC调优:可以考虑直接将Survivor空间去掉(-XX:SurvivorRatio=65536、-XX:MaxTenuringThreshold=0),直接进入老年代,等到Major GC时区清理。
    • 修改数据结构
    • HashMap<Long,Long>的空间效率:
      • HashMap<Long,Long>结构,Key和Value所存放的两个长整型数据是有效数据,共16字节。
      • 用Long对象包装之后:
        • Long对象的空间,8字节的Mark Word、8字节的Klass指针,再加8字节存储数据的long值,共24字节。
        • 2个Long对象组成Map.Entry之后,又多了16字节的对象头,然后一个8字节的next字段和4字节的int型的hash字段,为了对齐,还必须添加4字节的空白填充
        • HashMap中对这个Entry的8字节的引用
      • 结论:空间效率,16字节/88字节=18%。

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表