深刻理解JVM——JVM性能调优实战

如何在高性能服务器上进行JVM调优?后端

为了充分利用高性能服务器的硬件资源,有两种JVM调优方案,它们都有各自的优缺点,须要根据具体的状况进行选择。服务器

一、采用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. 尽可能缩短大对象的有效时间 

对象用完后尽快让它失效,好让垃圾收集器尽快将他回收,避免因在新生代呆的时间过长而进入老年代。

关注Java后端进阶公众号,给你带来更多有用、有趣的优质文章!

相关文章
相关标签/搜索