基礎篇:Object對象

1 Object的內存結構和指針壓縮瞭解一下

//hotspot的oop.hpp文件中class oopDesc
class oopDesc {
  friend class VMStructs;
  private:
  volatile markOop  _mark; //對象部分
  union _metadata {  // klassOop 類元數據指針
    Klass*      _klass;   
    narrowKlass _compressed_klass;
  } _metadata;
  • Object的實例數據內存使用三部分組成的,對象頭實際數據區域內存對齊區
  • 對象頭佈局以下:主要和鎖,hashcode,垃圾回收有關;因爲鎖機制的內容篇幅過長,這裏就很少解釋了;和鎖相關的markWord(markOop)內存佈局以下

  • 內存對齊區是什麼? HotSpot VM的自動內存管理系統要求對象起始地址必須是8字節的整數倍,換句話說就是對象的大小必須是8字節的整數倍。所以當對象實例數據部分沒有對齊的話,就須要經過對齊填充來補全。
  • 內存對齊好處html

    • 有利於內存的管理
    • 更快的CPU讀取,CPU從內存獲取數據,並非一個個字節的讀取,而是按CPU能處理的長度獲取,如32位機,是4個字節的內存塊;當只需其中兩個字節時,則由內存處理器處理挑選。若是須要三個字節分佈在兩個不一樣內存塊(四字節的內存塊),則須要讀取內存兩次(若是是存在同一內存塊只需一次讀取)。而當對象按必定的規則合理對齊時,CPU就能夠最少地請求內存,加快CPU的執行速度
  • 指針壓縮java

    • 在上圖能夠看到,在64位jvm裏Object的MarkWord會比32位的大一倍;其實klassOop也佔了64位(數組長度部分則是固定四字節)。指針的寬度增大,可是對於堆內存小於4G的,好像也用不到64位的指針。這能夠優化嗎?答案是就是指針壓縮
    • 指針壓縮的原理是利用jvm植入壓縮指令,進行編碼、解碼
    • 哪些信息會被壓縮數組

      • 會被壓縮對象:類屬性、對象頭信息、對象引用類型、對象數組類型
      • 不被壓縮對象:本地變量,堆棧元素,入參,返回值,NULL這些指針
    • 指針壓縮開啓,klassOop大小能夠由64bit變成32bit;對象的大小能夠看看下面的具體對比:JVM - 剖析JAVA對象頭OBJECT HEADER之指針壓縮
    public static void main(String[] args){
        Object a = new Object(); // 16B   關閉壓縮仍是16B,須要是8B倍數;12B+填充的4B
        int[] arr = new int[10]; // 24B   關閉壓縮則是16B
    }
    
    public class ObjectNum {
        //8B mark word
        //4B Klass Pointer   若是關閉壓縮則佔用8B
        //-XX:-UseCompressedClassPointers或-XX:-UseCompressedOops,
        int id;        //4B
        String name;   //4B  若是關閉壓縮則佔用8B
        byte b;        //1B  實際內存可能會填充到4B
        Object o;      //4B  若是關閉壓縮則佔用8B
    }
    • 爲何開啓指針壓縮時,堆內存最好不要超過32G,指針使用32個bit,爲何最大可以使用內存不是4G而是32G
<br/>jvm要求對象起始位置對齊8字節的倍數,能夠利用這點提高選址範圍,理論上能夠提高到`2^11 * 4G`。不過jvm將只是指針左移三位,所以`2^3 * 4G = 32G`。若是**大於32G**,指針壓縮會失效。若是GC堆大小在 **4G如下**,直接砍掉高32位,避免了編碼解碼過程
- 啓用指針壓縮`-XX:+UseCompressedOops`(**默認開啓**),禁止指針壓縮:`-XX:-UseCompressedOops`

2 Object的幾種基本方法

  • 本地方法jvm

