学习参考资料:周志明老师的著作《深入理解Java虚拟机(第3版)》
1.大内存硬件上的程序部署
一般单体应用在较大内存的硬件方式,有下面两种:
1)通过一个单独的Java虚拟机实例来管理大量的Java堆内存
2)同时使用若干个Java虚拟机,建立逻辑集群利用资源
第一种方法,看似没有太大问题,但前提是要把Full GC 的频率降得很低(在夜间设置定时任务专门进行回收),因为在一个Java虚拟机管理较大堆内存时,一次Full GC 会导致很长时间停顿。此外还应该考虑下面可能发生的问题:
- 必须确保应用程序足够稳定,如果这种大型单体应用发生了内存溢出,几乎无法产生堆转储快照(要产生十几GB甚至更大的文件)
- 大内存必须要有64位Java虚拟机的支持,但由于压缩指针、处理器缓存行容量等因素,64位虚拟机的性能测试结果普遍略低于32位虚拟机
- 相同程序在64位虚拟机中消耗的内存一般比32位更大,这是因为指针膨胀以及数据类型对齐补白等因素导致的,可以开启(默认开启)压缩指针功能开缓解
第二种方法,做法就是在一台机器上启动多个服务器进程分配其不同的端口号,然后在前端搭建一个负载均衡,以反向代理的方式分配请求。但这种方法也并不是完美的,也应该考虑下面可能发生的问题:
- 大量使用本地缓存,会造成大量资源浪费,因为每一个逻辑结点都有一份缓存,解决方法是改为集中式缓存
- 很难比较高效的利用某些资源池,譬如连接池,一般在各个结点建立连接池,假如一个结点连接池满了,另一个结点还有剩余。
- 如果使用32位Java虚拟机会受到限制,如在32为Windows平台中每个进程只能使用2GB内存空间,在某些Linux和Unix系统中可以开到3或4GB,但是32位仍然会受到最高内存4GB(2的32位)限制
2.其他问题
-
当使用集群方式部署,如果有需求要进行同步(例如最后一次访问或操作时间),那么一个页面就可能出现多个请求,也就会导致各个结点之间网络通信会非常频繁,假如网络情况不满足传输需求,重发数据在内存中不断堆积,就可能会导致内存溢出。(集群同步导致内存溢出) -
直接内存溢出,Java虚拟机也会对直接内存进行回收,但是直接内存即将溢出时,并不会通知Java虚拟机触发GC,只能等待老年代满了以后触发Full GC 帮它顺便清理,如果有需要可以catch 块里面通过System.gc() 进行主动触发回收。(堆外内存导致的溢出错误) -
在应用程序中应该避免经常使用“fork”这类系统调用,我们知道“fork”会复制一个和当前环境变量相同的进程,会浪费很多处理器和内存的开销。(外部命令导致系统缓慢) -
当双方服务处理速度完全不对等(比如2分钟),若一个服务受到请求需要告知另一个服务,时间越长就累计了越多web服务没有调用完成,导致在等待的线程和Socket连接越来越多,最终超过虚拟机的承受能力后导致虚拟机进程崩溃。可以通过生产者/消费者模式的消息队列来解决。(服务器虚拟机进程崩溃) -
大量数据使用不恰当的数据结构,比如数以百万计的HashMap<Long,Long> ,一个long型要占8字节,封装成Long对象后,还要加上8字节的Mark Word 、8字节的Klass 指针; 然后两个Long 对象还要封装成Entry 对象,还要加上16字节对象头、8字节next 字段和4字节的int 型hash 字段; 为了对齐必须加4字节的空白填充; 最后还有HashMap对它的引用8字节。这样实际损耗就是88字节,16/88=18% ,有效数据占比太低了。(不恰当数据结构导致内存占用过大) -
GUI桌面程序,最小化时内存明显减小,而虚拟内存没有变化,也就是它的工作内存被交换到磁盘的页面文件之中了,这样发生垃圾收集器时就有可能因为回复页面文件的操作导致不正常的垃圾收集停顿。可以设置启动文件中相关参数等于true,保证程序在恢复最小化时能够立即响应。(由Windows虚拟内存导致的长时间停顿)
后面还会陆陆续续更新这系列的读书笔记,期待您的关注~~
|