2025. 4. 30. 17:20ㆍJAVA/JAVA 실행 원리
Java 프로그래밍에서 널리 사용되는 문자열은 일련의 문자입니다. Java 프로그래밍 언어에서 문자열은 객체입니다.
Java 플랫폼은 문자열을 생성하고 조작하기 위한 String 클래스를 제공합니다.
🔹 자바에서의 String 특징
| 항목 | 설명 |
| 클래스 | java.lang.String 클래스(import 필요 없음) |
| 불변성 | 한번 생성된 문자열은 변경 불가능(Immutable) |
| 내부 구조 | 내부적으로 char[] 배열을 이용해 문자를 저장 |
| 리터럴 저장 | 문자열 리터럴은 String Pool(문자열 상수 풀)에 저장되어 재사용됨 |
문자열 리터럴은 컴파일 시에 만들어지고, 실행 시에 자바의 문자열 상수 풀(String Constatnt Pool)에 저장됩니다. 문자열 리터럴은 소스 코드에서 직접 정의된 문자열을 의미하며, 컴파일러는 이러한 문자열 상수 풀에 저장하여 메모리 사용을 최적화합니다.
문자열 리터럴 생성 시점
1. 컴파일 시 :
- 소스 코드에 있는 문자열 리터럴은 컴파일 시에 클래스 파일에 저장
- 컴파일러는 소스 코드의 모든 문자열 리터럴을 찾아 문자열 상수 풀에 저장할 준비를 한다
2. 클래스 로딩 시 :
- 프로그램이 실행될 때, JVM이 클래스를 로드하면서 문자열 리터럴을 문자열 상수 풀에 저장한다.
- 이 과정에서 동일한 문자열 리터럴이 여러 번 사용되면, 동일한 객체를 재사용하여 메모리 낭비를 줄인다
🔒 String의 불변성(Immutability)
String 클래스는 immutable, 즉 불변 객체입니다. 한 번 생성된 문자열은 절대 변경되지 않습니다.
🔹이유
- 보안성 (Security) : 패스워드, 파일 경로 등 민감한 문자열이 변하지 않음
- 스레드 안정성 (Thread-safety) : 공유해도 동기화 문제 없음
- 캐싱과 재사용 가능 : Pool에서 공유 가능 → 성능 최적화
🧾 예제1 : 값 변경 시 새로운 객체 생성
public class StringImmutableExample1 {
public static void main(String[] args) {
String s1 = "hello";
String s2 = s1;
s1 = s1 + " world";
System.out.println("s1: " + s1); // "hello world"
System.out.println("s2: " + s2); // "hello"
System.out.println("s1 == s2: " + (s1 == s2)); // false
}
}
🔍 설명
- s1과 s2는 처음에 "hello" 문자열 리터를을 공유.
- s1을 변경하면 새로운 문자열 "hello world"가 새 객체로 생성됨.
- 따라서 s1과 s2는 더 이상 같은 객체가 아님.
- 불변이기 때문에 기존 문자열은 그대로 유지됨.
🧾 예제2 : 메서드에서 문자열 변경 시(Call By Value와 String의 불변성)
public class StringImmutableExample2 {
public static void changeString(String str) {
str = str + " world";
}
public static void main(String[] args) {
String str = "hello";
changeString(str);
System.out.println("s: " + str); // "hello"
}
}
🔍 설명
- main()에서 str 은 "hello"라는 문자열 리터럴을 참조함
- changeString(str)을 호출하면서 str이라는 문자열 참조 변수의 복사본이 changeString() 메서드로 넘어감
- 메서드 안에서 str = str + " world" 는 새로운 문자열 "hello world" 를 생성해서 changeString() 메서드 내부의 str 변수만 바꿈
- 원래 main() 의 str 은 영향을 받지 않음
String은 불변이기 때문에,
"hello" + " world" 는 문자열을 수정하는 것이 아니라, 새로운 문자열을 생성함
기존 "hello" 는 절대 변경되지 않음
그래서 str = str + " world"; 는 str = new String("hello world")와 거의 같음
📞 자바는 Call By Value
- 자바는 참조 변수의 복사본을 넘긴다.
- 즉, changeString() 에서 받은 str 은 main()의 str이 가리키던 주소값의 복사본이다.
- 따라서 메서드 내부에서 새로운 객체를 가리키게 해도 main()의 str에는 아무 영향이 없다.
main() changeString()
str ───► "hello" str (복사본) ───► "hello" → "hello world"
→ changeString()의 str만 "hello world"를 가리킴
→ main()의 str은 여전히 "hello"
🧾 예제3 : 객체 상태에 String 필드가 있을 때
public class Person {
String name;
Person(String name) {
this.name = name;
}
void changeName(String newName) {
this.name = newName;
}
}
public class StringImmutableExample3 {
public static void main(String[] args) {
Person p = new Person("Alice");
String name = p.name;
p.changeName("Bob");
System.out.println("Name: " + name); // Alice
System.out.println("Changed: " + p.name); // Bob
}
}
🔍 설명
- Person p = new Person("Alice");
→ p.name 은 "Alice" 라는 String 객체를 참조함 - String name = p.name;
→ "Alice" 문자열 객체에 대한 참조가 복사됨(문자열 자체가 복사되는게 아님)
→ 변수 name 도 "Alice" 를 가리킴 - p.changeName("Bob");
→ p.name이 "Bob" 이라는 새로운 문자열 객체를 참조하게 됨
하지만!!!
→ name 은 여전히 "Alice" 를 가리킴
→ 왜냐하면 String 은 불변이기 때문에 기존 문자열 "Alice"는 절대 바뀌지 않음
💾 String Constant Pool(Literal Pool)
Java에서 String 클래스는 매우 자주 사용되며, 자바 프로그램 내 문자열의 생성과 비교는 전체 성능에 큰 영향을 줄 수 있습니다. 이를 최적화하기 위한 대표적인 JVM의 문자열을 저장하는 특수 메모리 영역인 String Constant Pool 입니다.
Java의 String Pool은 OS의 ELF 포맷에 있는 .rodata(read only data)처럼 불변 데이터를 공유하여 메모리를 절약하려는 철학적 목적은 비슷하지만, 구현은 JVM 내부의 동적 메모리 관리 전략으로 설계된 별개의 구조입니다.
String pool은 문자열 리터럴이 저장되는 자바 Heap의 저장 영역일 뿐입니다. 객체 할당과 비슷합니다.
기본적으로는 비어 있으며 자바 String 클래스에 의해 pirvate으로 관리됩니다.
문자열을 생성할 때마다 문자열 객체는 힙 메모리의 일부 공간을 차지합니다. 다수의 문자열을 생성하면 비용과 메모리가 증가할 수 있으며 이는 성능 저하로 이어질 수 있습니다.
JVM은 문자열 리터럴 초기화 동안 성능을 향상시키고 메모리 부담을 줄이기 위해 몇 가지 단계를 수행합니다. JVM에서 생성되는 문자열 객체의 수를 줄이기 위해 String 클래스는 String Constant Pool을 유지합니다.
문자열 리터럴을 생성할 때, JVM은 먼저 문자열 풀이 해당 리터럴을 가지고 있는지 확인합니다.
만약 문자열 풀이 이미 해당 리터럴을 가지고 있다면, 풀에 있는 인스턴스에 대한 참조를 반환합니다.
문자열 풀이 해당 리터럴을 가지고 있지 않다면, 새로운 String 객체가 문자열 풀에 생성됩니다.
⌨️ 자바에서의 문자열 생성
▶️ 문자열 리터럴 사용
String str1 = "Java";
String str2 = "Literal";
String str3 = "Java"; // 이 스테이트먼트를 실행 할때 JVM이 해당 문자열을 확인
// 이미 리터럴 풀이 해당 리터럴을 가지고 있음을 발견
// 따라서 새로운 인스턴스 생성하는 대신, 문자열 풀에 있는 인스턴스의 참조를 반환
// 즉, str1의 참조를 반환함
- 컴파일 시 설계도(.class 파일)에 문자열 리터럴로 등록 됨
- 클래스가 로딩될 때 JVM이 "Java" 를 String Pool(Heap의 특수 영역)에 저장함
- 동일한 문자열 리터럴이 있을 경우, 기존 참조를 재사용함
- 매우 효율적(메모리 절약)
▶️ new 연산자 사용
String str4 = new String("Java");
String str5 = new String("Cat");
String str6 = new String("Dog");
- "Java" 는 여전히 String Pool에 있음
- 하지만 new String()은 Pool에 있는 문자열을 복사해서 Heap에 새 객체를 생성함
- Pool 재사용 X, 무조건 새로 만듦
- == 비교 시 Pool 객체와 다르기 때문에 false → str1 == str4는 false
위 코드들은 다음과 같이 Java Heap에서 스트링 리터럴을 생성한다.