    • private static native void registerNatives() 將Object定義的本地方法和java程序連接起來。對JNI方面瞭解很少,就很少解釋了Object類中的registerNatives
    • public final native Class<?> getClass() 獲取java的Class元數據
    • public native int hashCode() 獲取對象的哈希Code
    • protected native Object clone() throws CloneNotSupportedException 得到對象的克隆對象,淺複製
    • public final native void notify() 喚醒等待對象鎖waitSet隊列中的一個線程
    • public final native void notifyAll() 相似notify(),喚醒等待對象鎖waitSet隊列中的所有線程
    • public final native void wait(long timeout) 釋放對象鎖,進入對象鎖的waitSet隊列
  • 普通方法ide

    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)實現的
    public final void wait() throws InterruptedException;
    wait(long timeout, int nanos)、wait() 
    //jvm回收對象前,會特地調用此方法 
    protected void finalize() throws Throwable;

3 == 、 equals、Comparable.compareTo、Comparator.compara 四種比較方法

如不指定排序順序,java裏的默認排序順序是升序的,從小到大oop

  • ==, (A)對於基本類型之間的比較是值 (B)基本類型和封裝類型比較也是值比較 (C)對於引用類型之間的比較則是內存地址
  • equals(Object o), 在Object基本方法裏能夠看到public boolean equals(Object obj) { return (this == obj);} 是使用 == 去比較的。equals方法的好處是咱們能夠重寫該方法
  • Comparable.compareTo 是接口Comparable裏的抽象方法;若是對象實現該接口,可以使用Collections.sort(List< T> col)進行排序。接下來看看源碼怎麼實現的佈局

    Collections.java
    //Collections.sort(List<T> list),調用的是List的sort方法
    public static <T extends Comparable<? super T>> void sort(List<T> list) {
        list.sort(null);
    }

    List的sort 則調用了Arrays.sort優化

    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);
        }
    }

    若是Comparator c 爲null,則是調用 Arrays.sort(Object[] a) ;最終調用LegacyMergeSort(歸併排序)方法處理this

    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);
        }
    }

    LegacyMergeSort方法裏的一段代碼;最終底層是使用歸併排序和compareTo來排序編碼

    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也是一個接口,不過提供了更豐富的操做,須要實現int compare(T o1, T o2)方法
    <br/>Comparator提供了經常使用的幾個靜態方法thenComparing、reversed、reverseOrder(操做對象須要實現Comparator或者Comparable);可配合List.sort、Stream.sorted、Collections.sort使用。

    @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());
        col.stream().sorted(Comparator.comparing(Pair::getOne).thenComparing(Pair::getTwo))
                .forEach(item ->  System.out.println(item.toString()) );
    }

    Collections.sort默認是升序排序的,能夠看到reverseOrder將順序反過來了; 用了thenComparing的col則是先判斷Pair::getOne的大小,若是相等則判斷Pair::getTwo大小來排序

    result:
    4-6
    4-2
    1-3
    ----------------
    1-3
    4-2
    4-6

4 方法的重寫和重載

  • 方法的重寫是指子類定義和父類方法的名稱、參數及順序一致的方法;須要注意的是,子類重寫方法修飾符不能更加嚴格,就是說父類方法的修飾符是protected,子類不能使用private修飾而可用public,拋出的異常也不能比父類方法定義的更廣
  • 方法的重載則是同一個類中定義和已有方法的名稱一致而參數及順序不一致的方法,(返回值不能決定方法的重載)
  • 重載的方法在編譯時就可肯定(編譯時多態),而重寫的方法須要在運行時肯定(運行時多態,咱們常說的多態)
    <br/>多態的三個必要條件 一、有繼承關係 二、子類重寫父類方法 三、父類引用指向子類對象

5 構造方法是否可被重寫

構造方法是每個類獨有的,並不能被子類繼承,由於構造方法沒有返回值,子類定義不了和父類的構造方法同樣的方法。可是在同一個類中,構造方法能夠重載

