⏱️ 1 스택과 힙
📄 1. 두 메모리간 비교
1 뭘 저장하나?
, 2 속도 (접근, 생성해제)?
, 3 데이터 접근법, 공유 범위?
,
4 최대 할당 사이즈는?
, 5 메모리 관리
비교 | Stack : 정적 메모리 | Heap : 동적 메모리 |
---|---|---|
① 저장 하는 것 | 함수내 지역변수, 매개변수, this 매개변수 참조 변수(주소값) 함수/메소드 호출 정보(Stack프레임) |
객체 그 자체 저장(객체 멤버 변수들도) (new) |
② 속도 (생성과 해제 & 데이터 접근) | 빠르다. | 느리다 |
③ 접근 방법과 공유 가능성 | 1. LIFO 순서 2. 함수내 블럭에서만 접근 가능 |
1. 무작위 접근 2. 프로그램간 공유가 가능하다. |
④ 최대 할당 사이즈 | 1. 컴파일 타임에 사이즈 제한 2. arr[]와 같은 문법 3. 약 1M 혹은 500M정도 작다. |
1. 고정 크기가 없지만 런타임에 결정 2. malloc 와 같은 문법 3. 제한이 거희 2GB도 가능하다. |
⑤ 메모리 관리 할당과 해제 | 자동 관리 블럭 내에서만 사용 가능 Stack 메모리는 빠르게 할당되고, 함수 종료시 자동으로 해제된다 |
1. UnMannaged 직접 관리 : 프로그래머가 직접 Heap 메모리 해제를 관리 2. Managed 자동 관리 : 가비지 컬렉터가 메모리의 참조를 검사하여 자동으로 관리 |
그 외
- Data Memory : 프로그램이 종료될 때까지 지워지지 않을 데이터 저장
- Static Memory : 전역 변수, static, 상수
📄 2. Stack Memory
스택 메모리란?
① 다음 요소가 저장되는 메모리다.
- Method가 호출 될때의 메모리 지역
- 인수(Arguments)가 넘겨질때, Push되는 메모리 지역
- 로컬 변수가 저장되는 메모리 지역
② State of each thread's execution (스레드가 실행을 나타내는 상태)
- Stack + Instruction Pointer
③ 스택의 속성
- 모든 변수들은 스레드가 돌아가는 메모리 위에 속한다.
All variables belong to the thread executing on that stack - 스레드가 생성될때, 스택은 정적으로 할당되고, 스택의 사이즈는 고정적이다.
- 만약 스택의 호출이 너무 깊게 들어가게 된다면, StackOverflow (일반적으로 재귀 함수 실행때 발생하는 오류) 발생
- 장점
- Compact Data : 메모리 단편화를 방지한다. Cache Hit Ratio를 높여줌으로 성능 향상에 기여한다.
- 생성과 해제에 대한 자원, 시간 예측 가능성 : 컴파일 될때 이미 정적 할당 공간을 계산하게 되므로 생성과 해제가 빠르다.
1). Stack메모리와 명령 처리 절차
- 메서드의 스택 프레임 은 LIFO 방식으로 할당되고,
- 지역변수또한 마찬가지로 LIFO 방식으로 Push 된다.
- 해제 될때, 선입 선출로 해제 된다.
좌 : 메인 스레드가 실행할 코드 | 우 : 스레드와 관련된 스텍 메모리 영역 레이아웃
①
void main(int argc, String args)
main :: int x = 1; int y = 2;
스레드가 메서드에 들어가면 곧바로 스택 위로 공간을 메서드에 맞게 할당하는데 이 공간을 (하늘색) 스택 프레임으로 부른다
main 함수를 포함하여 어떤 함수가 실행 될때마다 Stack Frame이 FIFO하게 생성된다.
이런 과정과 동시에 cpu에는 stack register가 Stack Pointer을 저장하며 Stack Pointer가 갱신된다.
이 스택 프레임이 존재하는 의의는 함수 호출후 원래 직전까지 실행했던 주소를 알아야 하기 때문이다.
stack register를 사용함으로 메모리를 cache로 fetch 되는것을 기다릴 필요가 없어서 빠르다.
함수가 동작하며 증가하는 esp,와 스택 프레임
함수 블럭내부 지역 변수의 메모리를 StackFrame 내부에서 자료형 크기만큼 연속적으로 할당한다.
할당이라는 과정은 "로컬 변수들이 출현 순서대로 Stack Pushing 되는 동작"을 의미한다.
만약 외부에서 넘겨준 매개 변수가 있다면 Call By Value/Reference 에 따라
Reference는 0xFFF 주소를 복사하고, Value는 값 복사를 한다.
이렇게 복사되는 동작은 "Stack Pusing"을 의미한다.
② main :: sum(x, y);
sum :: int s = x + y;
마찬가지로 x, y, s Stack Pushing
③ sum :: return s;
main :: int result = sum(x,y);
함수 종료시.
함수가 값을 리턴하지 않으면 직전 주소로 돌아가 계속 실행
함수가 값을 리턴하면 직전 주소에 리턴값 크기만큼 Push를 수행한다.
sum(a,b)를 호출했던 Main Stack에 푸시됩니다
④ main exit();
나중에 주요 메서드가 끝나면 스택 프레임도 무효화됩니다
이제 왜 스택으로 부르는지 이해할 수 있겠쥬
2). Stack 메모리 정적 할당
- 동적 할당을 스택에 사용할 수 없는 이유는 바로,
이미 할당된 사이즈에 초과하여 스텍 메모리에 덮어 씌워짐 때문이다,
아래와 같은 동작은 Heap에서 이뤄져야 하는것이고, Stack 은 명시적인 크기를 기입하여 정적 할당해야 한다.
📄 3. Heap Memory
공유 메모리 영역이고 "모든 스레드가 공유하고 접근할 수 있는" 개체가 저장됨.
- 객체 :
String
Object(Class Instance)
- 멤버 : 정적 변수가 클래스의 멤버면 힙에 저장될 수도 있다.
2). Reference != Object
-
가끔 레퍼런스랑 객체랑 헷갈릴때가 있는데
-
다음 예시를 보면 객체를 가르키는 레퍼런스는 여러가지 라는 점만 봐도 알 수 있다.
-
Reference와 Object 어느 메모리에 저장될까?
Reference Object allocated where? Stack, Heap(members of a class) allways Heap public class Example { private Map<Integer, String> idToNameMap; private static long numberOfInstances = 0; public Example() { this.idToNameMap = new HashMap<>(); numberOfInstances++; } public List<String> getAllNames() { int count = idToNameMap.size(); // 레퍼런스 : allNames | 지역 변수, 스텍 할당 // new ArrayList<String> | 객체, 힙 할당 List<String> allNames = new ArrayList<>(); allNames.addAll(idToNameMap.values()); return allNames; } }
📄 결론
스택이 빠른 이유는
-
지역 변수, 함수 등등 메모리에 연속적으로 할당된다.
연속 할당의 장점은 Cache Hit Ratio를 높이며,
메모리 접근 예측성 Locality도 향상된다.
-
함수 블럭 단위 마다 스택 프레임으로 나눠
할당 해제가 자동으로 이뤄진다.
지역 변수, 함수 등등 메모리에 연속적으로 할당된다.
연속 할당의 장점은 Cache Hit Ratio를 높이며,
메모리 접근 예측성 Locality도 향상된다.
함수 블럭 단위 마다 스택 프레임으로 나눠
할당 해제가 자동으로 이뤄진다.
참조
'CS > OS' 카테고리의 다른 글
| 니앙팽이 - 멀티스레딩 | 5 | 성능 평가 척도 (0) | 2025.01.18 |
---|---|
| 니앙팽이 - 멀티스레딩 | 4 | 프로세스 (0) | 2025.01.18 |
| 니앙팽이 - 멀티스레딩 | 3 | 프로그램을 처리하는 발전 흐름 (0) | 2025.01.18 |
| 니앙팽이 - 멀티스레딩 | 2 | OS가 메모리를 관리하는 방법 (0) | 2025.01.18 |
| 니앙팽이 - 멀티스레딩 | 0 | 하드웨어와 캐시 메모리 (0) | 2025.01.18 |