上周,一同學給我發來,他們那里的案例
一看就是新生代產生過多對象,肯定是批量或者循環操作導致的,導致新生代一直在進行回收導致。
如果是老生代出現這樣的問題,大部分情況下是列表或者集合導致的。
因此我們在獲取數據的時候,往list里面放數據不要放太多,像上篇hbase數據遷移的時候,一次性遷移大概在200行數據,而不是一次性讀入到內存中,必定會導致內存溢出。
對象創建銷毀大多發生在新生代,而集合等等因為存活時間較長發生在老生代。
下面看下java實例化的一個例子
JVM的內存空間:
(1). Heap 堆空間:分配對象 new Student()
(2). Stack 棧空間:臨時變量 Student stu
(3).Code 代碼區 :類的定義,靜態資源 Student.class
eg:Student stu = new Student(); //new 在內存的堆空間創建對象 stu.study(); //把對象的地址賦給stu引用變量
上例實現步驟:
a.JVM加載Student.class 到Code區 b.new Student()在堆空間分配空間並創建一個Student實例 c.將此實例的地址賦值給引用stu, 棧空間
java內存分區
運行時數據區即是java內存,而且數據區要存儲的東西比較多,如果不對這塊內存區域進行划分管理,會顯得比較雜亂無章。程序喜歡有規律的東西,最討厭雜亂無章的東西。
根據存儲數據的不同,java內存通常被划分為5個區域:程序計數器(Program Count Register)、本地方法棧(Native Stack)、方法區(Methon Area)、棧(Stack)、堆(Heap)。
程序計數器(Program Count Register)
又叫程序寄存器。JVM支持多個線程同時運行,當每一個新線程被創建時,它都將得到它自己的PC寄存器(程序計數器)。如果線程正在執行的是一個Java方法(非native),那么PC寄存器的值將總是指向下一條將被執行的指令,如果方法是 native的,程序計數器寄存器的值不會被定義。 JVM的程序計數器寄存器的寬度足夠保證可以持有一個返回地址或者native的指針。
棧(Stack):
又叫堆棧。JVM為每個新創建的線程都分配一個棧。也就是說,對於一個Java程序來說,它的運行就是通過對棧的操作來完成的。棧以幀為單位保存線程的狀態。
JVM對棧只進行兩種操作:以幀為單位的壓棧和出棧操作。我們知道,某個線程正在執行的方法稱為此線程的當前方法。我們可能不知道,當前方法使用的幀稱為當前幀。當線程激活一個Java方法,JVM就會在線程的 Java堆棧里新壓入一個幀,這個幀自然成為了當前幀。在此方法執行期間,這個幀將用來保存參數、局部變量、中間計算過程和其他數據。
從Java的這種分配機制來看,堆棧又可以這樣理解:棧(Stack)是操作系統在建立某個進程時或者線程(在支持多線程的操作系統中是線程)為這個線程建立的存儲區域,該區域具有先進后出的特性。
其相關設置參數:
-Xss --設置方法棧的最大值
本地方法棧(Native Stack):
存儲本地方方法的調用狀態。
方法區(Method Area):
當虛擬機裝載一個class文件時,它會從這個class文件包含的二進制數據中解析類型信息,然后把這些類型信息(包括類信息、常量、靜態變量等)放到方法區中,該內存區域被所有線程共享,如下圖所示。本地方法區存在一塊特殊的內存區域,叫常量池(Constant Pool),這塊內存將與String類型的分析密切相關。
堆(Heap):
Java堆(Java Heap)是Java虛擬機所管理的內存中最大的一塊。Java堆是被所有線程共享的一塊內存區域。在此區域的唯一目的就是存放對象實例,幾乎所有的對象實例都是在這里分配內存,但是這個對象的引用卻是在棧(Stack)中分配。
如,執行String s = new String("s")時,需要從兩個地方分配內存:在堆中為String對象分配內存,在棧中為引用(這個堆對象的內存地址,即指針)分配內存,如下圖所示。
JAVA虛擬機有一條在堆中分配新對象的指令,卻沒有釋放內存的指令,正如你無法用Java代碼區明確釋放一個對象一樣。虛擬機自己負責決定如何以及何時釋放不再被運行的程序引用的對象所占據的內存,通常,虛擬機把這個任務交給垃圾收集器(Garbage Collection)。其相關設置參數:
-Xms -- 設置堆內存初始大小 -Xmx -- 設置堆內存最大值 -XX:MaxTenuringThreshold -- 設置對象在新生代中存活的次數 -XX:PretenureSizeThreshold -- 設置超過指定大小的大對象直接分配在舊生代中
Java堆是垃圾收集器管理的主要區域,因此又稱為“GC 堆”(Garbage Collectioned Heap)。現在的垃圾收集器基本都是采用的分代收集算法,所以Java堆還可以細分為:新生代(Young Generation)和老年代(Old Generation),如下圖所示。分代收集算法的思想:第一種說法,用較高的頻率對年輕的對象(young generation)進行掃描和回收,這種叫做minor collection,而對老對象(old generation)的檢查回收頻率要低很多,稱為major collection。這樣就不需要每次GC都將內存中所有對象都檢查一遍,以便讓出更多的系統資源供應用系統使用;另一種說法,在分配對象遇到內存不足時,先對新生代進行GC(Young GC);當新生代GC之后仍無法滿足內存空間分配需求時, 才會對整個堆空間以及方法區進行GC(Full GC)。
在這里可能會有讀者表示疑問:記得還有一個什么永久代(Permanent Generation)的啊,難道它不屬於Java堆?親,你答對了!其實傳說中的永久代就是上面所說的方法區,存放的都是jvm初始化時加載器加載的一些類型信息(包括類信息、常量、靜態變量等),這些信息的生存周期比較長,GC不會在主程序運行期對PermGen Space進行清理,所以如果你的應用中有很多CLASS的話,就很可能出現PermGen Space錯誤。其相關設置參數:
-XX:PermSize --設置Perm區的初始大小 -XX:MaxPermSize --設置Perm區的最大值
新生代(Young Generation)又分為:Eden區和Survivor區,Survivor區有分為From Space和To Space。Eden區是對象最初分配到的地方;默認情況下,From Space和To Space的區域大小相等。JVM進行Minor GC時,將Eden中還存活的對象拷貝到Survivor區中,還會將Survivor區中還存活的對象拷貝到Tenured區中。在這種GC模式下,JVM為了提升GC效率, 將Survivor區分為From Space和To Space,這樣就可以將對象回收和對象晉升分離開來。新生代的大小設置有2個相關參數:
-
-Xmn -- 設置新生代內存大小。
-
-XX:SurvivorRatio -- 設置Eden與Survivor空間的大小比例
老年代(Old Generation): 當 OLD 區空間不夠時, JVM 會在 OLD 區進行 major collection ;完全垃圾收集后,若Survivor及OLD區仍然無法存放從Eden復制過來的部分對象,導致JVM無法在Eden區為新對象創建內存區域,則出現"Out of memory錯誤" 。