問:String沸呐,StringBuffer,StringBuilder的區(qū)別
答:
- String是java中的字符串類型呢燥,被final關鍵字修飾崭添,內(nèi)部使用final修飾的char[] value存放字符,和final修飾的count來存儲數(shù)組長度叛氨。因為是final修飾的類呼渣,所以不能被繼承。因為final修飾的 value和count寞埠,所以不可被改變屁置,所以String是不可變數(shù)據(jù),可以被線程安全的共享畸裳。所以總結下來缰犁,String本質(zhì)是字符數(shù)組,不可繼承切不可改變。對String進行操作時帅容,每次都是生成新的String對象颇象。
- 因為String的特性,java的字符串運算比較耗性能并徘,所以提供了可變的字符串實現(xiàn)遣钳,也就是StringBuilder。StringBuilder繼承了AbstractStringBuilder麦乞,AbstractStringBuilder也是通過一個char類型的數(shù)組value和count實現(xiàn)的蕴茴。value和count是可變的,所以append方法中會把新的字符添加到value中并且改變count的值姐直。當value放不下的時候倦淀,會進行擴容操作。擴容是將value容量變成count+1再乘以2声畏,如果還小于所需的最小長度撞叽,就擴容的所需的最小長度。擴容之后用Arrays.copyOf復制字符插龄。
- StringBuffer跟StringBuilder的實現(xiàn)原理一樣愿棋,只是所有的方法都加了同步操作,所以效率會低均牢,但是線程安全糠雨。
問: 下面一行代碼到底做了什么呢?
String s = new String("ddd")
答:
- 在編譯期徘跪,編譯器到常量池里面尋找有沒有"ddd"這個字符串甘邀,如果沒有則在常量池中開辟空間存放"ddd";在運行時垮庐,在堆內(nèi)存中開辟空間存放String類型的對象鹃答,然后在棧中開辟空間存放變量s。
問:既然講到常量池突硝,堆內(nèi)存,棧置济,那就講一下這些都是什么
答:
堆內(nèi)存解恰,棧內(nèi)存這些都是jvm的邏輯內(nèi)存模型。java的運行期數(shù)據(jù)區(qū)分為5部分浙于,分別是方法區(qū)护盈,java堆,java棧羞酗,程序計數(shù)器和本地方法棧腐宋。其中方法區(qū)和java堆是所有線程共享的,其他的都是線程私有的。
程序計數(shù)器胸竞,是一塊很小的內(nèi)存空間欺嗤,可以看作是當前線程執(zhí)行的字節(jié)碼的行號。存儲的是正在執(zhí)行的java方法的虛擬機字節(jié)碼指令地址卫枝。如果線程在執(zhí)行native方法煎饼,則存儲0.此內(nèi)存區(qū)域是唯一沒有OutOfMemoryError的區(qū)域。
-
方法區(qū)校赤,jvm內(nèi)部存儲類型信息的地方吆玖。類型信息是由類加載器加載類的時候生成的。一個類要被使用會由java虛擬機對.class文件進行裝載马篮,連接(驗證沾乘,準備,解析)和初始化浑测。方法區(qū)會存儲類型信息翅阵,字段信息,方法信息和其他信息尽爆。其他信息中就包含上一個問題說到的常量池怎顾。jvm為每一個已加載的類分配一個常量池,包含實際的常量(String漱贱,Integer和floating pointer常量)和對類型槐雾,字段和方法的符號引用。
- 方法區(qū)是所有線程共享的幅狮,所以數(shù)據(jù)必須被設計成線程安全的募强。例如,假如同時有兩個線程都企圖訪問方法區(qū)中的同一個類崇摄,而這個類還沒有被裝入JVM擎值,那么只允許一個線程去裝載它,而其它線程必須等待
- 方法區(qū)大小可以動態(tài)調(diào)整逐抑,可以通過-XX:PermSize 和 -XX:MaxPermSize 參數(shù)限制方法區(qū)的大小鸠儿。
- 方法區(qū)也會發(fā)生gc,主要是針對常量池的回收和對類型的卸載
- HotSpot會用永久代來實現(xiàn)方法區(qū)
-
java棧厕氨,也叫虛擬機棧进每,是描述java方法執(zhí)行的內(nèi)存模型的。每個方法被執(zhí)行的時候命斧,都會生成一個棧幀用于存儲局部變量表田晚,操作數(shù)棧,動態(tài)鏈接国葬,方法出口等信息贤徒。java棧是線程私有的芹壕,隨線程的生命周期。每一個方法被調(diào)用到執(zhí)行完成接奈,就對應著一個棧幀從入棧到出棧踢涌。對于執(zhí)行引擎來說,只有棧頂?shù)臈怯行У啮瓿茫环Q作當前棧幀斯嚎。
- 局部變量表,用于存放方法參數(shù)和方法內(nèi)部定義的局部變量挨厚。局部變量表以變量槽(slot)為單位堡僻,32位系統(tǒng)中,一個slot可以存放32位以內(nèi)的數(shù)據(jù)類型疫剃,包括(boolean钉疫,byte,short巢价,int牲阁,float,reference和returnAddress)壤躲。returnAddress類型是為字節(jié)碼指令jsr城菊、jsr_w和ret服務的,它指向了一條字節(jié)碼指令的地址碉克。
- 操作數(shù)棧凌唬,和局部變量表一樣被設計成以字長為單位的數(shù)組。但操作只能按照棧的方式來操作漏麦。也是操作一樣的數(shù)據(jù)類型客税。byte,short和char在入棧之前會轉換成int類型的撕贞。虛擬機的大部分工作都是基于操作數(shù)棧更耻。比如加法操作,就是先往棧里壓入兩個int類型的數(shù)據(jù)捏膨,再彈出兩個變量執(zhí)行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>- 動態(tài)鏈接号涯,虛擬機運行的時候,運行時常量池會保存大量的符號引用熬北,這些符號引用可以看成是每個方法的間接引用。如果代表棧幀A的方法想調(diào)用代表棧幀B的方法诚隙,那么這個虛擬機的方法調(diào)用指令就會以B方法的符號引用作為參數(shù),但是因為符號引用并不是直接指向代表B方法的內(nèi)存位置起胰,所以在調(diào)用之前還必須要將符號引用轉換為直接引用久又,然后通過直接引用才可以訪問到真正的方法巫延。如果符號引用是在類加載階段或者第一次使用的時候轉化為直接應用,那么這種轉換成為靜態(tài)解析地消,如果是在運行期間轉換為直接引用炉峰,那么這種轉換就成為動態(tài)連接。
- 返回地址脉执,方法返回有兩種情況疼阔,1是正常退出,2是異常退出半夷。正常退出要根據(jù)方法定義看是否返回數(shù)據(jù)給上層調(diào)用者婆廊,調(diào)用者的pc計數(shù)器的值就可以作為返回地址;異常退出要根據(jù)異常處理表來確定返回地址巫橄。方法退出時要恢復上層方法的局部變量表和操作數(shù)棧淘邻,如果有返回值的話,就把返回值壓入調(diào)用者棧幀的操作數(shù)棧湘换,并把pc計數(shù)器的值調(diào)整為調(diào)用入口的下一條指令
本地方法棧宾舅,與虛擬機棧類似。只不過虛擬機棧為執(zhí)行java方法服務彩倚,本地方法棧為執(zhí)行native方法服務筹我。
-
java堆,堆是jvm管理的最大的內(nèi)存塊帆离,所有的線程共享蔬蕊,用于存放對象實例。垃圾回收也主要發(fā)生在這塊區(qū)域中盯质。
- 堆的大小可以通過 -Xms和-Xmx配置
- 從內(nèi)存回收的角度看袁串,現(xiàn)在收集器都采用分代收集算法。將內(nèi)存分為新生代和老年代呼巷。新生代又分為Eden區(qū)和Survivor區(qū)囱修,Survivor區(qū)又分為from區(qū)和to區(qū)
- 老年代用于存放經(jīng)過新生代GC后還存活的對象和大對象
- 通過-Xmn指定新生代大小,通過-XX:PretenureSizeThreshold設置多大的對象直接分配在老年代中
- 內(nèi)存分配過程
- JVM 會試圖為相關Java對象在Eden Space中初始化一塊內(nèi)存區(qū)域王悍。
- 當Eden空間足夠時破镰,內(nèi)存申請結束;否則到下一步压储。
- JVM 試圖釋放在Eden中所有不活躍的對象(這屬于1或更高級的垃圾回收)鲜漩。釋放后若Eden空間仍然不足以放入新對象,則試圖將部分Eden中活躍對象放入Survivor區(qū)集惋。
- Survivor區(qū)被用來作為Eden及Old的中間交換區(qū)域孕似,當Old區(qū)空間足夠時,Survivor區(qū)的對象會被移到Old區(qū)刮刑,否則會被保留在Survivor區(qū)喉祭。
- 當Old區(qū)空間不夠時养渴,JVM 會在Old區(qū)進行完全的垃圾收集(0級)。
- 完全垃圾收集后泛烙,若Survivor及Old區(qū)仍然無法存放從Eden復制過來的部分對象理卑,導致JVM無法在Eden區(qū)為新對象創(chuàng)建內(nèi)存區(qū)域,則出現(xiàn)“outofmemory”錯誤蔽氨。
問:剛剛講到java內(nèi)存中的垃圾回收藐唠,可以說一下你理解的垃圾回收嗎
答:
- 垃圾回收機制最早出現(xiàn)在lisp語言中,后來java借鑒了過來鹉究。垃圾回收主要是讓程序自己管理內(nèi)存的回收宇立,而不用程序員手動去回收內(nèi)存。一般來說坊饶,java程序員可以不重視java內(nèi)存分配和垃圾回收泄伪,但充分了解jvm的GC機制可以讓程序員更好的利用計算機資源。
- gc要為程序員管理內(nèi)存匿级,就要確定哪些內(nèi)存需要回收蟋滴,什么時候回收和怎么回收。
- 確定哪些內(nèi)存需要回收痘绎,有兩種方法:
- 引用計數(shù)法津函,創(chuàng)建對象的時候,為這個對象在堆棧中分配內(nèi)存孤页,同時產(chǎn)生一個引用計數(shù)器并加1尔苦,對象的一個引用銷毀的時候,引用計數(shù)器減1行施,引用計數(shù)器為0的時候允坚,表示這個對象可以回收了。jdk1.2之前用的這種方法蛾号。但是這樣會有循環(huán)引用問題稠项,就是A保留B的引用,B保留A的引用鲜结,雖然都沒有外部引用了展运,但引用計數(shù)器不為0,所以不能回收精刷。
- 根搜索算法拗胜,利用圖論中的理論,從一個節(jié)點GC ROOT根開始搜索怒允,所有子引用節(jié)點搜索完成后埂软,剩余未被搜索到的節(jié)點即無用節(jié)點,可以被回收纫事。
- 虛擬機棧(棧幀中的本地變量表)中引用的對象
- 方法區(qū)中靜態(tài)屬性引用的對象
- 方法區(qū)中常量引用的對象
- 本地方法棧中引用的對象
- 兩種判定算法都用到了引用仰美,去判斷對象是否可以回收迷殿。一般引用分為4種類型,強引用咖杂,軟引用,弱引用和虛引用蚊夫。
- 強引用就是我們平時代碼中诉字,指向新建對象的變量。只要強引用在知纷,對象就不會被回收壤圃。
- 軟引用描述一些還有用但非必要存在的對象,內(nèi)存不足時才會被回收琅轧,一般和引用隊列(ReferenceQueue)共同使用做內(nèi)存敏感的高速緩存
- 弱引用也是描述非必要對象伍绳,不管內(nèi)存足不足都會回收
- 虛引用不影響對象的生命周期,虛引用必須和引用隊列關聯(lián)使用乍桂,程序可以判斷引用隊列中是否加入虛引用來判斷對象是否要被回收
- 解決了哪些內(nèi)存要回收的問題之后冲杀,再就是怎么回收的問題了。垃圾回收的策略有四種睹酌,分別是標記-清除算法权谁,復制算法,標記-整理算法和分代收集算法
- 標記-清除算法分兩個階段憋沿,先標識哪些內(nèi)存需要清除旺芽,然后統(tǒng)一回收要清除的對象。這樣有兩個問題辐啄,標記和清除的效率都不高采章,而且會產(chǎn)生內(nèi)存碎片問題
- 復制算法就是把內(nèi)存平均分配成兩塊,每次使用其中一塊壶辜,內(nèi)存回收時悯舟,把存活的對象復制到另一塊上。特點是實現(xiàn)簡單效率高士复,適用于短生命周前對象图谷,但可用內(nèi)存少了一半
- 標記-整理算法,對于存活率較高的對象阱洪,如果采用復制算法就會導致多次復制便贵,效率十分低。老年代對象就有這種特點冗荸。標記-整理算法是一種老年代回收算法承璃,過程跟標記-清除算法類似。不同的是在第二階段不會直接清除蚌本,而是將對象向一端移動盔粹,然后直接清理掉端邊界以外的內(nèi)存
- 分代算法就是將內(nèi)存分為不同的塊隘梨,根據(jù)每塊內(nèi)存的特點選擇使用合適的回收算法。一般新生代采用復制算法舷嗡,老年代采用標記整理算法
- 回收算法是理論基礎轴猎,jvm提供了多種算法的實現(xiàn),也就是我們說的垃圾回收器
- Serial回收器进萄,采用復制算法捻脖,回收時暫停所有用戶線程。適合單核cpu中鼠。是新生代垃圾回收器
- ParNew可婶,Serial的多線程版本。適合多cpu場景援雇。出了Serial矛渴,只有它可以和CMS配合使用
- Parallel Scavenge,也是新生代收集器惫搏,目標是提高吞吐量具温,適應主要適合在后臺運算而不需要太多交互的任務。
- Serial Old晶府,是老年代的單線程收集器桂躏,作為CMS收集器的后備預案
- Parallel Old,是ParallelScavenge收集器的老年代版本川陆,使用多線程和“標記-整理”算法剂习。
- CMS是一種以獲取最短回收停頓時間為目標的收集器,優(yōu)點是并發(fā)收集较沪,停頓低鳞绕。缺點是對cpu敏感,沒法處理浮動垃圾尸曼,會產(chǎn)生內(nèi)存碎片問題
- G1们何,可以參考深入理解G1
- G1對空間壓縮有優(yōu)勢
- 通過將內(nèi)存分成區(qū)域(region)的方式避免內(nèi)存碎片
- Eden, Survivor, Old區(qū)不再固定、在內(nèi)存使用效率上來說更靈活
- G1可以通過設置預期停頓時間(Pause Time)來控制垃圾收集時間避免應用雪崩現(xiàn)象
- G1在回收內(nèi)存后會馬上同時做合并空閑內(nèi)存的工作控轿、而CMS默認是在STW(stop the world)的時候做
- G1會在Young GC中使用冤竹、而CMS只能在O區(qū)使用
- 觸發(fā)GC
- Minor GC(新生代回收)的觸發(fā)條件比較簡單,Eden空間不足就開始進行Minor GC回收新生代茬射。
- 而Full GC(老年代回收鹦蠕,一般伴隨一次Minor GC)則有幾種觸發(fā)條件:
- 老年代空間不足
- PermSpace空間不足
- 統(tǒng)計得到的Minor GC晉升到老年代的平均大小大于老年代的剩余空間
- 確定哪些內(nèi)存需要回收痘绎,有兩種方法: