1. 程式人生 > >Java substring方法與記憶體溢位

Java substring方法與記憶體溢位

說明:本文是閱讀《Java程式效能優化》(作者:葛一明)一書中關於substring方法記憶體溢位一節的筆記。

一、String物件及其特點

1、在C語言中對字串的處理通常是採用char陣列,但是對於陣列本身來說,它無法封裝字串操作的一些基本方法,所以在Java中,String物件可以看成是char陣列的一種封裝。Java中陣列的基本實現如下圖,主要由char陣列、偏移量和String的長度這三部分組成,其中char陣列表示的是String的內容,它是String物件所表示的字串的超集,而String的真實內容就是由這個偏移量和長度在這個char陣列中進行定位和擷取。理解了這點,就能更好的理解為什麼substring方法可能會導致記憶體溢位。

2、不變性與不變模式

String物件一旦生成,則不能被改變。該特性可以泛化成不變模式,即一個物件的狀態在物件被建立之後就不再發生變化。該模式主要用於多執行緒共享物件,可以省略同步或鎖等待的時間,從而提高效能。

3、針對常量池的優化

當兩個String物件擁有相同的值時,它們只引用常量池中的同一個拷貝。如下圖所示,str1和str2引用了相同的地址,但str3卻重新開闢了一塊記憶體空間,但是str3在常量池中的位置和str1是一樣的,也就是說,str3雖然單獨佔用了堆空間,但它指向的實體和str1完全一樣。而使用intern()方法就可以返回String物件在常量池中的引用。

4、類的final定義

final定義的String類使得該類不能有任何子類,保護了系統的安全,同時,在JDK1.5之前,使用final定義有助於幫助虛擬機器尋找機會,內聯所有的final方法,從而提高系統效率;在JDK1.5之後,這種優化的效果並不明顯。

二、substring方法與記憶體溢位

1、Java中Stirng類的substring方法用於擷取子字串,該方法有兩個定義:

  • public Stirng substring(int beginIndex)
  • public String substring(int beginIndex, int endIndex)

以第二個方法為例,在JDK6中該方法的實現如下圖:

在方法的最後,使用了String類中如下圖所示的一個建構函式新建了一個String物件作為返回:

注意這個建構函式的定義與在建構函式之上的註釋語句,這是一個包作用域的建構函式,其目的是為了高效而快速的共享String內的char陣列。但是這種通過偏移量來擷取字串的方法使得String的原生內容value陣列被複制到新的子字串中,如果原生字串很大,擷取的字串長度卻很短,那麼擷取的子字串中包含了原生字串的所有內容,並佔據了相應的記憶體空間,而僅僅通過偏移量和長度來決定子字串的實際內容,這雖然提高了運算速度卻浪費了大量的記憶體空間,所以說這個建構函式使用了以空間換取時間的策略,但是這有可能就會導致記憶體溢位。

2、例項演示substring方法導致記憶體溢位

如下程式碼所示,當在main方法中執行的是strA.getSub(1, 5)時,在我的機器上執行時就出現了“Exception in thread "main" java.lang.OutOfMemoryError: Java heap space”的異常資訊,跟蹤記憶體和GC情況可以發現程式佔用的記憶體不斷擴大,直到溢位,雖然垃圾收集器不停的工作,但是每次回收的內容很小。如果沒有出現該資訊,可以試著加大for迴圈的迴圈次數。而同樣的迴圈次數,對於執行的是strB.getSub(1, 5)時就不會出現該異常資訊,跟蹤記憶體和GC情況可以發現垃圾收集器每次總能將系統的記憶體消耗量釋放到初始狀態。

public class SubstringOOM {

	private static class StrA {
		// 指定了String物件中的char陣列以及長度等資訊,這裡使用的長度是100000,說明String的原生內容很長
		private String str = new String(new char[100000]);
		
		// 使用了有可能導致記憶體溢位的建構函式來擷取子字串
		public String getSub(int begin, int end) {
			return str.substring(begin, end);
		}
	}
	
	private static class StrB {
		// 指定了String物件中的char陣列以及長度等資訊,這裡使用的長度是100000,說明String的原生內容很長,與StrA中一樣
		private String str = new String(new char[100000]);
		
		// 雖然使用了有可能導致記憶體溢位的建構函式來擷取子字串,但是重新生成了String物件
		public String getSub(int begin, int end) {
			return new String(str.substring(begin, end));
		}
	}
	
