如何在高性能服务器上进行 JVM 调优?
为了充分利用高性能服务器的硬件资源, 有两种 JVM 调优方案, 它们都有各自的优缺点, 需要根据具体的情况进行选择.
小编这里有一份 Java 学习资料, 加我的个人 QQ:2397938575, 就能免费领取这份资料, 我会私发给你, 长期真实有效! 以下为部分 Java 基础资料截图!
采用 64 位操作系统, 并为 JVM 分配大内存
我们知道, 如果 JVM 中堆内存太小, 那么就会频繁地发生垃圾回收, 而垃圾回收都会伴随不同程度的程序停顿, 因此, 如果扩大堆内存的话可以减少垃圾回收的频率, 从而避免程序的停顿.
因此, 人们自然而然想到扩大内存容量. 而 32 位操作系统理论上最大只支持 4G 内存, 64 位操作系统最大能支持 128G 内存, 因此我们可以使用 64 位操作系统, 并使用 64 位 JVM, 并为 JVM 分配更大的堆内存. 但问题也随之而来.
堆内存变大后, 虽然垃圾收集的频率减少了, 但每次垃圾回收的时间变长. 如果对内存为 14G, 那么每次 Full GC 将长达数十秒. 如果 Full GC 频繁发生, 那么对于一个网站来说是无法忍受的.
因此, 对于使用大内存的程序来说, 一定要减少 Full GC 的频率, 如果每天只有一两次 Full GC, 而且发生在半夜, 那完全可以接受.
要减少 Full GC 的频率, 就要尽量避免太多对象进入老年代, 可以有以下做法:
确保对象都是 "朝生夕死" 的
一个对象使用完后应尽快让他失效, 然后尽快在新生代中被 Minor GC 回收掉, 尽量避免对象在新生代中停留太长时间.
提高大对象直接进入老年代的门槛
通过设置参数 - XX:PretrnureSizeThreshold 来提高大对象的门槛, 尽量让对象都先进入新生代, 然后尽快被 Minor GC 回收掉, 而不要直接进入老年代.
注意: 使用 64 位 JDK 的注意点
64 位 JDK 支持更大的堆内存, 但更大的堆内存会导致一次垃圾回收时间过长.
现阶段, 64 位 JDK 的性能普遍比 32 位 JDK 低.
堆内存过大无法在发生内存溢出时生成内存快照
若将堆内存设为 10G, 那么当堆内存溢出时就要生成 10G 的大文件, 这基本上是不可能的.
相同程序, 64 位 JDK 要比 32 位 JDK 消耗更大的内存
2. 使用 32 位 JVM 集群
针对于 64 位 JDK 种种弊端, 我们更多选择使用 32 位 JDK 集群来充分利用高性能机器的硬件资源.
如何实现?
在一台服务器上运行多个服务器程序, 这些程序都运行在 32 位的 JDK 上. 然后再运行个服务器作为反向代理服务器, 由它来实现负载均衡.
由于 32 位 JDK 最多支持 2G 内存, 因此每个虚拟结点的堆内存可以分配 1.6G, 一共运行 10 个虚拟结点的话, 这台物理服务器可以拥有 16G 的堆内存.
有啥弊端?
多个虚拟节点竞争共享资源时容易出现问题
如多个虚拟节点共同竞争 IO 操作, 很可能会引起 IO 异常.
很难高效地使用资源池
如果每个虚拟节点使用各自的资源池, 那么无法实现各个资源池的负载均衡. 如果使用集中式资源池, 那么又存在竞争的问题.
每个虚拟节点最大内存为 2G
别忘了直接内存也可能导致内存溢出!
问题描述
有个小型网站, 使用 32 位 JDK, 堆 1.6G. 运行期间发现老是出现内存溢出. 为了判断是否是堆内存溢出, 在程序运行前添加参数:-XX:+HeapDumpOnOutOfMemeryError(添加这个参数后当堆内存溢出时就会输出异常日至). 但当再次发生内存溢出时, 没有生成相关异常日志. 从而可以判定, 不是堆内存发生溢出.
问题分析
我们可以发现, 在 32 位 JDK 中, 将 1.6G 分配给了堆, 还有一部分分配给了 JVM 的其它内存, 只有少于 0.4G 的内存为非 JVM 内存. 我们知道, 如果使用了 NIO, 那么 JVM 会在 JVM 内存之外分配内存空间, 这部分内存也叫 "直接内存". 因此, 如果程序中使用了 NIO, 那么就要小心 "直接内存" 不足时发生内存溢出异常了!
直接内存的垃圾回收过程
直接内存虽然不是 JVM 内存空间, 但它的垃圾回收也有 JVM 负责. 直接内存的垃圾回收发生在 Full GC 时, 只有当老年代内存满时, 垃圾收集器才会顺便收集一下直接内存中的垃圾.
如果直接内存已满, 但老年代没满, 这时直接内存先是抛出异常, 相应的 catch 块中调用 System.gc(). 由于 System.gc() 只是建议 JVM 回收, JVM 可能不马上回收内存, 那么这时直接内存就抛出内存溢出异常, 使得程序终止.
JVM 崩溃的原因
当内存溢出时, JVM 仅仅会终止当前运行的程序, 那么什么时候 JVM 会崩溃呢?
什么是异步请求?
我们知道, web 服务器和客户端采用 HTTP 通信, 而 HTTP 底层采用 TCP 通信. 异步通信就是当客户端向服务器发送一个 HTTP 请求后, 将这个请求的 TCP 连接委托给其它线程, 然后它转而做别的事, 那条被委托的线程保持 TCP 连接, 等待服务器的回信. 当收到服务器回信后, 再将收到的数据转交给刚才的线程. 这个过程就是异步通信过程.
异步请求如何造成 JVM 崩溃?
如果一个 Web 应用使用了较多的异步请求 (Ajax), 每次主线程发送完请求后都将 TCP 连接交给一条新的线程去等待服务器回信, 那么如果网络不流畅时, 这些受委托的线程迟迟等不到服务器的回信, 因此保持着 TCP 连接. 当 TCP 连接过多时, 超过 JVM 的承受能力, JVM 就发生崩溃.
如何处理大对象?
大对象对于 JVM 来说是个噩耗. 如果对象过大, 当前新生代的剩余空间装不下它, 那么就需要使用分配担保机制, 将当前新生代的对象都复制到老年代中, 给大对象腾出空间. 分配担保涉及到大量的复制, 因此效率很低.
那么, 如果将大对象直接放入老年代, 虽然避免了分配担保过程, 但该对象只有当 Full GC 时才能被回收, 而 Full GC 的代价是高昂的. 如果大对象过多时, 老年代很快就装满了, 这时就需要进行 Full GC, 如果 Full GC 频率过高, 程序就会变得很卡.
因此, 对于大对象, 有如下几种处理方法:
1. 在写程序的时候尽量避免大对象
从源头降低大对象的出现, 尽量选择空间利用率较高的数据结构存储.
2. 尽量缩短大对象的有效时间
对象用完后尽快让它失效, 好让垃圾收集器尽快将他回收, 避免因在新生代呆的时间过长而进入老年代.
来源: http://www.jianshu.com/p/455c8cd2f887