문자열 리터럴 사용
- 문자열 리터럴 "Java" 생성 후, String Pool에 저장한다.
- 다음 문자열 "Literal"를 생성. 이 또한 문자열 풀에 저장한다.
- 마지막으로 문자열 "Java"을 생성, 그러나 이번에는 JVM이 해당 문자열을 확인하고, 이미 문자열 풀이 해당 문자열 리터럴을 가지고 있음을 발견한다.
- 새로운 인스턴스를 생성하는 대신, 문자열 풀에 있는 인스턴스의 참조를 반환함. 즉, str1의 참조를 반환한다.
new 연산자 사용
- new 키워드를 사용하여 문자열 리터럴을 생성할 때도 문자열 풀에 저장한다.
- "Java", "Cat", "Dog" 세 개의 문자열 리터럴을 생성한다.
- "Cat"와 "Dog" 문자열 리터럴은 새로운 것이지만, "Java" 리터럴은 이미 String Pool에 존재하는 것을 발견한다.
- 이 시점에서 JVM은 Java Heap에 "Java" 리터럴을 위한 공간을 할당한다.
- new 키워드로 생성된 모든 몬자열 리터럴은 String Pool에 저장되지 않고 Java Heap에 저장된다는 점을 기억!!!
▶️ Java String.intern()
String.intern() 메서드는 문자열을 문자열 풀에 넣거나 동일한 값을 가진 문자열 풀의 다른 문자열 객체를 참조한다.
문자열 풀이 이미 해당 문자열 객체와 동일한 문자열을 포함하고 있는 경우, 풀에서 문자열을 반환한다.
이 문자열은 String.equals(Object) 메서드를 사용하여 결정한다.
문자열이 이미 존재하지 않는 경우, 문자열 객체가 풀에 추가되고 이 문자열 객체애 대한 참조가 반환된다.
즉, Heap에 있는 문자열을 String Pool로 이전하거나, 이미 String Pool에 있으면 그 참조를 반환하는 기능을 한다.
1. 문자열이 String Pool에 존재하면 → 해당 참조 변환
2. 없다면 → String Pool에 문자열 리터럴을 추가 후 참조 변환
🧾 간단 예제
public class InternDemo {
public static void main(String[] args) {
String str1 = new String("Java"); // Heap 영역에 저장
String str2 = "Java"; // String Pool 영역에 저장
System.out.println(str1 == str2); // false
System.out.println(str1.intern() == str2.intern()); // true
System.out.println(str1.equals(str2)); // 위 코드랑 등가
}
}
두 문자열 str1과 str2에 대해, str1.intern() == str2.intern()이 true가 되려면, str1.equals(str2)가 true여야만 한다.
str1.intern() == str2.intern()이 true를 반환한다는 것은 str1.equals(str2)가 true를 반환한다는 것과 같은 의미다.
이것은 두 문자열이 동일한 내용을 가지며, 동일한 문자열 풀 객체를 참조하고 있음을 의미한다.
💬 자바에서 모든 문자열 리터럴과 문자열 값을 가지는 상수 표현식은 자동으로 interned(인터닝)된다. 이는 메모리 사용의 효율성을 높이고 문자열 리터럴이 여러 번 사용될 때 메모리 낭비를 줄이기 위합니다.
인터닝(Interning)은 자바에서 문자열을 효율적으로 관리하기 위한 기술이다. 문자열을 인턴(intern)한다는 것은 동일한 내용의 문자열이 여러 개 존재하지 않도록 문자열을 공유하여 메모리 사용을 최적화하는 것을 의미한다.
🤔 new String(" ");과 new String(" ").intern(); 두 메서드의 차이점은?
public class InternDemo {
public static void main(String[] args) {
String str1 = new String("Java");
String str2 = new String("Java").intern();
String str3 = "Java";
System.out.println(str1 == str2); // false
System.out.println(str1 == str3); // false
System.out.println(str2 == str3); // true
System.out.println(str1.equals(str2)); // true
System.out.println(str1.equals(str3)); // true
}
}
| 구문 | new String("Java") | new String("Java").intern() |
| 메모리 위치 | Heap | Heap에서 생성 후 → String Pool 참조 반환 |
| String Pool 등록 여부 | ❌ (String Pool에는 "Java"가 있음, 하지만 이객체는 별도) |
✅ String Pool의 "Java"를 참조 |
| 참조 비교(==) | 다른 객체 | String Pool 참조 |
1. new String("Java");
- Heap에 새로운 String 객체를 무조건 생성
- "Java" 리터럴은 이미 String Pool에 있지만, 생성된 객체는 별개
- 비용이 크고, String Pool과는 분리된 인스턴스
❗ 즉, 이 객체는 String Pool을 절대 참조하지 않음
2. new String("Java").intern()
- 먼저 Heap에 객체 생성
- .intern() 호출 시:
▶ "Java"가 String Pool에 있으면 → 그 참조를 반환
▶ 없으면 → String Pool에 추가 후 그 참조 반환 - 따라서 .intern() 호출 결과는 항상 String Pool 참조
❗ 이 참조는 문자열 리터럴 "Java"와 동일 객체임
💾 String Length
length() 메서드는 Java String 클래스에서 제공하는 인스턴스 메서드로,
문자열 내 문자의 개수(길이)를 반환한다.
String text = "Java";
System.out.println(text.length()); // 출력: 4
"Java"는 4개의 문자 → length() 결과는 4
회문(palindrome)은 대칭적인 단어나 문장입니다. 즉, 대소문자와 구두점을 무시하고 앞뒤로 동일하게 철자가 작성됩니다. 다음은 회문 문자열을 뒤집는 짧고 비효율적인 프로그램입니다.
0부터 계산하여 문자열의 i번째 문자를 반환하는 문자열 메서드 charAt(i)를 호출합니다.
public class StringDemo {
public static void main(String[] args) {
String palindrome = "Dot saw I was Tod";
int len = palindrome.length();
char[] tempCharArray = new char[len];
char[] charArray = new char[len];
// put original string in an
// array of chars
for (int i = 0; i < len; i++) {
tempCharArray[i] =
palindrome.charAt(i); // 문자 : character
// 문자열의 각 문자는 어디에 저장? 배열
// 배열은 각 엘리먼트의 순서를 지정하는 index
// String의 인스턴스 메서드인 charAt 파라미터(i)는 무엇을 의미?
// 각 문자의 인덱스를 달라고 전달하라고 함
}
// 위의 for 메서드와 동일한 메서드
palindrome.getChars(0, // 이 문자열의 0번째 인덱스인 문자부터
len, // 17개(문자열의 범위)
tempCharArray, // tempCharArray 배열에 복사한다.
0); // tempCharArray 배열의 0번째 인덱스인 char 변수부터...
// reverse array of chars
for (int j = 0; j < len; j++) {
charArray[j] =
tempCharArray[len - 1 - j];
// 인덱스의 내림차순
// length - 1은 마지막 엘리먼트 인덱스 값
}
String reversePalindrome =
new String(charArray);
System.out.println(reversePalindrome);
}
}
출력 시
doT saw I was toD
⚠️ 주의할 점 ⚠️
1. 공백도 문자로 취급됨
String s = "Hello World";
// 중간 공백(스페이스)
System.out.println(s.length()); // 11 (공백 포함)
2. 빈 문자열의 길이는 0
String empty = ""; // <- 빈문자열
System.out.println(empty.length()); // 0
3. null.length() 는 에러 발생
String str = null;
System.out.println(str.length()); // ❌ NullPointerException 발생
// Null pointer access: The variable str can only be null at this location
💾 Concatenation Strings
.concat() 메서드는 두 문자열을 연결하는 메서드다
string1.concat(string2);
그러면 끝에 string2가 추가된 string1인 새 문자열이 반환된다.
public static void main(String... args) {
String hello = "hello ";
String world = "world";
String concates = hello.concat(world);
// 문자열을 이어주는 인스턴스 메서드
concates = "My name is ".concat("Rumplestiltskin");
// 이런 코딩은 하드코딩임
System.out.println(hello.concat(world));
System.out.println(concates);
return;
}
문자열은 다음과 같이 + 연산자와 더 일반적으로 연결된다.
"Hello," + " world" + "!"
결과
"Hello, world!"
💾 Creating Format Strings
포멧이 지정된 숫자로 출력을 프린트하기 위해 printf() 및 format() 메서드를 사용하는 방법이다.
String 클래스에는 PrintStream 객체아 아닌 String 객체를 반환하는 동등한 클래스 메서디은 format()이 있다.
String의 정적 foramt() 메서드를 사용하면 일회성 print 문과 달리 재사용할 수 있는 포멧화된 문자여를 만들수 있다.
public class StringFormat {
static {
Integer intVar = 20;/*Integer.valueOf(1);*/ /*new Integer(1)*/ // AutoBoxing
// deprecated.. 낙후된 : 써도 되는데 사용하지 마시요!! 언젠가 차후 자바 버전에서 이 컨스트럭터는 private이 될것!
Float floatVar = Float.valueOf(3.0f);//new Float(3.0f);//3.0f; /*new Float(3.of);*/ // AutoBoxing
String stringVar = "Hello";
Float.valueOf(3.0f);
String valueString = "10";
Integer what = Integer.valueOf(valueString);
// printf의 f 는 format
// printf을 F3을 했을때
// public PrintStream printf(String format, Object ... args)
// Object... args는 호출 할 아규먼트 수가 정해지지 않았다 몇개가 오든 상관없다.
// variable arguments
System.out.printf("The value of the float " +
"variable is %f, while " + // %f 의 %는 값을 특정 문자 값으로 변환해라 의미
"the value of the " +
"integer variable is %d, " + // %d 는 십진수로 출력해달라는 의미
"and the string is %s",
floatVar, // Float 클레스 기본 데이터 타입이 float인 value 필드의 값을 전달
intVar, // integer 클래스 value 필드의 기본 데이터 타입은 int
stringVar);
// 이 메서드의 아규먼트는 4개
// , 가 3개
// 저 + 문장은 하나의 아규먼트
// 위에 printf 메서드랑 동일 메서드
// 대신 위 메서드는 콘솔창에 출력하는 메서드
// 아래 메서드는 fs에 참조 값을 전달해주는 메서드
String fs;
fs = String.format("The value of the float " +
"variable is %f, while " +
"the value of the " +
"integer variable is %d, " +
" and the string is %s",
floatVar,
intVar,
stringVar);
String fs1;
fs1 = String.format("0x%x, %d", // %x 16진수로 출력
intVar, intVar);
System.out.println(fs);
System.out.println(fs1);
}
public static void main(String... args) {
}
}'JAVA > JAVA 실행 원리' 카테고리의 다른 글
| Abstract Class(추상 클래스) (0) | 2025.05.28 |
|---|---|
| Fully Qualified Name (1) | 2025.04.30 |
| Semantic Error와 Syntax Error (0) | 2025.04.23 |
| 2의 보수 (0) | 2025.04.16 |
| JVM(자바 가상 머신) (0) | 2025.03.28 |