728x90

 

 다음 세 가지로 나눠서 JVM에게 메모리를 어떻게 할당되는지 살펴 볼 것이다.

 

  • 클래스 변수
  • 인스턴스 변수
  • 지역 변수

 

 

다음 코드를 예시로 그림으로 가시화 하여 실제로 메모리가 어떻게 올라가는지 살펴보겠습니다.

 

class My_Obj {
	Integer value;
	static Integer classVar;
}

public class Main {
	public static void main(String[] args) {
		My_Obj obj = new My_Obj();
		obj.value = 2;
		obj.classVar = 1;

		int a = 10;

		My_Obj obj2 = new My_Obj();
		obj2.value = 10;
		obj2.classVar = 9;
	}
}

 

 

들어가기 앞서 코드 설명을 하자면,

 

  • obj 객체를 생성한다.
  • 이 객체의 맴버 변수인 value, classVar 에 각각 2, 1를 할당한다.
  • obj 객체의 맴버 변수를 출력한다. 그 다음에, obj2 객체를 생성한다.
  • 그리고 이 객체의 맴버 변수인 value, classVar 에 각각 10, 11을 할당한다. 그 다음 obj2 객체의 맴버 변수를 출력한다.

 

그림으로 어떻게 변수들이 할당되는지 살펴보자.

 

 

크게 3가지 영역으로 나누어본다. Method, Heap, Stack 영역 으로 나뉜다. 이 중에서 클래스 변수는 클래스 파일을 읽어들이고 로더가 JVM 메모리에 올릴 때 메모리에 올라간다. 그리고 프로그램이 종료될 때 사라진다. 힙에 있는 인스턴스들은, GC의 대상이 될 때 처리가 된다. 마지막으로 지역 변수는 자신이 선언된 블록이 끝났을 때 사라진다.

 

 

처음 obj 객체의 출력 까지만 그림으로 표현해보았다. Heap 영역에는 GC가 있기 때문에 대상이 되는 인스턴스들과 Constant Pool 안에 있는 것들은 사용되지 않으면 정리가 된다. 변수에 할당된 모든 리터럴 값들은 Constant Pool 에 있고 Method 영역이나 Stack 영역에서 각 객체들은 Constant Pool 안의 해당 값의 참조 주소를 갖고 있다.

그 다음 obj2 객체가 생성이 되면 이렇게 된다.

 

classVar 클래스 변수가 갱신이 되면서 Constant Pool 내에 리터럴 값 9의 참조 주소를 참조하는 것으로 갱신된 것을 볼 수 있다.

 

그러면 My_Obj 객체의 value 필드의 타입을 참조타입에서 원시타입으로 변경 해서 살펴보자.

class My_Obj {
	int value; << 원시 타입으로 변경
}

public class Main {
	public static void main(String[] args) {
		My_Obj obj = new My_Obj();
		obj.value = 2;

		int a = 11;  << 새로 추가

		My_Obj obj2 = new My_Obj();
		obj2.value = 10;
	}
}

 

내부 메모리상으로는 다음과 같을 것이다.

 

 

여기서 알아가야 할게 Integer는 내부적으로 캐시가 된다는 점이다.

 

Integer 객체는 범위 -128에서 127 사이의 값일 경우 Constant Pool에서 캐싱된 값을 사용하겠지만, 그렇지 않다면 새로운 객체가 힙에 생성됨. 증거 자료는 다음과 같다. (Integer 래퍼 클래스 내부에 정적 프라이빗 클래스인 IntegerCachevalueOf 메소드이다.)

 

 

 

그러므로 예를 들어,

Integer i1 = 100;
Integer i2 = 100;
Integer i3 = 200;
Integer i4 = 200;

System.out.println(i1 == i2);  // true (캐싱된 객체를 재사용)
System.out.println(i3 == i4);  // false (새로운 객체가 각각 생성됨)

 

 

  • i1 == i2는 true를 반환함. 왜냐하면 100은 -128 ~ 127 사이에 있으므로 캐싱된 같은 객체를 사용하기 때문.
  • 반면, i3 == i4는 false를 반환함. 왜냐하면 200은 캐싱 범위 밖이므로 새로운 객체가 각각 생성되었기 때문.

 

 

그러면, 이점을 응용을 해보자. 다음 코드를 보자.

 

class My_Obj {
	int value;
}

