Java가 인기 있었던 이유 중 하나는 OS에 구애받지 않고 사용할 수 있었던 것이다. JVM을 사용하면 윈도우에서 작성했건 리눅스에서 했건 맥에서 했건 간에 모두 사용할 수 있다. JVM은 Java Virtual Machine으로 다양한 역할을 하지만 자바 바이트 코드를 운영체제가 이해할 수 있도록 바꿔주는 역할도 한다. Java가 운영체제마다 명령어 체계가 다르기 때문에 발생했던 문제점을 해결한 것이다. 물론 운영체제에 맞는 JVM을 설치해야한다는 점이 있긴 한데, 개발자들 입장에서는 같은 코드로 다양한 운영체제에서 사용할 수 있다는 것은 엄청난 메리트가 아닐 수 없었다. 그렇다면 Java가 컴파일 되는 과정을 좀 더 자세히 알아보도록하겠다.
1. 먼저 개발자가 *.java 파일인 실제 소스코드를 작성한다.
2. 자바 컴파일러를 사용해서 *.class 파일이 생성된다. *.class 파일이 바로 자바 바이트코드이고 JVM이 읽어서 전환할 수 있는 코드이다.
3. 자바 바이트 코드가 JVM의 클래스 로더에게 전달 된다.
4. 클래스가 필요해지면 클래스 로더가 Dynamic Loading을 통해 필요한 클레스들을 JVM의 메모리 영역에 로드한다.
5. Execution Engine이 JVM 메모리 영역에 올라온 바이트 코드들을 하나하나씩 실행한다. 실행엔진이 바이트코드를 처리하는 방법은 인터프리터, JIT(Just In Time)2가지가 있는데 따로 분리되어 있는게 아니라 동시적으로 적용된다고 보면 된다.
첫번째로는 인터프리터이다. 잘 아시다시피 명령어를 하나 하나씩 실행하는 방법이다. 바이트코드를 하나하나씩 실행하기 때문에 한번에 컴파일 해놓고 실행하는 c언어와 다르게 속도가 조금 느린 점이 있다.
두번째로는 JIT 컴파일러이다. 인터프리터만 사용하기에는 너무 느리기 때문에 인터프리터를 사용하면서 캐싱을 사용하는 방법이다. 자바에서 인터프리터는 총 4단계의 컴파일러를 사용하는데 1 ~ 3레벨에서는 단순 컴파일만 실행하고 4레벨 컴파일러는 캐싱까지 수행한다.
hello();
hello();
hello();
bye();
예를 들어서 hello()라는 메소드와 bye()라는 메소드가 있다고 가정해보자. hello() 메소드는 연속으로 3번 호출하고 bye()는 1번 호출한다고 하면 hello()라는 메소드의 사용 횟수가 많기 때문에 4레벨 컴파일러가 hello()메소드를 자주 사용하는 코드로 판단하고 캐싱을 한다. 그래서 다음부터 호출할 때 hello() 메소드는 컴파일 하지 않고 이미 캐싱 되어있는 기계어를 불러온다. 이렇게 해서 인터프리터의 단점을 보완하고 있다. 아래 사진에서 3번째 열이 컴파일 레벨이라고 보면 되는데 실제로 이렇게 실행 되니 참고바란다.
어떤 블로그들에 보면 JIT컴파일러는 바이트 코드를 전체를 컴파일 하고 해당 메서드를 직접 바이너리로 실행한다는 글들이 많은데, 생각해보면 넌센스인 것 같다. 전체를 컴파일해서 쓰면 캐싱자체가 필요 없는데 잘못된 글들이 아닐까 생각이 든다. 정답은 위에서 설명한 부분부분 캐싱해서 쓰는 것이 맞다고 생각이 든다.
'Java' 카테고리의 다른 글
Java - String pool (0) | 2022.08.03 |
---|---|
Java - 가비지 컬렉션(Garbage Collection)이란? (0) | 2022.08.02 |
Java - HashSet, LinkedHashSet, TreeSet 차이 (0) | 2022.07.31 |
Java - HashTable이란? (0) | 2022.07.30 |
Java - HashMap, LinkedHashMap, TreeMap 차이 (0) | 2022.07.29 |