Java中一種字符串的內存管理方法

2020-12-11 電子產品世界

Java[1]語言為字符串操作提供了豐富的支持,它將字符串封裝在三個類中並提供多種字符串操作接口。在Java應用程式中,由於對字符串的使用量比較高,從而使得其需要消耗較大的堆空間。例如在J2EE應用伺服器運行過程中,約40%的活躍堆空間被用來保存字符串數據[2]。

通過對Java字符串操作接口的分析可以發現,隨著這些操作的運行會產生較多的無用字符串,它們不再被Java類封裝並且也不被任何變量引用。這些無用字符串數據將一直停留在活躍堆中,直到Java虛擬機啟動垃圾收集將其回收。而由於字符串數據具有單個對象佔用空間較小但總體數量很大的特徵,大量的無用字符串數據不僅會影響堆空間的利用率,並且對Java虛擬機垃圾收集的性能有較大影響。

當前對Java中字符串的內存管理優化方案主要關注於字符串的使用效率上,如消除常量重複、延遲分配等技術[2],通過修改Java虛擬機對字符串分配回收的支持來提高堆中字符串的使用效率。然而這些方案無法處理堆中已經成為無用字符串的數據,只能等待垃圾收集來處理。

近期編譯時的獨立對象回收策略[3]則專注於在編譯階段對應用程式做分析並插入回收指令以回收無用對象空間,但是該方案對Java庫函數隻做保守分析從而無法回收這些無用字符串。為此,本文從對字符串操作接口的分析出發,識別各類操作對字符串的改變情況以利用獨立對象回收策略中的指令插樁技術來主動回收無用字符串對象,以提高堆空間的利用率、減低垃圾回收的負擔、改善Java虛擬機的性能。

1 Java中字符串的支持與分析

1.1 Java中字符串的支持

Java語言將字符串的表示和操作都封裝在StringBuilder、StringBuffer和String三個類中。其中前兩個類指向的字符串是可變的,String類指向的字符串是不變的。這三個類的內部結構基本上一致,以StringBuilder為例,StringBuilder在Java中的結構如圖1所示。

從圖1可以看出,字符串數據由StringBuilder對象指向的value域保存,在內存空間上反映為兩個對象:StringBuilder對象通過value域指向字符串對象。由於該類提供常用的可變字符串操作接口且相對另一個類StringBuffer具有較高的執行效率,對字符串數據的操作在Java虛擬機中一般會將其轉換為StringBuilder對象再做處理。下面以一個語句示例來說明這一點:

本文引用地址:http://www.eepw.com.cn/article/257789.htm

 String s=new String(『aa』+』bb』+』cc』);
該語句的語義是將三個字符串連接在一起並生成一個String對象,在Java語言的源程序級別上不會出現StringBuilder對象,但是經過編譯器優化之後,這條語句實際被翻譯為下面的字節碼形式(為簡化描述,本文以源語言來表示字節碼的操作):
StringBuilder t=new StringBuilder(『aa』);
t.append(『bb』);
t.append(『cc』);
String s=t.toString();

即Java編譯器會首先創建一個StringBuilder對象,完成字符串的連接工作之後再將其轉變為String對象。由於類似於這種情況的字符串操作較多地出現在輸出方法和字符串創建方法中,所以可推斷出StringBuilder有著較大的使用頻率,故將以其為代表分析其提供的接口對字符串的影響。

1.2 無用字符串的產生

在上節的示例中,StringBuilder類提供的append()接口將會改變value域所指向的字符串,其做法是:新建長度為連接後字符串長度之和的字符數組,分段複製之後使其成為value域指向的新數組,而value域指向的原數組將被丟棄成為無用字符串。

圖2為示例語句中append()接口引起的value域指向字符串變化圖。

從圖2可以看出,在append()接口執行過後,對象的字符數組將指向新建的字符串』aabb』,原有字符串』aa』將不被任何變量指向而成為無用字符串。

由於StringBuilder類的value域指向的字符串是可變的,在其提供的接口中存在大量類似append()可能對value域做出改變的接口,如insert()、replace()等。而在Java虛擬機對這些接口的調用頻率較多,表1是基準測試程序Jolden[4]的4個子程序中字符串操作接口調用次數以及可能對value域做出改變的接口調用次數對比。

由表1可以看出,可能對value域做出改變的調用次數佔字符串操作接口調用次數的22.6%~45.7%,佔有不可忽視的比例。下面將深入分析這些可能改變value域的操作接口的具體實現。

