基礎(chǔ)篇:JAVA - Object對(duì)象

1 Object的內(nèi)存結(jié)構(gòu)和指針壓縮了解一下

image
//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)存布局如下


    image
  • 內(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)的
    Collections.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);
    }
    
    List的sort 則調(diào)用了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,則是調(diào)用 Arrays.sort(Object[] a) 勤揩;最終調(diào)用LegacyMergeSort(歸并排序)方法處理
    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也是一個(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使用虐急。
    @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()) );
    }
    
    Collections.sort默認(rèn)是升序排序的箱残,可以看到reverseOrder將順序反過來了; 用了thenComparing的col則是先判斷Pair::getOne的大小止吁,如果相等則判斷Pair::getTwo大小來排序
    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ì)象
image
  • 題外話: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 引用相同
image

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

參考文章

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末板惑,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子偎快,更是在濱河造成了極大的恐慌冯乘,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,383評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件晒夹,死亡現(xiàn)場(chǎng)離奇詭異裆馒,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)丐怯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門喷好,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人读跷,你說我怎么就攤上這事梗搅。” “怎么了效览?”我有些...
    開封第一講書人閱讀 157,852評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵无切,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我丐枉,道長(zhǎng)哆键,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,621評(píng)論 1 284
  • 正文 為了忘掉前任瘦锹,我火速辦了婚禮籍嘹,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘弯院。我一直安慰自己辱士,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,741評(píng)論 6 386
  • 文/花漫 我一把揭開白布听绳。 她就那樣靜靜地躺著颂碘,像睡著了一般。 火紅的嫁衣襯著肌膚如雪辫红。 梳的紋絲不亂的頭發(fā)上凭涂,一...
    開封第一講書人閱讀 49,929評(píng)論 1 290
  • 那天祝辣,我揣著相機(jī)與錄音,去河邊找鬼切油。 笑死蝙斜,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的澎胡。 我是一名探鬼主播孕荠,決...
    沈念sama閱讀 39,076評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼攻谁!你這毒婦竟也來了稚伍?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,803評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤戚宦,失蹤者是張志新(化名)和其女友劉穎个曙,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體受楼,經(jīng)...
    沈念sama閱讀 44,265評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡垦搬,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,582評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了艳汽。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片猴贰。...
    茶點(diǎn)故事閱讀 38,716評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖河狐,靈堂內(nèi)的尸體忽然破棺而出米绕,到底是詐尸還是另有隱情,我是刑警寧澤馋艺,帶...
    沈念sama閱讀 34,395評(píng)論 4 333
  • 正文 年R本政府宣布栅干,位于F島的核電站,受9級(jí)特大地震影響丈钙,放射性物質(zhì)發(fā)生泄漏非驮。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,039評(píng)論 3 316
  • 文/蒙蒙 一雏赦、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧芙扎,春花似錦星岗、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至圈浇,卻和暖如春寥掐,著一層夾襖步出監(jiān)牢的瞬間靴寂,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工召耘, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留百炬,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,488評(píng)論 2 361
  • 正文 我出身青樓污它,卻偏偏與公主長(zhǎng)得像剖踊,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子衫贬,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,612評(píng)論 2 350