顯示具有 Java 標籤的文章。 顯示所有文章
顯示具有 Java 標籤的文章。 顯示所有文章

2010年1月10日 星期日

[Java] 小心 substring 的 memory leak

這個內容並不是我自己發現的,剛好在 PTT 的 Java 版中看到某位高手所說得!其實,我是今天才知道有這回事!
簡單的來說,假設我們有一段程式碼:

public class SubstringMemoryLeak {
    
    private String str = new String(new byte[10000]);

    public String substring() {
        return this.str.substring(0, 2);
    }
    /**
     * @param args
     */
    public static void main(String[] args) {
        List<String> substringList = new ArrayList<String>(10000);
        for (int i=0, n=10000; i<n; i++) {
            substringList.add(new SubstringMemoryLeak().substring());
        }
    }

}

如果執行上面的程式碼,你就會出現 Exception:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at java.lang.StringCoding$StringDecoder.decode(StringCoding.java:133)
    at java.lang.StringCoding.decode(StringCoding.java:173)
    at java.lang.StringCoding.decode(StringCoding.java:185)
    at java.lang.String.(String.java:570)
    at java.lang.String.
(String.java:593)
    at silver8250.tools.SubstringMemoryLeak.
(SubstringMemoryLeak.java:8)
    at silver8250.tools.SubstringMemoryLeak.main(SubstringMemoryLeak.java:19)


但是如果將 substring() method 修改成:


public String substring() {
    return new String(this.str.substring(0, 2));
}
神奇的事情發生了!程式竟然可以如願的執行!問題出現在哪?上面的程式碼最主要是建立一堆很佔記憶體的 String 物件,然後取其中的一小段!重點就在於 String 的 substring() method 的實做方式。
首先,String 物件在記憶體中會以 char array 方式呈現,當我們每次建立一個 String 時,記憶體就會長出一塊 char array 來存放。String class 有一個 non-public constructor:

String (int offset, int count, char[] value)

當我們使用 substring 時,實際是用這個 constructor 來完成,也就是原先建立很佔記憶體的 String 物件並不會縮小,而是保持原來的 char array 所佔的大小。所以 Java 的 GC 就無法對此 char array 進行回收的動作。所以使用 substring() method 對字串內容來說,我們看到的是部份的,但是在記憶體中卻是佔有原先的大小!

後來,我們改用 new String() constructor 來包裝 substring 的內容,這就會讓 Java 重新建一個 char array 來放置 substring 的內容,相對的,所佔得記憶體就小了,而且原先較大的 char array 就沒有任何 reference ,所以 GC 就可以直接回收了!

老實說,這種情況可能不太常見,至少對我來說啦!我對於 String 的建立都會盡量改用 StringBuffer 物件,這樣不僅可以提供較好得效能,對於記憶體的利用也比較不會有問題!

與大家分享之~

2009年4月4日 星期六

[JUnit] 如何測試一個 private method

上次幫老師帶了一堂課後,那天同學有反應說關於測試的問題:要如何測試 private method 呢?這個問題好像有點難,不過如果我們利用 Java 提供的 reflection 機制就可以輕鬆的辦到!不過那也要會用才行囉!所以我就示範一下如何撰寫這樣的程式。
假設我們有一個程式如下:
public class HavePrivateMethod
{
private int add(int x, int y)
{
return x + y;
}
}

這個程式很簡單,可能你連用都不想用XD,不過因為是示範就別太計較了!反正就是有個 private method 為 add,parameter 與 return type 都是 int。以下就是我們的測試程式:

public class HavePrivateMethodTest
{
@Test
public void add() throws Exception
{
int x = 1;
int y = 2;
int expectedResult = 3;
/*
* 1.取得 instance
*/
HavePrivateMethod havePrivateMethodClass =
HavePrivateMethod.class.newInstance();
/*
* 2.取得我們要測試的 method
*/
Method addMethod =
havePrivateMethodClass.getClass().getDeclaredMethod(
"add",int.class,int.class);
/*
* 3.設定存取性
*/
addMethod.setAccessible(true);
/*
* 4.實際呼叫
*/
Object actualResult =
addMethod.invoke(havePrivateMethodClass, x, y);
addMethod.setAccessible(false);
Assert.assertEquals(expectedResult, actualResult);
}
}

總之,我們要先取得一個該 class 的 instance,再來就是設定我們要測試的 private method,要取得此 method 的 Method instance,由於我們測試的 private method 有兩個 primitive type 的 int,所以我們就用 int.class,而不是用 Integer.class 喔!因為這不一樣的~
取得 Method instance 後,我們就要先設定讓此 method 是可以被存取的,接下來才是呼叫(invoke) 此 method,呼叫後的回傳值一律都是 Object 型態,我們就透過 JUnit 提供的 assertEquals() method 來幫助我們檢查!
這樣就完成我們的目標囉~

