후덜; 제가 뭘 잘못 알고있나요? Java String 초기화
2012.02.20 23:37
JDK 1.7입니다.
Java String에서 초기화 할 때 제가 무언가 잘못 알고 있는 것 같습니다.
String str = null;
이클립스에서 변수 찍어보면 null이라 나옵니다.
옆에 값의 물리주소도 안들어 있습니다. 진짜 null이죠.
근데 여기에
str += "10";
이렇게 넣으니
str = null10이라 나오고, 물리주소도 나옵니다;;;
그래서 String str = "";로 초기화하니
str += "10";이 정상적으로 10만 나옵니다.
제가 뭘 잘못 알고있나요?
null은 물리주소값을 할당하지 않고 variable의 이름만 선언하는 초기화하는게 아니었단 말인가요;; (C에서 int* asdf = NULL)
""은 빈 내용을 가지는 물리주소를 가지는 variable 선언으로 알고 있는데.. (C에서 int* asdf = new int)
코멘트 29
-
고구마
02.20 23:48
-
고구마
02.20 23:56
JVM에서는 null 을 객체로 본다고 하는군요. null Object로 말이죠.
우리 상식선에선 '아무것도 가르키지 않는다.' 이지만
JVM이 볼 땐 'null Object를 가르키는 참조변수이다.' 라고 보여진다는거죠.. 그렇다네요^^
-
클라우드나인
02.21 00:00
그런 이유 때문이었군요.. C++ 코딩스타일이 버릇이 되어서 계속 null을 써 왔는데 조심해야겠네요...
감사합니다. -
고구마
02.21 00:05
아 참 그리고 자바에서는 String 객체를 초기화 할때 null을 사용하지 않는답니다.
그래서 전 항상 당연히 안써와서 몰랐습니다 사실 저렇게 된다는 거;;
자바에서 String은 "" 으로 초기화를 합습죠..
-
Lock3rz
02.21 00:04
나름 java 공부했는데... null Object를 잊고있었네요... 칫...
역시 레퍼런스북이 없으면, 말짱도루묵 ㅠㅠ
-
클라우드나인
02.21 00:07
역시 안쓰면 까먹나보네요. 저도 슬슬 배웠던 기억이 납니다.
그런데 아무리 그래도 null인 string과의 concatenation연산에서 null string을 null value를 가진 string으로 처리할줄은 누구도 상상하지 못했을 것입니다;; -
Lock3rz
02.21 00:54
엿장수맴이죠...이런부분은 정말...
가끔 회식자리에서 이런거 가지고 내기걸고 하던데 ㅋㅋ;;;
양쪽다 틀리는 경우가 허다하죠;;;
-
Lock3rz
02.20 23:59
변수형에 따라서 자동으로 준비[?]해놓는다던지..ㅋㅋ;;;
str=null10은 좀 이해가 안되는 부분이군요... 참;;;
null도 예약어 맞죠? 전 항상 NULL로 써서...
NULL과 null를 구분하긴 하지만,
물리주소도 할당 안되어 있는데, 후에 +연산자를 문자열 붙이기로 알아듣는다면
자료형이 str이니 따옴표도 안붙였는데 jvm마음대로 값을 넣은듯 넣지않은듯 가지고 있다가
후에 문자열이 추가되면 추가했다는 결과인데... 뭔가 심히 이상하네요. 시키지도 않은일을 하다니 ㅡㅡ;
String str =""; 로 초기화 하시면 [""+문자열 끝을 나타내는 데이터] 로 들어가니 당연히 "10"이 출력되어야 하겠죠...
뭔가 확실히 이상합니다 +_+
-
클라우드나인
02.21 00:05
null은 JDK버전에 따라 다른 것 같은데 제가 주로 쓰는 JDK1.6, 1.7에서는 null만 예약어인 것 같습니다.
C++에서는 NULL이고, C에선 없구요. (그래봐야 C++는 #define NULL 0으로 알고 있습니다.)
Java에서는 null도 오브젝트인건 첨 알았네요;; -
Lock3rz
02.21 00:52
C++에다가 오브젝티브한 요소를 더 추가하다보니, 혼란스럽게 짜여진듯 합니다 ㅎㅎ
-
Doctor
02.21 00:13
이게 조금 웃긴 문제인데제가 알기로는 null + null 을 해도 스트링으로 "nullnull" 이렇게 출력됩니다.그리고
String str = null;StringBuffer buffer = new StringBuffer();buffer.append( null );이렇게 해도 nullnull 문자열이 출력됩니다.아무래도 null로 초기화된 객체는 아무것도 할수 없는데
거기다 타 객체들과는 달리 + 연산자 오버로딩을 빈번히 코더들이 사용하고
(아마 객체중엔 String 객체 혼자 연산자 오버로딩이 되지 않나요?)
JVM은 NullPointerException은 내야되긴 하겠고,,
대뜸 저렇게 오류가 나면 코더는 당황할꺼같고안되에~ nullnull이라고 문자열이라도 찍어줘야 겠어~라고 JVM이 외치고 있는거 아닐까요..ㅋ -
스파르타
02.21 00:25
ㅎㅎ 댓글보면서 배워가내요 ㅋ 이제 소켓프로그램 배우는대 언재 다할지;;; ㅠㅠ(머 끝이 있진안치만요 ㅎㅎ)
-
꼬소
02.21 02:11
우왕~~ 전문가님들 ㄷㄷㄷㄷㄷㄷ
null이 뭐에욤??
-
Lock3rz
02.21 08:37
"아무것도 없다"라는 뜻을 내포하고 있습니다.
더이상의 자세한 설명은 생략한다 -
김강욱
02.21 21:02
인생무상, 공수래 공수거를 뜻합니다. 토토록토토토토토~
-
가영아빠
02.21 02:42
쩝. 전문분야가 아니라서 안 쓰려다가 요즘 java관련 사이트에 도움을 주고 있어서 조금 적어봅니다.
일단 Java에서 null은 예약어임과 동시에 JVM의 캐쉬내에 항상 존재하는 오브젝트입니다.
System.out.print( (Object)null );
과 같은 식으로 null이 오브젝트라는 것을 확인할 수 있습니다.
이 null 은 특히 여러 오브제트에서 중요하지만 문자계열에서 특히 더 중요합니다.
특히나 가장 많이 쓰고 가장 흔하게 사용하는 String 클래스에서 좀 중요합니다.예를 들어보면
for( int index = 0 ; index < 10000 ; index++) {
String s = null;(어쩌고 저쩌고 코드)
if (s != null ) {
(어쩌고 저쩌고 코드)
}
}와 단지 null을 "" 으로 바꾸었을 뿐인데 코드를 살펴보겠습니다.
String s = null;
의 경우에 s는 스택에 위치잡게 되겠죠. null은 JVM에 항상 존재하는 오브젝트고요.
반면 ""를 지정하게 되면 ""가 힙에 잡히고, 이어서 s가 스택에 잡히게 되겠죠.
스택은 한번에 와인딩이 가능 하기 때문에 별 문제가 없지만 힙은 곤란하죠.
한마디로 쓸데없이 힙메모리를 사용하고 이 String을 가지고 연산을 반복하다보면 심지어는 힙단편화를 가져올 수 있습니다.
JVM의 메모리 관리 옵션들에 따라서 해제 시점은 상당히 밴드가 넓기 때문에 어느 순간 단편화된 힙으로 인해서
메모리를 해제하는 동안 프리징이 생길 수도 있습니다. 게다가 JVM의 캐쉬의 히트를 떨어뜨릴 수도 있습니다.따라서 사용할지 안할 지 알 수 없는 String은 null로 초기화해야 하고
사용할 것이라고 분명히 존재지어진 문자열 연산은 가능하면 StringBuffer를 이용해야 하는 이유가 여기에 있습니다.그런데 null이 오브젝트라서 별희안한 일들이 발생합니다. 예를 들면 이 질문글에서 처럼
System.out.print( null + "10" );
과 같은 상황인데요. 이건 null이 객체고, 객체는 toString을 가지고 있는 자바의 특성상 존재하는 자연스럽지만 기괴한 현상일 뿐입니다. 결국 String 객체가 필요한 곳에서 null 객체와 문자열을 더해버렸으니 나온 결과입니다.
재밌는 것은 이건 어디까지나 JVM이 암시적으로 null의 toString을 호출 했을 때만 가능한 현상이라는 점입니다.
우리가 명시적으로 null은 예약어임과 동시에 말그대로 널 객체라는 이유로 toString이라는 메세지를 전달할 수는 없습니다.System.out.print( ((Object)null).toString() + "10");
와 같은 코드는 그래서 NullPointerException 을 발생시킵니다. JVM이 호출한 것과 다르게 우리는 메세지를 전달하는 개념이기 때문에 null객체에게 메시지를 전달하려고 했으니 당연한 결과겠죠.
JAVA를 설계한 사람들의 아주 섬세한 배려라고 할까요.
이는 우리에게 있어서 String 클래스를 사용함에 있어서 null로 초기화를 하게 시키고 결국
String s = null;
(어쩌고 저쩌고 코드)
if ( s != null ) {과 같이 != null 의 사용을 부추기고 실제로 JAVA에서는 != null 이 까딱잘 못하면 매우 오용되어서 소스코드 곳곳에 != null 이 사용되는 상황을 가지고 옵니다.
흔히 이런 != null의 오용이 싫은 사람들은 String을 ""으로 초기화 하자고 하지만 이는 가벼운 프로그램에서는 상관없지만 메시브한 사용자와 상대해야 하는 프로그램에서는 힙단편화와 더 오랜시간의 가비지컬렉션수행을 가져옵니다. WAS라면 모르지만 동접이 수천인 게임서버라도 된다면 프리징은 절대 악이겠죠.
그래서 우리는 String은 null로 초기화하고 다시 한번 말하지만 문자열 연산은 StringBuffer를 이용해야 하고
String의 경우는 != null 이나 == null은 꼭 필요한 장소에서만 사용하는 습관을 길러야 합니다.
예를 들면 다음과 같은 코드죠.public void func(String arg) {
if (arg == null) {
throw new NullPointerException();
}
(어쩌고 어쩌고)
}그리고 또 다른 중요한 점은 NullPointerException 입니다.
자바의 여러 Exception 중에서 이 NullPointerException은 좀 특이한 녀석입니다.
다른 여러 Exception과 다르게 NullPointerException을 포함하는 몇몇 Exceptioin은 catch 해주지 않으면JVM의 메모리 관리 대상에서 빠집니다.
한마디로 가비지콜렉션이 안되고 Memory Leak이 생기게 되고, 이 메모리는 JVM이 종료가 되는 시점에서 반환이 됩니다.
주기적으로 생성하는 쓰레드에서 NullPointerException이 발생했는데 무시하고 catch 안한다면
어느순간에 JVM은 out of memory 상태가 되버릴 수도 있습니다. out of memory만 발생하면 메모리 왕창 박아서 돈으로 바른 머쉰에서는 문제가 안 될 수도 있지만 가비지컬렉션의 알고리즘을 생각하면 이는 JVM의 퍼포먼스 저하로 이어집니다. 황당하죠.
이 역시 JAVA를 설계한 사람들의 섬세한 배려(?)라고도 볼 수 있겠지요.늦은 밤에 주저리 주저리 써봤는데 JAVA는 사실 C/C++과는 참 다른 언어입니다. Objective-C와 더 비슷한 면이 있다고 봅니다.
-
가영아빠
02.21 02:55
다시 한번 강조하지만 String의 사용은 최대한 자제해야 합니다. 별거 아닌 문자열 몇개지만 String의 연산으로 어마어마하게 많은 힙메모리가 소모됩니다.
String a = "hello";
String b = ", ";
String c = "world!";
라고 했을 때 별거 아닌데 이걸 초당 30프레임씩 더해서 OpenGL로 보내서 출력하는 함수가 있다고 합시다.
우리는 단지 a + b + c 라는 수식을 그 함수에 보내겠죠.
그런데 이 와중에 우리는 매 프레임 힙에 "hello" ", " "world!" 를 생성하고
연산과정의 중간으로 "hello ," 와 최종값으로 "hello, world!"를 추가로 생성합니다. 예상보다 어마어마하죠.
진짜 아무 것도 아닌데 JVM의 버전이나 버전별 메모리 관리 옵션에 따라서는 메모리가 몇 분간 쭉쭉쭉쭉~ 증가하다가 어느 순간에 가비지 컬렉션이 일어나면서 초당 30프레임이 안나오고 순간 프레임 드롭이 걸리는 것을 볼 수 있습니다.
일반 SI라면 별 문제가 아닐 수도 있지만 게임과 같이 프레임이 중요하다면 이는 커다란 오점이 되겠죠.
이걸 초반에 알았거나 발견했다면 별 문제가 안되고 간단하게 해결 할 수 있겠죠.
문제는 프로젝트 중반 이후에 발견했다면? 기껏하는 대책이라는게 프레임마다 강제로 플러싱을 주어서 가비지 컬렉션을 일으키는 것 뿐일 겁니다. 아니면? 용감하게 다 갈아엎던가요.
JAVA에서는 우리가 가비지컬렉션 알고리즘은 선택할 수 있지만 가비지컬렉션이 일어나는 시점은 선택할 수 없기 때문에 힙메모리는 진짜 섬세하게 사용해야 합니다.
-
Lock3rz
02.21 08:39
그렇죠... 가비지컬렉션 알고리즘 믿고[우와~ 쓰레기처리를 알아서? 헐~]
막짜다가 혼난적 있습니다 ㅡㅡ;;;
-
클라우드나인
02.21 08:46
헉...... 가영아버님 내공은 항상 놀랍습니다;; 이렇게 디테일한 내용을 설명하실 수 있다니.....
대략적으로 JVM이란게 어떤 놈인지 이해가 되는군요. 잘 써야겠네요.
그나저나 쓰다보면 오히려 C++가 쓰기 편하다고 느껴지고, 특히 자바의 privacy leak issue보면 더 그런 생각이 들더군요.
포인터를 랭귀지 단위에서는 안보이게 하려다 보니 상세한 설계가 필요해 졌고, 그게 가끔 전혀 생각치 못한 곳에서 문제가 되는 것 같습니다. 물론, 경험하면 같은 실수는 잘 안하게 되긴 해도요.
이번 문제점도 전혀 생각치 못한 곳에서 발생한 문제였습니다. 제 생각엔 string concatenation에서 하나의 operand가 null이라면 그냥 assign되는 형태로 구현되어있지 않을까 싶었는데 - C like languages에선 이런게 일반적이기도 하고 - 자바는 전혀 다른 방식으로 구현이 되어 있네요.
Java 기본서에서는 string concatenation을 적극 권장하는 느낌인데, 실제로는 좀 위험한 녀석이 될수도 있겠네요.
저는 오죽하면 이클립스에서도 auto indentation을 돌리면 하나의 string이 여러 개의 strings으로 나눠지고 중간에 + 연산자로 concatenation하길래, 바이트코드 생성단에서 다 처리되는 줄로만 알았습니다. 성능상 지장이 없어서 그렇게 바꿔주는 줄 알았거든요.
자세한 답변 감사드립니다. 많은 도움 되었습니다.
-
가영아빠
02.21 09:03
아닙니다. 컴파일러의 발달에 따라서 JVM버전 별로 최적화코드는 다릅니다.
제가 말한 건 어디까지나 일반적인 얘기지만 위에 String 남발하다가 프리징 먹은 얘기는 JDK1.6 시절의 경험담입니다.
C++로 된 클라이언트를 JAVA로 포팅했는데, 엉뚱하게도 몇분마다 한번 씩 60프레임에서 10프레임 미만으로 프레임 드랍이 나온다고 하고 전 그걸 원인도 몰랐으니까요. 창피하죠.
이 JAVA의 최대 단점이 제가보기에는 그 가비지 컬렉션입니다. 시점만 미묘하게 컨트롤 되도 좋을 텐데요.
당시 같이 작업하던 서버분은 가비지컬렉션을 방지하고 서버의 성능을 극대화하기 위해서 메모리 많이 먹고 반환하면 자주 쓰는 클래스는 자체적으로 비슷한 기능으로 만들어서 몇만개씩 풀링하더군요. 메모리 16G이상 필요하던데 그냥 돈으로 해결하고 말이죠. 좋은 프로그래머의 자세죠. ^^;;
-
클라우드나인
02.21 09:17
서버는 돈좀 들어도 괜찮으니까요. 클라이언트쪽 코드가 메모리/성능 최적화가 많이 들어가야 하니 고생 많이 하셨겠네요.. ^^;;
가비지컬렉팅 할 때에는 시스템 전체가 멈춰야 하니, 아무래도 성능저하가 크고..
그러다보니 안드로이드에서도 가비지컬렉팅 인터벌 맞추느라 고생 많이 했더라고요.
이클레어2.1까지는 가비지컬렉팅이 꽤 잦아서 성능이 안나오는 대신 메모리 확보가 잘 됐는데,
프로요2.2에서는 가비지컬렉팅이 띄엄띄엄 작동돼서 메모리는 많이 먹어도 전반적인 성능은 많이 올라갔었습니다.
그런데 역시 메모리를 많이 먹어서, 진저브레드2.3부터는 가비지컬렉팅 알고리즘을 바꾸고 인터벌도 이클리어와 프로요 중간점으로 맞췄더라구요. 그래서 진저브레드 올리고 메모리 점유는 낮아졌다는 말이 나와도 성능이 좋아졌다는 말은 거의 안나왔었던 기억입니다.
아이폰은 듣기로는 가비지컬렉팅을 거의 안하는 수준이라고 하네요. 아무래도 그놈은 백그라운드 작업이 없고 홈버튼 누르면 다 죽여버리니, 굳이 가비지컬랙팅을 자주 할 이유도 없는 것 같습니다.
재미있는 이야기 감사드리고요, 좋은하루 되세요. ^^
-
Lock3rz
02.21 11:28
아이폰도 iOS 5부터는 가비지 컬렉션을 지원합니다 [물론 선택사항]
iOS의 쾌적함은, 프로그래머가 한줄한줄 직접 메모리 관리를 스케줄링하는데서 오죠 ㅡㅡ;;;
멀티테스킹도, 진짜 쓰레드가 도는게 아니라, 아예 빈사상태로 죽여놓죠...
애플의 고집이 모바일분야에는 정말 잘 맞아 떨어진것 같네요.
-
꼬소
02.21 10:22
메모리 때문에 수고들 많으시네욤;;;
그냥 프로그래밍 안하는게 가장 맘편한 생활을 할 수 있을것 같아용...;; 생각만 해도 머리아포;;
-
Lock3rz
02.21 11:29
특히 모바일 분야에서는,
와장창 중창[?]으로 프로그래밍 해놨는데,
실제기기 테스트에서 메모리 경고 뜰때가 가장... 햄볶습니다 ㅜㅠ
-
꼬소
02.21 13:56
커널에서 메모리 간당간당한거 느껴 보셨나욤? ㅋㅋ 시스템 메모리가 4MB에서 깔짝깔짝 거리시는거 보면...
"우리 프로그램 이대로 괜찮을까?"를 심각하게 고민합니다.
물론 그런 상황을 가정하고 만든거지만 ㅋㅋㅋㅋㅋㅋ................ 그러다 패닉;; 그럼 디버깅이죠.
-
냠냠
02.21 12:11
돈으로 박을 수 있는 게 행복한 거죠.
그리고 돈으로 박아서 처리해야 합니다. 라고 확실히 이야기하고 진행할 수 있는 개발자가 있다면 그 역시 행복한 거구요.
물론 그 쯤 되려면 꽤 오랜 경험이 있어야 하니깐.. 것도 큰 어려움.......
-
piloteer
02.21 17:40
객체를 이어서 string을 만들기 때문인가 보군요.
-
김강욱
02.21 21:13
JSP 스크립트도 결국은 String으로 처리하는 것이니, 매우 느린 경우가 되겠군요.
가끔 서버가 프리징 때문에 중지되는 경우가 있어서 현재 GCor 만 바꾼 상태인데...이런 경우는 어떻게 처리해야 할까요?
실제 Massive String Concate 때문에 서버가 죽은 경우가 있어서, 왠만한 건 다 SB 를 사용했는데, 이것마저도 프리징 된다면 GCor 를 바꾸는 것 외에는 무엇을 해야 할 수 있을지 두렵네요.
-
가영아빠
02.21 21:49
전에 제가 했던 것중에 하나인데요, 비슷한 경험이지 않을까 해서요. StringBuffer의 재할당이 심각한 문제가 될만큼의 길이를 가지는 문자열에 대한 처리 문제였거든요.
제가 시도했던 해결책은 2가지였습니다.
1. 파일을 마샬링해서 스트림으로 사용, 인터페이스단만 래핑해서 String처리하게끔. (예상대로 동작은 했으나 버그 많아서 중도 포기)
2. MappedByteBuffer를 이용해서 하드를 메모리맵으로 이용해서 거의 무한하게 처리 (이건 성공했으나 속도포기)
근데 후에 ByteBuffer에서 다시 JSP로 파싱가능한지는 모르겠네요. 단순히 스트링컨케트네이션이 문제라면 2번으로 처리할 수 있을 겁니다.
그러게요 이런시도를 해보진 않아서 몰랐는데 일부러 다른 객체 null로 초기화한 상태에서
String과 참조변수를 더하니 null이 문자열로 변환되서 더해지네요.
초기화 하는건 맞습니다. String도 클래스니까. 다만 객체가 null로 초기화 되었을 때 "null" 이라는 문자를 출력하네요.
그냥 JVM 이 그런갑다 해야될거 같네요 ㅎㅎ