[소개] Java 6,7,8의 String intern – String Pool 이 글에서는 String intern 메소드가 Java 6에서 어떻게 구현되고, 이 메소드가 Java 7에서 어떻게 작동하는지에 대해 설명합니다. Java 8에서 조정이 이루어졌습니다. String string string pool (이름 문자열로 표준화됨)
Java 6의 이 매개변수는 여전히 고정된 Permgen 메모리 크기로 제한되어 있기 때문에 별로 도움이 되지 않습니다. 후속 논의에서는 Java 6
을 무시합니다. String.intern( )을 사용하려면 더 큰 -XX:StringTalbeSize
값(기본값 1009에 비해)을 설정해야 합니다. 그렇지 않으면 이 값을 사용합니다. 메서드는 이를 0(풀 크기)으로 빠르게 줄입니다.
이 기사에서는 Java 6에서 String.intern
메소드가 구현되는 방법과 이 메소드가 구현되는 방법에 대해 설명합니다. Java 7 및 Java 8에서 어떤 조정이 이루어졌나요?
문자열 풀링(명명된 문자열 정규화)은 고유한 공유 String
객체 를 사용하여 수행되어 동일한 값을 사용하지만 주소 문자열이 다른 문자를 나타냅니다. 프로세스. 자신이 정의한 <a href="http://www.php.cn/code/8210.html" target="_blank">Map<code><a href="http://www.php.cn/code/8210.html" target="_blank">Map</a><String, String>
String.intern()
을 사용할 수도 있습니다.
Java 6에서는 많은 표준에서 사용을 금지하고 있습니다String.intern()
풀을 자주 사용하면 시에서 관리하게 되어 OutOfMemory<a href="%EA%B0%80%20%EB%B0%9C%EC%83%9D%ED%95%A0%20%EA%B0%80%EB%8A%A5%EC%84%B1%EC%9D%B4%20%EB%86%92%EA%B8%B0%20%EB%95%8C%EB%AC%B8%EC%9E%85%EB%8B%88%EB%8B%A4.%20http://www.php.cn/wiki/265.html" target="_blank">예외<code>OutOfMemory<a href="http://www.php.cn/wiki/265.html" target="_blank">Exception</a>
. Oracle Java 7에서는 문자열 풀이 많이 개선되었습니다. bugs.sun.com/view_bug.do?bug_id=6962931 및 bugs.sun.com/view_bug.do?bug_id=6962930
옛날에는 모든 공유 String 객체가 PermGen에 저장되었습니다. PermGen은 주로 로드된 클래스 객체와 문자열 풀을 저장하는 데 사용되는 힙의 고정 크기 부분이었습니다. 명시적으로 공유된 문자열 외에도 PermGen 문자열 풀에는 모든 프로그램에서 사용되는 문자열도 포함됩니다(여기서 문자열이 사용된다는 점에 유의하세요. 클래스나 메서드가 로드되거나 사용되지 않으면 모든 상수 가 로드되지 않습니다).
Java 6에서 문자열 풀의 가장 큰 문제는 해당 위치인 PermGen입니다. PermGen의 크기는 고정되어 있으며 런타임 시 확장할 수 없습니다. -XX:MaxPermSize=N
구성을 사용하여 크기를 조정할 수 있습니다. 내가 아는 한, 기본 PermGen 크기는 다양한 플랫폼에서 32M에서 96M까지입니다. 크기를 확장할 수 있지만 크기 사용법은 고정되어 있습니다. 이러한 제한으로 인해 String.intern
을 사용할 때는 매우 주의해야 합니다. 제어할 수 없는 사용자 입력을 인턴하는 데 이 방법을 사용하지 않는 것이 좋습니다. 이것이 바로 JAVA6에서 Map
문자열 풀을 구현하기 위해
Java 7의 Oracle 엔지니어가 문자열 풀을 구현하기 위해 수동 관리를 주로 사용하는 이유입니다. 크게 변경되었습니다 - 문자열 풀의 위치가 힙으로 조정되었습니다. 이는 더 이상 고정된 메모리 공간의 제약을 받지 않는다는 의미입니다. 모든 문자열은 다른 일반 개체와 마찬가지로 힙에 저장되므로 애플리케이션을 조정할 때만 힙 크기를 조정할 수 있습니다. 이러한 변경으로 인해 Java 7에서 String.intern() 사용을 재고해야 할 충분한 이유가 생겼습니다.
예, JVM 문자열 풀의 모든 문자열은 해당 값이 애플리케이션에 참조가 없는 경우 가비지 수집됩니다. . 이는 모든 버전의 Java에서 사용됩니다. 즉, 인턴된 문자열이 범위를 벗어나고 참조가 없으면 JVM의 문자열 풀에서 가비지 수집됩니다.
힙으로 재배치되어 가비지 수집이 되기 때문에 JVM의 스트링 풀은 스트링을 저장하기에 적합한 장소인 것 같죠? 이론적으로는 사용법에 어긋나는 문자열을 풀에서 수집하게 되는데, 이는 외부에서 문자가 입력되어 풀에 존재하는 경우 메모리를 절약할 수 있다. 완벽한 메모리 절약 전략처럼 보입니까? 이에 답하기 전에 문자열 풀링이 어떻게 구현되는지 알아야 합니다.
문자열 풀은 고정 용량을 사용합니다HashMap
각 요소에는 동일한 해시 값 목록이 있는 문자열이 포함됩니다. 일부 구현 세부 정보는 Java 버그 보고서 bugs.sun.com/view_bug.do?bug_id=6962930
기본 풀 크기는 1009입니다(위에서 언급한 버그 보고서의 소스 코드에 표시되며 Java7u40에서 증가되었습니다). 이는 JAVA 6의 초기 버전에서는 상수였으며 나중에 java6u30에서 java6u41까지 구성 가능하도록 조정되었습니다. Java 7에서는 처음부터 구성 가능합니다(적어도 java7u02에서는 구성 가능). -XX:StringTableSize=N
매개변수를 지정해야 하며, N은 문자열 풀 Map
의 크기입니다. 성능 조정을 위해 미리 크기가 조정되었는지 확인하세요.
Java 6에서는 여전히 고정된 PermGen 메모리 크기로 제한되어 있으므로 이 매개변수는 별로 도움이 되지 않습니다. 후속 논의에서는 Java 6
즉, Java7에서는 더 큰 힙 메모리 중간으로 제한됩니다. 즉, 문자열 풀의 크기를 미리 설정할 수 있습니다(이 값은 애플리케이션 요구 사항에 따라 다름). 일반적으로 프로그램이 메모리를 소비하기 시작하면 메모리는 수백 메가바이트씩 증가합니다. 이 경우 100만 개의 문자열 개체가 있는 문자열 풀에 8~16M의 메모리를 할당하는 것이 더 적절해 보입니다. -XX:StringTaleSize
의 값 – 소수가 아닙니다. 대신 1,000,003
을 사용하세요.
지도에서 문자열 할당에 대해 뭔가를 기대할 수 있습니다. HashCode 메서드 조정에 대한 이전 경험을 읽어보세요.
String.intern()을 더 자주 사용하려면 더 큰
-XX:StringTalbeSize
값(기본값 1009에 비해)을 설정해야 합니다. 그렇지 않으면 이 방법이 매우 빠릅니다. 0(풀 크기).
100자보다 작은 문자열을 삽입할 때 종속성을 느끼지 못했습니다(50자가 반복되는 문자열은 실제 데이터와 비슷하지 않다고 생각하므로 100자는 테스트하기에 좋은 제한과 같습니다)
다음은 기본 풀 크기에 대한 애플리케이션 로그입니다. 첫 번째 열은 인터닝된 문자열 수이고 두 번째 열은 항상 10,000개의 문자열입니다(초)
0; time = 0.0 sec 50000; time = 0.03 sec 100000; time = 0.073 sec 150000; time = 0.13 sec 200000; time = 0.196 sec 250000; time = 0.279 sec 300000; time = 0.376 sec 350000; time = 0.471 sec 400000; time = 0.574 sec 450000; time = 0.666 sec 500000; time = 0.755 sec 550000; time = 0.854 sec 600000; time = 0.916 sec 650000; time = 1.006 sec 700000; time = 1.095 sec 750000; time = 1.273 sec 800000; time = 1.248 sec 850000; time = 1.446 sec 900000; time = 1.585 sec 950000; time = 1.635 sec 1000000; time = 1.913 sec
테스트는 Core i5-3317U@1.7Ghz CPU 장치에서 수행되었습니다. 선형적으로 증가하는 것을 볼 수 있으며 백만 개의 문자열이 포함된 JVM 문자열 풀을 사용하면 여전히 초당 약 5000개의 문자열을 인턴할 수 있습니다. 이는 메모리에서 많은 데이터를 처리하는 애플리케이션에 적합합니다. 너무 느립니다.
이제 -XX:StringTableSize=100003
매개변수를 조정하여 테스트를 다시 실행하세요.
50000; time = 0.017 sec 100000; time = 0.009 sec 150000; time = 0.01 sec 200000; time = 0.009 sec 250000; time = 0.007 sec 300000; time = 0.008 sec 350000; time = 0.009 sec 400000; time = 0.009 sec 450000; time = 0.01 sec 500000; time = 0.013 sec 550000; time = 0.011 sec 600000; time = 0.012 sec 650000; time = 0.015 sec 700000; time = 0.015 sec 750000; time = 0.01 sec 800000; time = 0.01 sec 850000; time = 0.011 sec 900000; time = 0.011 sec 950000; time = 0.012 sec 1000000; time = 0.012 sec
보시다시피 문자열을 삽입하는 시간은 대략 일정합니다(문자열 목록의 평균 문자열). Map에서는 10개 이하), 동일한 설정의 결과는 다음과 같습니다. 하지만 이번에는 1천만 개의 문자열을 풀에 삽입합니다(이는 Map의 문자열 목록에 평균 100개의 문자열이 포함된다는 의미입니다)
2000000; time = 0.024 sec 3000000; time = 0.028 sec 4000000; time = 0.053 sec 5000000; time = 0.051 sec 6000000; time = 0.034 sec 7000000; time = 0.041 sec 8000000; time = 0.089 sec 9000000; time = 0.111 sec 10000000; time = 0.123 sec
이제 먹는 크기를 100만(정확히는 1,000,003)으로 늘려보겠습니다.
아아아아보시다시피 시간이 매우 균등하고 "0~100만" 표와 일치하지 않습니다. 큰 차이가 있습니다. 풀 크기가 충분히 크더라도 내 노트북에는 초당 1,000,000개의 문자 개체가 추가됩니다.
이제 JVM 문자열 풀을 시뮬레이션하는 데 사용할 수 있는 JVM 문자열 풀과 WeakHashMap<String, WeakReference<String>>
을 비교해 보겠습니다. String.intern
을 대체하는 데 다음 방법이 사용됩니다.
1000000; time = 0.005 sec 2000000; time = 0.005 sec 3000000; time = 0.005 sec 4000000; time = 0.004 sec 5000000; time = 0.004 sec 6000000; time = 0.009 sec 7000000; time = 0.01 sec 8000000; time = 0.009 sec 9000000; time = 0.009 sec 10000000; time = 0.009 sec
아래에서 직접 작성한 풀에 대해 동일한 테스트를 수행합니다.
private static final WeakHashMap<String, WeakReference<String>> s_manualCache = new WeakHashMap<String, WeakReference<String>>( 100000 ); private static String manualIntern( final String str ) { final WeakReference<String> cached = s_manualCache.get( str ); if ( cached != null ) { final String value = cached.get(); if ( value != null ) return value; } s_manualCache.put( str, new WeakReference<String>( str ) ); return str; }
손으로 작성한 풀은 JVM에 메모리가 충분할 때 좋은 성능을 제공합니다. 불행하게도 내 테스트(String.valueOf(0 < N < 1,000,000,000)
유지)는 매우 짧은 문자열을 유지했으며 -Xmx1280M
매개 변수를 사용할 때 이러한 문자열을 2.5M 유지할 수 있었습니다. 반면에 JVM 문자열 풀(크기=1,000,003)은 JVM 메모리가 충분할 때 JVM 문자열 풀이 12.72M 문자열을 포함하고 모든 메모리(5배 이상)를 소비할 때까지 동일한 성능 특성을 제공합니다. 제 생각에는 애플리케이션에서 모든 수동 문자열 풀링을 제거하는 것이 좋습니다.
Java7u40 버전은 문자열 풀 크기(필수 성능 업데이트)를 60013으로 확장합니다. 이 값을 사용하면 풀에 약 30,000개의 문자열이 포함됩니다. 독특한 문자열. 일반적으로 이는 저장해야 하는 데이터에 충분하며 -XX:+PrintFlagsFinal
JVM 매개변수를 통해 이 값을 얻을 수 있습니다.
Java 7 기능과 호환되도록 -XX:StringTableSize
매개변수를 계속 지원하는 Java 8의 원래 릴리스에서 동일한 테스트를 실행해 보았습니다. 주요 차이점은 Java 8의 기본 풀 크기가 60013으로 증가했다는 것입니다.
0; manual time = 0.001 sec 50000; manual time = 0.03 sec 100000; manual time = 0.034 sec 150000; manual time = 0.008 sec 200000; manual time = 0.019 sec 250000; manual time = 0.011 sec 300000; manual time = 0.011 sec 350000; manual time = 0.008 sec 400000; manual time = 0.027 sec 450000; manual time = 0.008 sec 500000; manual time = 0.009 sec 550000; manual time = 0.008 sec 600000; manual time = 0.008 sec 650000; manual time = 0.008 sec 700000; manual time = 0.008 sec 750000; manual time = 0.011 sec 800000; manual time = 0.007 sec 850000; manual time = 0.008 sec 900000; manual time = 0.008 sec 950000; manual time = 0.008 sec 1000000; manual time = 0.008 sec
이 기사의 테스트 코드는 간단합니다. 방법. 10,000개의 문자열을 유지하는 데 걸리는 시간을 측정할 수 있습니다. 가비지 수집이 언제 어떻게 발생하는지 확인하려면 -verbose:gc
JVM 매개변수를 사용하여 이 테스트를 실행하는 것이 가장 좋습니다. 또한 -Xmx
매개변수를 사용하여 힙의 최대 크기를 적용하는 것이 좋습니다.
这里有两个测试:testStringPoolGarbageCollection
将显示 JVM 字符串池被垃圾收集 — 检查垃圾收集日志消息。在 Java 6 的默认 PermGen 大小配置上,这个测试会失败,因此最好增加这个值,或者更新测试方法,或者使用 Java 7.
第二个测试显示内存中保留了多少字符串。在 Java 6 中执行需要两个不同的内存配置 比如: -Xmx128M
以及 -Xmx1280M
(10 倍以上)。你可能发现这个值不会影响放入池中字符串的数量。另一方面,在 Java 7 中你能够在堆中填满你的字符串。
/** - Testing String.intern. * - Run this class at least with -verbose:gc JVM parameter. */ public class InternTest { public static void main( String[] args ) { testStringPoolGarbageCollection(); testLongLoop(); } /** - Use this method to see where interned strings are stored - and how many of them can you fit for the given heap size. */ private static void testLongLoop() { test( 1000 * 1000 * 1000 ); //uncomment the following line to see the hand-written cache performance //testManual( 1000 * 1000 * 1000 ); } /** - Use this method to check that not used interned strings are garbage collected. */ private static void testStringPoolGarbageCollection() { //first method call - use it as a reference test( 1000 * 1000 ); //we are going to clean the cache here. System.gc(); //check the memory consumption and how long does it take to intern strings //in the second method call. test( 1000 * 1000 ); } private static void test( final int cnt ) { final List<String> lst = new ArrayList<String>( 100 ); long start = System.currentTimeMillis(); for ( int i = 0; i < cnt; ++i ) { final String str = "Very long test string, which tells you about something " + "very-very important, definitely deserving to be interned #" + i; //uncomment the following line to test dependency from string length // final String str = Integer.toString( i ); lst.add( str.intern() ); if ( i % 10000 == 0 ) { System.out.println( i + "; time = " + ( System.currentTimeMillis() - start ) / 1000.0 + " sec" ); start = System.currentTimeMillis(); } } System.out.println( "Total length = " + lst.size() ); } private static final WeakHashMap<String, WeakReference<String>> s_manualCache = new WeakHashMap<String, WeakReference<String>>( 100000 ); private static String manualIntern( final String str ) { final WeakReference<String> cached = s_manualCache.get( str ); if ( cached != null ) { final String value = cached.get(); if ( value != null ) return value; } s_manualCache.put( str, new WeakReference<String>( str ) ); return str; } private static void testManual( final int cnt ) { final List<String> lst = new ArrayList<String>( 100 ); long start = System.currentTimeMillis(); for ( int i = 0; i < cnt; ++i ) { final String str = "Very long test string, which tells you about something " + "very-very important, definitely deserving to be interned #" + i; lst.add( manualIntern( str ) ); if ( i % 10000 == 0 ) { System.out.println( i + "; manual time = " + ( System.currentTimeMillis() - start ) / 1000.0 + " sec" ); start = System.currentTimeMillis(); } } System.out.println( "Total length = " + lst.size() ); } }
由于 Java 6 中使用固定的内存大小(PermGen)因此不要使用 String.intern()
方法。
Java7 和 8 在堆内存中实现字符串池。这以为这字符串池的内存限制等于应用程序的内存限制。
在 Java 7 和 8 中使用 -XX:StringTableSize
来设置字符串池 Map 的大小。它是固定的,因为它使用 HashMap
实现。近似于你应用单独的字符串个数(你希望保留的)并且设置池的大小为最接近的质数并乘以 2 (减少碰撞的可能性)。它是的 String.intern
可以使用相同(固定)的时间并且在每次插入时消耗更小的内存(同样的任务,使用java WeakHashMap将消耗4-5倍的内存)。
在 Java 6 和 7(Java7u40以前) 中 -XX:StringTableSize
参数的值是 1009。Java7u40 以后这个值调整为 60013 (Java 8 中使用相同的值)。
如果你不确定字符串池的用量,参考:-XX:+PrintStringTableStatistics
JVM 参数,当你的应用挂掉时它告诉你字符串池的使用量信息。
위 내용은 Java 코드 성능 향상을 위한 다양한 기술 공유의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!