NOTE:(2009-05-06)
在上面的範例程式中,我們採用 HavePrivateMethod.class.newInstance(); 來初始化被測試的物件,不過這樣的初始化動作只限定用在被測的 class 具備有 default constructor!如果我們的被測試 class 沒有 default constructor 的話,我們就只能用一般的初始化方式,也就是 new Object 囉~這樣在 method reflection 也是會有效的!
感謝學弟大頭跟 Fred 提出這樣的問題~

2008年3月11日 星期二

[Java] JUnit 3.8.1 : An Unit Test Framework for Java

Introduction

對於 programmers 來說,如何確保自己的程式碼是正確的,通常都必須要自己親自去執行程式碼,然後自己扮演使用者的角色來手動測試,這已經是早期的 programmers 會做的是了。JUnit 就是一套 framework,他幫助 programmers 解決測試中繁雜的工作,透過撰寫 unit test code 來達到測試的效果,並且採用自動化、可重複測試的功能來降低 programmers 的工作。

clip_image002

圖一 JUnit 官方網頁

在 Eclipse 中,JUnit 是核新的 plug-in,除非您將他反安裝了,否則您可以在 Eclipse 中找到。

Example

要撰寫一個 unit test code 其實是一件很簡單的事,因為 JUnit library 都已經將繁雜的工作改以簡短的程式碼來解決。

(1) 首先,我們要先建立一個 JUnit 的 class,點選 eclipse 中的 [File] -> [New] -> [Others],接著您就會看到如圖二的畫面出現。

clip_image004

圖二 建立新的元件視窗

(2) 點選該視窗的 [JUnit Test Case],並且會出現如圖三的畫面。其中我們選擇上方的 [New JUnit 3 test],並且我們將我們的 unit test case class name 填入 name 欄位,接著選擇下方的 [Class under test] 右方的 [Browse…] 按鈕,接著會出現視窗讓我們選擇我們想要測試的 class。

clip_image006

圖三 建立新的 JUnit Test Case

(3) 選擇好上述的資訊並填寫完畢後,按下 [Next>] 後會出現如圖四的視窗。我們可以由此視窗中選擇我們對於該 class 中我們想要測試的 methods,並將他打勾,eclipse 會自動幫我們產生測試的基本程式框架,以方便我們測試。按下 [Finish] 後就會開啟測是的程式碼,我們會看到剛才我們所勾選的 methods 會以 testXXX() 的方式出現。

NOTE:本文件示範的 JUnit 版本為 JUnit 3,若您有興趣可以自行研究 JUnit 4,不過要先對於 JDK 5.0 的 Annotation 有基本的概念才行。

clip_image008

圖四 選擇目標 class 我們想要測試的 methods

(4) 有了程式的框架,我們當然還是要自己撰寫測試的 scenarios。首先我們從新假設我們要測試的目標 class 內容如下:

public class Math {

    public int add(int x, int y) { return x + y; }

    public int subtract(int x, int y) { return x - y; }

}

(5) 接著我們就用上述的 eclipse 工具幫我們產生測試程式碼的框架:

public class MathTest extends TestCase {

    public void setUp() { super.setUp(); }

    public void testAdd() {}

    public void testSubtract() {}

}

上述程式碼中的 setup() method 是用於起始設定的 method,也就是 JUnit 在呼叫每個測試 methods 前都會先執行 setup() method,如上面的測試程式碼中,JUnit 會依照下面的順序執行測試:setUp(), testAdd(), setUp(), testSubtract()。如果您的測試中是需要先建利一些物件,如:資料庫的連線 class等,您就不必在每個測試 methods 中撰寫建立這些物件的程式碼,您可以統一撰寫在 setup() method 中。

您所有的測試 methods 都必須以 test作為開頭,因為 JUnit 測試會根據 method 前面為 test 開頭的來進行測試。

(6) 接著我們就要開始撰寫測試的 scenario,在此我們就只示範 testAdd() method。

public class MathTest extends TestCase {

    public void setUp() { super.setUp(); }

    public void testAdd() {

        int x = 10;

        int y = 50;

        int expected = 60;

        int result = this.math.add(x, y);

        super.assertEquals(result, expected);

    }

}

上述程式碼中,我們先宣告一個class內部變數 math他是我們測試的目標,並且我們在 setup() method 中將他初始化。接著假設我們要輸入的 x and y 的值,以及我們所期望的輸出結果 expected 變數,然後我們就呼叫 Math 的 add() method 來獲得結果並存在 result 變數中,最後我們就呼叫 super class 中的 assertEquals 來斷定結果一定是要相同。

(7) 再來就是執行我們的測試案例,選擇 [Run] -> [Run As] -> [JUnit Test]。測試結果會如圖五。

clip_image013

圖五 測試結果報告視窗

如果您的測試無誤的話,就會在右邊出現綠色的 bar,左邊上方有一些測試的結果資訊,紀錄著您總共測試的 methods、測試中出現的 Errors 與測試中測試與您結果不符合的 Failures。

References

[1] JUnit 官方網站 http://junit.sourceforge.net/junit3.8.1/index.html

[2] Eclipse 官方網站 http://www.eclipse.org