<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Java內(nèi)存溢出的解決思路

          共 25400字,需瀏覽 51分鐘

           ·

          2021-03-18 09:40

          點(diǎn)擊上方藍(lán)色字體,選擇“標(biāo)星公眾號(hào)”

          優(yōu)質(zhì)文章,第一時(shí)間送達(dá)

            作者 |  積淀

          來(lái)源 |  urlify.cn/2YZney

          內(nèi)存溢出是指應(yīng)用系統(tǒng)中存在無(wú)法回收的內(nèi)存或使用的內(nèi)存過(guò)多,最終使得程序運(yùn)行要用到的內(nèi)存大于虛擬機(jī)能提供的最大內(nèi)存。

             引起內(nèi)存溢出的原因有很多種,常見的有以下幾種:
            1.內(nèi)存中加載的數(shù)據(jù)量過(guò)于龐大,如一次從數(shù)據(jù)庫(kù)取出過(guò)多數(shù)據(jù);
            2.集合類中有對(duì)對(duì)象的引用,使用完后未清空,使得JVM不能回收;
            3.代碼中存在死循環(huán)或循環(huán)產(chǎn)生過(guò)多重復(fù)的對(duì)象實(shí)體;
            4.使用的第三方軟件中的BUG;
            5.啟動(dòng)參數(shù)內(nèi)存值設(shè)定的過(guò)小;

          內(nèi)存溢出的解決方案:
                第一步,修改JVM啟動(dòng)參數(shù),直接增加內(nèi)存。(-Xms,-Xmx參數(shù)一定不要忘記加。)

            第二步,檢查錯(cuò)誤日志,查看“OutOfMemory”錯(cuò)誤前是否有其它異常或錯(cuò)誤。

            第三步,對(duì)代碼進(jìn)行走查和分析,找出可能發(fā)生內(nèi)存溢出的位置。

          重點(diǎn)排查以下幾點(diǎn):
            1.檢查對(duì)數(shù)據(jù)庫(kù)查詢中,是否有一次獲得全部數(shù)據(jù)的查詢。一般來(lái)說(shuō),如果一次取十萬(wàn)條記錄到內(nèi)存,就可能引起內(nèi)存溢出。這個(gè)問(wèn)題比較隱蔽,在上線前,數(shù)據(jù)庫(kù)中數(shù)據(jù)較少,不容易出問(wèn)題,上線后,數(shù)據(jù)庫(kù)中數(shù)據(jù)多了,一次查詢就有可能引起內(nèi)存溢出。因此對(duì)于數(shù)據(jù)庫(kù)查詢盡量采用分頁(yè)的方式查詢。
            2.檢查代碼中是否有死循環(huán)或遞歸調(diào)用。 

            3.檢查是否有大循環(huán)重復(fù)產(chǎn)生新對(duì)象實(shí)體。 

            4.檢查對(duì)數(shù)據(jù)庫(kù)查詢中,是否有一次獲得全部數(shù)據(jù)的查詢。一般來(lái)說(shuō),如果一次取十萬(wàn)條記錄到內(nèi)存,就可能引起內(nèi)存溢出。這個(gè)問(wèn)題比較隱蔽,在上線前,數(shù)據(jù)庫(kù)中   數(shù)據(jù)較少,不容易出問(wèn)題,上線后,數(shù)據(jù)庫(kù)中數(shù)據(jù)多了,一次查詢就有可能引起內(nèi)存溢出。因此對(duì)于數(shù)據(jù)庫(kù)查詢盡量采用分頁(yè)的方式查詢。 

            5.檢查L(zhǎng)ist、MAP等集合對(duì)象是否有使用完后,未清除的問(wèn)題。List、MAP等集合對(duì)象會(huì)始終存有對(duì)對(duì)象的引用,使得這些對(duì)象不能被GC回收。

            第四步,使用內(nèi)存查看工具動(dòng)態(tài)查看內(nèi)存使用情況

          從內(nèi)存溢出看Java 環(huán)境中的內(nèi)存結(jié)構(gòu)

            作為有個(gè)java程序員,我想大家對(duì)下面出現(xiàn)的這幾個(gè)場(chǎng)景并不陌生,倍感親切,深惡痛絕,抓心撓肝,一定會(huì)回過(guò)頭來(lái)問(wèn)為什么為什么為什么會(huì)這樣,嘿嘿,讓我們看一下我們?nèi)粘T陂_發(fā)過(guò)程中接觸內(nèi)存溢出的異常:  

          Exception in thread "main" [Full GCjava.lang.OutOfMemoryError: Java heap space 
              at java.util.Arrays.copyOf(Unknown Source)
              at java.util.Arrays.copyOf(Unknown Source)
              at java.util.ArrayList.grow(Unknown Source)
              at java.util.ArrayList.ensureExplicitCapacity(Unknown Source)
              at java.util.ArrayList.ensureCapacityInternal(Unknown Source)
              at java.util.ArrayList.add(Unknown Source)
              at oom.HeapOOM.main(HeapOOM.java:21)
          Exception in thread "main" java.lang.StackOverflowError
              at java.nio.CharBuffer.arrayOffset(Unknown Source)
              at sun.nio.cs.UTF_8.updatePositions(Unknown Source)
              at sun.nio.cs.UTF_8$Encoder.encodeArrayLoop(Unknown Source)
              at sun.nio.cs.UTF_8$Encoder.encodeLoop(Unknown Source)
              at java.nio.charset.CharsetEncoder.encode(Unknown Source)
              at sun.nio.cs.StreamEncoder.implWrite(Unknown Source)
              at sun.nio.cs.StreamEncoder.write(Unknown Source)
              at java.io.OutputStreamWriter.write(Unknown Source)
              at java.io.BufferedWriter.flushBuffer(Unknown Source)
              at java.io.PrintStream.write(Unknown Source)
              at java.io.PrintStream.print(Unknown Source)
              at java.io.PrintStream.println(Unknown Source)
          java.lang.OutOfMemoryError: PermGen space 
          Exception in thread "main" java.lang.OutOfMemoryError
              at sun.misc.Unsafe.allocateMemory(Native Method)
              at oom.DirectMemoryOOM.main(DirectMemoryOOM.java:23)

            是不是有大家很熟悉的,遇見這樣的問(wèn)題解決起來(lái)可能不簡(jiǎn)單,但是如果現(xiàn)在讓大家寫個(gè)程序,故意讓程序出現(xiàn)下面的異常,估計(jì)能很快寫出來(lái)的也不是很多,這就要求開發(fā)人員對(duì)于java內(nèi)存區(qū)域以及jvm規(guī)范有比較深的了解。

            既然拋出了異常,首先我們肯定這些都是內(nèi)存異常,只是內(nèi)存異常中的不同種類,我們就試著了解一下為什么會(huì)出現(xiàn)以上的異常,可以看出有兩種異常狀況::

            OutOfMemoryError

            StackOverflowError

            其中OutOfMemoryError是在程序無(wú)法申請(qǐng)到足夠的內(nèi)存的時(shí)候拋出的異常,StackOverflowError是線程申請(qǐng)的棧深度大于虛擬機(jī)所允許的深度所拋出的異常。可是從上面列出的異常內(nèi)容也可以看出在OutOfMemoryError類型的一場(chǎng)中也存在這很多異常的可能。這是為什么?以為是在內(nèi)存的不同結(jié)構(gòu)中出現(xiàn)的錯(cuò)誤,所以拋出的異常也就形形色色,說(shuō)道這我們不得不介紹一下java的內(nèi)存結(jié)構(gòu),請(qǐng)看下圖(從網(wǎng)上摘的):

            

            在運(yùn)行時(shí)的內(nèi)存區(qū)域有5個(gè)部分,Method Area(方法區(qū)),Java stack(java 虛擬機(jī)棧),Native MethodStack(本地方法棧),Heap(堆),Program Counter Regster(程序計(jì)數(shù)器)。從圖中看出方法區(qū)和堆用黃色標(biāo)記,和其他三個(gè)區(qū)域的不同點(diǎn)就是,方法區(qū)和堆是線程共享的,所有的運(yùn)行在jvm上的程序都能訪問(wèn)這兩個(gè)區(qū)域,堆,方法區(qū)和虛擬機(jī)的生命周期一樣,隨著虛擬機(jī)的啟動(dòng)而存在,而棧和程序計(jì)數(shù)器是依賴用戶線程的啟動(dòng)和結(jié)束而建立和銷毀。

            Program Counter Regster(程序計(jì)數(shù)器):每一個(gè)用戶線程對(duì)應(yīng)一個(gè)程序計(jì)數(shù)器,用來(lái)指示當(dāng)前線程所執(zhí)行字節(jié)碼的行號(hào)。由程序計(jì)數(shù)器給文字碼解釋器提供嚇一條要執(zhí)行的字節(jié)碼的的位置。根據(jù)jvm規(guī)范,在這個(gè)區(qū)域中不會(huì)拋出OutOfMemoryError的內(nèi)存異常。

            Java stack(java 虛擬機(jī)棧):這個(gè)區(qū)域是最容易出現(xiàn)內(nèi)存異常的區(qū)域,每一個(gè)線程對(duì)應(yīng)生成一個(gè)線程棧,線程每執(zhí)行一個(gè)方法的時(shí)候,都會(huì)創(chuàng)建一個(gè)棧幀,用來(lái)存放方法的局部變量表,操作樹棧,動(dòng)態(tài)連接,方法入口,這和C#是不一樣的,在C#CLR中沒有棧幀的概念,都是在線程棧中通過(guò)壓棧和出棧的方式進(jìn)行數(shù)據(jù)的保存。jvm規(guī)范對(duì)這個(gè)區(qū)域定義了兩種內(nèi)存異常,OutOfMemoryError,StackOverflowError。

            Native MethodStack(本地方法棧):和虛擬機(jī)棧一樣,不同的是處理的對(duì)象不一樣,虛擬機(jī)棧處理java的字節(jié)碼,而本地棧則是處理的Native方法。其他方面一致。

            Heap(堆):前面說(shuō)了堆是所有線程都能訪問(wèn)的,隨著虛擬機(jī)的啟動(dòng)而存在,這塊區(qū)域很大,因?yàn)樗械木€程都在這個(gè)區(qū)域保存實(shí)例化的對(duì)象,因?yàn)槊恳粋€(gè)類型中,每個(gè)接口實(shí)現(xiàn)類需要的內(nèi)存不一樣,一個(gè)方法內(nèi)的多個(gè)分支需要的內(nèi)存也不盡相同,我們只有在運(yùn)行的時(shí)候才能知道要?jiǎng)?chuàng)建多少對(duì)象,需要分配多大的地址空間。GC關(guān)注的正是這樣的部分內(nèi)容,所以很多時(shí)候也將堆稱為GC堆。堆中肯定不會(huì)拋出StackOverflowError類型的異常,所以只有OutOfMemoryError相關(guān)類型的異常。

            Method Area(方法區(qū)):用于存放已被虛擬機(jī)加載的類信息,常量,靜態(tài)方法,即使編譯后的代碼。同樣只能拋出OutOfMemoryError相關(guān)類型的異常。

            介紹完jvm內(nèi)存結(jié)構(gòu)中的常見區(qū)域,下面該是和我們主題呼應(yīng)的時(shí)候了,在什么情況下,在那個(gè)區(qū)域,如何才能復(fù)現(xiàn)開始提到的異常信息?從第一個(gè)開始,異常信息的內(nèi)容為:  

          Exception in thread "main" [Full GCjava.lang.OutOfMemoryError: Java heap space 
              at java.util.Arrays.copyOf(Unknown Source)
              at java.util.Arrays.copyOf(Unknown Source)
              at java.util.ArrayList.grow(Unknown Source)
              at java.util.ArrayList.ensureExplicitCapacity(Unknown Source)
              at java.util.ArrayList.ensureCapacityInternal(Unknown Source)
              at java.util.ArrayList.add(Unknown Source)
              at oom.HeapOOM.main(HeapOOM.java:21)

            可想而知是在堆中出現(xiàn)的問(wèn)題,如何重現(xiàn),由于是在堆中出現(xiàn)這個(gè)異常,那么就要處理好,不能被垃圾回收器給回收了,設(shè)置一下jvm中堆的最大值(這樣才能夠更快的出現(xiàn)錯(cuò)誤),設(shè)置jvm值的方法是通過(guò)-Xms(堆的最小值),-Xmx(堆的最大值)。下面動(dòng)手試一下:

          package oom;

          import java.util.ArrayList;
          import java.util.List;

          import testbean.UserBean;

          /*** 
           * 
           * @author Think
           * 
           */
          public class HeapOOM {
              static class OOMObject {
              }

              public static void main(String[] args) {
                  List<UserBean> users = new ArrayList<UserBean>();
                  while (true) {
                      users.add(new UserBean());
                  }
              }
          }

          UserBean對(duì)象定義如下:

          package testbean;

          public class UserBean {
              String name;
              int age;

              public String getName() {
                  return name;
              }

              public void setName(String name) {
                  this.name = name;
              }

              public int getAge() {
                  return age;
              }

              public void setAge(int age) {
                  this.age = age;
              }

              public UserBean() {
                  super();
              }

          }

          然后在運(yùn)行的時(shí)候設(shè)置jvm參數(shù),如下:

            

           運(yùn)行一下看看結(jié)果:  

          Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
              at java.util.Arrays.copyOf(Unknown Source)
              at java.util.Arrays.copyOf(Unknown Source)
              at java.util.ArrayList.grow(Unknown Source)
              at java.util.ArrayList.ensureExplicitCapacity(Unknown Source)
              at java.util.ArrayList.ensureCapacityInternal(Unknown Source)
              at java.util.ArrayList.add(Unknown Source)
              at oom.HeapOOM.main(HeapOOM.java:21)

          成功在java虛擬機(jī)堆中溢出。下面看第二個(gè)關(guān)于棧的異常,內(nèi)容如下:  

          Exception in thread "main" java.lang.StackOverflowError
              at java.nio.CharBuffer.arrayOffset(Unknown Source)
              at sun.nio.cs.UTF_8.updatePositions(Unknown Source)
              at sun.nio.cs.UTF_8$Encoder.encodeArrayLoop(Unknown Source)
              at sun.nio.cs.UTF_8$Encoder.encodeLoop(Unknown Source)
              at java.nio.charset.CharsetEncoder.encode(Unknown Source)
              at sun.nio.cs.StreamEncoder.implWrite(Unknown Source)
              at sun.nio.cs.StreamEncoder.write(Unknown Source)
              at java.io.OutputStreamWriter.write(Unknown Source)
              at java.io.BufferedWriter.flushBuffer(Unknown Source)
              at java.io.PrintStream.write(Unknown Source)
              at java.io.PrintStream.print(Unknown Source)
              at java.io.PrintStream.println(Unknown Source)

           因?yàn)槭桥c棧相關(guān)的話,那么我們?cè)谥噩F(xiàn)異常的時(shí)候就要相應(yīng)的將棧內(nèi)存容量設(shè)置的小一些,設(shè)置棧大小的方法是設(shè)置-Xss參數(shù),看如下實(shí)現(xiàn):  

          package oom;

          import testbean.Recursion;

          /*** 
           * 
           * 
           * 
           */
          public class VMStackOOM { 

              public static void main(String[] args) {
                  Recursion recursion = new Recursion();
                  try {
                      recursion.recursionself();
                  } catch (Throwable e) {
                      System.out.println("current value :" + recursion.currentValue);
                      throw e;
                  }
              }

          }

           Recursion的定義如下:  

          package testbean;

          public class Recursion {
              public int currentValue = 0;

              public void recursionself() {
                  currentValue += 1;
                  recursionself();
              }
          }

            運(yùn)行時(shí)jvm參數(shù)的設(shè)置如下:

            

            運(yùn)行結(jié)果如下:  

          current value :999
          Exception in thread "main" java.lang.StackOverflowError
              at testbean.Recursion.recursionself(Recursion.java:7)
              at testbean.Recursion.recursionself(Recursion.java:8)
              at testbean.Recursion.recursionself(Recursion.java:8)
              at testbean.Recursion.recursionself(Recursion.java:8)
              at testbean.Recursion.recursionself(Recursion.java:8)
              at testbean.Recursion.recursionself(Recursion.java:8)

            第三個(gè)異常是關(guān)于perm的異常內(nèi)容,我們需要的是設(shè)置方法區(qū)的大小,實(shí)現(xiàn)方式是通過(guò)設(shè)置-XX:PermSize和-XX:MaxPermSize參數(shù),內(nèi)容如下:  

          java.lang.OutOfMemoryError: PermGen space

            如果程序加載的類過(guò)多,例如tomcatweb容器,就會(huì)出現(xiàn)PermGen space異常,如果我將HeapOOM類的運(yùn)行時(shí)的XX:PermSize設(shè)置為2M,如下:

            

            那么程序就不會(huì)執(zhí)行成功,執(zhí)行的時(shí)候出現(xiàn)如下異常:  

          Error occurred during initialization of VM
          java.lang.OutOfMemoryError: PermGen space
              at sun.misc.Launcher$ExtClassLoader.getExtClassLoader(Unknown Source)
              at sun.misc.Launcher.<init>(Unknown Source)
              at sun.misc.Launcher.<clinit>(Unknown Source)
              at java.lang.ClassLoader.initSystemClassLoader(Unknown Source)
              at java.lang.ClassLoader.getSystemClassLoader(Unknown Source)

            第四個(gè)異常估計(jì)遇到的人就不多了,是DirectMemory內(nèi)存相關(guān)的,內(nèi)容如下:  

          Exception in thread "main" java.lang.OutOfMemoryError
              at sun.misc.Unsafe.allocateMemory(Native Method)
              at oom.DirectMemoryOOM.main(DirectMemoryOOM.java:23)

            DirectMemoruSize可以通過(guò)設(shè)置 -XX:MaxDirectMemorySize參數(shù)指定容量大小,如果不指定的話,那么就跟堆的最大值一致,下面是代碼實(shí)現(xiàn):  

          package oom;

          import java.lang.reflect.Field;

          import sun.misc.Unsafe;

          /*** 
           * 
           * @author Think
           * 
           */
          public class DirectMemoryOOM {

              private static final int _1MB = 1024 * 1024;

              public static void main(String[] args) throws IllegalArgumentException,
                      IllegalAccessException {
                  Field unsafeField = Unsafe.class.getDeclaredFields()[0];
                  unsafeField.setAccessible(true);
                  Unsafe unsafe = (Unsafe) unsafeField.get(null);
                  while (true) {
                      unsafe.allocateMemory(_1MB);
                  }

              }
          }

            運(yùn)行時(shí)設(shè)置的jvm參數(shù)如下:

            

            很容易就復(fù)線了異常信息:  

          Exception in thread "main" java.lang.OutOfMemoryError
              at sun.misc.Unsafe.allocateMemory(Native Method)
              at oom.DirectMemoryOOM.main(DirectMemoryOOM.java:23)

          關(guān)于JAVA中內(nèi)存溢出的解決辦法

          J2ee應(yīng)用系統(tǒng)是運(yùn)行在J2EE應(yīng)用服務(wù)器上的,而j2ee應(yīng)用服務(wù)器又是運(yùn)行在JVM上的,

          生成環(huán)境中JVM參數(shù)的優(yōu)化和設(shè)置對(duì)于J2EE應(yīng)用系統(tǒng)性能有著決定性的作用。要優(yōu)化系統(tǒng),則需要對(duì)JVM參數(shù)進(jìn)行合理的設(shè)置,所以我們需要了解究竟在什么地方進(jìn)行設(shè)置、有哪些參數(shù)以及各參數(shù)的意義分別是什么,并且我們還得了解JVM的內(nèi)存管理機(jī)制究竟是個(gè)什么玩意兒?其實(shí)我們?cè)诰W(wǎng)上搜索引擎上,一搜就有可以獲取到一大把相關(guān)信息,關(guān)鍵是我們?nèi)绾紊钊氲睦斫馑鼈儭D敲聪旅嫖覀兙秃?jiǎn)單的介紹一下究竟什么是JVM的內(nèi)存管理機(jī)制吧~!  

          JVM的早期版本并沒有進(jìn)行分區(qū)管理;這樣的后果是JVM進(jìn)行垃圾回收時(shí),不得不掃描JVM所管理的整片內(nèi)存,所以搜集垃圾是很耗費(fèi)資源的事情,也是早起JAVA程序的性能低下的主要原因。隨著JVM的發(fā)展,JVM引進(jìn)了分區(qū)管理的機(jī)制。 

          JVM所管理的所有內(nèi)存資源分為2個(gè)大的部分。永久存儲(chǔ)區(qū)(Permanent Space) 和堆空間(The Heap Space)。其中對(duì)空間又分為新生區(qū)和養(yǎng)老區(qū),新生區(qū)又分為伊甸園,幸存者0區(qū)、幸存1區(qū)。如下圖:

           

           

           

          關(guān)于個(gè)分區(qū)的用途,大家可以參考其他相關(guān)文檔。本教程所要處理的問(wèn)題是如何解決內(nèi)存溢出的問(wèn)題。接下來(lái)以tomcat服務(wù)器為例:

          我們首先得找到內(nèi)存管理所要設(shè)置的參數(shù)在哪個(gè)文件:<CATALINA_HOME>/bin/catalina.bat。

          需要添加一行代碼:

          JAVA_OPTS="-Xms512m-Xmx512m -Xss1024K -XX:PermSize=256m -XX:MaxPermSize=256m"

          下面分別對(duì)各參數(shù)進(jìn)行介紹和解釋:

          JVM 相關(guān)參數(shù):

          參數(shù)名參數(shù)說(shuō)明

          -server 啟用能夠執(zhí)行優(yōu)化的編譯器, 顯著提高服務(wù)器的性能,但使用能夠執(zhí)行優(yōu)化的編譯器時(shí),服務(wù)器的預(yù)備時(shí)間將會(huì)較長(zhǎng)。生產(chǎn)環(huán)境的服務(wù)器強(qiáng)烈推薦設(shè)置此參數(shù)。

          -Xss 單個(gè)線程堆棧大小值;JDK5.0 以后每個(gè)線程堆棧大小為1M,以前每個(gè)線程堆棧大小為256K。在相同物理內(nèi)存下,減小這個(gè)值能生成更多的線程。但是操作系統(tǒng)對(duì)一個(gè)進(jìn)程內(nèi)的線程數(shù)還是有限制的,不能無(wú)限生成,經(jīng)驗(yàn)值在3000~5000左右。

          -XX:+UseParNewGC 可用來(lái)設(shè)置年輕代為并發(fā)收集【多CPU】,如果你的服務(wù)器有多個(gè)CPU,你可以開啟此參數(shù);開啟此參數(shù),多個(gè)CPU 可并發(fā)進(jìn)行垃圾回收,可提高垃圾回收的速度。此參數(shù)和+UseParallelGC,-XX:ParallelGCThreads搭配使用。

          +UseParallelGC 選擇垃圾收集器為并行收集器。此配置僅對(duì)年輕代有效。即上述配置下,年輕代使用并發(fā)收集,而年老代仍舊使用串行收集。可提高系統(tǒng)的吞吐量。

          -XX:ParallelGCThreads 年輕代并行垃圾收集的前提下(對(duì)并發(fā)也有效果)的線程數(shù),增加并行度,即:同時(shí)多少個(gè)線程一起進(jìn)行垃圾回收。此值最好配置與處理器數(shù)目相等。永久存儲(chǔ)區(qū)相關(guān)參數(shù):參數(shù)名參數(shù)說(shuō)明

          -Xnoclassgc 每次永久存儲(chǔ)區(qū)滿了后一般GC 算法在做擴(kuò)展分配內(nèi)存前都會(huì)觸發(fā)一次FULL GC,除非設(shè)置了-Xnoclassgc.

          -XX:PermSize 應(yīng)用服務(wù)器啟動(dòng)時(shí),永久存儲(chǔ)區(qū)的初始內(nèi)存大

          -XX:MaxPermSize 應(yīng)用運(yùn)行中,永久存儲(chǔ)區(qū)的極限值。為了不消耗擴(kuò)大JVM 永久存儲(chǔ)區(qū)分配的開銷,將此參數(shù)和-XX:PermSize這個(gè)兩個(gè)值設(shè)為相等。堆空間相關(guān)參數(shù)參數(shù)名參數(shù)說(shuō)明

          -Xms 啟動(dòng)應(yīng)用時(shí),JVM 堆空間的初始大小值。

          -Xmx 應(yīng)用運(yùn)行中,JVM 堆空間的極限值。為了不消耗擴(kuò)大JVM 堆控件分配的開銷,將此參數(shù)和-Xms 這個(gè)兩個(gè)值設(shè)為相等,考慮到需要開線程,講此值設(shè)置為總內(nèi)存的80%.

          -Xmn 此參數(shù)硬性規(guī)定堆空間的新生代空間大小,推薦設(shè)為堆空間大小的1/4。

          上面所列的JVM 參數(shù)關(guān)系到系統(tǒng)的性能,而其中-XX:PermSize,

          -XX:MaxPermSize,-Xms,-Xmx 和-Xmn 這5 個(gè)參數(shù)更是直接關(guān)系到系統(tǒng)的性能,系統(tǒng)是否會(huì)出現(xiàn)內(nèi)存溢出。

          -XX:PermSize 和-XX:MaxPermSize 分別設(shè)置應(yīng)用服務(wù)器啟動(dòng)時(shí),永久存儲(chǔ)區(qū)的初始大小和極限大小;在生成環(huán)境中強(qiáng)烈推薦將這個(gè)兩個(gè)值設(shè)置為相同的值,以避免分配永久存儲(chǔ)區(qū)的開銷,具體的值可取系統(tǒng)“疲勞測(cè)試”獲取到的永久存儲(chǔ)區(qū)的極限值;如果不進(jìn)行設(shè)置-XX:MaxPermSize 默認(rèn)值為64M,一般來(lái)說(shuō)系統(tǒng)的類定義文件大小都會(huì)超過(guò)這個(gè)默認(rèn)值。

          -Xms 和-Xmx 分別是服務(wù)器啟動(dòng)時(shí),堆空間的初始大小和極限值。-Xms的默認(rèn)值是物理內(nèi)存的1/64 但小于1G,-Xmx 的默認(rèn)值是物理內(nèi)存的1/4 但小于1G.在生產(chǎn)環(huán)境中這些默認(rèn)值是肯定不能滿足我們的需要的。也就是你的服務(wù)器有8g 的內(nèi)存,不對(duì)JVM 參數(shù)進(jìn)行設(shè)置優(yōu)化,應(yīng)用服務(wù)器啟動(dòng)時(shí)還是按默認(rèn)值來(lái)分配和約束JVM 對(duì)內(nèi)存資源的使用,不會(huì)充分的利用所有的內(nèi)存資源。

           

          結(jié)論:“永久存儲(chǔ)區(qū)溢出(java.lang.OutOfMemoryError:Java Permanent Space)”乃是永久存儲(chǔ)區(qū)設(shè)置太小,不能滿足系統(tǒng)需要的大小,此時(shí)只需要調(diào)整-XX:PermSize 和-XX:MaxPermSize 這兩個(gè)參數(shù)即可。“JVM 堆空間溢出(java.lang.OutOfMemoryError: Java heap space)”錯(cuò)誤是JVM 堆空間不足,此時(shí)只需要調(diào)整-Xms 和-Xmx 這兩個(gè)參數(shù)即可。

           

          到此我們知道了,當(dāng)系統(tǒng)出現(xiàn)內(nèi)存溢出時(shí),是哪些參數(shù)設(shè)置不合理需要調(diào)整。但我們?cè)趺粗婪?wù)器啟動(dòng)時(shí),到底JVM 內(nèi)存相關(guān)參數(shù)的值是多少呢?

          這個(gè)問(wèn)題其實(shí)Sun公司早已經(jīng)意料到了,所以給我們開發(fā)了內(nèi)存使用監(jiān)控工具jvmstat.

          大家可以到ORACLE官網(wǎng)進(jìn)行下載。用它可以很方便的看到我們的服務(wù)器內(nèi)存使用情況。

          將下載的jvmstat包解壓到如“C:\ProgramFiles\Java\jvmstat”(這是我本地java路徑,大家可以根據(jù)自己所安裝的java環(huán)境的路徑進(jìn)行解壓)。啟動(dòng)完之后我們就可以使用visualgc命令了,cmd進(jìn)入命令符窗口,輸入tasklist(windows下查看進(jìn)程任務(wù)PID)查找到你要檢測(cè)進(jìn)程PID.然后直接輸入visuglgc PID 就會(huì)彈出三個(gè)可見視圖。

            

          內(nèi)存溢出與數(shù)據(jù)庫(kù)鎖表的問(wèn)題,可以說(shuō)是開發(fā)人員的噩夢(mèng),一般的程序異常,總是可以知道在什么時(shí)候或是在什么操作步驟上出現(xiàn)了異常,而且根據(jù)堆棧信息也很容易定位到程序中是某處出現(xiàn)了問(wèn)題。內(nèi)存溢出與鎖表則不然,一般現(xiàn)象是操作一般時(shí)間后系統(tǒng)越來(lái)越慢,直到死機(jī),但并不能明確是在什么操作上出現(xiàn)的,發(fā)生的時(shí)間點(diǎn)也沒有規(guī)律,查看日志或查看數(shù)據(jù)庫(kù)也不能定位出問(wèn)題的代碼。

          更嚴(yán)重的是內(nèi)存溢出與數(shù)據(jù)庫(kù)鎖表在系統(tǒng)開發(fā)和單元測(cè)試階段并不容易被發(fā)現(xiàn),當(dāng)系統(tǒng)正式上線一般時(shí)間后,操作的并發(fā)量上來(lái)了,數(shù)據(jù)也積累了一些,系統(tǒng)就容易出現(xiàn)內(nèi)存溢出或是鎖表的現(xiàn)象,而此時(shí)系統(tǒng)又不能隨意停機(jī)或重啟,為修正BUG帶來(lái)很大的困難。

          本文以筆者開發(fā)和支持的多個(gè)項(xiàng)目為例,與大家分享在開發(fā)過(guò)程中遇到的Java內(nèi)存溢出和數(shù)據(jù)庫(kù)鎖表的檢測(cè)和處理解決過(guò)程。

          2.內(nèi)存溢出的分析
          內(nèi)存溢出是指應(yīng)用系統(tǒng)中存在無(wú)法回收的內(nèi)存或使用的內(nèi)存過(guò)多,最終使得程序運(yùn)行要用到的內(nèi)存大于虛擬機(jī)能提供的最大內(nèi)存。為了解決Java中內(nèi)存溢出問(wèn)題,我們首先必須了解Java是如何管理內(nèi)存的。Java的內(nèi)存管理就是對(duì)象的分配和釋放問(wèn)題。在Java中,內(nèi)存的分配是由程序完成的,而內(nèi)存的釋放是由垃圾收集器(Garbage Collection,GC)完成的,程序員不需要通過(guò)調(diào)用GC函數(shù)來(lái)釋放內(nèi)存,因?yàn)椴煌腏VM實(shí)現(xiàn)者可能使用不同的算法管理GC,有的是內(nèi)存使用到達(dá)一定程度時(shí),GC才開始工作,也有定時(shí)執(zhí)行的,有的是中斷式執(zhí)行GC。但GC只能回收無(wú)用并且不再被其它對(duì)象引用的那些對(duì)象所占用的空間。Java的內(nèi)存垃圾回收機(jī)制是從程序的主要運(yùn)行對(duì)象開始檢查引用鏈,當(dāng)遍歷一遍后發(fā)現(xiàn)沒有被引用的孤立對(duì)象就作為垃圾回收。

          引起內(nèi)存溢出的原因有很多種,常見的有以下幾種:

          l         內(nèi)存中加載的數(shù)據(jù)量過(guò)于龐大,如一次從數(shù)據(jù)庫(kù)取出過(guò)多數(shù)據(jù);

          l         集合類中有對(duì)對(duì)象的引用,使用完后未清空,使得JVM不能回收;

          l         代碼中存在死循環(huán)或循環(huán)產(chǎn)生過(guò)多重復(fù)的對(duì)象實(shí)體;

          l         使用的第三方軟件中的BUG;

          l         啟動(dòng)參數(shù)內(nèi)存值設(shè)定的過(guò)小;

          3.內(nèi)存溢出的解決
          內(nèi)存溢出雖然很棘手,但也有相應(yīng)的解決辦法,可以按照從易到難,一步步的解決。

          第一步,就是修改JVM啟動(dòng)參數(shù),直接增加內(nèi)存。這一點(diǎn)看上去似乎很簡(jiǎn)單,但很容易被忽略。JVM默認(rèn)可以使用的內(nèi)存為64M,Tomcat默認(rèn)可以使用的內(nèi)存為128MB,對(duì)于稍復(fù)雜一點(diǎn)的系統(tǒng)就會(huì)不夠用。在某項(xiàng)目中,就因?yàn)閱?dòng)參數(shù)使用的默認(rèn)值,經(jīng)常報(bào)“OutOfMemory”錯(cuò)誤。因此,-Xms,-Xmx參數(shù)一定不要忘記加。

          第二步,檢查錯(cuò)誤日志,查看“OutOfMemory”錯(cuò)誤前是否有其它異常或錯(cuò)誤。在一個(gè)項(xiàng)目中,使用兩個(gè)數(shù)據(jù)庫(kù)連接,其中專用于發(fā)送短信的數(shù)據(jù)庫(kù)連接使用DBCP連接池管理,用戶為不將短信發(fā)出,有意將數(shù)據(jù)庫(kù)連接用戶名改錯(cuò),使得日志中有許多數(shù)據(jù)庫(kù)連接異常的日志,一段時(shí)間后,就出現(xiàn)“OutOfMemory”錯(cuò)誤。經(jīng)分析,這是由于DBCP連接池BUG引起的,數(shù)據(jù)庫(kù)連接不上后,沒有將連接釋放,最終使得DBCP報(bào)“OutOfMemory”錯(cuò)誤。經(jīng)過(guò)修改正確數(shù)據(jù)庫(kù)連接參數(shù)后,就沒有再出現(xiàn)內(nèi)存溢出的錯(cuò)誤。

          查看日志對(duì)于分析內(nèi)存溢出是非常重要的,通過(guò)仔細(xì)查看日志,分析內(nèi)存溢出前做過(guò)哪些操作,可以大致定位有問(wèn)題的模塊。

          第三步,安排有經(jīng)驗(yàn)的編程人員對(duì)代碼進(jìn)行走查和分析,找出可能發(fā)生內(nèi)存溢出的位置。重點(diǎn)排查以下幾點(diǎn):

          l         檢查代碼中是否有死循環(huán)或遞歸調(diào)用。

          l         檢查是否有大循環(huán)重復(fù)產(chǎn)生新對(duì)象實(shí)體。

          l         檢查對(duì)數(shù)據(jù)庫(kù)查詢中,是否有一次獲得全部數(shù)據(jù)的查詢。一般來(lái)說(shuō),如果一次取十萬(wàn)條記錄到內(nèi)存,就可能引起內(nèi)存溢出。這個(gè)問(wèn)題比較隱蔽,在上線前,數(shù)據(jù)庫(kù)中數(shù)據(jù)較少,不容易出問(wèn)題,上線后,數(shù)據(jù)庫(kù)中數(shù)據(jù)多了,一次查詢就有可能引起內(nèi)存溢出。因此對(duì)于數(shù)據(jù)庫(kù)查詢盡量采用分頁(yè)的方式查詢。

          l         檢查L(zhǎng)ist、MAP等集合對(duì)象是否有使用完后,未清除的問(wèn)題。List、MAP等集合對(duì)象會(huì)始終存有對(duì)對(duì)象的引用,使得這些對(duì)象不能被GC回收。

          第四步,使用內(nèi)存查看工具動(dòng)態(tài)查看內(nèi)存使用情況。某個(gè)項(xiàng)目上線后,每次系統(tǒng)啟動(dòng)兩天后,就會(huì)出現(xiàn)內(nèi)存溢出的錯(cuò)誤。這種情況一般是代碼中出現(xiàn)了緩慢的內(nèi)存泄漏,用上面三個(gè)步驟解決不了,這就需要使用內(nèi)存查看工具了。

          內(nèi)存查看工具有許多,比較有名的有:Optimizeit Profiler、JProbe Profiler、JinSight和Java1.5的Jconsole等。它們的基本工作原理大同小異,都是監(jiān)測(cè)Java程序運(yùn)行時(shí)所有對(duì)象的申請(qǐng)、釋放等動(dòng)作,將內(nèi)存管理的所有信息進(jìn)行統(tǒng)計(jì)、分析、可視化。開發(fā)人員可以根據(jù)這些信息判斷程序是否有內(nèi)存泄漏問(wèn)題。一般來(lái)說(shuō),一個(gè)正常的系統(tǒng)在其啟動(dòng)完成后其內(nèi)存的占用量是基本穩(wěn)定的,而不應(yīng)該是無(wú)限制的增長(zhǎng)的。持續(xù)地觀察系統(tǒng)運(yùn)行時(shí)使用的內(nèi)存的大小,可以看到在內(nèi)存使用監(jiān)控窗口中是基本規(guī)則的鋸齒形的圖線,如果內(nèi)存的大小持續(xù)地增長(zhǎng),則說(shuō)明系統(tǒng)存在內(nèi)存泄漏問(wèn)題。通過(guò)間隔一段時(shí)間取一次內(nèi)存快照,然后對(duì)內(nèi)存快照中對(duì)象的使用與引用等信息進(jìn)行比對(duì)與分析,可以找出是哪個(gè)類的對(duì)象在泄漏。

          通過(guò)以上四個(gè)步驟的分析與處理,基本能處理內(nèi)存溢出的問(wèn)題。當(dāng)然,在這些過(guò)程中也需要相當(dāng)?shù)慕?jīng)驗(yàn)與敏感度,需要在實(shí)際的開發(fā)與調(diào)試過(guò)程中不斷積累。

          總體上來(lái)說(shuō),產(chǎn)生內(nèi)存溢出是由于代碼寫的不好造成的,因此提高代碼的質(zhì)量是最根本的解決辦法。有的人認(rèn)為先把功能實(shí)現(xiàn),有BUG時(shí)再在測(cè)試階段進(jìn)行修正,這種想法是錯(cuò)誤的。正如一件產(chǎn)品的質(zhì)量是在生產(chǎn)制造的過(guò)程中決定的,而不是質(zhì)量檢測(cè)時(shí)決定的,軟件的質(zhì)量在設(shè)計(jì)與編碼階段就已經(jīng)決定了,測(cè)試只是對(duì)軟件質(zhì)量的一個(gè)驗(yàn)證,因?yàn)闇y(cè)試不可能找出軟件中所有的BUG。

           

          --------------------------------------------------------------------------------------------------------------------------------

           

          原因有很多種,比如:

          1.數(shù)據(jù)量過(guò)于龐大;死循環(huán) ;靜態(tài)變量和靜態(tài)方法過(guò)多;遞歸;無(wú)法確定是否被引用的對(duì)象;

          2.虛擬機(jī)不回收內(nèi)存(內(nèi)存泄漏);

              說(shuō)白了就是程序運(yùn)行要用到的內(nèi)存大于虛擬機(jī)能提供的最大內(nèi)存就發(fā)生內(nèi)存溢出了。內(nèi)存溢出的問(wèn)題要看業(yè)務(wù)和系統(tǒng)大小而定,對(duì)于某些系統(tǒng)可能內(nèi)存溢出不常見,但某些系統(tǒng)還是很常見的解決的方法,

          一個(gè)是優(yōu)化程序代碼,如果業(yè)務(wù)龐大,邏輯復(fù)雜,盡量減少全局變量的引用,讓程序使用完變量的時(shí)候釋放該引用能夠讓垃圾回收器回收,釋放資源。
          二就是物理解決,增大物理內(nèi)存,然后通過(guò):-Xms256m -Xmx256m -XX:MaxNewSize=256m -XX:MaxPermSize=256m的修改

          一、內(nèi)存溢出類型 
          1 、 java.lang.OutOfMemoryError: PermGen space

          JVM 管理兩種類型的內(nèi)存,堆和非堆。堆是給開發(fā)人員用的上面說(shuō)的就是,是在 JVM 啟動(dòng)時(shí)創(chuàng)建;非堆是留給 JVM 自己用的,用來(lái)存放類的信息的。它和堆不同,運(yùn)行期內(nèi) GC 不會(huì)釋放空間。如果 web app 用了大量的第三方 jar 或者應(yīng)用有太多的 class 文件而恰好 MaxPermSize 設(shè)置較小,超出了也會(huì)導(dǎo)致這塊內(nèi)存的占用過(guò)多造成溢出,或者 tomcat 熱部署時(shí)侯不會(huì)清理前面加載的環(huán)境,只會(huì)將 context 更改為新部署的,非堆存的內(nèi)容就會(huì)越來(lái)越多。

          2 、 java.lang.OutOfMemoryError: Java heap space

          第一種情況是個(gè)補(bǔ)充,主要存在問(wèn)題就是出現(xiàn)在這個(gè)情況中。其默認(rèn)空間 ( 即 -Xms) 是物理內(nèi)存的 1/64 ,最大空間 (-Xmx) 是物理內(nèi)存的 1/4 。如果內(nèi)存剩余不到 40 %, JVM 就會(huì)增大堆到 Xmx 設(shè)置的值,內(nèi)存剩余超過(guò) 70 %, JVM 就會(huì)減小堆到 Xms 設(shè)置的值。所以服務(wù)器的 Xmx 和 Xms 設(shè)置一般應(yīng)該設(shè)置相同避免每次 GC 后都要調(diào)整虛擬機(jī)堆的大小。假設(shè)物理內(nèi)存無(wú)限大,那么 JVM 內(nèi)存的最大值跟操作系統(tǒng)有關(guān),一般 32 位機(jī)是 1.5g 到 3g 之間,而 64 位的就不會(huì)有限制了。

          注意:如果 Xms 超過(guò)了 Xmx 值,或者堆最大值和非堆最大值的總和超過(guò)了物理內(nèi)存或者操作系統(tǒng)的最大限制都會(huì)引起服務(wù)器啟動(dòng)不起來(lái)。

          垃圾回收 GC 的角色

          JVM 調(diào)用 GC 的頻度還是很高的,主要兩種情況下進(jìn)行垃圾回收:

          當(dāng)應(yīng)用程序線程空閑;另一個(gè)是 java 內(nèi)存堆不足時(shí),會(huì)不斷調(diào)用 GC ,若連續(xù)回收都解決不了內(nèi)存堆不足的問(wèn)題時(shí),就會(huì)報(bào) out of memory 錯(cuò)誤。因?yàn)檫@個(gè)異常根據(jù)系統(tǒng)運(yùn)行環(huán)境決定,所以無(wú)法預(yù)期它何時(shí)出現(xiàn)。

          根據(jù) GC 的機(jī)制,程序的運(yùn)行會(huì)引起系統(tǒng)運(yùn)行環(huán)境的變化,增加 GC 的觸發(fā)機(jī)會(huì)。

          為了避免這些問(wèn)題,程序的設(shè)計(jì)和編寫就應(yīng)避免垃圾對(duì)象的內(nèi)存占用和 GC 的開銷。顯示調(diào)用 System.GC() 只能建議 JVM 需要在內(nèi)存中對(duì)垃圾對(duì)象進(jìn)行回收,但不是必須馬上回收,

          一個(gè)是并不能解決內(nèi)存資源耗空的局面,另外也會(huì)增加 GC 的消耗。

          二、 JVM 內(nèi)存區(qū)域組成 
          簡(jiǎn)單的說(shuō) java中的堆和棧

          java把內(nèi)存分兩種:一種是棧內(nèi)存,另一種是堆內(nèi)存

          1。在函數(shù)中定義的基本類型變量和對(duì)象的引用變量都在函數(shù)的棧內(nèi)存中分配;

          2。堆內(nèi)存用來(lái)存放由 new創(chuàng)建的對(duì)象和數(shù)組

          在函數(shù)(代碼塊)中定義一個(gè)變量時(shí), java就在棧中為這個(gè)變量分配內(nèi)存空間,當(dāng)超過(guò)變量的作用域后, java會(huì)自動(dòng)釋放掉為該變量所分配的內(nèi)存空間;在堆中分配的內(nèi)存由 java虛擬機(jī)的自動(dòng)垃圾回收器來(lái)管理

          堆的優(yōu)勢(shì)是可以動(dòng)態(tài)分配內(nèi)存大小,生存期也不必事先告訴編譯器,因?yàn)樗窃谶\(yùn)行時(shí)動(dòng)態(tài)分配內(nèi)存的。缺點(diǎn)就是要在運(yùn)行時(shí)動(dòng)態(tài)分配內(nèi)存,存取速度較慢;

          棧的優(yōu)勢(shì)是存取速度比堆要快,缺點(diǎn)是存在棧中的數(shù)據(jù)大小與生存期必須是確定的無(wú)靈活 性。

          java 堆分為三個(gè)區(qū):New 、 Old 和 Permanent

          GC 有兩個(gè)線程:

          新創(chuàng)建的對(duì)象被分配到 New 區(qū),當(dāng)該區(qū)被填滿時(shí)會(huì)被 GC 輔助線程移到 Old 區(qū),當(dāng) Old 區(qū)也填滿了會(huì)觸發(fā) GC 主線程遍歷堆內(nèi)存里的所有對(duì)象。Old 區(qū)的大小等于 Xmx 減去 -Xmn

          java棧存放

          棧調(diào)整:參數(shù)有 +UseDefaultStackSize -Xss256K,表示每個(gè)線程可申請(qǐng) 256k的棧空間

          每個(gè)線程都有他自己的 Stack

          三、 JVM如何設(shè)置虛擬內(nèi)存 
          提示:在 JVM中如果 98%的時(shí)間是用于 GC且可用的 Heap size 不足 2%的時(shí)候?qū)伋龃水惓P畔ⅰ?/span>

          提示:Heap Size 最大不要超過(guò)可用物理內(nèi)存的 80%,一般的要將 -Xms和 -Xmx選項(xiàng)設(shè)置為相同,而 -Xmn為 1/4的 -Xmx值。

          提示:JVM初始分配的內(nèi)存由 -Xms指定,默認(rèn)是物理內(nèi)存的 1/64;JVM最大分配的內(nèi)存由 -Xmx指定,默認(rèn)是物理內(nèi)存的 1/4。

          默認(rèn)空余堆內(nèi)存小于 40%時(shí), JVM就會(huì)增大堆直到 -Xmx的最大限制;空余堆內(nèi)存大于 70%時(shí), JVM會(huì)減少堆直到 -Xms的最小限制。因此服務(wù)器一般設(shè)置 -Xms、 -Xmx相等以避免在每次 GC 后調(diào)整堆的大小。

          提示:假設(shè)物理內(nèi)存無(wú)限大的話, JVM內(nèi)存的最大值跟操作系統(tǒng)有很大的關(guān)系。

          簡(jiǎn)單的說(shuō)就 32位處理器雖然可控內(nèi)存空間有 4GB,但是具體的操作系統(tǒng)會(huì)給一個(gè)限制,

          這個(gè)限制一般是 2GB-3GB(一般來(lái)說(shuō) Windows系統(tǒng)下為 1.5G-2G, Linux系統(tǒng)下為 2G-3G), 而 64bit以上的處理器就不會(huì)有限制了

          提示:注意:如果 Xms超過(guò)了 Xmx值,或者堆最大值和非堆最大值的總和超過(guò)了物理內(nèi) 存或者操作系統(tǒng)的最大限制都會(huì)引起服務(wù)器啟動(dòng)不起來(lái)。

          提示:設(shè)置 NewSize、 MaxNewSize相等, “new”的大小最好不要大于 “old”的一半,原因是 old區(qū)如果不夠大會(huì)頻繁的觸發(fā) “主 ” GC ,大大降低了性能

          JVM使用 -XX:PermSize設(shè)置非堆內(nèi)存初始值,默認(rèn)是物理內(nèi)存的 1/64;

          由 XX:MaxPermSize設(shè)置最大非堆內(nèi)存的大小,默認(rèn)是物理內(nèi)存的 1/4。

          解決方法:手動(dòng)設(shè)置 Heap size

          修改 TOMCAT_HOME/bin/catalina.bat

          在“ echo “Using CATALINA_BASE: $CATALINA_BASE””上面加入以下行:

          JAVA_OPTS=”-server -Xms800m -Xmx800m -XX:MaxNewSize=256m”    


          四、性能檢查工具使用 
          定位內(nèi)存泄漏:

          JProfiler 工具主要用于檢查和跟蹤系統(tǒng)(限于 Java 開發(fā)的)的性能。JProfiler 可以通過(guò)時(shí)時(shí)的監(jiān)控系統(tǒng)的內(nèi)存使用情況,隨時(shí)監(jiān)視垃圾回收,線程運(yùn)行狀況等手段,從而很好的監(jiān)視 JVM 運(yùn)行情況及其性能。


          1. 應(yīng)用服務(wù)器內(nèi)存長(zhǎng)期不合理占用,內(nèi)存經(jīng)常處于高位占用,很難回收到低位;

          2. 應(yīng)用服務(wù)器極為不穩(wěn)定,幾乎每?jī)商熘匦聠?dòng)一次,有時(shí)甚至每天重新啟動(dòng)一次;

          3. 應(yīng)用服務(wù)器經(jīng)常做 Full GC(Garbage Collection),而且時(shí)間很長(zhǎng),大約需要 30-40秒,應(yīng)用服務(wù)器在做 Full GC的時(shí)候是不響應(yīng)客戶的交易請(qǐng)求的,非常影響系統(tǒng)性能。

          因?yàn)殚_發(fā)環(huán)境和產(chǎn)品環(huán)境會(huì)有不同,導(dǎo)致該問(wèn)題發(fā)生有時(shí)會(huì)在產(chǎn)品環(huán)境中發(fā)生, 通常可以使用工具跟蹤系統(tǒng)的內(nèi)存使用情況,在有些個(gè)別情況下或許某個(gè)時(shí)刻確實(shí) 是使用了大量?jī)?nèi)存導(dǎo)致 out of memory,這時(shí)應(yīng)繼續(xù)跟蹤看接下來(lái)是否會(huì)有下降,

          如果一直居高不下這肯定就因?yàn)槌绦虻脑驅(qū)е聝?nèi)存泄漏。

          五、不健壯代碼的特征及解決辦法 
          1 、盡早釋放無(wú)用對(duì)象的引用。好的辦法是使用臨時(shí)變量的時(shí)候,讓引用變量在退出活動(dòng)域后,自動(dòng)設(shè)置為 null ,暗示垃圾收集器來(lái)收集該對(duì)象,防止發(fā)生內(nèi)存泄露。

          對(duì)于仍然有指針指向的實(shí)例, jvm 就不會(huì)回收該資源 , 因?yàn)槔厥諘?huì)將值為 null 的對(duì)象作為垃圾,提高 GC 回收機(jī)制效率;

          2 、我們的程序里不可避免大量使用字符串處理,避免使用 String ,應(yīng)大量使用 StringBuffer ,每一個(gè) String 對(duì)象都得獨(dú)立占用內(nèi)存一塊區(qū)域;

          String str = “aaa”;   
            
          String str2 = “bbb”;   
            
          String str3 = str + str2;// 假如執(zhí)行此次之后 str ,str2 以后再不被調(diào)用 , 那它就會(huì)被放在內(nèi)存中等待 Java 的 gc 去回收 , 程序內(nèi)過(guò)多的出現(xiàn)這樣的情況就會(huì)報(bào)上面的那個(gè)錯(cuò)誤 , 建議在使用字符串時(shí)能使用 StringBuffer 就不要用 String, 這樣可以省不少開銷;   

          3 、盡量少用靜態(tài)變量,因?yàn)殪o態(tài)變量是全局的, GC 不會(huì)回收的;

          4 、避免集中創(chuàng)建對(duì)象尤其是大對(duì)象, JVM 會(huì)突然需要大量?jī)?nèi)存,這時(shí)必然會(huì)觸發(fā) GC 優(yōu)化系統(tǒng)內(nèi)存環(huán)境;顯示的聲明數(shù)組空間,而且申請(qǐng)數(shù)量還極大。

          這是一個(gè)案例想定供大家警戒:

          使用jspsmartUpload作文件上傳,現(xiàn)在運(yùn)行過(guò)程中經(jīng)常出現(xiàn)java.outofMemoryError的錯(cuò)誤,用top命令看看進(jìn)程使用情況,發(fā)現(xiàn)內(nèi)存不足2M,花了很長(zhǎng)時(shí)間,發(fā)現(xiàn)是jspsmartupload的問(wèn)題。把jspsmartupload組件的源碼文件(class文件)反編譯成Java文件,如夢(mèng)方醒:

          m_totalBytes = m_request.getContentLength();        
          m_binArray = new byte[m_totalBytes];    

          變量m_totalBytes表示用戶上傳的文件的總長(zhǎng)度,這是一個(gè)很大的數(shù)。如果用這樣大的數(shù)去聲明一個(gè)byte數(shù)組,并給數(shù)組的每個(gè)元素分配內(nèi)存空間,而且m_binArray數(shù)組不能馬上被釋放,JVM的垃圾回收確實(shí)有問(wèn)題,導(dǎo)致的結(jié)果就是內(nèi)存溢出。

          jspsmartUpload為什末要這樣作,有他的原因,根據(jù)RFC1867的http上傳標(biāo)準(zhǔn),得到一個(gè)文件流,并不知道文件流的長(zhǎng)度。設(shè)計(jì)者如果想文件的長(zhǎng)度,只有操作servletinputstream一次才知道,因?yàn)槿魏瘟鞫疾恢来笮 V挥兄牢募L(zhǎng)度了,才可以限制用戶上傳文件的長(zhǎng)度。為了省去這個(gè)麻煩,jspsmartUpload設(shè)計(jì)者直接在內(nèi)存中打開文件,判斷長(zhǎng)度是否符合標(biāo)準(zhǔn),符合就寫到服務(wù)器的硬盤。這樣產(chǎn)生內(nèi)存溢出,這只是我的一個(gè)猜測(cè)而已。

          所以編程的時(shí)候,不要在內(nèi)存中申請(qǐng)大的空間,因?yàn)閣eb服務(wù)器的內(nèi)存有限,并且盡可能的使用流操作,例如

          byte[] mFileBody = new byte[512];   
                   Blob vField= rs.getBlob("FileBody");   
                InputStream instream=vField.getBinaryStream();   
                FileOutputStream fos=new FileOutputStream(saveFilePath+CFILENAME);   
                   int b;   
                                while( (b =instream.read(mFileBody)) != -1){   
                                  fos.write(mFileBody,0,b);   
                                   }   
                  fos.close();   
                instream.close();  

          5 、盡量運(yùn)用對(duì)象池技術(shù)以提高系統(tǒng)性能;生命周期長(zhǎng)的對(duì)象擁有生命周期短的對(duì)象時(shí)容易引發(fā)內(nèi)存泄漏,例如大集合對(duì)象擁有大數(shù)據(jù)量的業(yè)務(wù)對(duì)象的時(shí)候,可以考慮分塊進(jìn)行處理,然后解決一塊釋放一塊的策略。

          6 、不要在經(jīng)常調(diào)用的方法中創(chuàng)建對(duì)象,尤其是忌諱在循環(huán)中創(chuàng)建對(duì)象。可以適當(dāng)?shù)氖褂?hashtable , vector 創(chuàng)建一組對(duì)象容器,然后從容器中去取那些對(duì)象,而不用每次 new 之后又丟棄

          7 、一般都是發(fā)生在開啟大型文件或跟數(shù)據(jù)庫(kù)一次拿了太多的數(shù)據(jù),造成 Out Of Memory Error 的狀況,這時(shí)就大概要計(jì)算一下數(shù)據(jù)量的最大值是多少,并且設(shè)定所需最小及最大的內(nèi)存空間值。




          鋒哥最新SpringCloud分布式電商秒殺課程發(fā)布

          ??????

          ??長(zhǎng)按上方微信二維碼 2 秒





          感謝點(diǎn)贊支持下哈 

          瀏覽 76
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  五月丁香啪啪 | 激情偷乱人成视频在线观看 | 超清无码在线网上 | 欧美wwwxxx | 日本色婷婷 |