	public static void main(String[] args) {
		List<String> strList = new ArrayList<String>();
		
		for (int i = 0; i < 10000; i++) {
			StrA strA = new StrA();
			StrB strB = new StrB();
			strList.add(strA.getSub(1, 5));
		}
	}
}
當執行的是strB.getSub(1, 5)時,它還使用了沒有記憶體洩漏的String的另一個建構函式來重新生成了String物件,使得由substring方法返回的、有可能存在記憶體溢位的String物件失去所有的強引用,從而被垃圾收集器識別為垃圾物件而進行回收。

3、關於substring方法中使用的String類建構函式

從前面的分析中知道了substring方法有可能導致記憶體溢位是因為它使用了一個包內私有的建構函式生成了新的String物件,包內私有就是說我們的應用程式是不能使用它的,所以在實際使用中,不必擔心,但是應該瞭解java.lang包內的物件對它的呼叫是否會引起記憶體溢位。比如在JDK6中Integer類的toString(int i)方法中就使用了該建構函式,所以這些方法都有可能和substring方法一樣存在記憶體洩漏的問題。



相關推薦

Java substring方法記憶體溢位

說明:本文是閱讀《Java程式效能優化》(作者:葛一明)一書中關於substring方法記憶體溢位一節的筆記。 一、String物件及其特點 1、在C語言中對字串的處理通常是採用char陣列,但是對於陣列本身來說,它無法封裝字串操作的一些基本方法,所以在Java中,Str

學習筆記1:深入理解Java虛擬機器——JVM高階特性最佳實踐_走進java_java記憶體區域記憶體溢位異常

第一部分:走進java Java虛擬機器 程式碼在華章下載 jdk釋出了六個命令列工具和兩個視覺化故障處理工具。 推薦書籍 設計原本 領域特定語言 現在著名的Java虛擬機器 hotspot vm(預設) jrockit vm j9 vm jdk sun jdk op

java虛擬機器—-java記憶體區域記憶體溢位異常

一,java虛擬機器所管理的執行時資料區域分為:程式計數器、java虛擬機器棧、本地方法棧、java堆、方法區、執行時常量池。                     1,程式計數器:

《深入理解Java虛擬機器—JVM高階特性實踐 周志明 著》之第2章 Java記憶體區域記憶體溢位異常

1、Java虛擬機器所管理的記憶體包括以下幾個執行時資料區域: 2、程式計數器:          1. 可以看作是當前執行緒所執行的位元組碼的行號指示器,是一塊較小的記憶體空間;  &nbs

JAVA虛擬機器(JVM)劃重點 第二章 Java記憶體區域記憶體溢位異常 之 虛擬機器物件

本部落格參考《深入理解Java虛擬機器》(第二版)一書,提取重點知識,再加以個人的理解編寫而成。轉載請標明來源。 JAVA虛擬機器(JVM)劃重點 第二章 Java記憶體區域與記憶體溢位異常 之 虛擬機器物件 Java物件的建立 1、類載入過程

JAVA虛擬機器(JVM)劃重點 第二章 Java記憶體區域記憶體溢位異常 之 執行時資料區域

本部落格參考《深入理解Java虛擬機器》(第二版)一書,提取重點知識,再加以個人的理解編寫而成。轉載請標明來源。 JVM劃重點 第二章 Java記憶體區域與記憶體溢位異常 之 執行時資料區域 概述 執行時資料區域 程式計數器 Java虛擬機

《深入理解java虛擬機器》讀書筆記(二)---- Java記憶體區域記憶體溢位異常

執行時資料區域 java虛擬機器所管理的記憶體將會包括以下幾個執行時資料區域: 1、程式計數器 程式計數器是一塊較小的記憶體空間,它可以看作是當前執行緒所執行位元組碼的行號指示器。在虛擬機器的概念模型裡,位元組碼直譯器的工作就是通過改變這個計數器的值來選取下一條需要執

JVM-Java記憶體區域記憶體溢位

JVM虛擬機器執行時資料區結構分為:      其中方法區和堆是所有執行緒共享的記憶體區域,而Java棧、本地方法棧、程式計數器是執行緒私有的。 我們詳細介紹執行時資料區的各個區域及其作用。  程式計數器:   一塊較小的記憶體空間,位元組碼指示器工作時通過改變計數器的值來選取下一條需

Java記憶體區域記憶體溢位

記憶體區域     Java虛擬機器在執行Java程式的過程中會把他所管理的記憶體劃分為若干個不同的資料區域。Java虛擬機器規範將JVM所管理的記憶體分為以下幾個執行時資料區:程式計數器、Java虛擬機器棧、本地方法棧、Java堆、方法區。下面詳細闡述各

