從String,StringBuffer,StringBuilder的區別說起

問:String,StringBuffer,StringBuilder的區別

答:

  • String是java中的字符串類型,被final關鍵字修飾,內部使用final修飾的char[] value存放字符,和final修飾的count來存儲數組長度。因為是final修飾的類,所以不能被繼承。因為final修飾的 value和count,所以不可被改變,所以String是不可變數據,可以被線程安全的共享。所以總結下來,String本質是字符數組,不可繼承切不可改變。對String進行操作時,每次都是生成新的String對象。
  • 因為String的特性,java的字符串運算比較耗性能,所以提供了可變的字符串實現,也就是StringBuilder。StringBuilder繼承了AbstractStringBuilder,AbstractStringBuilder也是通過一個char類型的數組value和count實現的。value和count是可變的,所以append方法中會把新的字符添加到value中并且改變count的值。當value放不下的時候,會進行擴容操作。擴容是將value容量變成count+1再乘以2,如果還小于所需的最小長度,就擴容的所需的最小長度。擴容之后用Arrays.copyOf復制字符。
  • StringBuffer跟StringBuilder的實現原理一樣,只是所有的方法都加了同步操作,所以效率會低,但是線程安全。

問: 下面一行代碼到底做了什么呢?

String s = new String("ddd")

答:

  • 在編譯期,編譯器到常量池里面尋找有沒有"ddd"這個字符串,如果沒有則在常量池中開辟空間存放"ddd";在運行時,在堆內存中開辟空間存放String類型的對象,然后在棧中開辟空間存放變量s。

問:既然講到常量池,堆內存,棧,那就講一下這些都是什么