1.3 字符串操作接口分析

可能對value域做出改變的操作接口有一個共同點,即this對象不會發生變化,只是其value域指向一個新建的字符串。對字符串的操作接口做深入分析後可知,在append()等可能改變value域指向的操作接口的實現中,存在兩條改變分支:例如在接口append(s)中,如果s為null或者s的value域指向一個空字符串,則該接口不會改變this對象的value域指向;否則才會新建一個字符串以被this對象的value域指向。

可以將這兩條改變分支表現為下面的形式:
分支1:不做任何改變。
分支2:新建字符串,使其被this對象的value域指向,原有字符串成為無用字符串。
下面將給出根據本節的分析給出的無用字符串回收方案。

2 字符串的回收方案

對無用字符串的回收存在兩個難點:(1)不可深入改變Java的庫函數實現。因為回收方案需要具有較強的通用性和靈活性;(2)由於操作接口具體實現中對value域的改變存在分支,並且只能在應用程式的運行階段判斷究竟執行的是哪個分支。

本文採用獨立對象顯式回收策略中的指令插樁技術來解決上述兩個難題:在可能發生改變的字符串操作接口調用點處插入判定語句來對操作接口執行的分支做判斷,然後根據結果來實施字符串的回收方案。由於這些語句都插樁在用戶程序中,不會改變Java庫函數的實現,而且這些語句會隨著字符串操作接口的執行而執行,所收集的信息屬於運行時信息,故可以很好地判斷運行時分支的情況。

由於兩條分支的不同之處表現為操作接口執行完畢之後,this對象的value域指向是否發生了變化,故可以採取接口調用前後value域比較的方式來判斷具體執行的分支。本文使用指令插樁技術,在Java虛擬機重編譯Java字節碼時對其做指令插樁工作,其處理流程為:

(1)在Java虛擬機處理應用程式指令時判斷其是否為可能引起字符串變化的操作接口調用指令。
(2)如果是則實施步驟(3)~(5)的指令插樁工作。
(3)在調用指令之前插入this對象的value域引用保存指令。
(4)在調用指令之後插入this對象的value域引用保存指令。
(5)安插兩個引用的對比指令,如果不同,則插入回收指令以回收調用之前保存的引用;否則將不做處理。

本方案用到了獨立對象回收技術中的回收指令,需要在Java虛擬機的內存管理模塊支持這個回收指令。由於對回收指令的支持對原有的分配和回收方案影響很小,故其實現較簡單並且具有一定的通用性。

以圖3為例來說明本文的字符串回收方案。由於該方案處理的為Java字節碼,為了方便理解,將以實際代碼的形式體現:由圖3可看出,在調用點前後加入了value域引用保存指令記錄了調用點執行前後的value域的引用信息,然後將兩者做對比處理來判斷調用點是否對value域的引用做出了改變,如果引用信息有了變化,則之前的value域引用成為了無用字符串,可以插入回收指令將其回收。

可以將該無用字符串回收方案應用到其他可能對對象內部value域指向的字符串做出改變的接口調用點,即可以在運行時回收由這些接口運行而出現的無用字符串數據。

3 實驗結果及分析

在Apache的開源Java SE(Standard Edition)平臺Harmony[5]上實現了針對字符串的回收方案,並做了相關的實驗測試。測試平臺的作業系統是Windows 7 Ultimate,CPU為Intel Pentium Dual E2200,主頻為2.2 GHz,內存為2 GB。測試用例為Jolden中的4個基準程序。

在實現了無用字符串的顯式回收之後,可以在運行中主動回收一些無用字符串以提高活躍堆空間的利用率和降低Java虛擬機垃圾回收的開銷。表2給出了主動回收的無用字符串大小和總分配大小的比較情況。

由表2可以看出,本文的回收方案主動回收的無用字符串佔應用程式總分配空間的5%~18%,對堆空間的利用率有較大的提升。對無用字符串的主動回收也帶來了Java虛擬機的性能提升,因為可以減輕垃圾收集的負擔。表3給出了實現無用字符串回收方案前後測試程序在Java虛擬機中的執行時間對比。

 由表3可以看出,經過對無用字符串的主動回收處理,Java虛擬機對應用程式的執行效率也有了改善,分別減少了1%~5%。這說明無用字符串的回收可以提升Java虛擬機的性能。

本文提出一種對Java應用程式中無用字符串進行顯式回收的方案,以指令插樁的形式收集應用程式運行時信息並主動回收堆中無用字符串,以提高堆空間的利用率。實驗結果表明,該方案可以有效提高Java虛擬機的性能和Java應用程式的執行效率。