public class Main {
	public static void main(String[] args) {
		My_Obj obj = new My_Obj();
		obj.value = 10;
        
        int a = 10;
        
        System.out.println(obj.value == a) // true 가 나온다.
	}
}

 

 

이런 그림이 될 것 이다.

 

결론

메모리 구조를 한번 더 정리하는 계기가 되었다.

 

 

 

👉🏻 참조

https://codingdog.tistory.com/entry/java-클래스-변수-어떻게-메모리에-올라갈까

'언어 > 자바' 카테고리의 다른 글

List<?> 와 List 의 차이  (0) 2023.05.03
[JAVA] List<?> 와 List<Object>의 차이  (2) 2022.09.21
728x90

람다 SnapStart 를 적용하며 

람다에 자바로 된 함수를 띄었다. 자바 파일은 아무래도 node.js에 비해 사이즈가 컸다. 
기존에 회사 넥서스에 올라가 있는 라이브러리를 사용하려다 보니 자바로 어쩔 수 없이 해야 했던 상황이다. (다시 js로 구현 해도 되지만 알림톡쪽은 급하게 개선 해야 하는 비즈니스 로직이라 그건 추후로 미뤘다)
그래서 50mb 정도 되는 자바 코드를 그대로 올려서 cold start 로 실행 시켰다.
그러다 보니. 람다 자체내에서 코드를 다운로드 하고, 컨테이너를 띄우고, 전역 코드를 실행 후 내부 함수를 실행하는 순서 까지가 일반적인 방식으로 40000~50000ms(4~50s) 정도가 걸렸다.

처음에 ColdStart로 40초 정도 걸리니 방법을 모색 해보았다. 처음에는 다음과 같은 방법을 찾았다.
- 타임아웃 늘리기
- node.js 로 다시 구현
- 프로비저닝 동시성 구성
- 해당 함수 실행 예상 시간 전에 health check를 통해 warm up 해놓기

 

타임아웃 늘리기

근본적인 해결책은 아닌 것 같았다. 해당 함수가 띄어질 때 까지 40~50초 가량 걸리는데, 빌드될 때 까지 기다리고 처리될 때 까지 함수가 내려지지 않을 정도의 시간을 주는 것이었다.


node.js 로 다시 구현

위에서 말했듯이 다시 js로 구현 해도 되지만 알림톡쪽은 급하게 개선 해야 하는 비즈니스 로직이라 그건 추후로 미뤘다. 그리고 해당 라이브러리에 은근 로직들이 많아 새로 다른 언어로 로직을 만드는데 리스크를 안아야 하는점도 있었다.


프로비저닝 동시성 구성

항상 함수를 띄어 놓으면 그만큼 과금이 된다. 하루에 특정 시간대에 한 두번 도는 함수인데, 함수가 무거워서 컨테이너가 올라갈 때 시간이 많이 걸린다는 이유만으로 해당 금액을 지불하기엔 돈이 아까웠다.


해당 함수 실행 예상 시간 전에 health check를 통해 warm up 해놓기

해당 로직을 처리 해야 하는 예상 시간을 추측 해서 5분 마다 헬스체크 하면서 warm up 시켜놓는 방식도 괜찮았지만, 근본적인 해결책이라고는 볼 수 없었다. 헬스체크를 보내야 하는데, 이를 보내는 주체에 의존하는 모양이 되기 때문에, 주체에 문제가 생기면 warm up이 안되기 때문이다.


지금까지의 고민을 SnapStart이 모든것을 해결해 주었다.

 

SnapStart 란 해당 버전의 빌드 파일의 스냅샷을 따서 캐싱 하고(스냅샷은 Amazon EBS 에 저장), Cold Start를 할 때 이를 활용하여 실행하는 기능이다. 그래서 람다 컨테이너를 올릴 때 아무리 큰 함수라도 빠르게 올릴 수 있다.

 

스냅샷은 구체적으로 함수 코드가 로드되고 초기화된 상태를 저장한다.

 

메모리상태
- Java 런타임에 의해 로드되고 초기화된 모든 클래스
- 정적 변수
- 초기화된 객체
- JIT 컴파일된 코드
- 의존성
- 캐시된 데이터
- 연결 풀
- 스레드 상태


디스크 상태
- 함수 코드와 의존성 라이브러리들이 로드된 상태
- 런타임 환경에 의해 생성된 파일 시스템 구조


