1 Object的內(nèi)存結(jié)構(gòu)和指針壓縮了解一下
//hotspot的oop.hpp文件中class oopDesc
class oopDesc {
friend class VMStructs;
private:
volatile markOop _mark; //對(duì)象頭部分
union _metadata { // klassOop 類元數(shù)據(jù)指針
Klass* _klass;
narrowKlass _compressed_klass;
} _metadata;
- Object的實(shí)例數(shù)據(jù)內(nèi)存使用三部分組成的臭杰,對(duì)象頭,實(shí)際數(shù)據(jù)區(qū)域、內(nèi)存對(duì)齊區(qū)
-
對(duì)象頭布局如下:主要和鎖疚俱,hashcode,垃圾回收有關(guān)缩多;由于鎖機(jī)制的內(nèi)容篇幅過長(zhǎng)呆奕,這里就不多解釋了;和鎖相關(guān)的markWord(markOop)內(nèi)存布局如下
- 內(nèi)存對(duì)齊區(qū)是什么? HotSpot VM的自動(dòng)內(nèi)存管理系統(tǒng)要求對(duì)象起始地址必須是8字節(jié)的整數(shù)倍衬吆,換句話說就是對(duì)象的大小必須是8字節(jié)的整數(shù)倍梁钾。因此當(dāng)對(duì)象實(shí)例數(shù)據(jù)部分沒有對(duì)齊的話,就需要通過對(duì)齊填充來補(bǔ)全逊抡。
-
內(nèi)存對(duì)齊好處
- 有利于內(nèi)存的管理
- 更快的CPU讀取姆泻,CPU從內(nèi)存獲取數(shù)據(jù),并不是一個(gè)個(gè)字節(jié)的讀取冒嫡,而是按CPU能處理的長(zhǎng)度獲取拇勃,如32位機(jī),是4個(gè)字節(jié)的內(nèi)存塊孝凌;當(dāng)只需其中兩個(gè)字節(jié)時(shí)方咆,則由內(nèi)存處理器處理挑選。如果需要三個(gè)字節(jié)分布在兩個(gè)不同內(nèi)存塊(四字節(jié)的內(nèi)存塊)蟀架,則需要讀取內(nèi)存兩次(如果是存在同一內(nèi)存塊只需一次讀劝曷浮)。而當(dāng)對(duì)象按一定的規(guī)則合理對(duì)齊時(shí)辜窑,CPU就可以最少地請(qǐng)求內(nèi)存钩述,加快CPU的執(zhí)行速度
-
指針壓縮
- 在上圖可以看到,在64位jvm里Object的MarkWord會(huì)比32位的大一倍穆碎;其實(shí)klassOop也擴(kuò)大一倍占了64位(數(shù)組長(zhǎng)度部分則是固定四字節(jié))牙勘。指針的寬度增大,但是對(duì)于堆內(nèi)存小于4G的所禀,好像也用不到64位的指針方面。這可以優(yōu)化嗎?答案是就是指針壓縮
- 指針壓縮的原理是利用jvm植入壓縮指令色徘,進(jìn)行編碼恭金、解碼
- 哪些信息會(huì)被壓縮
- 會(huì)被壓縮對(duì)象:類屬性、對(duì)象頭信息褂策、對(duì)象引用類型横腿、對(duì)象數(shù)組類型
- 不被壓縮對(duì)象:本地變量颓屑,堆棧元素,入?yún)⒐⒑福祷刂稻镜耄琋ULL這些指針
- 指針壓縮開啟,klassOop大小可以由64bit變成32bit罗侯;對(duì)象的大小可以看看下面的具體對(duì)比:JVM - 剖析JAVA對(duì)象頭OBJECT HEADER之指針壓縮
public static void main(String[] args){ Object a = new Object(); // 16B 關(guān)閉壓縮還是16B器腋,需要是8B倍數(shù);12B+填充的4B int[] arr = new int[10]; // 16B 關(guān)閉壓縮則是24B } public class ObjectNum { //8B mark word //4B Klass Pointer 如果關(guān)閉壓縮則占用8B //-XX:-UseCompressedClassPointers或-XX:-UseCompressedOops, int id; //4B String name; //4B 如果關(guān)閉壓縮則占用8B byte b; //1B 實(shí)際內(nèi)存可能會(huì)填充到4B Object o; //4B 如果關(guān)閉壓縮則占用8B }
- 為什么開啟指針壓縮時(shí)钩杰,堆內(nèi)存最好不要超過32G纫塌,指針使用32個(gè)bit,為什么最大可使用內(nèi)存不是4G而是32G
jvm要求對(duì)象起始位置對(duì)齊8字節(jié)的倍數(shù)讲弄,可以利用這點(diǎn)提升選址范圍措左,理論上可以提升到2^11 * 4G
。不過jvm只是將指針左移三位垂睬,因此2^3 * 4G = 32G
媳荒。如果大于32G,指針壓縮會(huì)失效抗悍。如果GC堆大小在 4G以下驹饺,直接砍掉高32位,避免了編碼解碼過程 - 啟用指針壓縮
-XX:+UseCompressedOops
(默認(rèn)開啟)缴渊,禁止指針壓縮:-XX:-UseCompressedOops
2 Object的幾種基本方法
- 本地方法
-
private static native void registerNatives()
將Object定義的本地方法和java程序鏈接起來赏壹。Object類中的registerNatives -
public final native Class<?> getClass()
獲取java的Class元數(shù)據(jù) -
public native int hashCode()
獲取對(duì)象的哈希Code -
protected native Object clone() throws CloneNotSupportedException
獲得對(duì)象的克隆對(duì)象,淺復(fù)制 -
public final native void notify()
喚醒等待對(duì)象鎖waitSet隊(duì)列中的一個(gè)線程 -
public final native void notifyAll()
類似notify(),喚醒等待對(duì)象鎖waitSet隊(duì)列中的全部線程 -
public final native void wait(long timeout)
釋放對(duì)象鎖,進(jìn)入對(duì)象鎖的waitSet隊(duì)列
-
- 普通方法
public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode());} public boolean equals(Object obj) { return (this == obj);} public final void wait(long timeout, int nanos) throws InterruptedException; //都是基于native void wait(long timeout)實(shí)現(xiàn)的 public final void wait() throws InterruptedException; wait(long timeout, int nanos)衔沼、wait() //jvm回收對(duì)象前蝌借,會(huì)特意調(diào)用此方法 protected void finalize() throws Throwable;
3 == 、 equals指蚁、Comparable.compareTo菩佑、Comparator.compara 四種比較方法
如不指定排序順序,java里的默認(rèn)排序順序是升序的凝化,從小到大
- ==稍坯, (A)對(duì)于基本類型之間的比較是值 (B)基本類型和封裝類型比較也是值比較 (C)對(duì)于引用類型之間的比較則是內(nèi)存地址
- equals(Object o), 在Object基本方法里可以看到
public boolean equals(Object obj) { return (this == obj);}
是使用 == 去比較的搓劫。equals方法的好處是我們可以重寫該方法 - Comparable.compareTo 是接口Comparable里的抽象方法瞧哟;如果對(duì)象實(shí)現(xiàn)該接口,可使用Collections.sort(List< T> col)進(jìn)行排序枪向。接下來看看源碼怎么實(shí)現(xiàn)的
List的sort 則調(diào)用了Arrays.sortCollections.java //Collections.sort(List<T> list),調(diào)用的是List的sort方法 public static <T extends Comparable<? super T>> void sort(List<T> list) { list.sort(null); }
如果Comparator c 為null,則是調(diào)用 Arrays.sort(Object[] a) 勤揩;最終調(diào)用LegacyMergeSort(歸并排序)方法處理List.java default void sort(Comparator<? super E> c) { Object[] a = this.toArray(); Arrays.sort(a, (Comparator) c); ListIterator<E> i = this.listIterator(); for (Object e : a) { i.next(); i.set((E) e); } }
LegacyMergeSort方法里的一段代碼;最終底層是使用歸并排序和compareTo來排序Arrays.java public static <T> void sort(T[] a, Comparator<? super T> c) { if (c == null) { sort(a); } else { if (LegacyMergeSort.userRequested) legacyMergeSort(a, c); else TimSort.sort(a, 0, a.length, c, null, 0, 0); } }
Arrays.java ...... if (length < INSERTIONSORT_THRESHOLD) { for (int i=low; i<high; i++) for (int j=i; j>low && ((Comparable) dest[j-1]).compareTo(dest[j])>0; j--) swap(dest, j, j-1); return; }
- Comparator也是一個(gè)接口秘蛔,不過提供了更豐富的操作陨亡,需要實(shí)現(xiàn)
int compare(T o1, T o2)
方法
Comparator提供了常用的幾個(gè)靜態(tài)方法thenComparing傍衡、reversed、reverseOrder(操作對(duì)象需要實(shí)現(xiàn)Comparator或者Comparable)负蠕;可配合List.sort聪舒、Stream.sorted、Collections.sort使用虐急。
Collections.sort默認(rèn)是升序排序的箱残,可以看到reverseOrder將順序反過來了; 用了thenComparing的col則是先判斷Pair::getOne的大小止吁,如果相等則判斷Pair::getTwo大小來排序@Data @AllArgsConstructor static class Pair implements Comparator<Pair>, Comparable<Pair> { Integer one; Integer two; @Override public String toString() { return one + "-" + two; } @Override public int compareTo(Pair o) { return one.compareTo(o.one); } @Override public int compare(Pair o1, Pair o2) {return o1.compareTo(o2);} } public static void main(String[] args) { List<Pair> col = Arrays.asList( new Pair(4, 6), new Pair(4, 2),new Pair(1, 3)); col.sort(Comparator.reverseOrder()); System.out.println("----------------"); col.stream().sorted(Comparator.comparing(Pair::getOne).thenComparing(Pair::getTwo)) .forEach(item -> System.out.println(item.toString()) ); }
result: 4-6 4-2 1-3 ---------------- 1-3 4-2 4-6
4 方法的重寫和重載
- 方法的重寫是指子類定義和父類方法的名稱被辑、參數(shù)及順序一致的方法;需要注意的是敬惦,子類重寫方法修飾符不能更加嚴(yán)格盼理,就是說父類方法的修飾符是protected,子類不能使用private修飾而可用public,拋出的異常也不能比父類方法定義的更廣
- 方法的重載則是同一個(gè)類中定義和已有方法的名稱一致而參數(shù)或者參數(shù)順序不一致的方法俄删,(返回值不能決定方法的重載)
- 重載的方法在編譯時(shí)就可確定(編譯時(shí)多態(tài))宏怔,而重寫的方法需要在運(yùn)行時(shí)確定(運(yùn)行時(shí)多態(tài),我們常說的多態(tài))
多態(tài)的三個(gè)必要條件 1畴椰、有繼承關(guān)系 2臊诊、子類重寫父類方法 3、父類引用指向子類對(duì)象
5 構(gòu)造方法是否可被重寫
構(gòu)造方法是每一個(gè)類獨(dú)有的斜脂,并不能被子類繼承抓艳,因?yàn)闃?gòu)造方法沒有返回值,子類定義不了和父類的構(gòu)造方法一樣的方法帚戳。但是在同一個(gè)類中玷或,構(gòu)造方法可以重載
public class TestEquals {
int i;
public TestEquals() { i = 0; }
//構(gòu)造方法重載
public TestEquals(int i) { this.i = i }
}
6 Object的equals和hashCode
equals是用來比較兩個(gè)對(duì)象是否相等的,可以重寫該方法來實(shí)現(xiàn)自定義的比較方法片任;而hashCode則是用來獲取對(duì)象的哈希值偏友,也可以重寫該方法。當(dāng)對(duì)象存儲(chǔ)在Map時(shí),是首先利用Object.hashCode判斷是否映射在同一位置对供,若在同一映射位位他,則再使用equals比較兩個(gè)對(duì)象是否相同。
7 equals一樣犁钟,hashCode不一樣有什么問題棱诱?
如果重寫equals導(dǎo)致對(duì)象比較相同而hashCode不一樣,是違反JDK規(guī)范的涝动;而且當(dāng)用HashMap存儲(chǔ)時(shí)迈勋,可能會(huì)存在多個(gè)我們自定義認(rèn)為相同的對(duì)象,這樣會(huì)為我們代碼邏輯埋下坑醋粟。
8 Object.wait和Thread.sheep
Object.wait是需要在synchronized修飾的代碼內(nèi)使用靡菇,會(huì)讓出CPU,并放棄對(duì)對(duì)象鎖的持有狀態(tài)重归。而Thread.sleep則簡(jiǎn)單的掛起,讓出CPU,沒有釋放任何鎖資源
9 finalize方法的使用
- 如果對(duì)象重寫了finalize方法厦凤,jvm會(huì)把當(dāng)前對(duì)象注冊(cè)到FinalizerThread的ReferenceQueue隊(duì)列中鼻吮。對(duì)象沒有其他強(qiáng)引用被當(dāng)垃圾回收時(shí),jvm會(huì)判斷ReferenceQueue存在該對(duì)象较鼓,則暫時(shí)不回收椎木。之后FinalizerThread(獨(dú)立于垃圾回收線程)從ReferenceQueue取出該對(duì)象,執(zhí)行自定義的finalize方法博烂,結(jié)束之后并從隊(duì)列移除該對(duì)象香椎,以便被下次垃圾回收
- finalize會(huì)造成對(duì)象延后回收,可能導(dǎo)致內(nèi)存溢出禽篱,慎用
- finally和finalize區(qū)別
- finally是java關(guān)鍵字畜伐,用來處理異常的,和try搭配使用
- 如果在finally之前return躺率,finally的代碼塊會(huì)執(zhí)行嗎玛界?
try內(nèi)的continue,break,return都不能繞過finally代碼塊的執(zhí)行,try結(jié)束之后finally是一定會(huì)被執(zhí)行的
- 相似的關(guān)鍵字final
- final修飾類悼吱,該類不能被繼承慎框;修飾方法,方法不能被重寫舆绎;修飾變量鲤脏,變量不能指向新的值;修飾數(shù)組吕朵,數(shù)組引用不能指向新數(shù)組,但是數(shù)組元素可以更改
- 如果對(duì)象被final修飾窥突,變量有哪幾種聲明賦值方式努溃?
- fianl修飾普通變量:1、定義時(shí)聲明 2阻问、類內(nèi)代碼塊聲明 3梧税、構(gòu)造器聲明
- fianl修飾靜態(tài)變量:1、定義時(shí)聲明 2称近、類內(nèi)靜態(tài)代碼塊聲明
10 創(chuàng)建對(duì)象有哪幾種方法
- 1第队、使用new創(chuàng)建
- 2、運(yùn)用反射獲取Class,在newInstance()
- 3刨秆、調(diào)用對(duì)象的clone()方法
- 4凳谦、通過反序列化得到,如:
ObjectInputStream.readObject()
11 猜猜創(chuàng)建對(duì)象的數(shù)量
-
String one = new String("Hello");
兩個(gè)對(duì)象和一個(gè)棧變量:一個(gè)棧變量one和一個(gè)new String()實(shí)例對(duì)象衡未、一個(gè)"hello"字符串對(duì)象
- 題外話:string.intern();intern先判斷常量池是否存相同字符串,存在則返回該引用尸执;否則在常量池中記錄堆中首次出現(xiàn)該字符串的引用家凯,并返回該引用。
如果是先執(zhí)行String s = "hello" ;
相當(dāng)于執(zhí)行了intern();先在常量池創(chuàng)建"hello",并且將引用A存入常量池如失,返回給s绊诲。此時(shí)String("hello").intern()會(huì)返回常量池的引用A返回
String one = "hello";
String two = new String("hello");
String three = one.intern();
System.out.println(two == one);
System.out.println(three == one);
result:
false // one雖然不等于two;但是它們具體的char[] value 還是指向同一塊內(nèi)存的
true // one 和 three 引用相同
12 對(duì)象拷貝問題
- 引用對(duì)象的賦值復(fù)制是復(fù)制的引用對(duì)象,
A a = new A(); A b = a;
此時(shí)a和b指向同一塊內(nèi)存的對(duì)象 - 使用Object.clone()方法褪贵,如果字段是值類型(基本類型)則是復(fù)制該值掂之,如果是引用類型則復(fù)制對(duì)象的引用而并非對(duì)象
@Getter static class A implements Cloneable{ private B b; private int index; public A(){ b = new B(); index = 1000; } public A clone()throws CloneNotSupportedException{ return (A)super.clone(); } } static class B{ } public static void main(String[] args) throws Exception{ A a = new A(); A copyA = a.clone(); System.out.println( a.getIndex() == copyA.getIndex() ); System.out.println( a.getB() == copyA.getB() ); }
//返回結(jié)果都是true,引用類型只是復(fù)制了引用值 true true
- 深復(fù)制:重寫clone方法時(shí)使用序列化復(fù)制,(注意需要實(shí)現(xiàn)Cloneable,Serializable)
public A clone() throws CloneNotSupportedException { try { ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(byteOut); out.writeObject(this); ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray()); ObjectInputStream inputStream = new ObjectInputStream(byteIn); return (A) inputStream.readObject(); } catch (Exception e) { e.printStackTrace(); throw new CloneNotSupportedException(e.getLocalizedMessage()); } }