相關焦點

  • 談談Java內存管理
    但是在寫程序的過程中卻也往往因為這樣而造成了一些不容易察覺到的內存問題,並且在內存問題出現的時候,也不能很快的定位並解決。因此,了解並掌握Java的內存管理是一個合格的Java程式設計師必需的技能,也只有這樣才能寫出更好的程序,更好地優化程序的性能。
  • 淺析java內存管理機制
    不同的程式語言有不同的內存管理機制,本文在對比C++和Java語言內存管理機制的不同的基礎上,淺析java中的內存分配和內存回收機制,包括java對象初始化及其內存分配,內存回收方法及其注意事項等……中,內存管理由JVM完全負責,java中的「垃圾回收器」負責自動回收無用對象佔據的內存資源,這樣可以大大減少程序猿在內存管理上花費的時間,可以更集中於業務邏輯和具體功能實現;但這並不是說java有了垃圾回收器程序猿就可以高枕無憂,將內存管理拋之腦外了!
  • Java中的字符串常用方法
    public int indexOf(String str): 返回指定字符串在此字符串中第一次出現處的索引。public int indexOf(int ch,int fromIndex):返回指定字符在此字符串中從指定位置後第一次出現處的索引。
  • 這些Java字符串細節,能讓你百尺竿頭更進一步
    */    private final char value[];/  字符串在內存中的保存方式  /我們都知道如何去創建一個字符串,那麼, 字符串在內存中的保存方式是怎樣的呢?在內存中有一個區域叫做常量池,而當我們以這樣的方式去創建字符串:String s1 = "abc";String s2 = "abc";這個字符串就一定會被保存到常量池中。而Java虛擬機如果發現常量池中已經存在需要創建的字符串中,它就不會重複創建,而是指向那個字符串即可。
  • Java字符串拼接效率分析及最佳實踐
    java連接字符串有多種方式,比如+操作符,StringBuilder.append方法,這些方法各有什麼優劣(可以適當說明各種方式的實現細節)? 按照高效的原則,那麼java中字符串連接的最佳實踐是什麼? 有關字符串處理,都有哪些其他的最佳實踐?
  • Java內存分配和String類型的深度解析
    一、引題在java語言的所有數據類型中,String類型是比較特殊的一種類型,同時也是面試的時候經常被問到的一個知識點,本文結合java內存分配深度分析關於String的許多令人迷惑的問題。下面是本文將要涉及到的一些問題,如果讀者對這些問題都了如指掌,則可忽略此文。1、java內存具體指哪塊內存?這塊內存區域為什麼要進行劃分?是如何劃分的?
  • 跟我學java編程—Java字符串類型
    在Java語言中,字符串類型被定義為類,類名為「String」,該類提供了多個構造方法和字符串操作方法,用於滿足對字符串不同的處理要求。String類的初始化String類可用字符串常量對其初始化。編譯「StringInitSample.java」文件,在命令行窗口輸入「javac StringInitSample.java」並執行命令,編譯通過後,在命令行窗口輸入「java StringInitSample」運行Java程序,命令行窗口顯示如下信息:
  • 淺談Java中字符串的初始化及字符串操作類
    字符串常量池是Java常量池技術的一種實現, 在近代的JDK版本中(1.7後), 字符串常量池被實現在Java堆內存中。對於這種直接通過雙引號""聲明字符串的方式, 虛擬機首先會到字符串常量池中查找該字符串是否已經存在. 如果存在會直接返回該引用, 如果不存在則會在堆內存中創建該字符串對象, 然後到字符串常量池中註冊該字符串。在本案例中虛擬機首先會到字符串常量池中查找是否有存在"hello"字符串對應的引用.
  • java基礎編程題之String字符串練習
    5.字符串操作: 1)從字符串「java程序訓練營20100228」中提取開班日期 2)將「CSDN JAVA」字符串中的「JAVA」替換為「J2EE」。 3)取出「java程序訓練營20100228」第8個字符。 4)清除「java程序訓練營20100228」中所有的0。
  • java開發工程師 javascript的字符串
    冰凍三尺非一日之寒,希望大家在學習java的日子裡一定一定要堅持不懈,嚴格要求。多練,多問,多百度。祝大家早日成為一名優秀的軟體工程師! 字符串是不管在java前端還是後臺中都是用的最多最多的一個數據類型,所有前後臺交互的數據都是字符串String類型。那麼如何來定義String字符串呢,注意,用雙引號或者單引號引起來的一切的數據都是字符串。
  • 面試官:小夥子先來說一下可能引起Java內存洩露的場景吧
    本文分析一下可能引起java內存洩露的場景:通過 finalize() 方法終結器finalizers的使用是潛在內存洩漏問題的另一個來源。每當類的 finalize() 方法被重寫時,該類的對象不會立即被垃圾回收。相反,GC將它們排隊等待最後確定,這將在稍後的時間點發生。
  • Java字符串之性能優化
    不幸的是,這是最糟糕的實現方法了。要想知道為什麼,我們得先介紹下這個字符串拼接在Java裡是如何處理的。StringBuilder(String)這個構造方法會分配一塊16個字符的內存緩衝區。因此,如果後面拼接的字符不超過16的話,StringBuilder不需要再重新分配內存,不過如果超過16個字符的話StringBuilder會擴充自己的緩衝區。最後調用toString方法的時候,會拷貝StringBuilder裡面的緩衝區,新生成一個String對象返回。
  • 探秘java中的substring
    字符串截取是大家在開發過程中經常會用到的一個功能。要實現這個功能就會用到String類的substring方法。那大家知道這個substring是怎麼實現的這個功能呢。今天我們就來一探究竟。我本機安裝的是java8。先看看java8的源碼裡,這個substring有什麼秘密吧。我們可以看到在substring方法中先是進行一系列的驗證校驗,然後調用了new String新實例化一個對象。
  • JAVA學習之路(總結)--基礎篇二
    我們先回顧一下基礎篇一講了1.java概述 2.基本數據類型(重點)3.Java內存管理(難)4.對象和實例,對象的創建java為此做了一個優化措施。使得string對象為不可變對象。String常量池。當我們通過字面量,常量來初始化一個字符串時,JVM首先會從字符串的常量池(一個JVM內部維護的內存區域,用來保存已經創建過的字符串對象)中查詢用來保存該字符串的對象是否存在,若存在則直接引用,若不存在則創建該字符串對象並存入常量池,然後引用它。
  • Java編程中常見的異常
    ,還有一種情況,是程序中定義的數組的長度是通過某些特定方法決定的,不是事先聲明的,這個時候,最好先查看一下數組的length,以免出現這個異常。 java.lang.illegalargumentexception    這個異常的解釋是"方法的參數錯誤",很多j2me的類庫中的方法在一些情況下都會引發這樣的錯誤,比如音量調節方法中的音量參數如果寫成負數就會出現這個異常,再比如g.setcolor(int red,int green,int blue)這個方法中的三個值,如果有超過255的也會出現這個異常
  • 13-python中的字符串
    通過前兩天的文章12-python中的集合我們學習了有關集合的知識,今天我們將學習一下python中的字符串。(一)字符串的介紹    字符串,是python中的基本數據類型,是一個不可變的字符序列。    字符串的駐留機制,是僅保留一份相同且不可變字符串的一種方法。
  • 深入分析Java虛擬機堆和棧及OutOfMemory異常產生原因
    Heap(堆)堆是Java虛擬機所管理內存中最大的一塊,在虛擬機啟動時創建,被所有線程共享。堆在虛擬機啟動時創建,用於存儲所有的對象實例和數組(在某些特殊情況下不是)。堆中的對象永遠不會顯式地釋放,必須由GC自動回收。
  • 教你如何刪除Java字符串中的重複字符
    Java刪除字符串中的重複字符【問題描述】刪除字符串中的重複字符.【輸入形式】輸入一個字符串,全為字母字符【輸出形式】輸出刪除重複字符後的字符串【樣例輸入】abbcbd【樣例輸出】abcd【樣例說明】刪除第二個和第三個「b」,保留第一個遇到的不同字符/*【問題描述】刪除字符串中的重複字符.
  • Java 正則表達式教程及示例
    正則表達式定義了字符串的模式。正則表達式可以用來搜索、編輯或處理文本。正則表達式並不僅限於某一種語言,但是在每種語言中有細微的差別。Java正則表達式和Perl的是最為相似的。Java正則表達式的類在 java.util.regex 包中,包括三個類:Pattern,Matcher 和 PatternSyntaxException。
  • JVM-概述和內存區域
    方法區(永久代)在jdk8中又叫做元空間Metaspace運行時數據區概述堆內存:保存所有引用數據的真實信息;棧內存:基本類型、運算、指向堆內存的指針;方法區:所以定義的方法的信息都保存方法區中,屬於共享區