1、請查看 hashmap 如何初使化
代碼:
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
// Find a power of 2 >= initialCapacity
int capacity = 1;
while (capacity < initialCapacity)
capacity <<= 1;
this.loadFactor = loadFactor;
threshold = (int)(capacity * loadFactor);
table = new Entry[capacity];
init();
}
當
Map map = new HashMap(9);
while (capacity < initialCapacity)
capacity <<= 1;
capacity的容量為 16
threshold擴容閥值 16*0.75 =12
和9沒什么關系 哈
當
Map map = new HashMap(9,1);
capacity的容量為 16
threshold擴容閥值 16*1 =16
不過這個 9 變小一點 就不一樣了 如3時capacity 是4
學了這么久的Java,才知道Java的對象引用類型有4種。所以,趕緊把不知道的東西補上!
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
import java.util.Set;
import java.util.HashSet;
public class TestReferences
{
}
class MyObject
{
}
首先感謝阿寶同學的幫助,我才對這個gc算法的調整有了一定的認識,而不是停留在過去僅僅了解的階段。在讀過sun的文檔和跟阿寶討論之后,做個小小的總結,如果有謬誤,敬請指正。
CMS,全稱Concurrent Low Pause Collector,是jdk1.4后期版本開始引入的新gc算法,在jdk5和jdk6中得到了進一步改進,它的主要適合場景是對響應時間的重要性需求 大于對吞吐量的要求,能夠承受垃圾回收線程和應用線程共享處理器資源,并且應用中存在比較多的長生命周期的對象的應用。CMS是用于對tenured generation的回收,也就是年老代的回收,目標是盡量減少應用的暫停時間,減少full gc發生的幾率,利用和應用程序線程并發的垃圾回收線程來標記清除年老代。在我們的應用中,因為有緩存的存在,并且對于響應時間也有比較高的要求,因此希 望能嘗試使用CMS來替代默認的server型JVM使用的并行收集器,以便獲得更短的垃圾回收的暫停時間,提高程序的響應性。
CMS并非沒有暫停,而是用兩次短暫停來替代串行標記整理算法的長暫停,它的收集周期是這樣:
初始標記(CMS-initial-mark) -> 并發標記(CMS-concurrent-mark) -> 重新標記(CMS-remark) -> 并發清除(CMS-concurrent-sweep) ->并發重設狀態等待下次CMS的觸發(CMS-concurrent-reset)。
其中的1,3兩個步驟需要暫停所有的應用程序線程的。第一次暫停從root對象開始標記存活的對象,這個階段稱為初始標記;第二次暫停是在并發標記之后, 暫停所有應用程序線程,重新標記并發標記階段遺漏的對象(在并發標記階段結束后對象狀態的更新導致)。第一次暫停會比較短,第二次暫停通常會比較長,并且 remark這個階段可以并行標記。
而并發標記、并發清除、并發重設階段的所謂并發,是指一個或者多個垃圾回收線程和應用程序線程并發地運行,垃圾回收線程不會暫停應用程序的執行,如果你有多于一個處理器,那么并發收集線程將與應用線程在不同的處理器上運行,顯然,這樣的開銷就是會降低應用的吞吐量。Remark階段的并行,是指暫停了所有應用程序后,啟動一定數目的垃圾回收進程進行并行標記,此時的應用線程是暫停的。
CMS的young generation的回收采用的仍然是并行復制收集器,這個跟Paralle gc算法是一致的。
下面是參數介紹和遇到的問題總結,
1、啟用CMS:-XX:+UseConcMarkSweepGC。 咳咳,這里犯過一個低級錯誤,竟然將+號寫成了-號
2。CMS默認啟動的回收線程數目是 (ParallelGCThreads + 3)/4) ,如果你需要明確設定,可以通過-XX:ParallelCMSThreads=20來設定,其中ParallelGCThreads是年輕代的并行收集線程數
3、CMS是不會整理堆碎片的,因此為了防止堆碎片引起full gc,通過會開啟CMS階段進行合并碎片選項:-XX:+UseCMSCompactAtFullCollection,開啟這個選項一定程度上會影響性能,阿寶的blog里說也許可以通過配置適當的CMSFullGCsBeforeCompaction來調整性能,未實踐。
4.為了減少第二次暫停的時間,開啟并行remark: -XX:+CMSParallelRemarkEnabled。如果remark還是過長的話,可以開啟-XX:+CMSScavengeBeforeRemark選項,強制remark之前開始一次minor gc,減少remark的暫停時間,但是在remark之后也將立即開始又一次minor gc。
5.為了避免Perm區滿引起的full gc,建議開啟CMS回收Perm區選項:
+CMSPermGenSweepingEnabled -XX:+CMSClassUnloadingEnabled
6.默認CMS是在tenured generation沾滿68%的時候開始進行CMS收集,如果你的年老代增長不是那么快,并且希望降低CMS次數的話,可以適當調高此值:
-XX:CMSInitiatingOccupancyFraction=80
這里修改成80%沾滿的時候才開始CMS回收。
7.年輕代的并行收集線程數默認是(cpu <= 8) ? cpu : 3 + ((cpu * 5) / 8),如果你希望降低這個線程數,可以通過-XX:ParallelGCThreads= N 來調整。
8.進入重點,在初步設置了一些參數后,例如:
- -server -Xms1536m -Xmx1536m -XX:NewSize=256m -XX:MaxNewSize=256m -XX:PermSize=64m
- -XX:MaxPermSize=64m -XX:-UseConcMarkSweepGC -XX:+UseCMSCompactAtFullCollection
- -XX:CMSInitiatingOccupancyFraction=80 -XX:+CMSParallelRemarkEnabled
- -XX:SoftRefLRUPolicyMSPerMB=0
需要在生產環境或者壓測環境中測量這些參數下系統的表現,這時候需要打開GC日志查看具體的信息,因此加上參數:
-verbose:gc -XX:+PrintGCTimeStamps -XX:+PrintGCDetails -Xloggc:/home/test/logs/gc.log
在運行相當長一段時間內查看CMS的表現情況,CMS的日志輸出類似這樣:
- 4391.322: [GC [1 CMS-initial-mark: 655374K(1310720K)] 662197K(1546688K), 0.0303050 secs] [Times: user=0.02 sys=0.02, real=0.03 secs]
- 4391.352: [CMS-concurrent-mark-start]
- 4391.779: [CMS-concurrent-mark: 0.427/0.427 secs] [Times: user=1.24 sys=0.31, real=0.42 secs]
- 4391.779: [CMS-concurrent-preclean-start]
- 4391.821: [CMS-concurrent-preclean: 0.040/0.042 secs] [Times: user=0.13 sys=0.03, real=0.05 secs]
- 4391.821: [CMS-concurrent-abortable-preclean-start]
- 4392.511: [CMS-concurrent-abortable-preclean: 0.349/0.690 secs] [Times: user=2.02 sys=0.51, real=0.69 secs]
- 4392.516: [GC[YG occupancy: 111001 K (235968 K)]4392.516: [Rescan (parallel) , 0.0309960 secs]4392.547: [weak refs processing, 0.0417710 secs] [1 CMS-remark: 655734K(1310720K)] 766736K(1546688K), 0.0932010 secs] [Times: user=0.17 sys=0.00, real=0.09 secs]
- 4392.609: [CMS-concurrent-sweep-start]
- 4394.310: [CMS-concurrent-sweep: 1.595/1.701 secs] [Times: user=4.78 sys=1.05, real=1.70 secs]
- 4394.310: [CMS-concurrent-reset-start]
- 4394.364: [CMS-concurrent-reset: 0.054/0.054 secs] [Times: user=0.14 sys=0.06, real=0.06 secs]
其中可以看到CMS-initial-mark階段暫停了0.0303050秒,而CMS-remark階段暫停了0.0932010秒,因此兩次暫停的總共時間是0.123506秒,也就是123毫秒左右。兩次短暫停的時間之和在200以下可以稱為正常現象。
但是你很可能遇到兩種fail引起full gc:Prommotion failed和Concurrent mode failed。
Prommotion failed的日志輸出大概是這樣:
- [ParNew (promotion failed): 320138K->320138K(353920K), 0.2365970 secs]42576.951: [CMS: 1139969K->1120688K(
- 166784K), 9.2214860 secs] 1458785K->1120688K(2520704K), 9.4584090 secs]
這個問題的產生是由于救助空間不夠,從而向年老代轉移對象,年老代沒有足夠的空間來容納這些對象,導致一次full gc的產生。解決這個問題的辦法有兩種完全相反的傾向:增大救助空間、增大年老代或者去掉救助空間。 增大救助空間就是調整-XX:SurvivorRatio參數,這個參數是Eden區和Survivor區的大小比值,默認是32,也就是說Eden區是 Survivor區的32倍大小,要注意Survivo是有兩個區的,因此Surivivor其實占整個young genertation的1/34。調小這個參數將增大survivor區,讓對象盡量在survitor區呆長一點,減少進入年老代的對象。去掉救助空 間的想法是讓大部分不能馬上回收的數據盡快進入年老代,加快年老代的回收頻率,減少年老代暴漲的可能性,這個是通過將-XX:SurvivorRatio 設置成比較大的值(比如65536)來做到。在我們的應用中,將young generation設置成256M,這個值相對來說比較大了,而救助空間設置成默認大小(1/34),從壓測情況來看,沒有出現prommotion failed的現象,年輕代比較大,從GC日志來看,minor gc的時間也在5-20毫秒內,還可以接受,因此暫不調整。
Concurrent mode failed的產生是由于CMS回收年老代的速度太慢,導致年老代在CMS完成前就被沾滿,引起full gc,避免這個現象的產生就是調小-XX:CMSInitiatingOccupancyFraction參數的值,讓CMS更早更頻繁的觸發,降低年老代被沾滿的可能。我們的應用暫時負載比較低,在生產環境上年老代的增長非常緩慢,因此暫時設置此參數為80。在壓測環境下,這個參數的表現還可以,沒有出現過Concurrent mode failed。
參考資料:
《》 by 江南白衣
《記一次Java GC調整經歷》1,2 by Arbow
Java SE 6 HotSpot[tm] Virtual Machine Garbage Collection Tuning
Tuning Garbage Collection with the 5.0 JavaTM Virtual Machine
最近在使用Google的Gson包進行Json和Java對象之間的轉化,對于包含泛型的類的序列化和反序列化Gson也提供了很好的支持,感覺有點意思,就花時間研究了一下。
由于Java泛型的實現機制,使用了泛型的代碼在運行期間相關的泛型參數的類型會被擦除,我們無法在運行期間獲知泛型參數的具體類型(所有的泛型類型在運行時都是Object類型)。
但是有的時候,我們確實需要獲知泛型參數的類型,比如將使用了泛型的Java代碼序列化或者反序列化的時候,這個時候問題就變得比較棘手。
1 2 3 4 5 6 7 8 | class Foo<T> { T value; } Gson gson = new Gson(); Foo<Bar> foo = new Foo<Bar>(); gson.toJson(foo); // May not serialize foo.value correctly gson.fromJson(json, foo.getClass()); // Fails to deserialize foo.value as Bar |
對于上面的類Foo<T>,由于在運行期間無法得知T的具體類型,對這個類的對象進行序列化和反序列化都不能正常進行。Gson通過借助TypeToken類來解決這個問題。
1 2 3 4 5 6 7 8 | TestGeneric<String> t = new TestGeneric<String>(); t.setValue( "Alo" ); Type type = new TypeToken<TestGeneric<String>>(){}.getType(); String gStr = GsonUtils.gson.toJson(t,type); System.out.println(gStr); TestGeneric t1 = GsonUtils.gson.fromJson(gStr, type); System.out.println(t1.getValue()); |
TypeToken的使用非常簡單,如上面的代碼,只要將需要獲取類型的泛型類作為TypeToken的泛型參數構造一個匿名的子類,就可以通過getType()方法獲取到我們使用的泛型類的泛型參數類型。
下面來簡單分析一下原理。
要獲取泛型參數的類型,一般的做法是在使用了泛型的類的構造函數中顯示地傳入泛型類的Class類型作為這個泛型類的私有屬性,它保存了泛型類的類型信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | public class Foo<T>{ public Class<T> kind; public Foo(Class<T> clazz){ this .kind = clazz; } public T[] getInstance(){ return (T[])Array.newInstance(kind, 5 ); } public static void main(String[] args){ Foo<String> foo = new Foo(String. class ); String[] strArray = foo.getInstance(); } } |
這種方法雖然能解決問題,但是每次都要傳入一個Class類參數,顯得比較麻煩。Gson庫里面對于這個問題采用了了另一種解決辦法。
同樣是為了獲取Class的類型,可以通過另一種方式實現:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | public abstract class Foo<T>{ Class<T> type; public Foo(){ this .type = (Class<T>) getClass(); } public static void main(String[] args) { Foo<String> foo = new Foo<String>(){}; Class mySuperClass = foo.getClass(); } } |
聲明一個抽象的父類Foo,匿名子類將泛型類作為Foo的泛型參數傳入構造一個實例,再調用getClass方法獲得這個子類的Class類型。
這里雖然通過另一種方式獲得了匿名子類的Class類型,但是并沒有直接將泛型參數T的Class類型傳進來,那又是如何獲得泛型參數的類型的呢, 這要依賴Java的Class字節碼中存儲的泛型參數信息。Java的泛型機制雖然在運行期間泛型類和非泛型類都相同,但是在編譯java源代碼成 class文件中還是保存了泛型相關的信息,這些信息被保存在class字節碼常量池中,使用了泛型的代碼處會生成一個signature簽名字段,通過 簽名signature字段指明這個常量池的地址。
關于class文件中存儲泛型參數類型的具體的詳細的知識可以參考這里:http://stackoverflow.com/questions/937933/where-are-generic-types-stored-in-java-class-files
JDK里面提供了方法去讀取這些泛型信息的方法,再借助反射,就可以獲得泛型參數的具體類型。同樣是對于第一段代碼中的foo對象,通過下面的代碼可以得到foo<T>中的T的類型:
1 2 3 | Type mySuperClass = foo.getClass().getGenericSuperclass(); Type type = ((ParameterizedType)mySuperClass).getActualTypeArguments()[ 0 ]; System.out.println(type); |
運行結果是class java.lang.String。
分析一下這段代碼,Class類的getGenericSuperClass()方法的注釋是:
Returns the Type representing the direct superclass of the entity (class, interface, primitive type or void) represented by thisClass.
If the superclass is a parameterized type, the Type object returned must accurately reflect the actual type parameters used in the source code. The parameterized type representing the superclass is created if it had not been created before. See the declaration of ParameterizedType for the semantics of the creation process for parameterized types. If thisClass represents either theObject class, an interface, a primitive type, or void, then null is returned. If this object represents an array class then theClass object representing theObject class is returned
概括來說就是對于帶有泛型的class,返回一個ParameterizedType對象,對于Object、接口和原始類型返回null,對于數 組class則是返回Object.class。ParameterizedType是表示帶有泛型參數的類型的Java類型,JDK1.5引入了泛型之 后,Java中所有的Class都實現了Type接口,ParameterizedType則是繼承了Type接口,所有包含泛型的Class類都會實現 這個接口。
實際運用中還要考慮比較多的情況,比如獲得泛型參數的個數避免數組越界等,具體可以參看Gson中的TypeToken類及ParameterizedTypeImpl類的代碼。