▸JAVA/기본 상식

Java의 메모리 구조_자바가 상대적으로 느린 이유[2/3]

코데방 2019. 12. 9.
728x90

C언어에 비해 Java가 느린 이유는 크게 2가지를 들 수 있을 것 같습니다. 첫 번째는 객체지향언어라는 특성 때문이고, 두 번째는 JVM이라는 가상 머신을 사용하기 때문입니다. 둘 다 개발자의 편의성을 위해 성능을 희생하고 있는데, 사실 하드웨어도 빨라지고 Java 자체의 소프트웨어도 워낙 발달해서 초기에 이슈가 됐던 성능 이슈가 잠잠해졌습니다. 하지만 여전히 하드웨어 스펙을 짱짱하게 넣지 못하는 소규모 장비 등에서는 C언어를 주로 사용한다고 합니다.

 


 

[ 객체지향언어의 특성으로 인한 느림 ]

Java에서는 클래스 단위로 모든 코드를 작성합니다. C언어와 같이 함수 단위가 아닙니다. 따라서 다른 클래스에 있는 일부 메소드나 정보를 사용하기 위해서는 전체 클래스를 인스턴스로 만들어 메모리에 저장해야 합니다. 이를 피하기 위한 static 사용도 있지만 아래와 같은 단점이 있기 때문에 사용을 그리 권장하지는 않습니다.

 

※ Static 사용의 단점
1. 클래스가 많아지고 코드가 복잡해지면 static의 현재 상태를 추론하기가 힘들어짐
2. 각 클래스의 독립성을 추구하는 객체지향 방식의 이념에서 벗어남 (객체간 의존도를 높임)
3. 캡슐화를 통해 필드의 직접 노출을 막는 객체지향 방식의 이념에서 벗어남
4. 프로그램 종료시까지 메모리를 점유하고 있어 메모리 낭비 발생 가능

 

위와 같은 이유로 잦은 static 사용은 좋지 않은 코드가 됩니다. 따라서 대부분의 코드는 클래스 단위로 짜여서 인스턴스를 통해 객체를 생성한 후 관련 기능을 호출하게 됩니다. 그만큼 메모리를 많이 사용하게 되고 코드를 찾아서 로드하는 시간도 추가되는 것이죠. 만약 이런 문제를 피하기 위해 클래스 단위를 잘게 쪼개서 작성할 경우 또한 객체를 다루는 효율성이 떨어지게 되기 때문에, 어쩔 수 없는 현상이라고 할 수 있습니다.

객체를 중심으로 다루고, 이미 만들어진 클래스를 가져다 쓰는 일이 많으니 코드도 그만큼 많아집니다. 상속과 같은 기능을 통해서 코드의 재활용성을 높여 짧게 작성할 수 있지만, 컴퓨터의 입장에서 보면 인스턴스를 하나 생성할때는 상속 받아서 직접 작성하지 않은 필드나 메소드도 어차피 인스턴스 안에 포함되게 됩니다. 우리가 짠 코드가 짧아도 실제 코드는 길어지는 것이죠.

이러한 이유로 Java에서는 C언어와 다르게 모든 코드를 미리 메모리에 올려두지 않고 필요할 때마다 가져다 쓰는 동적 할당 방식을 사용합니다. 이 때문에 상대적으로 느릴 수밖에 없습니다. 물론 하드웨어와 소프트웨어 기술이 많이 발달해서 차이가 점점 줄어들고는 있지만 아직까지 속도차가 존재할 수밖에 없습니다.

 

 

 


 

[ JVM 사용으로 인한 느림 ]

 

C언어는 컴파일 시 코드를 모두 기계어로 직접 번역해서 바로 메모리에 올려두고 실행합니다. 하지만 Java에서는 먼저 바이트코드로 컴파일을 한 뒤, 동적할당 된 코드를 JVM의 Excution Engine이 번역해서 실행하게 됩니다. 이 기술이 많이 발전에서 JIT(Just In Time)과 같은 방식으로 속도를 높였지만, 그럼에도 C언어에 비해 상대적으로 느릴 수밖에 없습니다.

이렇게 하는 이유는 JVM이 OS나 기타 환경에 맞춰 약간씩 다르게 기계어로 번역한 뒤 실행해주기 때문입니다. 때문에 C언어는 OS 및 기타 환경에 따라 코드를 직접 따로 작성해줘야 하지만 Java는 같은 코드를 짜면 환경에 맞게 알아서 실행됩니다. 만약 새로운 버전의 Windows가 나와서 OS의 작동방식이 바뀌게 되면 C언어의 경우 코드를 이에 맞게 수정해줘야 하지만, Java는 코드 수정이 불필요합니다. JVM을 만드는 엔지니어들이 OS에 맞게 JVM을 다시 만들어주면 됩니다. 물론 C언어도 컴파일러가 각 환경에 맞게 컴파일해주는 방식으로 바뀐다면 신경쓸 필요가 없겠으나, 그렇게 해주고 있지 않습니다.

따라서 JVM이라는 하나의 프로그램 안에서 다시 프로그램이 실행되는 구조이므로 같은 프로그램이라도 C언어에서는 하나의 프로그램이, Java에서는 2개 이상의 프로그램이 동시에 작동됩니다. 그만큼 메모리 사용과 연산도 많아지고 상대적으로 느릴 수밖에 없습니다.

 

 

 

또한 C언어에서는 동적할당한 메모리를 개발자의 명령에 따라 즉시 해제하지만, Java에서는 직접 메모리를 다루지 않도록 되어 있기 때문에 이 작업을 GC(가비지 컬렉터)가 대신해줍니다. 힙 영역과 코드 영역을 점유한 메모리 공간을 참조하는 레퍼런스(포인터)가 있는지 확인하다가 완전히 없어지면 메모리 공간을 해제하는 방식인데 가비지 컬렉터도 프로그램이기 때문에 메모리와 연산 작업을 동반합니다. 개발자가 편해지는 만큼 컴퓨터가 일을 더 해야하니 더 느릴 수밖에 없는 구조입니다.

 


 

요즘 컴퓨터 사양에서는 그리 속도 차이가 없고, 임베디드 같은 소형 하드웨어에서나 체감된다고 하지만 그래도 일단 상대적으로 느린 것이 사실이라 정리해봤습니다. Java의 동작 방식과 메모리 구조를 이해하고 최적화된 코딩을 하는 것이 Java개발자의 숙명이 아닌가 싶습니다.

728x90

댓글

💲 추천 글