【JVM知识总结-1】JVM内存模型
【JVM知识总结-2】HotSpot虚拟机对象
【JVM知识总结-3】垃圾收集策略与算法
【JVM知识总结-4】HotSpot垃圾收集器
【JVM知识总结-5】内存分配与回收策略
【JVM知识总结-6】JVM性能调优
【JVM知识总结-7】类的文件结构
【JVM知识总结-8】类的加载时机
【JVM知识总结-9】类加载的过程
【JVM知识总结-10】类加载器

在高性能硬件上部署程序,目前有两周方式:

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

使用64位JDK管理内存

堆内存变大后,虽然垃圾回收的频率减少了,但每次垃圾回收的时间变长了。如果堆内存为14G,那么每次Full GC将长达数十秒。如果Full GC频繁发生,那么对于一个网站是无法忍受的。
对于用户交互性强、对停顿时间敏感的系统,可以给Java虚拟机分配超大堆的前提是有把握把应用程序的Full GC频率控制的足够低,至少要低到不印象用户使用。
可能面临的问题:

  • 内存回收导致长时间的停顿
  • 现阶段,64位JDK的性能普遍比32位JDK低
  • 需要保证程序足够稳定,因为这种应用要是产生堆溢出几乎就无法产生堆转储快照(因为要生产超过10GB的dump文件),哪怕生产了快照也无法分析。
  • 相同程序在64位JDK消耗内存一般比32位JDK大,这是由于指针膨胀,以及数据类型对齐补白等因素导致的。

使用32位JVM建立逻辑集群

在一台物理机上启动多个应用服务器进程,每个服务器进程分配不同端口,然后再前端搭建一个负载均衡器,以反向代理的方式来分配访问请求。
考虑在一台物理机器上建立逻辑集群的目的仅仅是为了尽可能的利用硬件资源,并不需要关心状态保留、热转移之类的高可用性能需求,也不需要保证每个虚拟机进程有绝对的负载均衡,因此使用无Session复制的亲合式集群是一个不错的选择,也不需要保证每一个虚拟机进程有绝对的负载均衡,因此使用无Session复制的亲合式集群是一个不错的选择。我们仅仅需要保障集群具备亲合性,也就是均衡器按一定的规则算法(一般根据SessionID分配)将一个固定的用户请求永远分配到固定的一个集群节点处理即可。
肯能遇到的问题:

  • 尽量避免节点竞争局部资源,如磁盘竞争,各节点如果同时访问某个磁盘文件的话,很可能导致IO异常
  • 很难高效的利用资源池,如连接池,一般都是在节点简历自己独立的连接池,这样有可能导致一些节点池满了而另外一些节点仍有较多空余;
  • 各节点受到32位的内存限制
  • 大量使用本地缓存的应用,在逻辑集群中会造成较大的内存浪费,因为每一个逻辑节点都有一份缓存,这时候可以考虑把本地缓存改成集中式缓存。

调优案例分析与实践

场景描述

一个小型系统,使用32位JDK,4G内存,测试期间发现服务端不定时的抛出内存溢出异常。加入-XX:+HeapDumpOnOutOfMemoryError(添加这个参数后,堆内存溢出就会输出异常日志),但再次发生溢出时,没有相关日志生成。

分析

在32位JDK上,1.6G分配给堆,还有一部分分配给JVM的其他内存,直接内存最大也只能在剩余的0.4G空间中分出一部分,如果使用了NIO,JVM会在JVM内存之外分配内存空间,那么就要小心“直接内存”不足时发生内存溢出异常了。

直接内存的回收过程

直接内存虽然不是JVM内存空间,但它的垃圾回收也由JVM负责。
垃圾收集进行时,虚拟机虽然会对直接内存进行回收,但是直接内存却不能像新生代、老年代那样,发现空间不足了就通知垃圾收集器进行垃圾回收,它只能等老年代满了后Full GC,然后“顺便”帮它清理掉内存的废弃对象。否则只能一直等到抛出内存溢出异常时,先catch掉,再在catch块里调用System.gc()。要是虚拟机还是不听,那就只能眼睁睁看着堆中还有许多空闲内存,自己却不得不抛出内存溢出异常了。


AllenYang
4 声望3 粉丝

We don't get to choose who we love