虛擬機器學習之一:java記憶體區域記憶體溢位異常

1.執行時資料區域 java虛擬機器在執行java程式的過程中會把它所管理的記憶體劃分為若干個不同的資料區域。這些區域都有各自的用途和建立、銷燬時間,有的區域伴隨虛擬機器程序的啟動而存在,有些區域則依賴使用者執行緒的啟動和結束而建立和銷燬。 1.1程式計數器 程式計數器

Java虛擬機器筆記-1(Java技術體系&自動記憶體管理機制&記憶體區域記憶體溢位&垃圾收集器記憶體分配策略)

世界上沒有完美的程式,但寫程式是不斷追求完美的過程。 Devices(裝置、裝置)、GlassFish(商業相容應用伺服器) 目錄 1. Java技術體系包括: Java技術體系的4個平臺 虛擬機器分類 HotSpot VM 模組化、混合程式設計 多核並行

java記憶體區域記憶體溢位異常

執行是記憶體區域 java虛擬機器在執行java程式的過程中會把它所管理的記憶體劃分為若干個不同的資料區域。 1.執行緒獨有的記憶體區域 (1)程式計數器(Program Counter Register) 這塊記憶體區域很小,它是當前執行緒所執行的位元組碼的行號

【深入Java虛擬機器】之一:Java記憶體區域記憶體溢位

記憶體區域     Java虛擬機器在執行Java程式的過程中會把他所管理的記憶體劃分為若干個不同的資料區域。Java虛擬機器規範將JVM所管理的記憶體分為以下幾個執行時資料區:程式計數器、Java虛擬機器棧、本地方法棧、Java堆、方法區。下面詳細闡述各資料區所儲存的資料型

java記憶體洩漏記憶體溢位

記憶體洩漏指你用malloc或new申請了一塊記憶體,但是沒有通過free或delete將記憶體釋放,導致這塊記憶體一直處於佔用狀態。 記憶體溢位指你申請了10個位元組的空間,但是你在這個空間寫入11或以上位元組的資料,就是溢位。 1. 記憶體溢位 out

Java 記憶體區域記憶體溢位異常(二)

一、虛擬機器中Java物件的建立 語言層面上,建立Java物件通常僅僅是一個new關鍵字而已。 在虛擬遇到new指令時: 1、首先檢查這個指令的引數是否能在常量池中定位到一個類的符號引用,並檢查這個類的符號引用代表的類是否已經載入,解析和初始化過。如果沒有,則必須執行類

Java 記憶體區域記憶體溢位異常(三)

實戰:OutOfMemoryError 異常 參考:《深入理解Java虛擬機器》-jvm高階特性與最佳實現(周志明著) 之前的兩篇中介紹Java虛擬機器中各個執行時記憶體區域的作用,這節中通過人為異常的方式驗證各個執行時區儲存的內容 一、Java堆溢位 Java堆中用

Java記憶體區域記憶體溢位異常簡單總結

目錄 1.簡述 2.作用 3.注意 1.簡介 3.異常 1.簡述 1. 簡述 2.異常 1.簡述 2.異常 1.JVM執行時資料區域簡圖 Java虛擬機器執行時資料區 2.程式計數器(Program Co

深入理解java虛擬機器---java記憶體區域記憶體溢位異常---3垃圾回收機制GC

  一、垃圾回收---物件存活演算法:     1、引用計數器法:在物件身上放上一個計數器,當有引用則加一,引用失效則減一,為零則可回收。(無法解決物件相互引用)     2、可達性分析法(java),GC roots為起始點,從節點向下搜尋,搜尋路徑為引用鏈,不在引用鏈的物件則是可回收的物件

(一)JAVA記憶體區域記憶體溢位異常

目錄 0、前沿 1、概述 2、執行時資料區域 2.1、程式計數器 2.2、JAVA虛擬機器棧 2.3、本地方法棧 2.4、JAVA堆 2.5、方法區 2.5.1、執行時常量池 3、HotSpot虛擬機器 3.1、物件的建立 3.2、物件的記憶體佈局

JAVA記憶體區域記憶體溢位異常---------執行時資料區域

執行時資料區域 程式計數器、JAVA 虛擬機器棧、本地方法棧 以及方法區、堆 1 、Program Counter Register  較小的一塊記憶體空間,可看作是當前程式執行的位元組碼的行號指示器。多執行緒中,每個執行緒都需要一個獨立的計數器,各條執行緒的計數器互不