『자바의 신 3판』 을 읽고 내용 정리 및 공부한 내용을 정리한 글입니다.
서적: 자바의 신 3판 구입처
여기서부터는 2권의 내용입니다. 2권은 1장부터 다시 시작하는데, 편의상 1권에 이어 19장으로 카운팅했습니다.
💡 자세한 것은 참고 사이트의 오라클 공식 타임라인을 보면 된다.
자바는 Oracle에서만 만드는 것이 아니다. 각 Java 버전에서 제공되어야 하는 표준 문서가 만들어지면, 그 기준에 해당하는 각 벤더에 맞는 JDK가 별도로 만들어진다.
우리나라에서 많이 사용하는 서버는 다음과 같다.
리눅스의 경우에는 Oracle에서 만드는 기본 JDK를 주로 사용한다.
어떤 OS에서 개발하든지 JDK의 버전만 맞으면, 적용할 OS에서 컴파일만 하면 애플리케이션 실행에 문제가 없다.
즉, Oracle JDK로 개발하고 HP의 JDK에서 컴파일하더라도 전혀 문제는 발생하지 않는다는 것이다(하지만 각 회사별 JDK의 내부 동작 구조는 상이하다).
이 외의 라이선스 문제가 되는 부분을 제거한 완전 오픈소스 버전의 OpenJDK와, JRockit이라는 JDK가 있다.
💡 Sun JDK와 Oracle JRockit
Sun JDK는 인터프리터를 사용하고, JRockit은 JIT 컴파일러만 사용한다. 장기 실행 프로세스에서 JRockit이 조금 더 나은 성능을 보인다고 한다.
먼저, 각 용어는 다음의 약자다.
여기서 Java 2에서 2가 빠진 것은 Java SE 6가 출시되면서부터이며, 마케팅을 위해서 보다 쉽게 자바를 부를 수 있도록 Java로 통칭했다고 한다.
이름에서 알 수 있듯이, JRE는 실행만을 위한 환경이다. JDK는 JRE(실행환경)도 포함하고 있다.
각각 어떤 걸 제공하는 지는 아래 그림을 참고할 것.
💡 출처: Java Platform Standard Edition 8 Documentation
💡 아래 특징들은 자바 개발 초기에 정해진 것으로 지금의 상황과는 약간 다른 부분이 있다. 하지만, 이 내용의 근간은 달라지지 않고 계속 유지되고 있으므로 기억해두면 좋을 것이다.
자세한 설명: Oracle 홈페이지
책에서는 이를 간추려서 다음 다섯 가지를 소개하고 있다.
💡 Portable한 프로그램이란
찾아보니 “설치 없이 들고 다니면서 사용할 수 있는 프로그램”이라고 한다.
여기서는 조금 다른 의미로, 이식성이 좋다는 뜻으로 말한 것 같다. OS에 JRE만 설치하면, 개발한 프로그램을 어떤 플랫폼이든 상관없이 실행시킬 수 있으니까.
즉, Java 로 작성된 프로그램이 여러 플랫폼에서 실행될 수 있는 특성으로 이해하자.
책에서 설명한 것 중, 유의해서 봐야하는 것만 중점적으로 적었다. 자세한 내용은 책을 참고하는 것을 추천한다.
가장 최초의 버전이다. 그 이후에 나온 마이너 버전인 JDK 1.0.2 버전은 최초 안정 버전이며, Java 1이라고 불리게 되었다.
AWT는 Abstract Window Toolkit의 약자로, 자바를 이용하여 UI를 구성할 때 사용되는 기반 기술이다. 이 기술을 오랜 시간동안 발전시켜 왔지만 그리 많이 사용되지는 않는다.
JavaBeans는 자바에서 제공하는 컴포넌트 모델 중 하나이다.
JDBC는 Java Database Connectivity의 약자로, 데이터베이스에 데이터를 담기 위한 API를 말한다.
RMI는 Remote Method Invocation의 약자로, 같은 JVM에 있는 메소드를 호출하는 것이 아니라, 원격 JVM에 있는 메소드를 호출하기 위한 기술을 의미한다.
JDK 1.2 버전부터 1.5버전까지는 J2SE로 불렀으며, 새로운 버전의 자바라는 의미에서 Java 2라는 이름이 생겼다.
다음과 같은 사항들이 추가되었다.
JIT는 Just-In-Time의 약자로 어떤 메소드의 일부 혹은 전체 코드를 네이티브 코드로 변환하여 JVM에서 번역하지 않도록 함으로써 보다 빠른 성능을 제공하는 기술이다.
Collections는 이후 챕터에서 자세히 알아본다.
JNDI는 쉽게 말하면, 어떤 객체를 쉽게 찾을 수 있도록 도와주는 이름을 지정한 후, 나중에 그 이름으로 객체를 찾아가는 것을 의미한다.
자바에서는 RMI, CORBA와 같은 것을 사용할 때나, LDAP, DMS 등 각종 주소를 쉽게 지정하기 위해서 사용된다.
JDK 1.4에서는 많은 라이브러리들이 추가되었으며, 자바 커뮤니티 프로세스(JSR)라는 절차에 따라서 개발된 첫 번째 릴리즈이기도 하다.
정규 표현식은 어떤 문자열에서 특정 조건에 맞는 값이 있는지를 확인하는 데 사용된다.
NIO는 이전 버전까지의 IO를 처리할 때 사용한 java.io 패키지에서 제공하는 기능의 단점을 보완하기 위한 것이다.
Java 5에서는 매우 많은 변화가 있었다.
이 밖의 JDK의 라이브러리에 추가된 주요 기능은 다음과 같다.
Java 6가 나온 목적은 안정성과 확장성이라고 볼 수 있다.
요즘 들어 JVM 위에서 실행 가능한 각종 스크립트 언어들이 나오고 있다. 메모리 관리를 JVM에서 잘 처리해주기 때문이다.
💡 스크립트 언어란 (script language)
다른 응용 프로그램에 삽입되어서 동작하는 프로그래밍 언어이다. 간략히 스크립트라고도 한다.
C, C++, Java 등은 컴파일된 후 독립적으로 작동하는 완전한 응용 프로그램이지만, 자바스크립트, 제이쿼리, JSP, PHP, 파이썬 등의 스크립트 언어는 다른 응용 프로그램 안에 삽입되어 해석되는 방식으로 작동한다.
출처: 해시넷
이전 버전으로부터 5년만에 출시되었다. 30장과 31장에서 별도로 살펴본다.
가장 큰 변화는 람다 표현식 사용이 가능하다는 것이다.
다만, 무작정 람다로 개발하다가는 오히려 코드가 복잡해질 수도 있으니 잘 알고 필요할 때만 사용하는 것이 좋다.
이 부분에 대해서는 32장에서 별도로 살펴본다.
자바에서 추가된 모든 기능들이 대부분 웹 기반 개발자들에게 필요한 것은 아니다. 예를 들어 CORBA와 같은 기능은 사용하는 곳이 별로 없다.
💡 참고
1. Java API Doc: /technotes/guides/language/enhancements.html
2. 영문 위키피디아: java version history
JIT는 Just-In-Time의 약자이며, 이것을 사용하는 언어에는 자바와 .NET 등이 있다. 좀 더 쉬운 말로 “동적 변환 Dynamic translation”이라고 할 수 있다.
실행 시에 적용되는 기술로, 프로그램 실행을 보다 빠르게 할 수 있다.
역사적으로 보면, 컴퓨터 프로그램을 실행하는 방식은 두 가지로 나눌 수 있다.
JIT는 이 두 가지 방식을 혼합한 것이라고 보면 된다.
변환 작업은 인터프리터에 의해서 지속적으로 수행되지만, 필요한 코드의 정보는 캐시에 담아두었다가(메모리에 올려두었다가) 재사용하게 된다.
자바의 모토 중 하나가 “Compile once, Run Anywhere”다. 즉, 한 번 컴파일한 코드로 리눅스, 맥, 윈도우 등에서 모두 사용할 수 있다.
우리는 javac 명령어를 사용해 컴파일 하여 class라는 바이트 코드(Byte code) 생성한다. 다시 말해서, 텍스트로 만든 java 파일을 어떤 OS에서도 수행될 수 있도록 바이트 코드라는 파일로 만든 것 뿐이다.
바이트 코드는 컴퓨터가 알 수 있도록 변환 작업이 필요하다. 이 변환 작업을 JIT 컴파일러에서 한다.
하지만, 최근 들어 CPU 성능이 많이 좋아졌고 JDK의 성능 개선도 많이 이루어졌기 때문에 방금 이야기한 단점도 많이 개선되었다.
Oracle이 개발한 자바 가상 머신(JVM)의 구현 중 하나로, 성능 향상과 최적화에 중점을 뒀다.
공식적으로 JDK 1.3부터 제공되었다. 자바에서는 “HotSpot 클라이언트 컴파일러”와 “HotSpot 서버 컴파일러”의 두 가지 컴파일러를 제공한다.
JDK 1.3 전에 나왔던 가상 머신과 구분하기 위해 HotSpot이라는 이름을 사용했다. 오라클 사이트에서는 이전 버전의 JVM을 Classic VM이라고 한다.
예전에는 대부분 PC의 CPU 코어 개수는 하나였다. 이렇게 CPU 코어가 하나뿐인 사용자를 위해 만들어진 것이 “HotSpot 클라이언트 컴파일러”다.
이 컴파일러의 주요 특징은 다음과 같다.
코어가 많은 장비에서 애플리케이션을 돌리기 위해 만들어진 것이다. 이 컴파일러는 애플리케이션 수행 속도에 초점이 맞추어져 있다.
기본적으로 자바가 시작할 때 알아서 클라이언트 장비인지 서버 장비인지를 확인한다. 서버 컴파일러를 선택하는 기준은 다음과 같다.
참고로, OS에 따라서 어느 컴파일러를 사용할지가 정해져 있기도 하는데, 윈도우는 기본적으로 지정해주지 않으면 클라이언트 컴파일러가 사용된다.
java 명령에 클라이언트면 -client
, 서버면 -server
라고 지정해주면 된다.
$java -server 클래스이름
$java -client 클래스이름
이 외에도 여러 가지 옵션을 지정할 수 있다. 각 옵션은 공백으로 구분해준다.
아래는 JVM의 시작 메모리 크기를 지정하는 -Xms
라는 옵션으로, 512 메가 바이트의 시작 크기를 같이 지정한다.
$java -server -Xms512m 클래스이름
일반적으로 자바를 설명할 때 많이 사용되는 용어들이다.
작성한 자바 프로그램이 수행되는 프로세스를 의미한다. 다시 말해, java라는 명령어를 통해서 애플리케이션이 수행되면, 이 JVM 위에서 애플리케이션이 동작한다.
자바의 메모리 관리는 JVM이 알아서 하기 때문에 개발자가 하지 않아도 된다. 이때, JVM 내에서 메모리 관리를 해주는 것을 바로 “가비지 컬렉터”라고 부른다.
사용하고 남아 있는 전혀 필요 없는 객체들은 쓰레기(가비지)에 속한다. 가비지 컬렉터가 쓰레기를 알아서 청소해준다고 하더라도 메모리를 효율적으로 사용하도록 개발하는 것은 매우 중요하다.
일반적으로 GC라고 하면 Garbage Collection을 의미한다.
자바에서 GC를 수행하는 방식은 매우 여러 가지가 있다. 여기서는 아주 간단하게 GC를 살펴봤다.
어떤 객체를 생성하더라도, 그 객체는 언젠가는 쓰레기가 되어 메모리에서 지워져야만 한다.
만약 지워지지 않으면, 자바 프로그램은 엄청난 메모리가 필요할 것이다.
그래서 C를 사용하여 개발할 때에는 메모리를 해제하는 작업을 꼭 해줘야 한다. 하지만, 자바는 가비지 컬렉터가 알아서 치워주기 때문에 그런 작업을 해 줄 필요가 없다.
Java 7부터 공식적으로 사용할 수 있는 G1 (Garbage First)라는 가비지 컬렉터를 제외한 나머지 JVM은 다음과 같이 영역을 나누어 힙이라는 공간에 객체들을 관리한다.
💡 여기서 사용되는 영역에 대한 용어들은 JDK 벤더에 따라서 조금씩 상이하며, 이 책은 오라클에서 제공하는 JDK를 기준으로 작성하였다.
일반적으로 자바에서 메모리가 살아가는 과정은 다음과 같다. 여기서의 GC는 가비지 컬렉션을 말한다.
Young GC가 Full GC보다 속도가 더 빠르다. 왜냐하면 일반적으로 더 작은 공간이 할당되고, 객체들을 처리하는 방식도 다르기 때문이다. 그렇다고 전체의 힙 영역을 영 영역으로 만들면 장애로 이어질 확률이 매우 높아진다.
오라클 JDK에서 제공하는 GC의 방식은 크게 4가지가 있으며, Java 7에서 추가된 G1 GC를 포함하여 총 5가지의 가비지 컬렉터가 존재한다.
각 GC 방식들은 서로 장단점이 존재하기 때문에, 어떤 GC 방식이 가장 적합하다고 이야기하기는 매우 어렵다.
자바의 역사가 어떻게 되는지를 외우고 있을 필요는 없지만, 개발하고 운영하는 시스템의 자바 버전이 낮다면 최신 버전의 JDK에서 제공하는 기능들을 사용하기 어렵고 호환성 문제가 있을 수 있다.
따라서 본인이 사용하는 JDK에서 어떤 것이 제공되고, 제공 안 되는지 정도는 알아두어야만 한다.
💡 책에 있는 내용이 아닙니다.
책을 읽으며 설명이 더 필요하거나, 추가로 궁금한 점에 대해 질문 형식으로 작성 후, 답을 구해보고 있습니다.
참고한 사이트나 영상은 [출처]로 달아두었으며, 오류 지적은 언제나 환영합니다.
💡 자바 보안을 강화하는 방법 (AhnLab | 보안 이슈에서 발췌)
원래 자바는 보안성이 뛰어난 것으로 유명하다. 자바의 핵심 아키텍처가 가상머신(JVM)과 샌드박스이기 때문이다.
JVM은 자바 코드를 실행하는 환경이다. 자바 코드는 시스템 자원에 직접 접근하지 못하고, JVM이 제공하는 API를 통해 시스템 자원에 접근해야 한다. 이는 자바 코드가 시스템을 손상시킬 위험을 줄인다.
샌드박스는 자바 코드가 실행되는 환경을 제한하는 기능이다. 샌드박스 내에서 실행되는 자바 코드는 특정 파일에만 접근할 수 있고, 다른 프로세스와 상호 작용할 수 없다. 따라서 자바 코드가 시스템에 피해를 줄 가능성은 낮다.
자바 보안은 자바 플랫폼과 애플리케이션을 보안 위협으로부터 보호하기 위한 기술과 정책의 집합이다. 자바 보안은 다음 2가지 영역으로 나눌 수 있다.
자바는 이러한 보안 기능을 통해 공격으로부터 시스템을 보호할 수 있다. 하지만 자바 개발자와 사용자도 보안을 위해 노력해야 한다.
최신 보안 업데이트를 적용하자.
: 자바는 정기적으로 보안 업데이트를 제공한다. 최신 보안 업데이트를 적용하면 취약점을 악용한 공격을 방지할 수 있다.
개발자는 안전한 코딩 관행을 따라야 한다.
: 자바는 안전한 코딩을 위한 가이드라인을 제공하는데, 이 가이드라인만 따라도 공격을 방지하는 데 도움이 된다.
안전한 사용 습관을 유지해야 한다.
: 신뢰할 수 없는 소스에서 다운로드한 자바 애플리케이션을 실행해서는 안 되며, 자바 애플리케이션 실행 시 올바른 보안 설정을 하는 것이 중요하다.
Java 에서도 Porinter 라는 개념이 있나요? - 인프런 | 질문 & 답변
자바는 명시적인 포인터 연산이나 주소 직접 조작을 할 수 없도록 설계되었다. 대신, 객체에 대한 참조(reference)를 사용하여 객체를 조작할 수 있다.
reference는 메모리 주소가 아닌 객체의 식별자로, reference를 사용하면서 개발자는 메모리 관리를 더 쉽게 할 수 있으며, 포인터로 인한 일반적인 버그(예: 포인터 연산 오류)를 방지할 수 있다.
C언어에서는 포인터를 이용해 메모리의 주소에 직접 접근이 가능했고, 메모리 관리를 개발자가 해야했다. 자바에서는 이러한 포인터를 JVM만 사용할 수 있도록 한 것과 같다.
메모리 안전성(Safety): 포인터 연산은 메모리의 잘못된 위치에 접근할 수 있으며, 이는 프로그램의 예측 불가능한 동작과 메모리 오류로 이어질 수 있다.
가비지 컬렉션의 간소화: 포인터가 있는 경우, 메모리 할당 및 해제가 더 복잡해진다. 자바는 가비지 컬렉션을 통해 메모리 관리를 자동으로 수행하여 개발자가 명시적으로 메모리를 해제할 필요가 없게 했다.
플랫폼 독립성(Portability): 자바에서는 한 번 작성한 코드는 어떤 플랫폼에서도 실행될 수 있어야 한다. 포인터 연산은 플랫폼에 종속적이며 이식성을 저하시킬 수 있습니다.
보안 강화: 포인터 연산은 보안 측면에서 취약점을 생성할 수 있다.
- 예를 들어, 잘못된 포인터 연산은 메모리 덮어쓰기와 같은 보안 문제를 야기할 수 있다.
프로그래머 실수 방지:
포인터 연산은 개발자가 실수를 저지를 가능성이 높은 작업 중 하나다. 자바는 이러한 실수를 방지하고 예측 가능한 동작을 유지하기 위해 명시적인 포인터를 제공하지 않음으로써 편리함과 안정성을 제공한다.
하지만, GC와 같이 메모리를 어느 시점에 청소해주는지는 개발자가 알 수가 없어서 오류가 발생해도 확인이 어렵다.
[Java] 자바에서 포인터가 아닌 참조(Reference) 자료형을 사용하는 이유는?
아키텍처에 중립적이다는 것은 플랫폼에 중립적이다, 라는 것이다. 포터블하다는 찾아보니 “이식성이 높다”정도로 생각하면 될 것 같다.
한 번 컴파일 한 후, 플랫폼에 관계없이 실행시킬 수 있다는 것이 포터블하다는 의미가 아닐까?
JVM을 통해서 독립적으로 만들어 주고 있다. 기존의 C나 C++등의 컴파일 방식은 OS 별로 기계어가 달랐기 때문에, 각 OS에 맞춰서 컴파일을 해줘야 했다.
그래서 Java에서는 처음에 JVM이 이해할 수 있는 바이트 코드로 컴파일을 시켜준다. 그리고 JVM은 각 OS별로 설치할 수 있도록 해둔다.
JVM이 바이트 코드를 알아서 읽고 기계어로 변환시켜주기 때문에, 개발자가 할 일은 컴파일하고, 각 OS에 맞는 JVM을 설치하는 것 뿐이다.
JVM을 생각하면 완전히 플랫폼에 독립적인 것은 아니지만, 개발자가 수작업으로 손대지 않아도 된다는 점에서는 독립적이라고 할 수 있다.
네이티브 언어는 C언어와 C++언어를 말하며, 다음과 같은 이유로 사용할 수 있게 했다고 한다.
하지만, 다음과 같은 문제가 있을 수 있으므로 신중히 사용해야 한다.
JIT 컴파일러는 런타임 시에 바이트 코드를 기계어로 컴파일함으로써 Java 프로그램의 성능을 향상시키는 데 도움이 된다.
JVM은 각 메소드에 대해 사전 정의된 컴파일 임계값을 지정하고, 해당 메소드가 호출될 때마다 값을 감소시킨다. 호출 개수가 0에 도달하면 해당 메소드에 대한 JIT(Just-In-Time) 컴파일이 실행된다.
자주 사용되는 메소드는 JVM이 시작된 직후 컴파일되며, 자주 사용되지 않는 메소드는 훨씬 나중에 컴파일되거나 컴파일되지 않는다.
즉, 실행 시점에서 기계어 코드를 생성하면서 그 코드를 캐싱함으로써, 같은 함수가 여러 번 불릴 때 매번 기계어 코드를 생성하는 것을 방지하는 것이다.
장점은 프로그램을 실행하는 동안에 코드를 기계어로 변환하므로 실행 시간에 특정 플랫폼에 최적화된 코드를 생성할 수 있다. 실행할 때마다 기계어 변환 과정이 필요한 인터프리트 방식, 혹은 중간코드 방식에 비해서 속도가 빠를 수밖에 없다.
단점은 초기에 실행이 시작되는 속도가 많이 느리며, 소스 코드 혹은 바이트코드 파일과 실행파일 코드가 같이 있기 때문에 용량을 많이 잡아먹게 된다. 소스 코드나 바이트코드가 바뀌었을 경우에 컴파일을 다시 해야 하므로 그 변화 여부를 체크하고 있어야 한다.