String 클래스는 Primitive 타입으로 취급(리터럴 방식일때만 한정)되기 때문에 new 키워드 없이 String 객체를 사용할 수 있습니다. String에 저장되는 문자열은 private final char[]의 형태이기 때문에 값이 변경되지 않습니다. String으로 할당된 문자열들을 더할때 마다(Str + Str + ...) 새로운 문자열을 생성하여 새로운 String 객체에 더해진 문자열을 저장하게 됩니다(private final의 형태이기 떄문에). 게다가 기존의 Str 객체가 새로운 String Str 객체를 참조하기 때문에 각각의 주소값들이 Stack 영역에 쌓이게 되고, 생성된 객체들은 Heap 영역에 쌓이게 됩니다.
따라서 GC의 호출 전까지 Heap 영역에 계속해서 새로운 객체들이 쌓이기 때문에 메모리 관리에 치명적입니다.
new 키워드를 사용하는 경우
Heap 영역에 String 객체가 생성된다.
new 키워드를 사용하지 않고 리터럴 방식을 사용할 경우
Heap 영역이 감싸고 있는 String Constant Pool 영역에 객체가 생성된다. 반복적으로 같은 문자열에 대한 객체가 생성되는것을 방지하기 위한 새로운 메모리 영역으로 s1과 s2는 같은 주소값을 가지고 있으며, s1과 s3은 다른 주소값을 가지고 있다.
위에서 알 수 있듯이 String은 불변객체이며 연산과정에서 비효율적인 메모리 할당이 일어나게 됩니다. 따라서 가변성의 성질을 갖는 StringBuilder나 StringBuffer 클래스의 .append() .delete() 등의 메서드를 사용하여 동일 객체내에서(새로이 객체를 생성하는 String 클래스와 차이점이 있다) 문자열 변경이 가능하게 됩니다.
StringBuilder는 비동기식 처리방식을 사용하기 때문에 멀티스레드 환경에서 안정성이 떨어지지만, 단일스레드 환경에서는 동기화를 고려하지 않는 만큼 성능이 StringBuffer보다 성능이 뛰어납니다.
StringBuffer는 동기식 처리방식을 사용하기 때문에 멀티스레드 환경에서 안정성(thread-safe)을 가지고있습니다.
멀티 스레드 환경에서 여러 스레드가 동시에 하나의 자원(객체 및 변수) 에 접근할 때, 기법을 활용하여 의도한 대로 동작하는 상황을 뜻합니다.
[참고문헌]
https://ifuwanna.tistory.com/221
https://github.com/WeareSoft/tech-interview/blob/master/contents/java.md
https://it-mesung.tistory.com/46
https://brunch.co.kr/@kd4/1