Skip to content

태그: 언어

GC
go
Go의 GC는 mark-sweep 방식으로 동작하며 두 가지 주요 단계로 나뉜다. 마킹: 메모리에서 살아있는 객체를 식별한다. 스윕: 죽은 객체를 해제하여 메모리를 반환한다. GC는 스윕 → 비활성 → 마킹 세 단계를 순회하며 주기적으로 진행된다. Go의 GC는 현시점의 Heap 크기와 직전 시점의 Heap 크기에 대한 증가율이 특정 정도에 다다르면 수행되는 로직으로 구현되었다. 이 때 그 정도를 정하는 변수는 GOGC라고 부른다. GOGC 값을 사용해 아래와 같이 목표 힙 메모리를 구한다. 목표 힙 메모리 = 살아있는 힙 메모리 + (살아있는 힙 메모리 + GC 루트) × GOGC / 100 예를 들어, 살아있는 힙이 8 MiB이고, GOGC가 100일 때, 목표 힙 메모리는 18 MiB이다. GO
slice
go
슬라이스는 내부적으로 사용하는 배열의 부분 영역인 세그먼트에 대한 메타 정보를 가지고 있다. 슬라이스는 크게 3개의 필드로 구성되어 있다. 내부적으로 사용하는 배열에 대한 포인터 정보 세그먼트의 길이 세그먼트의 최대 용량(Capacity) append append()가 슬라이스에 데이터를 추가할 때는 슬라이스 용량(capacity)이 아직 남아 있는 경우: 용량 내에서 슬라이스의 길이(length)를 변경하여 데이타를 추가한다. 용량(capacity)을 초과하는 경우: 현재 용량의 2배에 해당하는 새로운 Underlying array를 생성하고, 기존 배열 값들을 모두 새 배열에 복제한 후 다시 슬라이스를 할당한다. 아래 예제는 길이 0, 용량 3의 슬라이스에 1부터 15까지의 숫자를 계속 추가하면
가상스레드
thread
2023년 9월 19일에 릴리즈된 Java 21에서 ‘가상 스레드’ 라는 기능이 추가되었다. 가상 스레드란 기존의 전통적인 Java 스레드보다 가벼운 경량 스레드이다. 자바의 전통적인 스레드는 OS 스레드를 랩핑(wrapping)한 것으로, 플랫폼 스레드라고도 부르는데, 경량 스레드는 OS 스레드를 그대로 사용하지 않고 JVM 자체적으로 내부 스케줄링을 통해서 사용할 수 있도록 한다.는 낭비를 줄여야 할 필요가 있었다. 이 때문에 Non-blocknig 방식의 Reactive Programming이 발전하였다. 비동기 방식의 Reactive 프로그래밍을 사용하면 낭비되었던 스레드를 다른 요청을 처리하는데 사용할 수 있다. 하지만 이런 Reactive 코드는 이해하기 어렵고, 다른 라이브러리를 모두
동시성
스레드
대부분의 요즘 운영 체제에서, 실행되는 프로그램의 코드는 프로세스 내에서 실행되고, 프로그램 내에서도 동시에 실행되는 독립적인 스레드를 가진다. 계산 부분을 여러 개의 스레드로 쪼개는 것은 프로그램이 동시에 여러 개의 일을 할 수 있기 때문에 성능을 향상시킬 수 있지만, 프로그램을 복잡하게 만들기도 한다. 러스트 표준 라이브러리는 언어 런타임의 크기를 작게 유지하기 위해 1:1 스레드 구현만 제공한다. 스레드 생성 새로운 스레드를 생성하기 위해서는 thread::spawn 함수를 호출하고, 새로운 스레드 내에서 실행하기를 원하는 코드가 담겨 있는 클로저를 넘기면 된다. use std::thread;use std::time::Duration; fn main() { thread::spawn(|| {
고루틴 스케줄링
go
Go 런타임은 프로그램이 실행되는 내내 고루틴을 관리한다. Go 런타임은 모든 고루틴을 다중화된 스레드들에 할당하고 모니터링하며, 특정 고루틴이 블록되면 다른 고루틴이 실행될 수 있도록 교체하는 일을 반복한다. 이 말은 고루틴이 블록 되더라도 다중화된 스레드는 블록 되지 않는다는 것을 의미한다. CPU Core가 존재하고 있으며 OS Scheduler에 의해서 다수의 Thread가 Scheduling되어 동작하고 있는 모습을 나타내는 그림이다. Network Poller는 Network를 처리하는 별도의 독립된 Thread를 의미한다. Run Queue에는 GRQ (Global Run Queue)와 LRQ (Local Run Queue)가 2가지가 존재한다. GRQ는 의미 그대로
메모리 관리
go
Go는 힙에서 동적 할당이 필요한 메모리를 암묵적으로 할당한다. 할당이 암묵적으로 이뤄지기 때문에 코딩이 쉽지만, 메모리 할당과 해제가 명확하지 않으니 메모리 사용량이 높아질 수 있는 부분을 놓칠 가능성이 있다. Go는 참조 지향이 아닌 값 지향적 언어이다. Go 변수는 절대 객체를 참조하지 않고, 항상 객체의 전체 값을 저장한다. (포인터가 없다는 의미는 아니다.) 모든 변수 선언(함수 인자, 반환 인자, 메서드 리시버 포함)은 해당 타입 전체를 할당하거나 그 포인터만 할당한다. new(&x3C;type>)은 &x26;&x3C;type>과 동일하며, 힙 상의 포인터 박스와 별도의 메모리 블록에 타입을 할당한다. Go 할당자는 특정 스팬에 하나 혹은 여러 8KB 페이지를 포함하는 메모리
JAVA
java
객체 지향 프로그래밍(OOP, Object-Oriented Programming) 모든 데이터를 객체(object)로 취급하여 객체의 상태와 행동을 구체화 하는 형태의 프로그래밍 클래스 (class) 객체를 정의하는 틀 또는 설계도 필드 = 클래스에 포함된 변수 메소드 = (class에 종속된) 함수 static 클래스 변수 혹은 클래스 메소드 앞에 붙는거 인스턴스 변수와 다르게 인스턴스하지 않아도 그냥 사용가능 (클래스가 메모리에 올라갈 때 메소드 영역에 바로 저장 되기 때문) A라는 클래스 안에 num이라는 static 변수가 있으면 그냥 A.num하고 쓰면 됨 함수도 그냥 메소드 이름.하고 파라미터만 넣어서 씀 반면, 인스턴스 변수는 new 해서 인스턴스로 만든담에 힙 영역에 저장돼야 사용 가
JDKProxy와 CGLibProxy
java
Proxy를 구현한 것 중 가장 많이 쓰이는 종류로는 JDK Proxy와 CGLib Proxy가 있다. 두 방식의 가장 큰 차이점은 Target의 어떤 부분을 상속 받아서 프록시를 구현하느냐에 있다. JDK Proxy는 Target의 상위 인터페이스를 상속 받아 프록시를 만든다. 따라서 인터페이스를 구현한 클래스가 아니면 의존할 수 없다. Target에서 다른 구체 클래스에 의존하고 있다면, JDK 방식에서는 그 클래스(빈)를 찾을 수 없어 런타임 에러가 발생한다. 우리가 의무적으로 서비스 계층에서 인터페이스 -XXXXImpl 클래스를 작성하던 관례도 다 이러한 JDK Proxy의 특성 때문이기도 하다. 또한 내부적으로 Reflection을 사용해서 추가적인 비용이 발생한다. public class Exa
Heap 영역 구조와 GC
jvm
런타임 데이터 영역은 크게 Method영역, Heap영역, Stack영역 등등으로 나뉘어있는데, 런타임중 가장 많은 메모리가 새로 할당되는 영역이 Heap영역이기 때문에, GC 또한 Heap영역을 위주로 실행된다. 이때, Heap 영역은 GC와 메모리 관리를 위해 저장되어있는 데이터를 내부에서 여러 유형으로 분류하여 저장한다. Heap영역은 크게 3가지의 영역으로 나뉜다. Young Generation 영역 자바 객체가 생성되자마자 저장되는 영역이다. 이름 그대로 생긴지 얼마 안되는 어린 객체들이 속하는 곳이다. Heap 영역에 객체가 생성되면 그 즉시 Young Generation 중에서도 Eden 영역에 할당되고, 이 영역에 데이터가 어느정도 쌓이게 되면 참조정도에 따라 Servivor의 빈 공간으로
JVM 구성요소
jvm
JVM 구성요소는 아래와 같은 것들이 있다. 클래스 로더(Class Loader) 실행 엔진(Execution Engine) 인터프리터(Interpreter) JIT 컴파일러(Just-in-Time) 가비지 콜렉터(Garbage collector) 런타임 데이터 영역 (Runtime Data Area) 각각에 대해서 자세히 알아보자. 클래스 로더 JVM 내로 클래스 파일(*.class)을 로드하고, 링크를 통해 배치하는 작업을 수행하는 모듈이다. 런타임시 동적으로 클래스를 로드하고 jar 파일 내에 저장된 클래스들을 JVM 위에 탑재한다. 즉, 클래스를 처음으로 참조할 때, 해당 클래스를 로드하고 링크하는 역할을 한다. 실행 엔진 클래스 로더가 JVM내의 런타임 데이터 영역에 바이트 코드를 배치시
Permanent to Metaspace
jvm
JAVA8에서부터, Permanent가 사라지고 Metaspace가 생김으로써 그 역할을 일부 대체하게 되었다. Permanent는 JVM에 의해 크기가 강제되었지만, Metaspace는 OS가 자동으로 크기를 조절할 수 있는 Native memory 영역이기 때문에 기존과 비교해 큰 메모리 영역을 사용할 수 있게 되었다. Perm 영역에는 주로 클래스, 메소드 정보와 클래스 변수의 정보, static 변수와 상수 정보들이 저장되었는데, 이게 Metaspace로 대체되면서 Perm에 있었던 대부분의 정보가 Metaspace에 저장되도록 바뀌었다. 다만, 기존 Perm 영역에 존재하던 static Object는 Heap 영역으로 옮겨져서 최대한 GC의 대상이 될 수 있도록 하였다고 한다. The propos
Runtime Data Area
jvm
런타임 데이터 영역(Runtime Data Area)은 JVM이 자바 프로그램 실행을 위한 데이터와 명령어를 저장하기 위해 OS로부터 할당받는 메모리 공간이다. Runtime Data Area는 크게 Method영역, Heap영역, Stack영역, PC 레지스터 영역, Native Method Stack으로 나눌 수 있다. Method 영역 클래스 로더에 의해 로드된 클래스, 메소드 정보와 클래스 변수의 정보가 저장되는 영역이다. 프로그램 시작부터 종료될때까지 메모리에 적재되지만 명시적 null 선언시 GC가 청소하도록 만들 수 있다. 데이터가 가장 먼저 저장되는 영역이며, 모든 스레드가 공유한다. Heap 영역 런타임시 결정되는 참조 자료형이 저장되는 영역이다. new 연산자를 통해 생성
메모리누수
jvm
CS적으로 보면, 컴퓨터 프로그램이 필요하지 않은 메모리를 계속 점유하고 있는 현상이다. 할당된 메모리를 사용한 다음 반환하지 않는 것이 누적되면 메모리가 낭비된다. 즉, 더 이상 불핑요한 메모리가 해제되지 않으면서 메모리 할당을 잘못 관리할때 발생한다. 자바에서 메모리 누수 더이상 사용되지 않는 객체들이 GC에 의해 회수되지 않고 계속 누적되는 현상을 말한다. 메모리 누수가 생기면 Old 영역에 계속 누적된 객체로 인해 Major GC가 빈번하게 발생하게 되면서, 프로그램 응답 속도가 늦어지고 성능이 저하된다. 이는 결국 OutOfMemory Error로 프로그램이 종료되게 한다. 가비지 컬렉션을 소멸 대상이 되기 위해서는 어떠한 reference 변수에서 가르키지 않아야 한다. 다 쓴 객체에 대한 참조를
Thread 상태
thread
JVM은 쓰레드를 New, Runnable, Running, Wating, Terminate의 다섯가지 상태로 관리한다. 쓰레드의 상태는 getState() 메서드 호출로 반환받을 수 있다. New 객체를 생성한 뒤, 아직 start() 메서드 호출되기 전의 상태이다. Runnable start() 메서드를 호출하여 동작시킨 쓰레드 객체는 JVM의 쓰레드 스케줄링 대상이 되며 Runnable 상태에 돌입하게 된다. 한 번 Runnable 상태에 돌입한 쓰레드는 다시 New 상태가 될 수 없다. Running Runnable 상태의 쓰레드는 큐에 대기하게 되는데, JVM은 각 쓰레드의 우선순위에 따라서 Running 상태로 만들어 쓰레드를 동작시킨다. 쓰레드 스케줄러에 의해 Running 상태로 이동하게된
예외와 에러
java
프로그래밍에서 예외(Exception)란 입력 값에 대한 처리가 불가능하거나, 프로그램 실행 중에 참조된 값이 잘못된 경우 등 정상적인 프로그램의 흐름을 어긋나는 것을 말한다. 그리고 자바에서 예외는 개발자가 직접 처리할 수 있기 때문에 예외 상황을 미리 예측하여 핸들링할 수 있다. 한편, 에러(Error)는 시스템에 무엇인가 비정상적인 상황이 발생한 경우에 사용된다. 주로 자바 가상머신에서 발생시키는 것이며 예외와 반대로 이를 애플리케이션 코드에서 잡으려고 하면 안 된다. 에러의 예로는 OutOfMemoryError, ThreadDeath, StackOverflowError 등이 있다. Checked Exception과 Unchecked Exception Exception은 다시 Check
프로토타입
javascript
JavaScript에는 객체라는 구조가 존재하고, 각 객체에는 다른 객체에 대한 링크를 보유하는 프로토타입이라는 비공개 속성이 있다. 그 프로토 타입 객체도 자신만의 프로토타입을 가지고 있으며, 프로토타입으로 null을 가진 객체에 도달할 때까지 이 연결은 계속된다. 예시 코드 닭의 프로토타입을 참새로 지정하면, 닭의 날개갯수와 날수있나 값을 참조했을 때 참새가 가지고 있던 값과 동일한 값이 반환된다. 이떄 날수있나 값을 false로 재지정하면 값이 바뀌게 된다. function 참새(){ this.날개갯수 = 2; this.날수있나 = true;}const 참새1 = new 참새();console.log("참새의 날개 갯수 : ", 참새1.날개갯수); // 2 function 닭() { th
화살표 함수
javascript
ES6가 도입되면서, js에 화살표 함수라는 문법이 등장했다. 화살표 함수는 말 그대로 화살표(=>) 표시를 사용해서 함수를 선언하는 방법이다. // 기존 함수const foo = function () { console.log('기존 함수');} // 화살표 함수const foo = () =console.log('화살표 함수'); console.log(&x27;화살표 함수&x27;);"> 두 코드는 기능적으로 완전히 동일하게 작동한다. 그러나, 두 코드는 this 바인딩에서 차이가 있다. this 바인딩 JS의 this는 상황에 따라 다르게 바인딩 된다. 대표적으로 this에 바인딩되는 값들은 이렇다. 전역 공간의 this : 전역 객체메소드 호출 시 메소드 내부의 this : 해당 메소드를 호출한 객체함
InlineFuntion과 Reified
kotlin
inline 키워드는 자바에서는 제공하지 않는 코틀린만의 키워드이다. 코틀린 공식문서의 inline function을 보면, 코틀린에서 고차함수(High order functions, 함수를 인자로 전달하거나 함수를 리턴)를 사용하면 패널티가 발생한다고 나와있다. 패널티란 추가적인 메모리 할당 및 함수호출로 Runtime overhead가 발생한다는 것으로, 람다를 사용하면 각 함수는 객체로 변환되어 메모리 할당과 가상 호출 단계를 거치는데 여기서 런타임 오버헤드가 발생한다. inline functions는 내부적으로 함수 내용을 호출되는 위치에 복사하며, Runtime overhead를 줄여준다. 객체로 변환되는 오버헤드란? 아래와 같은 고차함수(함수를 인자로 전달하거나 함수를 리턴하는 함수)를 컴파일 하
람다 표현식
kotlin
람다는 익명 함수이며, 함수형 프로그래밍에서 자주 사용하는 패턴이다. 코틀린은 객체지향 프로그래밍 뿐만 아니라 함수형 프로그래밍 또한 지원하는 언어이기 때문에 당연히 람다 표현식을 사용할 수 있다. 익명함수 생성 익명함수는 아래처럼 이름없이 정의되는 함수를 말한다. 변수 printHello에 할당되고 있는 내용이 바로 익명함수이다. // 익명함수를 생성하여 printHello에 할당 val printHello = fun(){ println("Hello, world!") } // 익명함수 호출 printHello() 실행결과 Hello, world! 람다를 이용하면 더 간단히 익명함수를 정의할 수 있다. 아래는 위와 동일한 코드를 람다로 재작성 한 것이다. // 익명함수를 생성하
Nullable
변수
Kotlin에서는 코틀린은 원시 타입(primitive type)과 래퍼 타입(wrapper type)을 따로 구분하지 않고, Null일 수 있는 타입과, Null이 불가능한 타입으로 나누어 사용한다. 널이 될 수 있는지 여부를 타입 시스템을 통해 확실히 구분함으로써 컴파일러가 여러 가지 오류를 컴파일 시 미리 감지해서 실행 시점에 발생할 수 있는 예외의 가능성을 줄일 수 있다. 또, null 처리를 위한 다양한 연산자를 지원한다. NULL이 될 수 있는 타입 ? !! 코틀린의 모든 타입은 기본적으로 널이 될 수 없는 타입이다. 타입 옆에 물음표(?)를 붙이면 널이 될 수 있음을 뜻한다. 느낌표 2개(!!)를 변수 뒤에 붙이면 NULL이 될 수 있는 타입의 변수이지만, 현재는 NULL이 아님을 나타낼 수
Object
클래스
코틀린에서 클래스를 정의하는 키워드는 class이다. 하지만 간혹 object 키워드로 클래스 정의하는 경우를 볼 수 있다. object로 클래스를 정의하면, 싱클턴(Singleton) 패턴이 적용되어 객체가 한번만 생성되도록 한다. 코틀린에서 object를 사용하면 싱글톰을 구현하기 위한 Boiler plate를 작성하지 않아도 된다. 또는, 익명 객체를 생성할 때 쓰이기도 한다. object의 용도 싱글턴 클래스로 만들 때 익명 클래스 객체를 생성할 때 싱글턴 클래스 정의를 위한 Object 아까도 말했듯, object로 싱글턴 클래스를 정의할 수 있다. 원래 클래스를 정의할때 class가 있는 자리에 object를 입력해주면 이 클래스는 싱글턴으로 동작하게 된다. object CarFactory {
클래스 상속
클래스
상속은 클래스의 기능을 확장하고자 할때 현재의 클래스의 기능을 모두 가지고 자신만의 기능이 추가된 새로운 클래스를 정의 하는 방법이다. 따라서 상속을 하게 되면 클래스가 상, 하의 계층구조를 가지게 된다. 코틀린에서 모든 클래스는 공통의 상위클래스(superclass)로 Any 클래스를 가진다. 클래스에 상위 클래스를 선언하지 않아도 기본적으로 Any가 상위 클래스가 된다. 마치 JAVA에서 Object가 클래스의 기본 상위 클래스가 되는 것괴 비슷하다. 하지만 클래스 내용이 같지는 않다. Any와 Object 비교 Kotlin의 Any보다 Java의 Object가 가지는 메서드의 수가 더 많다. public open class Any { public open operator fun equals(
구조체 impl
rust
Rust에서 데이터를 표현하는 구조체(혹은 enum, trait 객체)의 맥락에서 그 구조체와 연관된 함수들을 정의할 수 있는데, 이렇게 연관되어진 함수들을 메서드라 한다. 메서드는 맥락이 되는 구조체 인스턴스로부터 호출되어 사용된다. Rust에서 구조체(struct)에 메서드들을 구현하는 구현 블럭(implementation block)은 “impl” 키워드를 사용하여 정의된다. impl 구현블럭 impl 키워드는 구현 블럭(implementation block)을 정의할 때 사용하는데, impl 뒤에 타입명을 적고 impl 블럭 안에 메서드(method) 혹은 연관 함수(associated function)를 넣게 된다. impl 안에 정의된 함수는 그 타입과 연관된 함수라는 의미에서 Associated
Rc 타입과 Weak 타입
메모리 참조
소유권 규칙에 따라 Rust에서 어떤 값은 여러 소유자를 가질 수 없다. Reference Counted를 의미하는 Rc는 힙 메모리에 할당된 타입 T 값의 소유권을 공유할 수 있게 해주는 타입이다. 즉, 스마트 포인터 Rc를 사용하면 타입 T의 값에 대한 여러 개의 소유자를 만들 수 있다. 기본적으로, Rc 타입은 Clone Trait을 구현하고 있고 clone을 호출해서 T 값에 대한 새로운 포인터를 생성하며, 모든 Rc 포인터가 해제되면 메모리에 할당된 T 값이 drop되는 구조이다. Rust에서 공유된 참조자는 수정할 수 없는데, Rc 타입 또한 예외가 아니며 일반적인 방법으로는 mutable한 참조를 얻을 수 없다. 만약, mutable 한 참조가 필요하면 Cell 타입이나 RefCel
소유권과 Lifetime
메모리 참조
기본적으로 모든 변수 바인딩은 유효한 “범위(스코프)“를 가지며, 범위 밖에서 변수 사용하면 에러가 발생한다. 스코프가 종료되면 변수는 “삭제(drop)“되었다고 하며 그 변수의 데이터는 메모리에서 해제된다. Rust에서는 스코프가 종료될 때 다른 리소스를 해제하기 위해 소멸자가 호출되도록 하는 것을 변수가 값을 소유한다고 정의한다. 러스트의 각각의 값은 해당값의 오너(owner)라고 불리우는 변수를 갖고 있으며 한번에 딱 하나의 오너만 존재할 수 있다. fn main() { let s1: String = String::from("Rust"); let s2: String = s1;} 위 코드는 "Rust"라는 String 값에 대한 소유권을 s1에서 s2로 이전한다. s2에 s1을 대입
스마트 포인터 활용
메모리 참조
스마트 포인터(Smart Pointer)는 포인터처럼 작동하지만 추가적인 메타데이터와 능력들도 가지고 있는 데이터 구조이다. String과 Vec&x3C;T>는 스마트 포인터이다. 스마트 포인터는 보통 구조체를 이용하여 구현되어 있다. 스마트 포인터가 일반적인 구조체와 구분되는 특성은 스마트 포인터가 Deref와 Drop 트레잇을 구현한다는 것이다. Deref 트레잇은 스마트 포인터 구조체의 인스턴스가 참조자처럼 동작하도록 하여, 참조자나 스마트 포인터 둘 중 하나와 함께 작동하는 코드를 작성하게 해준다. 스마트 포인터가 평범한 참조자처럼 취급될 수 있도록 구현한다. Deref를 구현한 구조체에 대해 *로 값을 참조하면, deref 함수를 호출한 후 *를 한번 호출하는 것으로 대치된다.
직렬화 serialVersionUID
언어
가끔 자바코드에서 이런 코드를 발견할 때가 있다. private static final long serialVersionUID = 3487495895819393L; 이 serialVersionUID라는 long 타입의 상수는 직렬화할 객체의 버전이 바뀌었는지 식별하기 위한 클래스의 고유 식별자이다. 직렬화와 역직렬화 할때 이 값이 동일하면 동일한 클래스의 데이터라는 것을 확인할 수 있고, 그렇지 않은 경우에는 다르거나 바뀐 클래스라고 생각하여 오류를 throw할 수 있다. 자바 스펙에 따르면, serialVersion을 명시적으로 선언해놓지 않았을 땐 직렬화 런타임이 기본 serialVersion을 클래스의 기본 해쉬값을 가지고 자동으로 계산해준다고 한다. 하지만 JVM에 의한 default ser