그러나 SnapStart에 제약사항은 존재한다.


런타임 제약사항

- 자바 전용이며 Java 11, 17을 지원.
- x86 아키텍처만 동작.
- 프로비저닝된 동시성 불가.
- EFS(Amazon Elastic File System) 연동 불가
- 512MB 이상의 임시 스토리지 불가
- 배포시간 증가 - 개발 생산성에 영향을 줌 (배포 파이프라인에 영향)
- 고유성에 영향을 줌 - 글로벌 변수에 난수 생성은 스냅샷에 포함되어 동일한 값이 사용되게 되기 때문이다. (함수에서 생성하거나 hook을 통해 처리 해야 함)

 

참고자료

 

728x90

master - slave 구조로 변경하려고 했다.

 

https://velog.io/@ch4570/MySQL-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%B6%84%EC%82%B0-%EC%B2%98%EB%A6%AC%EB%A5%BC-%EC%9C%84%ED%95%9C-Master-Slave-%EC%9D%B4%EC%A4%91%ED%99%94%EB%A5%BC-%EA%B5%AC%EC%84%B1%ED%95%98%EB%8B%A4MySQL-Replication-%EC%84%A4%EC%A0%95%EA%B3%BC-%EA%B5%AC%EC%84%B1

 

[Pet-Hub] MySQL 데이터 분산 처리를 위한 Master-Slave 이중화 구성(MySQL Replication 설정)

지금껏 프로젝트를 진행하면서 대부분 RDS 서비스를 이용해 한개의 데이터베이스만 사용해왔습니다.이번 프로젝트의 중점 목표 중 하나인 고가용성의 설계와 확장성 있는 구조를 가진 서버를

velog.io

https://velog.io/@zvyg1023/mysql-master-slave

 

[MySQL] Master-Slave 구축

MySQL 데이터 분산 처리를 위한 Master-Slave 이중화 구축 이중화를 하는 이유 데이터베이스의 트랜잭션을 readOnly로 셋팅할경우 성능이 상승하는 이점을 누릴 수 있습니다. 데이터베이스에서 일어나

velog.io

 

이 글들을 참고 해서 계속 시도를 해보았는데 조금 어려움을 겪었다.

 

하라는 대로 했는데 replication이 안됐다. 

 

SHOW SLAVE STATUS\G 명령을 실행하여 레플리케이션 상태를 확인 하는데, Slave_IO_Running Slave_SQL_Running이 모두 "Yes"였다. 잘 설정 되었다고 생각 했는데 안되는 것이었다.

 

그래서 생각 해보니 이런 현상이 발생한 주요 원인은 레플리케이션 설정 과정에서 초기 데이터 동기화가 제대로 이루어지지 않았기 때문일 가능성이 높다고 생각했다.

  1. 초기 데이터 불일치:
    레플리케이션을 설정할 때 마스터의 기존 데이터를 슬레이브로 복사하지 않았을 가능성.
  2. 바이너리 로그 포지션 오류:
    슬레이브가 마스터의 바이너리 로그에서 잘못된 위치부터 복제를함. 이 경우, 새로운 변경사항만 복제되고 이전 데이터는 누락 될 가능성이 있음.

그래서 master를 덤프하고 slave에 덮어 씌워 해결 했다.

 

1. 마스터 데이터베이스에서 덤프를 한다.

docker exec -it hoppang_database_master bash

 

mysqldump -u root -p --all-databases --master-data=2 --single-transaction --flush-logs --hex-blob > /master_data.sql

 

그러면 덤프 파일이 생긴 것을 확인할 수 있다.

 

덤프 파일을 호스트서버로 이동 시킨 후

docker cp hoppang_database_master:/master_data.sql /home/twj/master_data.sql

 

호스서버에서 슬레이브 데이터베이스로 이동시킨다.

docker cp ./master_data.sql hoppang_database_slave:/tmp/master_data.sql

 

슬레이브 데이터베이스로 이동 후.

docker exec -it hoppang_database_slave bash

 

덤프를 덮어 씌운다. ( 여기에서 hoppang은 스키마 이름이다. ) 저 명령어를 입력 하면 root로 가는 패스워드를 입력하면 성공한다.

mysql -u root -p hoppang < /tmp/master_data.sql

 

 

이 이후에 성공적으로 replication이 이루어졌다.

+ Recent posts