答:

  • 堆內存,棧內存這些都是jvm的邏輯內存模型。java的運行期數據區分為5部分,分別是方法區,java堆,java棧,程序計數器和本地方法棧。其中方法區和java堆是所有線程共享的,其他的都是線程私有的。

  • 程序計數器,是一塊很小的內存空間,可以看作是當前線程執行的字節碼的行號。存儲的是正在執行的java方法的虛擬機字節碼指令地址。如果線程在執行native方法,則存儲0.此內存區域是唯一沒有OutOfMemoryError的區域。

  • 方法區,jvm內部存儲類型信息的地方。類型信息是由類加載器加載類的時候生成的。一個類要被使用會由java虛擬機對.class文件進行裝載,連接(驗證,準備,解析)和初始化。方法區會存儲類型信息,字段信息,方法信息和其他信息。其他信息中就包含上一個問題說到的常量池。jvm為每一個已加載的類分配一個常量池,包含實際的常量(String,Integer和floating pointer常量)和對類型,字段和方法的符號引用。

    • 方法區是所有線程共享的,所以數據必須被設計成線程安全的。例如,假如同時有兩個線程都企圖訪問方法區中的同一個類,而這個類還沒有被裝入JVM,那么只允許一個線程去裝載它,而其它線程必須等待
    • 方法區大小可以動態調整,可以通過-XX:PermSize 和 -XX:MaxPermSize 參數限制方法區的大小。
    • 方法區也會發生gc,主要是針對常量池的回收和對類型的卸載
    • HotSpot會用永久代來實現方法區
  • java棧,也叫虛擬機棧,是描述java方法執行的內存模型的。每個方法被執行的時候,都會生成一個棧幀用于存儲局部變量表操作數棧動態鏈接方法出口等信息。java棧是線程私有的,隨線程的生命周期。每一個方法被調用到執行完成,就對應著一個棧幀從入棧到出棧。對于執行引擎來說,只有棧頂的棧幀是有效的,被稱作當前棧幀。

    • 局部變量表,用于存放方法參數和方法內部定義的局部變量。局部變量表以變量槽(slot)為單位,32位系統中,一個slot可以存放32位以內的數據類型,包括(boolean,byte,short,int,float,reference和returnAddress)。returnAddress類型是為字節碼指令jsr、jsr_w和ret服務的,它指向了一條字節碼指令的地址。
    • 操作數棧,和局部變量表一樣被設計成以字長為單位的數組。但操作只能按照棧的方式來操作。也是操作一樣的數據類型。byte,short和char在入棧之前會轉換成int類型的。虛擬機的大部分工作都是基于操作數棧。比如加法操作,就是先往棧里壓入兩個int類型的數據,再彈出兩個變量執行iadd操作,然后將計算的值存的局部變量表里。偽指令如下:

    <pre>
    begin
    iload_0 // push the int in local variable 0 ontothe stack
    iload_1 //push the int in local variable 1 onto the stack
    iadd // pop two ints, add them, push result
    istore_2 // pop int, store into local variable 2
    end
    </pre>

    • 動態鏈接,虛擬機運行的時候,運行時常量池會保存大量的符號引用,這些符號引用可以看成是每個方法的間接引用。如果代表棧幀A的方法想調用代表棧幀B的方法,那么這個虛擬機的方法調用指令就會以B方法的符號引用作為參數,但是因為符號引用并不是直接指向代表B方法的內存位置,所以在調用之前還必須要將符號引用轉換為直接引用,然后通過直接引用才可以訪問到真正的方法。如果符號引用是在類加載階段或者第一次使用的時候轉化為直接應用,那么這種轉換成為靜態解析,如果是在運行期間轉換為直接引用,那么這種轉換就成為動態連接
    • 返回地址,方法返回有兩種情況,1是正常退出,2是異常退出。正常退出要根據方法定義看是否返回數據給上層調用者,調用者的pc計數器的值就可以作為返回地址;異常退出要根據異常處理表來確定返回地址。方法退出時要恢復上層方法的局部變量表和操作數棧,如果有返回值的話,就把返回值壓入調用者棧幀的操作數棧,并把pc計數器的值調整為調用入口的下一條指令
  • 本地方法棧,與虛擬機棧類似。只不過虛擬機棧為執行java方法服務,本地方法棧為執行native方法服務。

  • java堆,堆是jvm管理的最大的內存塊,所有的線程共享,用于存放對象實例。垃圾回收也主要發生在這塊區域中。

    • 堆的大小可以通過 -Xms和-Xmx配置
    • 從內存回收的角度看,現在收集器都采用分代收集算法。將內存分為新生代老年代。新生代又分為Eden區Survivor區,Survivor區又分為from區to區
    • 老年代用于存放經過新生代GC后還存活的對象和大對象
    • 通過-Xmn指定新生代大小,通過-XX:PretenureSizeThreshold設置多大的對象直接分配在老年代中
    • 內存分配過程
      • JVM 會試圖為相關Java對象在Eden Space中初始化一塊內存區域。
      • 當Eden空間足夠時,內存申請結束;否則到下一步。
      • JVM 試圖釋放在Eden中所有不活躍的對象(這屬于1或更高級的垃圾回收)。釋放后若Eden空間仍然不足以放入新對象,則試圖將部分Eden中活躍對象放入Survivor區。
      • Survivor區被用來作為Eden及Old的中間交換區域,當Old區空間足夠時,Survivor區的對象會被移到Old區,否則會被保留在Survivor區。
      • 當Old區空間不夠時,JVM 會在Old區進行完全的垃圾收集(0級)。
      • 完全垃圾收集后,若Survivor及Old區仍然無法存放從Eden復制過來的部分對象,導致JVM無法在Eden區為新對象創建內存區域,則出現“outofmemory”錯誤。

問:剛剛講到java內存中的垃圾回收,可以說一下你理解的垃圾回收嗎