public class TestEquals {
    int i;
    public TestEquals() {   i = 0; }
    //構造方法重載
    public TestEquals(int i) {   this.i = i } 
}

6 Object的equals和hashCode

equals是用來比較兩個對象是否相等的,能夠重寫該方法來實現自定義的比較方法;而hashCode則是用來獲取對象的哈希值,也能夠重寫該方法。當對象存儲在Map時,是首先利用Object.hashCode判斷是否映射在同一位置,若在同一映射位,則再使用equals比較兩個對象是否相同。

7 equals同樣,hashCode不同有什麼問題?

若是重寫equals致使對象比較相同而hashCode不同,是違反JDK規範的;並且當用HashMap存儲時,可能會存在多個咱們自定義認爲相同的對象,這樣會爲咱們代碼邏輯埋下坑。

8 Object.wait和Thread.sheep

Object.wait是須要在synchronized修飾的代碼內使用,會讓出CPU,並放棄對對象鎖的持有狀態。而Thread.sleep則簡單的掛起,讓出CPU,沒有釋聽任何鎖資源

9 finalize方法的使用

  • 若是對象重寫了finalize方法,jvm會把當前對象註冊到FinalizerThread的ReferenceQueue隊列中。對象沒有其餘強引用被當垃圾回收時,jvm會判斷ReferenceQueue存在該對象,則暫時不回收。以後FinalizerThread(獨立於垃圾回收線程)從ReferenceQueue取出該對象,執行自定義的finalize方法,結束以後並從隊列移除該對象,以便被下次垃圾回收
  • finalize會形成對象延後回收,可能致使內存溢出,慎用
  • finally和finalize區別

    • finallyjava關鍵字是用來處理異常的,和try搭配使用
    • 若是在finally以前return,finally的代碼塊會執行嗎? <br/>try內的continue,break,return都不能繞過finally代碼塊的執行,try結束以後finally是必定會被執行的
  • 類似的關鍵字final

    • final修飾類,該類不能被繼承;修飾方法,方法不能被重寫;修飾變量,變量不能指向新的值;修飾數組,數組引用不能指向新數組,可是數組元素能夠更改
    • 若是對象被final修飾,變量有哪幾種聲明賦值方式?
    • fianl修飾普通變量:一、定義時聲明 二、類內代碼塊聲明 三、構造器聲明
    • fianl修飾靜態變量:一、定義時聲明 二、類內靜態代碼塊聲明

10 建立對象有哪幾種方法

  • 一、使用new建立
  • 二、運用反射獲取Class,在newInstance()
  • 三、調用對象的clone()方法
  • 四、經過反序列化獲得,如:ObjectInputStream.readObject()

11 猜猜建立對象的數量

  • String one = new String("Hello");

<br/> 兩個對象和一個棧變量:一個棧變量one和一個new String()實例對象、一個"hello"字符串對象

  • 題外話:string.intern();intern先判斷常量池是否存相同字符串,存在則返回該引用;不然在常量池中記錄堆中首次出現該字符串的引用,並返回該引用。

<br/>若是是先執行 String s = "hello" ;至關於執行了intern();先在常量池建立"hello",而且將引用A存入常量池,返回給s。此時String("hello").intern()會返回常量池的引用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 仍是指向同一塊內存的
    true  // one 和 three 引用相同

12 對象拷貝問題

  • 引用對象的賦值複製是複製的引用對象,A a = new A(); A b = a; 此時a和b指向同一塊內存的對象
  • 使用Object.clone()方法,若是字段是值類型(基本類型)則是複製該值,若是是引用類型則複製對象的引用而並不是對象

    @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() );
    }
    //返回結果都是true,引用類型只是複製了引用值
    true
    true
  • 深複製:重寫clone方法時使用序列化複製,(注意須要實現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());
            }
        }

關注公衆號,你們一塊兒交流

參考文章

相關文章
相關標籤/搜索