언어의 "숨겨진" 기능
이 기사에서는 대부분의 개발자가 실제로 거의 접하지 않는(그리고 그 존재조차 인식하지 못할 수도 있는) 매우 좁은 범위의 주제를 다룹니다.
방금 JavaScript를 배우기 시작했다면 이 장을 건너뛰는 것이 좋습니다.
가비지 수집 장에서 도달 가능성 원칙 의 기본 개념을 상기하면 JavaScript 엔진은 액세스 가능하거나 사용 중인 값을 메모리에 유지한다는 것을 알 수 있습니다.
예를 들어:
// 사용자 변수는 객체에 대한 강력한 참조를 보유합니다. 사용자 = { 이름: "John" }; // 사용자 변수의 값을 덮어쓰자 사용자 = null; // 참조가 손실되고 객체가 메모리에서 삭제됩니다.
또는 유사하지만 두 개의 강력한 참조가 있는 약간 더 복잡한 코드:
// 사용자 변수는 객체에 대한 강력한 참조를 보유합니다. 사용자 = { 이름: "John" }; // 개체에 대한 강력한 참조를 관리 변수에 복사했습니다. 관리자 = 사용자로 두십시오. // 사용자 변수의 값을 덮어쓰자 사용자 = null; // 객체는 여전히 admin 변수를 통해 접근 가능합니다.
객체 { name: "John" }
이에 대한 강력한 참조가 없는 경우에만 메모리에서 삭제됩니다( admin
변수의 값도 덮어쓴 경우).
JavaScript에는 WeakRef
라는 개념이 있는데, 이 경우에는 약간 다르게 동작합니다.
용어: "강한 참조", "약한 참조"
강력한 참조 – 가비지 수집기에 의해 삭제되는 것을 방지하는 개체 또는 값에 대한 참조입니다. 따라서 그것이 가리키는 객체나 값을 메모리에 유지합니다.
이는 객체 또는 값이 메모리에 남아 있고 이에 대한 활성 강력한 참조가 있는 한 가비지 수집기에 의해 수집되지 않음을 의미합니다.
JavaScript에서는 객체에 대한 일반적인 참조가 강력한 참조입니다. 예를 들어:
// 사용자 변수는 이 객체에 대한 강력한 참조를 보유합니다. 사용자 = { 이름: "John" };
약한 참조 – 가비지 수집기에 의해 삭제되는 것을 방지하지 않는 개체 또는 값에 대한 참조입니다. 객체나 값에 대한 남은 참조가 약한 참조인 경우 가비지 수집기에 의해 객체나 값이 삭제될 수 있습니다.
주의사항
이에 대해 자세히 알아보기 전에 이 기사에서 논의된 구조를 올바르게 사용하려면 매우 신중한 생각이 필요하며 가능하면 피하는 것이 가장 좋습니다.
WeakRef
– target
또는 referent
라고 불리는 다른 객체에 대한 약한 참조를 포함하는 객체입니다.
WeakRef
의 특징은 가비지 수집기가 참조 객체를 삭제하는 것을 막지 않는다는 것입니다. 즉, WeakRef
개체는 referent
개체를 활성 상태로 유지하지 않습니다.
이제 user
변수를 "참조 대상"으로 취하고 이 변수에서 admin
변수에 대한 약한 참조를 생성해 보겠습니다. 약한 참조를 생성하려면 WeakRef
생성자를 사용하여 대상 객체(약한 참조를 원하는 객체)를 전달해야 합니다.
우리의 경우 — 이것은 user
변수입니다:
// 사용자 변수는 객체에 대한 강력한 참조를 보유합니다. 사용자 = { 이름: "John" }; // admin 변수는 객체에 대한 약한 참조를 보유합니다. let admin = new WeakRef(사용자);
아래 다이어그램은 두 가지 유형의 참조, 즉 user
변수를 사용하는 강력한 참조와 admin
변수를 사용하는 약한 참조를 보여줍니다.
그런 다음 어느 시점에서 user
변수 사용을 중지합니다. WeakRef
인스턴스를 admin
변수에 유지하면서 사용자 변수를 덮어쓰거나 범위를 벗어나는 등의 작업을 수행합니다.
// 사용자 변수의 값을 덮어쓰자 사용자 = null;
객체에 대한 약한 참조만으로는 객체를 "살아있는" 상태로 유지하는 데 충분하지 않습니다. 참조 개체에 대한 유일한 남은 참조가 약한 참조인 경우 가비지 수집기는 자유롭게 이 개체를 파괴하고 해당 메모리를 다른 용도로 사용할 수 있습니다.
그러나 객체가 실제로 파괴될 때까지 약한 참조는 이 객체에 대한 강한 참조가 더 이상 없더라도 이를 반환할 수 있습니다. 즉, 우리의 객체는 일종의 "슈뢰딩거의 고양이"가 됩니다. 우리는 그것이 "살아있는" 것인지 "죽은" 것인지 확실히 알 수 없습니다.
이 시점에서 WeakRef
인스턴스에서 객체를 가져오기 위해 해당 deref()
메서드를 사용합니다.
deref()
메서드는 객체가 아직 메모리에 있는 경우 WeakRef
가리키는 참조 객체를 반환합니다. 가비지 수집기에 의해 객체가 삭제된 경우 deref()
메서드는 undefined
반환합니다.
ref = admin.deref()를 보자; 만약 (참조) { // 객체는 여전히 접근 가능합니다: 객체에 대해 어떤 조작이라도 수행할 수 있습니다. } 또 다른 { // 가비지 컬렉터가 객체를 수집했습니다. }
WeakRef
는 일반적으로 리소스 집약적인 개체를 저장하는 캐시 또는 연관 배열을 만드는 데 사용됩니다. 이를 통해 캐시 또는 연관 배열에 존재하는 객체만을 기준으로 가비지 수집기가 이러한 객체를 수집하는 것을 방지할 수 있습니다.
주요 예 중 하나는 수많은 바이너리 이미지 개체(예: ArrayBuffer
또는 Blob
으로 표시됨)가 있고 각 이미지에 이름이나 경로를 연결하려는 상황입니다. 기존 데이터 구조는 다음 목적에 적합하지 않습니다.
Map
사용하여 이름과 이미지 사이의 연관을 생성하거나 그 반대로 생성하면 이미지 객체가 Map
에 키 또는 값으로 존재하므로 메모리에 유지됩니다.
WeakMap
이 목표에 적합하지 않습니다. WeakMap
키로 표시되는 객체는 약한 참조를 사용하고 가비지 수집기에 의한 삭제로부터 보호되지 않기 때문입니다.
하지만 이 상황에서는 값에 약한 참조를 사용하는 데이터 구조가 필요합니다.
이를 위해 필요한 대형 개체를 참조하는 WeakRef
인스턴스 값을 갖는 Map
컬렉션을 사용할 수 있습니다. 결과적으로 우리는 이러한 크고 불필요한 객체를 원래보다 오랫동안 메모리에 보관하지 않을 것입니다.
그렇지 않은 경우 이는 캐시에 아직 접근할 수 있는 경우 캐시에서 이미지 객체를 가져오는 방법입니다. 가비지 수집된 경우 다시 생성하거나 다시 다운로드합니다.
이렇게 하면 일부 상황에서는 더 적은 메모리가 사용됩니다.
다음은 WeakRef
사용 기술을 보여주는 코드 조각입니다.
즉, 문자열 키와 WeakRef
개체를 값으로 사용하는 Map
사용합니다. WeakRef
개체가 가비지 수집기에 의해 수집되지 않은 경우 캐시에서 가져옵니다. 그렇지 않으면 다시 다운로드하여 추가 재사용을 위해 캐시에 넣습니다.
함수 fetchImg() { // 이미지 다운로드를 위한 추상 함수... } function WeakRefCache(fetchImg) { // (1) const imgCache = 새로운 맵(); // (2) return (imgName) => { // (3) const 캐시된Img = imgCache.get(imgName); // (4) if (cachedImg?.deref()) { // (5) 캐시된Img?.deref()를 반환합니다. } const newImg = fetchImg(imgName); // (6) imgCache.set(imgName, new WeakRef(newImg)); // (7) newImg를 반환합니다. }; } const getCachedImg = WeakRefCache(fetchImg);
여기서 무슨 일이 일어났는지 자세히 살펴보겠습니다.
weakRefCache
– 다른 함수인 fetchImg
인수로 사용하는 고차 함수입니다. 이 예에서는 fetchImg
함수에 대한 자세한 설명을 무시할 수 있습니다. 왜냐하면 이미지 다운로드를 위한 모든 로직이 될 수 있기 때문입니다.
imgCache
– fetchImg
함수의 캐시된 결과를 문자열 키(이미지 이름) 형식으로 저장하고 WeakRef
개체를 해당 값으로 저장하는 이미지 캐시입니다.
이미지 이름을 인수로 사용하는 익명 함수를 반환합니다. 이 인수는 캐시된 이미지의 키로 사용됩니다.
제공된 키(이미지 이름)를 사용하여 캐시에서 캐시된 결과를 가져오려고 합니다.
캐시에 지정된 키에 대한 값이 포함되어 있고 WeakRef
개체가 가비지 수집기에 의해 삭제되지 않은 경우 캐시된 결과를 반환합니다.
캐시에 요청된 키가 있는 항목이 없거나 deref()
메서드가 undefined
을 반환하는 경우( WeakRef
객체가 가비지 수집되었음을 의미), fetchImg
함수는 이미지를 다시 다운로드합니다.
다운로드한 이미지를 WeakRef
개체로 캐시에 넣습니다.
이제 키(문자열로 된 이미지 이름)와 값(값)이 이미지 자체를 포함하는 WeakRef
개체인 Map
컬렉션이 있습니다.
이 기술은 더 이상 아무도 사용하지 않는 리소스 집약적 개체에 많은 양의 메모리를 할당하는 것을 방지하는 데 도움이 됩니다. 또한 캐시된 객체를 재사용하는 경우 메모리와 시간이 절약됩니다.
다음은 이 코드의 모양을 시각적으로 나타낸 것입니다.
그러나 이 구현에는 단점이 있습니다. 시간이 지남에 따라 Map
참조 개체가 이미 가비지 수집된 WeakRef
가리키는 문자열로 채워질 것입니다.
이 문제를 처리하는 한 가지 방법은 주기적으로 캐시를 청소하고 "죽은" 항목을 지우는 것입니다. 또 다른 방법은 Finalizer를 사용하는 것입니다. 이에 대해서는 다음에 살펴보겠습니다.
WeakRef
의 또 다른 사용 사례는 DOM 개체를 추적하는 것입니다.
일부 타사 코드나 라이브러리가 DOM에 존재하는 한 페이지의 요소와 상호 작용하는 시나리오를 상상해 보겠습니다. 예를 들어, 시스템 상태를 모니터링하고 알리기 위한 외부 유틸리티일 수 있습니다(일반적으로 소위 "로거", "로그"라는 정보 메시지를 보내는 프로그램).
대화형 예:
결과
index.js
index.css
index.html
const startMessagesBtn = document.querySelector('.start-messages'); // (1) const closeWindowBtn = document.querySelector('.window__button'); // (2) const windowElementRef = new WeakRef(document.querySelector(".window__body")); // (3) startMessagesBtn.addEventListener('click', () => { // (4) startMessages(windowElementRef); startMessagesBtn.disabled = true; }); closeWindowBtn.addEventListener('click', () => document.querySelector(".window__body").remove()); // (5) const startMessages = (요소) => { const 타이머Id = setInterval(() => { // (6) if (element.deref()) { // (7) const payload = document.createElement("p"); payload.textContent = `메시지: 시스템 상태 정상: ${new Date().toLocaleTimeString()}`; element.deref().append(페이로드); } else { // (8) Alert("해당 요소가 삭제되었습니다."); // (9) ClearInterval(timerId); } }, 1000); };
.앱 { 디스플레이: 플렉스; 플렉스 방향: 열; 간격: 16px; } .start-메시지 { 너비: 내용에 맞게; } .창문 { 너비: 100%; 테두리: 2px 솔리드 #464154; 오버플로: 숨김; } .window__header { 위치: 끈적끈적함; 패딩: 8px; 디스플레이: 플렉스; 내용 정당화: 공백 사이; 항목 정렬: 중앙; 배경색: #736e7e; } .window__title { 여백: 0; 글꼴 크기: 24px; 글꼴 두께: 700; 색상: 흰색; 문자 간격: 1px; } .window__버튼 { 패딩: 4px; 배경: #4f495c; 개요: 없음; 테두리: 2px 솔리드 #464154; 색상: 흰색; 글꼴 크기: 16px; 커서: 포인터; } .window__body { 높이: 250px; 패딩: 16px; 오버플로: 스크롤; 배경색: #736e7e33; }
<!DOCTYPE HTML> <html lang="ko"> <머리> <meta charset="utf-8"> <link rel="stylesheet" href="index.css"> <title>WeakRef DOM 로거</title> </head> <본문> <div 클래스="앱"> <button class="start-messages">메시지 보내기 시작</button> <div 클래스="창"> <div 클래스="window__header"> <p class="window__title">메시지:</p> <button class="window__button">닫기</button> </div> <div 클래스="window__body"> 메시지가 없습니다. </div> </div> </div> <script type="module" src="index.js"></script> </body> </html>
"메시지 보내기 시작" 버튼을 클릭하면 소위 "로그 표시 창"( .window__body
클래스가 있는 요소)에 메시지(로그)가 나타나기 시작합니다.
그러나 이 요소가 DOM에서 삭제되자마자 로거는 메시지 전송을 중단해야 합니다. 이 요소 제거를 재현하려면 오른쪽 상단에 있는 "닫기" 버튼을 클릭하세요.
작업을 복잡하게 하지 않고 DOM 요소를 사용할 수 있을 때마다 제3자 코드에 알리지 않으려면 WeakRef
사용하여 이에 대한 약한 참조를 만드는 것으로 충분합니다.
요소가 DOM에서 제거되면 로거는 이를 인지하고 메시지 전송을 중지합니다.
이제 소스 코드( tab index.js
)를 자세히 살펴보겠습니다.
"메시지 보내기 시작" 버튼의 DOM 요소를 가져옵니다.
"닫기" 버튼의 DOM 요소를 가져옵니다.
new WeakRef()
생성자를 사용하여 로그 표시 창의 DOM 요소를 가져옵니다. 이런 방식으로 windowElementRef
변수는 DOM 요소에 대한 약한 참조를 보유합니다.
클릭 시 로거를 시작하는 이벤트 리스너를 "메시지 보내기 시작" 버튼에 추가합니다.
클릭 시 로그 표시 창을 닫는 이벤트 리스너를 "닫기" 버튼에 추가합니다.
매초마다 새 메시지 표시를 시작하려면 setInterval
사용하세요.
로그 표시 창의 DOM 요소에 여전히 액세스할 수 있고 메모리에 보관되어 있는 경우 새 메시지를 생성하고 보냅니다.
deref()
메서드가 undefined
반환하면 DOM 요소가 메모리에서 삭제되었음을 의미합니다. 이 경우 로거는 메시지 표시를 중지하고 타이머를 지웁니다.
로그 표시 창의 DOM 요소가 메모리에서 삭제된 후(예: "닫기" 버튼을 클릭한 후) 호출되는 alert
입니다. 메모리에서의 삭제는 가비지 수집기의 내부 메커니즘에만 의존하기 때문에 즉시 발생하지 않을 수 있습니다.
우리는 이 프로세스를 코드에서 직접 제어할 수 없습니다. 그러나 그럼에도 불구하고 우리는 여전히 브라우저에서 가비지 수집을 강제할 수 있는 옵션을 가지고 있습니다.
예를 들어 Google Chrome에서 이 작업을 수행하려면 개발자 도구(Windows/Linux에서는 Ctrl + Shift + J , macOS에서는 Option + ⌘ + J )를 열고 "성능" 탭으로 이동한 다음 휴지통 아이콘 버튼 - "쓰레기 수집":
이 기능은 대부분의 최신 브라우저에서 지원됩니다. 조치가 취해지면 즉시 alert
발생합니다.
이제 Finalizer에 대해 이야기할 차례입니다. 계속 진행하기 전에 용어를 명확히 하겠습니다.
정리 콜백(파이널라이저) – FinalizationRegistry
에 등록된 객체가 가비지 컬렉터에 의해 메모리에서 삭제될 때 실행되는 함수입니다.
그 목적은 객체가 메모리에서 최종적으로 삭제된 후 객체와 관련된 추가 작업을 수행할 수 있는 기능을 제공하는 것입니다.
레지스트리 (또는 FinalizationRegistry
) – 객체 등록 및 등록 취소와 해당 정리 콜백을 관리하는 JavaScript의 특수 객체입니다.
이 메커니즘을 사용하면 개체를 등록하여 정리 콜백을 추적하고 연결할 수 있습니다. 본질적으로 이는 등록된 개체 및 해당 정리 콜백에 대한 정보를 저장한 다음 개체가 메모리에서 삭제될 때 해당 콜백을 자동으로 호출하는 구조입니다.
FinalizationRegistry
의 인스턴스를 생성하려면 정리 콜백(종료자)이라는 단일 인수를 사용하는 생성자를 호출해야 합니다.
통사론:
함수 cleanupCallback(heldValue) { // 콜백 코드 정리 } const 레지스트리 = 새로운 FinalizationRegistry(cleanupCallback);
여기:
cleanupCallback
– 등록된 객체가 메모리에서 삭제될 때 자동으로 호출되는 정리 콜백입니다.
heldValue
– 정리 콜백에 인수로 전달되는 값입니다. heldValue
객체인 경우 레지스트리는 이에 대한 강력한 참조를 유지합니다.
registry
– FinalizationRegistry
의 인스턴스입니다.
FinalizationRegistry
메소드:
register(target, heldValue [, unregisterToken])
– 레지스트리에 개체를 등록하는 데 사용됩니다.
target
– 추적을 위해 등록되는 객체입니다. target
이 가비지 수집된 경우, heldValue
인수로 사용하여 정리 콜백이 호출됩니다.
선택 사항 unregisterToken
– 등록 취소 토큰입니다. 가비지 수집기가 객체를 삭제하기 전에 객체 등록을 취소하기 위해 전달될 수 있습니다. 일반적으로 target
객체는 표준 관행인 unregisterToken
으로 사용됩니다.
unregister(unregisterToken)
– unregister
메소드는 레지스트리에서 객체 등록을 취소하는 데 사용됩니다. unregisterToken
(객체 등록 시 얻은 등록 취소 토큰)이라는 하나의 인수를 사용합니다.
이제 간단한 예로 넘어가겠습니다. 이미 알려진 user
개체를 사용하고 FinalizationRegistry
인스턴스를 생성해 보겠습니다.
사용자 = { 이름: "John" }; const 레지스트리 = 새로운 FinalizationRegistry((heldValue) => { console.log(`${heldValue}가 가비지 수집기에 의해 수집되었습니다.`); });
그런 다음, register
메소드를 호출하여 정리 콜백이 필요한 객체를 등록합니다.
Registry.register(사용자, 사용자.이름);
레지스트리는 등록되는 객체에 대한 강력한 참조를 유지하지 않습니다. 그렇게 하면 그 목적이 무산될 수 있기 때문입니다. 레지스트리가 강력한 참조를 유지한다면 객체는 절대 가비지 수집되지 않습니다.
가비지 수집기에 의해 객체가 삭제되면 향후 특정 시점에 heldValue
가 전달된 정리 콜백이 호출될 수 있습니다.
// 가비지 수집기에 의해 사용자 개체가 삭제되면 다음 메시지가 콘솔에 인쇄됩니다. "John은 가비지 수집기에 의해 수집되었습니다."
정리 콜백을 사용하는 구현에서도 호출되지 않을 가능성이 있는 상황도 있습니다.
예를 들어:
프로그램이 작업을 완전히 종료하는 경우(예: 브라우저에서 탭을 닫는 경우)
FinalizationRegistry
인스턴스 자체가 더 이상 JavaScript 코드에 접근할 수 없는 경우. FinalizationRegistry
인스턴스를 생성하는 객체가 범위를 벗어나거나 삭제되는 경우 해당 레지스트리에 등록된 정리 콜백도 호출되지 않을 수 있습니다.
약한 캐시 예로 돌아가서 다음 사항을 확인할 수 있습니다.
WeakRef
에 래핑된 값을 가비지 컬렉터가 수집했음에도 불구하고, 가비지 컬렉터가 그 값을 수집한 나머지 키의 형태에서는 여전히 '메모리 누수' 문제가 있습니다.
FinalizationRegistry
사용한 향상된 캐싱 예제는 다음과 같습니다.
함수 fetchImg() { // 이미지 다운로드를 위한 추상 함수... } 함수weakRefCache(fetchImg) { const imgCache = 새로운 맵(); const Registry = new FinalizationRegistry((imgName) => { // (1) const 캐시된Img = imgCache.get(imgName); if (cachedImg && !cachedImg.deref()) imgCache.delete(imgName); }); 반환 (imgName) => { const 캐시된Img = imgCache.get(imgName); if (cachedImg?.deref()) { 캐시된Img?.deref()를 반환합니다. } const newImg = fetchImg(imgName); imgCache.set(imgName, new WeakRef(newImg)); Registry.register(newImg, imgName); // (2) newImg를 반환합니다. }; } const getCachedImg = WeakRefCache(fetchImg);
"죽은" 캐시 항목의 정리를 관리하기 위해 관련 WeakRef
개체가 가비지 수집기에 의해 수집될 때 FinalizationRegistry
정리 레지스트리를 만듭니다.
여기서 중요한 점은 "라이브" 항목을 삭제하지 않으려면 정리 콜백에서 항목이 가비지 수집기에 의해 삭제되고 다시 추가되지 않았는지 확인해야 한다는 것입니다.
새 값(이미지)이 다운로드되어 캐시에 저장되면 이를 종료자 레지스트리에 등록하여 WeakRef
개체를 추적합니다.
이 구현에는 실제 또는 "라이브" 키/값 쌍만 포함됩니다. 이 경우 각 WeakRef
객체는 FinalizationRegistry
에 등록됩니다. 그리고 가비지 수집기에 의해 객체가 정리된 후 정리 콜백은 undefined
모든 값을 삭제합니다.
다음은 업데이트된 코드를 시각적으로 나타낸 것입니다.
업데이트된 구현의 주요 측면은 종료자를 통해 "기본" 프로그램과 정리 콜백 간에 병렬 프로세스를 생성할 수 있다는 것입니다. JavaScript의 맥락에서 "기본" 프로그램은 애플리케이션이나 웹 페이지에서 실행되고 실행되는 JavaScript 코드입니다.
따라서 가비지 컬렉터가 객체에 삭제 표시를 한 순간부터 실제 정리 콜백이 실행되기까지 일정한 시간차가 있을 수 있습니다. 이 시간 간격 동안 주 프로그램이 개체를 변경하거나 심지어 메모리로 다시 가져올 수도 있다는 점을 이해하는 것이 중요합니다.
그렇기 때문에 정리 콜백에서 "라이브" 항목이 삭제되는 것을 방지하기 위해 기본 프로그램이 항목을 캐시에 다시 추가했는지 확인해야 합니다. 마찬가지로 캐시에서 키를 검색할 때 가비지 컬렉터에 의해 값이 삭제되었지만 정리 콜백이 아직 실행되지 않았을 가능성이 있습니다.
FinalizationRegistry
사용하는 경우 이러한 상황에는 특별한 주의가 필요합니다.
이론에서 실습으로 이동하여 사용자가 모바일 장치의 사진을 일부 클라우드 서비스(예: iCloud 또는 Google Photos)와 동기화하고 다른 장치에서 보고 싶어하는 실제 시나리오를 상상해 보십시오. 사진을 보는 기본 기능 외에도 이러한 서비스는 다음과 같은 많은 추가 기능을 제공합니다.
사진 편집 및 비디오 효과.
'추억'과 앨범을 만듭니다.
일련의 사진으로 만든 비디오 몽타주입니다.
…그리고 훨씬 더.
여기서는 예를 들어 이러한 서비스의 상당히 원시적인 구현을 사용하겠습니다. 주요 요점은 실제 생활에서 WeakRef
와 FinalizationRegistry
함께 사용하는 가능한 시나리오를 보여주는 것입니다.
다음은 그 모습입니다:
왼쪽에는 사진의 클라우드 라이브러리가 있습니다(썸네일로 표시됨). 페이지 오른쪽에 있는 "콜라주 만들기" 버튼을 클릭하면 필요한 이미지를 선택하고 콜라주를 만들 수 있습니다. 그런 다음 결과 콜라주를 이미지로 다운로드할 수 있습니다.
페이지 로딩 속도를 높이려면 사진 축소판을 다운로드하여 압축된 품질로 표시하는 것이 합리적입니다. 하지만 선택한 사진으로 콜라주를 만들려면 다운로드하여 원본 크기 품질로 사용하세요.
아래에서 썸네일의 기본 크기가 240x240픽셀임을 알 수 있습니다. 로딩 속도를 높이기 위해 의도적으로 크기를 선택했습니다. 또한 미리보기 모드에서는 전체 크기 사진이 필요하지 않습니다.
4장의 사진으로 구성된 콜라주를 만들어야 한다고 가정해 보겠습니다. 사진을 선택한 다음 "콜라주 만들기" 버튼을 클릭합니다. 이 단계에서 이미 알려진 weakRefCache
함수는 필요한 이미지가 캐시에 있는지 확인합니다. 그렇지 않은 경우 클라우드에서 다운로드하여 추가 사용을 위해 캐시에 넣습니다. 이는 선택한 각 이미지에 대해 발생합니다.
콘솔의 출력에 주목하면 어떤 사진이 클라우드에서 다운로드되었는지 확인할 수 있습니다. 이는 FETCHED_IMAGE 로 표시됩니다. 이것이 콜라주를 생성하려는 첫 번째 시도이기 때문에 이는 이 단계에서 "약한 캐시"가 여전히 비어 있었고 모든 사진이 클라우드에서 다운로드되어 여기에 저장되었음을 의미합니다.
하지만 이미지를 다운로드하는 과정과 함께 가비지 컬렉터에 의한 메모리 정리 과정도 있습니다. 이는 약한 참조를 사용하여 참조하는 캐시에 저장된 개체가 가비지 수집기에 의해 삭제된다는 의미입니다. 그리고 종료자가 성공적으로 실행되어 이미지가 캐시에 저장된 키가 삭제됩니다. CLEANED_IMAGE가 이에 대해 알려줍니다.
다음으로 우리는 결과 콜라주가 마음에 들지 않는다는 것을 깨닫고 이미지 중 하나를 변경하고 새 이미지를 만들기로 결정합니다. 이렇게 하려면 불필요한 이미지를 선택 취소하고 다른 이미지를 선택한 다음 "콜라주 만들기" 버튼을 다시 클릭하세요.
그러나 이번에는 모든 이미지가 네트워크에서 다운로드되지 않았으며 그 중 하나는 약한 캐시에서 가져왔습니다. CACHED_IMAGE 메시지가 이에 대해 알려줍니다. 이는 콜라주 생성 시 가비지 컬렉터가 아직 이미지를 삭제하지 않았으며 이를 캐시에서 과감히 가져옴으로써 네트워크 요청 수를 줄이고 콜라주 생성 프로세스의 전체 시간을 단축했다는 의미입니다.
이미지 중 하나를 다시 바꾸고 새 콜라주를 만들어서 좀 더 "놀아보세요".
이번에는 결과가 더욱 인상적이었습니다. 선택한 4개의 이미지 중 3개는 취약한 캐시에서 가져온 것이며 네트워크에서 하나만 다운로드해야 했습니다. 네트워크 부하 감소는 약 75%였습니다. 인상적이지 않나요?
물론, 그러한 동작은 보장되지 않으며 가비지 수집기의 특정 구현 및 작동에 따라 달라진다는 점을 기억하는 것이 중요합니다.
이를 바탕으로 완전히 논리적인 질문이 즉시 발생합니다. 가비지 수집기에 의존하는 대신 엔터티를 직접 관리할 수 있는 일반 캐시를 사용하면 어떨까요? 맞습니다. 대부분의 경우 WeakRef
및 FinalizationRegistry
사용할 필요가 없습니다.
여기서 우리는 흥미로운 언어 기능을 갖춘 사소하지 않은 접근 방식을 사용하여 유사한 기능의 대체 구현을 간단히 시연했습니다. 그러나 지속적이고 예측 가능한 결과가 필요한 경우에는 이 예에 의존할 수 없습니다.
샌드박스에서 이 예제를 열 수 있습니다.
WeakRef
– 객체에 대한 약한 참조를 생성하도록 설계되어 객체에 대한 강력한 참조가 더 이상 없으면 가비지 수집기가 메모리에서 삭제할 수 있습니다. 이는 과도한 메모리 사용량을 해결하고 애플리케이션에서 시스템 리소스 활용도를 최적화하는 데 유용합니다.
FinalizationRegistry
– 더 이상 강력하게 참조되지 않는 개체가 삭제될 때 실행되는 콜백을 등록하기 위한 도구입니다. 이를 통해 메모리에서 개체를 삭제하기 전에 개체와 관련된 리소스를 해제하거나 기타 필요한 작업을 수행할 수 있습니다.