答:

  • 垃圾回收機制最早出現在lisp語言中,后來java借鑒了過來。垃圾回收主要是讓程序自己管理內存的回收,而不用程序員手動去回收內存。一般來說,java程序員可以不重視java內存分配和垃圾回收,但充分了解jvm的GC機制可以讓程序員更好的利用計算機資源。
  • gc要為程序員管理內存,就要確定哪些內存需要回收,什么時候回收和怎么回收。
    • 確定哪些內存需要回收,有兩種方法:
      • 引用計數法,創建對象的時候,為這個對象在堆棧中分配內存,同時產生一個引用計數器并加1,對象的一個引用銷毀的時候,引用計數器減1,引用計數器為0的時候,表示這個對象可以回收了。jdk1.2之前用的這種方法。但是這樣會有循環引用問題,就是A保留B的引用,B保留A的引用,雖然都沒有外部引用了,但引用計數器不為0,所以不能回收。
      • 根搜索算法,利用圖論中的理論,從一個節點GC ROOT根開始搜索,所有子引用節點搜索完成后,剩余未被搜索到的節點即無用節點,可以被回收。
        • 虛擬機棧(棧幀中的本地變量表)中引用的對象
        • 方法區中靜態屬性引用的對象
        • 方法區中常量引用的對象
        • 本地方法棧中引用的對象
    • 兩種判定算法都用到了引用,去判斷對象是否可以回收。一般引用分為4種類型,強引用,軟引用,弱引用和虛引用。
      • 強引用就是我們平時代碼中,指向新建對象的變量。只要強引用在,對象就不會被回收。
      • 軟引用描述一些還有用但非必要存在的對象,內存不足時才會被回收,一般和引用隊列(ReferenceQueue)共同使用做內存敏感的高速緩存
      • 弱引用也是描述非必要對象,不管內存足不足都會回收
      • 虛引用不影響對象的生命周期,虛引用必須和引用隊列關聯使用,程序可以判斷引用隊列中是否加入虛引用來判斷對象是否要被回收
    • 解決了哪些內存要回收的問題之后,再就是怎么回收的問題了。垃圾回收的策略有四種,分別是標記-清除算法,復制算法,標記-整理算法和分代收集算法
      • 標記-清除算法分兩個階段,先標識哪些內存需要清除,然后統一回收要清除的對象。這樣有兩個問題,標記和清除的效率都不高,而且會產生內存碎片問題
      • 復制算法就是把內存平均分配成兩塊,每次使用其中一塊,內存回收時,把存活的對象復制到另一塊上。特點是實現簡單效率高,適用于短生命周前對象,但可用內存少了一半
      • 標記-整理算法,對于存活率較高的對象,如果采用復制算法就會導致多次復制,效率十分低。老年代對象就有這種特點。標記-整理算法是一種老年代回收算法,過程跟標記-清除算法類似。不同的是在第二階段不會直接清除,而是將對象向一端移動,然后直接清理掉端邊界以外的內存
      • 分代算法就是將內存分為不同的塊,根據每塊內存的特點選擇使用合適的回收算法。一般新生代采用復制算法,老年代采用標記整理算法
    • 回收算法是理論基礎,jvm提供了多種算法的實現,也就是我們說的垃圾回收器
      • Serial回收器,采用復制算法,回收時暫停所有用戶線程。適合單核cpu。是新生代垃圾回收器
      • ParNew,Serial的多線程版本。適合多cpu場景。出了Serial,只有它可以和CMS配合使用
      • Parallel Scavenge,也是新生代收集器,目標是提高吞吐量,適應主要適合在后臺運算而不需要太多交互的任務。
      • Serial Old,是老年代的單線程收集器,作為CMS收集器的后備預案
      • Parallel Old,是ParallelScavenge收集器的老年代版本,使用多線程和“標記-整理”算法。
      • CMS是一種以獲取最短回收停頓時間為目標的收集器,優點是并發收集,停頓低。缺點是對cpu敏感,沒法處理浮動垃圾,會產生內存碎片問題
      • G1,可以參考深入理解G1
        • G1對空間壓縮有優勢
        • 通過將內存分成區域(region)的方式避免內存碎片
        • Eden, Survivor, Old區不再固定、在內存使用效率上來說更靈活
        • G1可以通過設置預期停頓時間(Pause Time)來控制垃圾收集時間避免應用雪崩現象
        • G1在回收內存后會馬上同時做合并空閑內存的工作、而CMS默認是在STW(stop the world)的時候做
        • G1會在Young GC中使用、而CMS只能在O區使用
    • 觸發GC
      • Minor GC(新生代回收)的觸發條件比較簡單,Eden空間不足就開始進行Minor GC回收新生代。
      • 而Full GC(老年代回收,一般伴隨一次Minor GC)則有幾種觸發條件:
        1. 老年代空間不足
        2. PermSpace空間不足
        3. 統計得到的Minor GC晉升到老年代的平均大小大于老年代的剩余空間
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,431評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,637評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,555評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,900評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,629評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,976評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,976評論 3 448
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,139評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,686評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,411評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,641評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,129評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,820評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,233評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,567評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,362評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,604評論 2 380

推薦閱讀更多精彩內容