String對象是我們日常使用的對象類型,字符串對象或者其等價對象(如char數組),在內存中總是占據了最大的空間塊,因此如何高效地處理字符串,是提高系統整體性能的關鍵。
在此之前,String作為一個對象類型,我們必須清楚Java對象的創建以為對象的內存結構。
創建一個對象通常需要使用new關鍵字,當虛擬機遇到一條new指令的時候,首先會檢查這個指令的參數是在常量池中定位到一個符號引用,并且檢查這個符號引用代表的類是否已經被加載、解析和初始化。如果是則執行相應的類加載過程。
類加載檢查結束之后,虛擬機將為新生對象分配內存,java中為對象分配內存有兩種方式,一種是
,該方法適用于內存規整的情況,在中間放一個指針作為分界點的指示器,使用過的內存和空閑的內存各放在一邊,當需要分配內存的時候只需要將指針移動即可。另一種是
,如果java堆中的內存不是規整的,虛擬機會維護一張列表,記錄哪塊內存可用,在分配的時候從列表中找到一塊足夠大的空間劃分給對象實例,并更新列表上的記錄。采用哪種分配方式是根據java堆是否規整決定的。而java堆是否規整由JVM是否使用帶有壓縮整理功能的垃圾收集器決定。
java string、另外需要考慮的是內存分配過程中線程安全的情況。有如下兩種解決方案;
堆內存分配的動作做同步處理。
另一種是把內存分配的動作按照線程劃分為不同的空間之中執行,即每一個線程在java堆中預先分配一小塊內存(TLAB),哪個線程需要分配內存首先在TLAB上分配,如果TLAB分配完了之后,才會同步分配新的TLAB。JVM是否使用TLAB由參數
來決定。
內存分配完畢之后想,虛擬機需要分配到的內存空間初始化為零值。這一步操作保證了對象的實例字段在java代碼中可以不賦初始值就可以使用,接下來虛擬機要對對象進行必要的設置,例如這個對象是哪個類的實例、如何才能找到類的元數據信息等,這些信息存放在對象的對象頭中。這些工作完成之后,從JVM的角度來看一個對象已經創建成功了,從java的角度來看還需要執行init方法,將對象按照程序員的意愿進行初始化,這樣一個真正可用的對象才算完全產生出來。
在HotSpot虛擬機中,對象在內存中存儲的布局可分為三個部分,即對象頭,實例數據和對齊填充。
java中string,對象頭包括兩個部分,第一部分用來存儲對象自身運行時的數據,如哈希碼,GC分代年齡、線程所持有的鎖等,官方稱為“Mark Word”。第二個部分為類型 指針,即對象指向它的類元數據的指針,虛擬機通過這個 指針來確定這個對象屬于哪個類的實例。
實例數據是對象真正存儲的有效信息,也是程序代碼中所定義的各種類型的字段內容。
對齊填充并不是必須的,僅僅起到占位符的作用,HotSpot虛擬機需要對象起始地址必須是8字節的整數倍,對象部分正好是8字節的整數倍,所以當實例數據部分沒有對齊時,需要通過對齊填充來對齊。
對于String類型,我們首先來看看其JDK內部的成員變量的聲明代碼:
sss.png
Java。我們會看到它內部維護著一個char數組,而且它是由final關鍵詞修飾的,說明它一旦創建之后不可變。對于String的創建,比較特殊一些,我們來看一下它的具體創建原理:
不管使用任何方式來創建一個字符串S的時候,Java運行時會拿著這個S字符串在String池中查找是否存在內容相同的字符串對象,如果不存在,則在池中創建一個字符串S,否則不會創建對象,也不會在池中添加。
前面提到使用new關鍵創建對象,那么肯定會在堆棧創建一個新的對象,String也是一樣的。
使用直接指定或者使用純字符串拼接來創建String對象,則僅僅會檢查String池中的字符串,池中沒有就創建一個,如果存在,就不需要創建新的,但是絕對不會在堆棧區再去創建對象。
使用包含變量的表達式來創建String對象時,則不僅會檢查并維護Sting池,而且還會在堆棧區創建一個新的String對象。
java this、最常見的String操作莫過于拼接字符串了,在拼接字符串時,我們盡量用+,因為通常編譯器會做出優化,如String test="hello "+"world",編譯器會將其視為String test="hello world"。所以在拼接國泰字符串時,我們需要盡量使用StringBuffer或者StringBuilder的append方法,這樣可以減少構造過多的臨時String對象。下面我們來看一個簡單的實例來證實:
ddd.png
在String對象中有一個特殊的方法,它是一個本地方法,當調用該方法時,如果池中已經包含了一個等于此String對象的字符串,則返回池中的字符串,否則,將此對象添加到池中,并且返回String對象的引用。
在上面的一個例子中,str1和str4并不是同一個對象引用,因此不相等,那么我們使用intern方法,添加一句,觀察運行結果:
sdsds.png
java多線程?也許很多人想到我們可以使用intern方法來創建對象,避免使用new創建大量的對象,但是這也有一個隱含的問題。
使用String的
方法返回JVM對字符串緩存池里已經存在的字符串引用,從而解決內存性能問題,但是intern方法使用的池是JVM全局的池,很多情況下我們的程序并不需要如此大作用域的緩存,而且,它所使用的是JVM heap中PermGen對應的區域,PermGen通常是用來存放裝載類和創建類實例時用到的元數據,因此,使用過多的intern方法會導致PermGen過度增長而最后返回OOM,因此垃圾收集器不會對緩存的String做垃圾回收,因此不建議使用。
實際中,如果需要創建大量的字符串,我們可以自己構建緩存,比如使用HashMap,將需緩存的String作為key和value放在HashMap中,例如下面代碼:
public String getCacheString(String key){
String temp=cacheMap.get(key);
javastring類方法?if(temp!=null){
return temp;
}else{
cacheMap.put(key,key);
return key;
javaint轉string、}
}
在字符串的使用中,另一個常見的操作是截取字符串,在String內部提供了
方法供我們使用,其源碼如下(1.8版本):
41.png
42.png
java string方法、43.png
從上面的源碼可以看出,substring方法截取字符串的時候,會將String的原生內容復制到新的子字符串中,從整個方法的調用鏈來看,它會保存原始String。因此這也引發了下面的問題。
在一個大字符串中我們需要截取的字符串遠遠小于其原始字符串的長度時,不建議直接使用substring方法截取后直接返回,這樣會造成內存泄漏,我們可以使用new String的方式來創建一個個字符串對象,將垃圾回收交給JVM GC,避免內存泄漏問題。
當在一個大字符串中我們需要截取的字符串幾乎和原始字符串長度相等的時候,我們可以放心的使用substring方法來截取返回。
所幸的是,在JDK1.7之后的版本中,將substring的內部實現修改為使用Arrays進行拷貝,不再復用之前的原字符串,因此使其得以回收,所以String內存泄漏的問題也得到了修復。
java char轉string,如果使用了1.7之前的API,也可以使用下面的方法來解決內存泄漏問題。
看一個用例:
public class TestSubString {
public static void main(String[] args) {
List list=new ArrayList();
java string數組?for(int i=0;i<1000;i++){
SubString1 str1=new SubString1();
SubString2 str2=new SubString2();
list.add(str1.getSubString(1,6));
list.add(str2.getSubString(1,6));
JAVA string,}
}
public static class SubString1{
public String str=new String(new char[10000000]);
public String getSubString(int begin,int end){
java indexof方法、return new String(str.substring(begin, end)); //使用new重新創建字符串
}
}
public static class SubString2{
public String str=new String(new char[10000000]);
Java 數組,public String getSubString(int begin,int end){
return str.substring(begin, end); //直接截取返回
}
}
}
在這個用例中,原始字符串很大,但是需要截取的卻是很小的一段,因此在這種場景下推薦使用SubString1重新new一個字符串來釋放原始字符串的方式來截取字符串,這樣避免了原始字符串不能被回收,存在內存泄漏的問題
版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。
工作时间:8:00-18:00
客服电话
电子邮件
admin@qq.com
扫码二维码
获取最新动态