[Spring/Web] 자료구조에 대해서
by Push's tone
static 정리글
https://vaert.tistory.com/101
-
static사용은 클래스의 인스턴스들이 공통적으로 한 값을 가지거나, 같은 값을 유지해야 하는 경우 static을 사용한다.
-
Class Loader가 클래스를 Loader할 때 Data 영역에 메모리가 할당되게 된다.
- static이 붙은 메서드(함수)에서는 인스턴스 변수를 사용할 수 없다. 인스턴스 변수는 인스턴스를 생성해야만 존재하기 때문에
- static 메서드는 (즉 메서드 내 인스턴스 변수를 사용하지 않는다면 붙일 수 있다) 메서드 호출시간이 짧아지기 때문에 가능하면 붙이는 것이 좋다. (static을 안붙인 메서드는 실행시 호출되어야할 메서드를 찾는 과정이 추가적으로 필요해진다)
- 클래스 설계시 static의 사용지침
- 먼저 클래스의 멤버변수중 모든 인스턴스에 공통된 값을 유지해야하는 것이 있는지 살펴보고 있으면, static을 붙여준다.
- 작성한 메서드 중에서 인스턴스 변수를 사용하지 않는 메서드에 대해서 static을 붙일 것을 고려한다.
static 지양해야 하는 이유
메모리 효율성을 달성할 수 있다는 장점이 있지만,
-
global state, 즉 코드 변화에따른 사이드 이펙트 추정이 어렵다.
-
또한 객체지향적이지 않다.(즉 각 데이터가 캡슐화 되어야 하는 원칙에 어긋남).
-
객체의 라이프타임이 프로그램 실행 내내이기 때문에, 메모리를 회수하지 못한다.
따라서 static 대신 다른 제어자나, 싱글톤 디자인 패턴을 사용하는게 맞다.
싱글톤 : Lazy-initialization가능, 테스트와 리팩토링 용이, 상속받아 재사용이 쉽다. + (스레드안정적
제어자
접근제어자
transient
transient
는 Serialize하는 과정에 제외하고 싶은 경우 선언하는 키워드입니다. 필드의 값을 null로 입력하여 직렬화함
volatile 변수
변수를 선언할 때 앞에 volatile을 붙이면 컴파일러는 해당 변수를 최적화에서 제외하여 항상 메모리에 접근하도록 만듭니다.
- volatile 자료형 변수이름;
volatile int num1 = 10; // 변수를 최적화에서 제외하여 항상 메모리에 접근하도록 만듦
volatile로 선언한 변수는 사용할 때 항상 메모리에 접근합니다. 즉, 이 변수는 언제든지 값이 바뀔 수 있으니까 항상 메모리에 접근하라고 컴파일러에게 알려주는 것입니다.
메서드 인라이닝
인라이닝은 Java Just-In-Time 컴파일러(현 HotSpot VM)에서 수행하는 최적화입니다.
https://jangsunjin.tistory.com/191
캐싱 알고리즘
Locality (Locality of Reference : 참조의 지역성)
Spatial vs temporal
temporal locality는 대부분 프로그램은 한 번 접근이 이루어진 메모리 영역에 자주 접근하는 특성을 의미한다.
for (int i = 0; i < 1000; ++i){
total += i;
}
위의 코드에서 변수 total이 저장된 메모리 공간은 1000번 접근이 이루어진다. 이는 temporal locality의 좋은 예이다.
spatial locality는 이미 이뤄진 영역 근처에 접근할 확률이 높다는 특성을 의미한다.
삽입 정렬(insertion sort)이 그 예다. arr[j + 1] = arr[j]과 같은 코드가 spatial locality의 좋은 예이다.
즉, 이미 상위 계층에 저장되어있는 데이터가 있다면, 그 데이터와 그 주변 데이터에 프로그램이 접근할 확률이 높다. 그래서 그 하위 계층에는 그 데이터와 그 주변 데이터를 저장한다. (주변데이터와 그 데이터를 한번에 저장하기위해서, 플록단위로 처리할 필요가 있고, 그러므로 메모리 계층 구조에서 데이터의 이동은 블록 단위로 이뤄진다)
캐시 교체 정책(Cache’s replacement policy)
L1 cache에 데이터가 없어 L2 캐시로부터 데이터 블록을 읽어야 할때
L1 cache가 가득 찬 상태에서 L2 cache의 데이터 블록을 L1으로 읽어들이려면 L1중 일부분을 밀어내야 한다. 이때 어떤 부분을 밀어낼지에 대한 정책이다.
가장 보편적인 캐시 교체정책은 LRU(Least-Recently Used)가 있다. 즉 가장 오래전에 접근한 블록을 밀어낸다.
대표적인 알고리즘
-
LRU(Least Recently Used) : 캐시 내에 가장 오랫동안 참조되지 않은 블록을 교체하는 방식
- FIFO(First In First Out) : 캐시 내에서 가장 오랫동안 있던 블록을 교체하는 방식
- LFU(Least Frequency Used) : 가장 적게 참조되었던 블록을 교체하는 알고리즘
LRU 알고리즘을 구현하기 위해서는 해당 프레임이 언제 사용되었는지를 알아야한다. 이를 위해서 두가지 방법이 있는데.
https://iwantadmin.tistory.com/43?category=74661
첫번째로, Counter를 이용하는 방법이 있다. CPU clock을 이용해서 매번 page호출이 일어날때 마다 시간을 체크하는 것이다. 그래서 page replace가 일어나야 할경우 time이 가장 오래된것을 선택하게 된다. 이 방법은 매번 replacement가 발생할때마다 Page table에서 가장 오래된것을 찾아야 하며, Page table이 변경될때마다 해당 페이지의 시간도 같이 변경 되어야 하는 단점이 있다.
다음으로는 Stack을 이용하는 방법이다. 여기서 Stack은 일반적인 스택의 기능에 doubly linked list가 추가된 것이어야 한다. 매번 page가 호출될때마다 그 페이지 번호를 스택에 저장하는 방식이다. 페이지가 호출될때 스택에 이미 그 페이지가 있을 경우에는 해당 페이지를 스택에서 pop()시키고 새로 push()한다. 스택에 존재하지 않을 경우에는 그냥 push()한다.
쓰기 정책
캐시의 데이터가 변화시에 메모리에도 똑같이 반영되어야 함.
Write Through
캐시 변화가 생기는 대로 즉각 메인 메모리에 반영
Write Back
위를 보안, 캐시 내의 데이터만 갱신하고, 메모리는 나중에 다시 캐시를 가져오는 과정에서 갱신. -> 캐시의 데이터와 메모리의 데이터가 상이한 모습을 보이는데, 이 상태를 Dirty라고 한다.
###컴퓨터 구조 캐시
디자인패턴
싱글톤
https://jeong-pro.tistory.com/86
고정된 메모리 영역 사용, 한번만 new -> 메모리 낭비 방지
한번 생성한 인스턴스가 전역 인스턴스 -> 즉 데이터를 공유 할 수 있다.
커넥션풀 같은 공통된 객체를 여러개 생성해야 하는 상황에서 사용
public class Something {
private Something() {
}
private static class LazyHolder {
public static final Something INSTANCE = new Something();
}
public static Something getInstance() {
return LazyHolder.INSTANCE;
}
}
- static 키워드는 클래스가 로딩될 때 한번 만 실행된다는 것을 JVM이 보장해줍니다. 따라서 getInstance() 메서드가 최초로 호출될 때 JVM이 클래스를 로드하며 INSTANCE를 생성해줍니다.
- final 키워드를 통해서 초기화된 이후 다시 값이 할당되는 것을 방지해서 싱글톤임을 보장합니다
https://javacan.tistory.com/entry/1
런타임에 동적으로 클래스를 로딩하다는 것은 JVM이 클래스에 대한 정보를 갖고 있지 않다는 것을 의미한다.
자바는 동적으로 클래스를 읽어온다. 즉, 런타임에 모든 코드가 JVM에 링크된다. 모든 클래스는 그 클래스가 참조되는 순간에 동적으로 JVM에 링크되며, 메모리에 로딩된다.
Uniqueness Principle
유일성 원칙은 하위 클래스로더는 상위 클래스로더가 로딩한 클래스를 다시 로딩하지 않게 해서 로딩된 클래스의 유일성을 보장하는 것이다. 유일성을 식별하는 기준은 클래스의
binary name
인데,toString()
으로 찍다보면 가끔 보이는java.lang.String
,javax.swing.JSpinner$DefaultEditor
,java.security.KeyStore$Builder$FileBuilder$1
,java.net.URLClassLoader$3$1
이런 것들이 바로binary name
이다.binary name
의 자세한 내용은 https://docs.oracle.com/javase/specs/jls/se8/html/jls-13.html#jls-13.1 를 참고한다.
또한 AbstractClass 인 ClassLoader.loadClass()메서드를 보면, Synchronized블록으로 처리되어 있음을 확인 가능하다.
loadClass() 메소드는 다음과 가은 순서로 클래스를 로딩한다.
- findLoadedClass()를 호출해서 캐시에 로드된 클래스를 찾는다.
- 부모 클래스 로더의 loadClass()를 호출해서 클래스 로딩을 시도한다.
- 마지막으로 findClass()를 사용해서 클래스 로딩을 한다.
- 클래스로더의 로딩 방법 두 가지
- 런타임 로딩 : 특정 클래스의 코드를 실행할 때 클래스를 로드하는 방법
- 로드 타임 로딩 : 런타임 로딩에 의해서 클래스(A)가 로딩될 때, 해당 클래스(A) 내부에서 참조(사용)하는 클래스(B, C, D, …)가 있다면 그 클래스도 로드하는 방법
자바는 클래스를 로딩하더라도 내부의 메서드 전체를 알지 못한다. 즉 내부 메서드를 실행해야 그 메서드에 의해 호출되는 클래스를 알게되고, 그때 클래스 로더가 로드 타임 로딩을 진행해서 해당 클래스를 로드한다.
클래스 내부에 static 클래스든 아니든 결국은 클래스로더는 내부 클래스를 쓸 때 로드됩니다. Something 클래스가 로드되어도 Holder는 로드되지 않습니다.
static이 언제 jvm상에 올라가는가
static이 실행되는 시점은 클래스가 메모리상에 올라갈 때이다.
Q. static 으로 클래스 내부에 선언된 변수는 클래스 로딩시에 메모리에 올라가는데, 왜 static 으로 내부에 선언된 클래스는 클래스 로딩시에 로딩되지 않는가?
전략패턴
“클라이언트가 전략을 생성해 전략을 실행할 컨텍스트에게 주입하는 패턴이다.”
클라이언트 = 스프링의 applicationContext에 해당.
컨텍스트 = 스프링에서 의존을 주입받는 컴포넌트들
팩토리 메서드 패턴
객체지향 5대 원칙
https://blog.martinwork.co.kr/theory/2017/12/10/oop-solid-principle.html
SOLID
객체지향의 장점
재사용성-> 생산성 향상
자연적인 모델링
유지보수의 용이성
단일 책임 원칙
모든 클래스, 메서드는 하나의 책임만 가져야 한다.
즉, 한 클래스를 수정해야할 이유가 단 한가지여야 한다.
하나의 클래스는 하나의 메소드만 가진다.
하나의 메소드는 하나의 기능만 수행한다.
모든 클래스, 메서드는 하나의 책임만 가지고, 그 책임은 완전히 캡슐화 되어야 한다. ex) 회원가입 클래스는, 회원가입 책임만 담당한다 -> 따라서 이 클래스 수정시 사이드이펙트의 범위를 예측하기 쉽다.
개방 폐쇄 원칙
https://cocomo.tistory.com/312?category=681264
변경에는 닫혀있고, 확장에는 열려있어야 한다.
- 코드의 수정사항이 발생될 때 다른 클래스가 영향을 받아서는 안된다. (Side Effect)
- 각 클래스들은 모두 추상화된 모듈로 만들어져야 한다.
잘 설계된 코드는 기존 코드의 변경 없이 확장이 가능하다.
쉽게 확장 가능하다 = 즉 추상화 되어있고, 다형성을 고려한 구조를 띈다, 이로써 재사용성, 유지보수성 등을 얻을 수 있다.
파생클래스 행위의 내부 상세를 안다는 것은 OCP open-close principle 또한 위반 하는 것입니다.
OCP를 가능하게 하는게 추상화와 다형성이다. 그리고 이를 가능하게 하는 것이 상속이다. 그리고 상속들 적용할때 최고의 상속구조를 위한 룰이 LSP(리스코프 치환원칙)이다.
리스코프 치환 원칙
LSP는 완벽한 is a 관계의 성립을 의미한다.
부모클래스가 들어갈 자리에 자식클래스를 넣어도 잘 구동한다.
직사각형이 정사각형을 포함하는 개념과는 다르다. 직사각형은 명백히 height, weight를 따로 메서드로 다뤄야 하지만, 직사각형은 그렇지 않다. 즉 두가지는 부모관계가 될수 없다. 오히려 다른 도형(shape)클래스의 자식들이 더 맞는 구조이다.
이 원칙을 지키지 않은 너무 과한 코드 재사용 욕심을 자제할 것.
인터페이스 분리 원칙
자신이 이용하지 않는 메서드에 의존하지 않아야 한다는 원칙이다.
큰 덩어리의 인터페이스들을 구체적이고 작은 단위들로 분리시킴으로써 클라이언트들이 꼭 필요한 메서드들만 이용할 수 있게 한다.
즉, 자신과 상관없는 기능은 구현되지 말아야 한다.
클라이언트는 자신이 사용하지 않는 메소드에 의존관계를 맺으면 안된다.
인터페이스 분리 원칙을 통해 시스템의 내부 의존성을 약화시켜 리팩토링, 수정, 재배포를 쉽게 할 수 있다.
https://cocomo.tistory.com/314
차라리 인터페이스를 여러 작은 인터페이스로 분리하여 이 인터페이스들 여러개를 구현하도록 할 것
즉, Interface와 abstract class는 역할에 대한 정의이다.
의존성 역전 원칙
구체적인 것이 추상적인 것에 의존해야 한다
https://cocomo.tistory.com/315?category=681264
- 상위 모듈은 하위 모듈에 종속되어서는 안된다. 둘 다 추상화에 의존해야 한다.
- 추상화는 세부사항에 의존하지 않는다. 세부사항은 추상화에 의해 달라져야 한다
- 즉 클래스간 결합 강도를 약하게 유지한다.
네트워크 7계층
JVM 실행순서
JVM(Java Virtual Machine)의 (일부)구조입니다.
위 그림은 이번에 알아볼 영역만 그린걸로, 모든 구조가 궁금하신분들은 JVM Structure 검색하셔서 보시기 바랍니다
(Execution engine, Garbage Collector, Method+Heap Area를 제외한 나머지 Runtime Data Area 등등 제외)
개발자가 소스코드를 작성후에 실행을 시키면 컴파일러라는 녀석이 .java를 .class(Byte Code)로 변환시켜줍니다
변환을 하고 나면 Class Loader가 .class파일(+라이브러리도)을 가져옵니다(로드)
Class Loader는 로드한 Class파일을 메모리영역(RunTime Data Area)에 올려주는 역할을 합니다
메모리영역(RunTime Data Area)에 Class파일에 올라간 파일은 실행엔진(Execution Engine)에 의하여 실행되게 됩니다
(.class파일들, 즉 byte로 변환되기 전의 상태로 Memory 영역(Runtime Data Area)로 올림. 이후 Jit 컴파일러가 사용시마다 class to byte로 컴파일 해가면서 사용한다)
(Execution Engine 밑에 Interpret, JIT는 궁금하신분은 한번 찾아보세여 여기까지 설명하기는 길어지기 때문에)
메소드 영역
- 모든 Thread가 공유하는 Memory 영역입니다. ( Class, Interface, Method, Field, Static Field 등이 저장됩니다. )
JVM 큰틀의 작동원리는 설명했고, 메소드영역(Method Area)에 대해 한번 알아보겠습니다**
메소드 영역(Method Area)이란 곳이 있습니다.(이 영역은 JVM이 실행될 때 생성됩니다)
한마디로 일단 정의하자면 메소드 영역은 Type정보를 가지고 있는 영역입니다
이 메소드 영역은 7개의 정보를 저장하고 있습니다.(또 머리가 아파옵니다)
여기서는 7개의 정보를 전부 알아보는 것이 아니라 Class Variable만 보겠습니다
- Class Variable
① Class Variable은 Static Variable 입니다(드디어 Static이 나왔습니다)
② Static 변수는 Class Variable에 저장됩니다
③ 어디서든 공유해서 쓸 수 있다는 변수를 의미합니다
④ Static변수는 클래스가 사용되기 전에 미리 메모리에 올라갑니다.
용어 정리
파이프라이닝이란?
Subscribe via RSS