git clone https://github.com/deviantony/docker-elk

nginx를 설치하고 docker 기반으로 ELK (elasticsearch, logstash, kibana)를 설치하고 nginx 로그를 filebeat를 설치하여 acces.log, error.log, syslog등을 전송해보자.

 

설치 

ELK를 도커에 설치하는 스크립트를 아래 github에 잘 정리되어 제공해주고 있다.
https://github.com/deviantony/docker-elk

ELK는 이걸로 설치하면 되는데 docker-compose로 nginx와 filebeat까지 함께 설치하기 위해서 아래 저장소에서 제공하는 nginx-filebeat 스크립트를 혼합해서 사용해보자.
https://github.com/spujadas/elk-docker/tree/master/nginx-filebeat

1. 우선 ELK 설치 스크립트를 가져오자.

git clone https://github.com/deviantony/docker-elk



2. 그리고 nginx-filebeat 파일을 다운 받아서 docker-elk 디렉토리 내부에 추가한다.

git clone https://github.com/spujadas/elk-docker
mv ./elk-docker/nginx-filebeat ./docker-elk



3. 그리고 nginx-filebeat까지 사용할 수 있도록 docker-compose.yml 스크립트를 수정해준다. 그리고 nginx.conf 파일을 쉽게 보고 생성된 log도 로컬에서 보기 위해서 mount를 로컬 폴더로 진행한다.

version: '3.2'

services:
  elasticsearch:
    container_name: "elasticsearch"
    build:
      context: elasticsearch/
      args:
        ELK_VERSION: $ELK_VERSION
    volumes:
      - type: bind
        source: ./elasticsearch/config/elasticsearch.yml
        target: /usr/share/elasticsearch/config/elasticsearch.yml
        read_only: true
      - type: volume
        source: elasticsearch
        target: /usr/share/elasticsearch/data
    ports:
      - "9200:9200"
      - "9300:9300"
    environment:
      ES_JAVA_OPTS: "-Xmx256m -Xms256m"
      ELASTIC_PASSWORD: changeme
    networks:
      - mynet

  logstash:
    container_name: "logstash"
    build:
      context: logstash/
      args:
        ELK_VERSION: $ELK_VERSION
    volumes:
      - type: bind
        source: ./logstash/config/logstash.yml
        target: /usr/share/logstash/config/logstash.yml
        read_only: true
      - type: bind
        source: ./logstash/pipeline
        target: /usr/share/logstash/pipeline
        read_only: true
    ports:
      - "5000:5000"
      - "9600:9600"
    environment:
      LS_JAVA_OPTS: "-Xmx256m -Xms256m"
    networks:
      - mynet
    depends_on:
      - elasticsearch

  kibana:
    container_name: "kibana"
    build:
      context: kibana/
      args:
        ELK_VERSION: $ELK_VERSION
    volumes:
      - type: bind
        source: ./kibana/config/kibana.yml
        target: /usr/share/kibana/config/kibana.yml
        read_only: true
    ports:
      - "5601:5601"
    networks:
      - mynet
    depends_on:
      - elasticsearch

  nginx:
    container_name: "nginx"
    build:
      context: nginx-filebeat/
    volumes:
      - type: bind
        source: /Users/we/Documents/docker/nginx
        target: /etc/nginx
      - type: bind
        source: /Users/we/Documents/docker/nginx_log
        target: /var/log
    ports:
      - "8080:80"
    networks:
      - mynet

networks:
  mynet:
    driver: bridge

volumes:
  elasticsearch:

 

4. 마지막으로 filebeat.xml에서 ssl 통신을 하지 않기 때문에 ssl 부분을 제거해준다. (이유는 하단에 나온다.)

output:
  logstash:
    enabled: true
    hosts:
      - logstash:5044
    timeout: 15

filebeat:
  inputs:
    -
      paths:
        - /var/log/syslog
        - /var/log/auth.log
      document_type: syslog
    -
      paths:
        - "/var/log/nginx/*.log"
      document_type: nginx-access

 

5. 그럼 지금까지 수정한 내용을 이용해서 docker-compose up -d 통해 설치 진행해보자. 설치후 제거 하고 싶으면 (docker-compose -v down) 명령어를 통해 제거 할 수 있다.

docker-compose up -d

 

6. 설치가 완료되면 키바나에 접속해서 확인해보면 logstash, elasticsearch, kibana 모두 설치 된 것을 알 수 있다. 초기 계정은 elastic / changeme이다.

ELK와 nginx docker process

7. logstash pipeline을 만들어줘야하는데 kibana에서 management → logstash → pipeline에서 설정해주면된다. 간단하게 5044포트로 받고 nginx_log 인덱스로 넣게 설정한다.

input {
    beats {
        client_inactivity_timeout => 19909
        port => "5044"
        ssl => false
    }
}
filter {
  if [type] == "nginx-access" {
    grok {
      match => { "message" => "%{NGINXACCESS}" }
    }
  }
}
output {
    elasticsearch {
        hosts => ["elasticsearch:9200"]
        index => "nginx_log"
        user => "elastic"
        password => "changeme"
    }
   stdout { codec => rubydebug }
}

 

8. 그럼 filebeat가 정상적으로 동작하는지 확인해보자.

filebeat 실행 상태 확인

/etc/init.d/filebeat status

 

filebeat.xml 기준으로 설정 정상 적용 상태 확인

filebeat test config

 

filebeat.xml에 설정된 output 정상 여부 확인

filebeat test output

filebeat 테스트 결과

위에 테스트를 진행하면 위에 화면처럼 나오는게 나와야 정상이다. 정상적으로 가동된걸 확인했다.

그럼 실제 nginx에서 나온 로그가 filebeat로 수집되어 logstash > elasticsearch로 정상적으로 적재되는지 보자.
localhost:8080에 접속하여 로그 발생시킨 후 filebeat 로그를 확인했는데 왠걸 다음과 같은 오류가 발생했다.

[2019-10-15T06:12:48,844][INFO ][org.logstash.beats.BeatsHandler] [local: 172.28.0.4:5044, remote: 172.28.0.2:55946] Handling exception: org.logstash.beats.BeatsParser$InvalidFrameProtocolException: Invalid Frame Type, received: 1
[2019-10-15T06:12:48,845][WARN ][io.netty.channel.DefaultChannelPipeline] An exceptionCaught() event was fired, and it reached at the tail of the pipeline. It usually means the last handler in the pipeline did not handle the exception.
io.netty.handler.codec.DecoderException: org.logstash.beats.BeatsParser$InvalidFrameProtocolException: Invalid Frame Type, received: 1
at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:459) ~[logstash-input-tcp-6.0.3.jar:?]
at io.netty.handler.codec.ByteToMessageDecoder.channelInputClosed(ByteToMessageDecoder.java:392) ~[logstash-input-tcp-6.0.3.jar:?]
at io.netty.handler.codec.ByteToMessageDecoder.channelInputClosed(ByteToMessageDecoder.java:359) ~[logstash-input-tcp-6.0.3.jar:?]
at io.netty.handler.codec.ByteToMessageDecoder.channelInactive(ByteToMessageDecoder.java:342) ~[logstash-input-tcp-6.0.3.jar:?]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelInactive(AbstractChannelHandlerContext.java:245) ~[logstash-input-tcp-6.0.3.jar:?]
at io.netty.channel.AbstractChannelHandlerContext.access$300(AbstractChannelHandlerContext.java:38) ~[logstash-input-tcp-6.0.3.jar:?]
at io.netty.channel.AbstractChannelHandlerContext$4.run(AbstractChannelHandlerContext.java:236) ~[logstash-input-tcp-6.0.3.jar:?]
at io.netty.util.concurrent.DefaultEventExecutor.run(DefaultEventExecutor.java:66) ~[logstash-input-tcp-6.0.3.jar:?]
at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:858) [logstash-input-tcp-6.0.3.jar:?]
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) [logstash-input-tcp-6.0.3.jar:?]
at java.lang.Thread.run(Thread.java:834) [?:?]
Caused by: org.logstash.beats.BeatsParser$InvalidFrameProtocolException: Invalid Frame Type, received: 1
at org.logstash.beats.BeatsParser.decode(BeatsParser.java:92) ~[logstash-input-beats-6.0.0.jar:?]
at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:489) ~[logstash-input-tcp-6.0.3.jar:?]
at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:428) ~[logstash-input-tcp-6.0.3.jar:?]
... 10 more

이 문제는 logStash 또는 filebeat가 동시에 ssl을 사용하지 않는데 한쪽만 ssl 통신을 했을 때 발생되는 오류이다.

그래서 아까 위에 filebeat에 ssl 부문을 지우고 pipeline에 ssl 설정을 false로 지정한 것이다. 그럼 다시한번 localhost:8080에 들어가보자.

지금은 index.html을 만들어 놓지 않아서 404 에러가 발생된다.

GET nginx_log/_search
{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 2,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "nginx_log",
        "_type" : "_doc",
        "_id" : "vc04zm0BZBO8vgBrBmV7",
        "_score" : 1.0,
        "_source" : {
          "ecs" : {
            "version" : "1.1.0"
          },
          "@version" : "1",
          "tags" : [
            "beats_input_codec_plain_applied"
          ],
          "message" : """2019/10/15 07:00:31 [error] 25#25: *16 "/etc/nginx/html/index.html" is not found (2: No such file or directory), client: 172.28.0.1, server: , request: "GET / HTTP/1.1", host: "localhost:8080"""",
          "host" : {
            "name" : "a31d3333d22b"
          },
          "agent" : {
            "type" : "filebeat",
            "version" : "7.4.0",
            "hostname" : "a31d3333d22b",
            "id" : "cc4eb582-e09c-4a83-bb2e-9721c39ee508",
            "ephemeral_id" : "a5918a0b-4688-458f-bbc2-4eb49a3fff03"
          },
          "log" : {
            "file" : {
              "path" : "/var/log/nginx/error.log"
            },
            "offset" : 8524
          },
          "@timestamp" : "2019-10-15T07:00:38.443Z"
        }
      },
      {
        "_index" : "nginx_log",
        "_type" : "_doc",
        "_id" : "vs04zm0BZBO8vgBrBmV8",
        "_score" : 1.0,
        "_source" : {
          "ecs" : {
            "version" : "1.1.0"
          },
          "@version" : "1",
          "tags" : [
            "beats_input_codec_plain_applied"
          ],
          "message" : """172.28.0.1 - - [15/Oct/2019:07:00:31 +0000] "GET / HTTP/1.1" 404 555 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36"""",
          "host" : {
            "name" : "a31d3333d22b"
          },
          "agent" : {
            "type" : "filebeat",
            "version" : "7.4.0",
            "hostname" : "a31d3333d22b",
            "id" : "cc4eb582-e09c-4a83-bb2e-9721c39ee508",
            "ephemeral_id" : "a5918a0b-4688-458f-bbc2-4eb49a3fff03"
          },
          "log" : {
            "file" : {
              "path" : "/var/log/nginx/access.log"
            },
            "offset" : 8528
          },
          "@timestamp" : "2019-10-15T07:00:38.443Z"
        }
      }
    ]
  }
}

 

정상적으로 적재가 잘되는것을 확인할 수 있다.

이로써 docker로 구성된 elk로 로그 적재를 진행해봤다. 

 

관련 패키지는 github에 올려놓았다.

https://github.com/weduls/elk_with_nginx

https://www.youtube.com/watch?v=dJ5C4qRqAgA

우아한 형제들에서 진행한 우아한 객체지향 세미나에 가고 싶었는데 아쉽게도 가지 못했다. 발표해주시는분이 객체지향의 사실과 오해를 쓰신 조영호분이라서 더 가보고 싶었는데 아쉽다. 책에 내용이 좋아서 동영상으로라도 보고 싶었는데 유튜브에 동영상이 올라와서 보고 정리해본다.

 

개념

- 설계는 코드에 어디에 놓을건지를 정하는 것.

- 의존성 문제의 핵심은 코드 변경시 영향을 주는지이다.

- 의존성 문제는 디커플링이 되어야 한다.

 

관계설명

연관관계 (Association)

A 클래스에 B클래스로 갈수 있는 영구적인 방법이 있는 경우

A → B

class A { 
	private B b;
}

 

의존관계 (Dependency)

A ---> B (파라미터, 리턴타입, 지역변수 등등)

일시적으로 관계를 맺고 사라지는 관계

class A { 
	public B method(B b) { 
		return new B(); 
	}
}

 

상속관계 (Inheritance)

A → B

부모에 구현이 바뀌면 영향을 받을 수 있음.

public class A extends B

 

실체화 관계 (Realization)

인터페이스에 오퍼레이션 시그니처가 변경되었을때만 영향을 받음

A ---> B

class A implements B

 

효율적인 객체 설계법

1. 양향향 의존성을 피하라

  • 순환참조를 피하라. 오직 단방향 참조를 하라.
  • 다중성이 적은 방향을 선택하라.
  • 1대다 보다 다대1로 설계하라. (아래 예시)
class A { 
	private Collection<B> bs;
}
보다

class B { 
	private A a
}
로 사용하라.

 

2. 제일 좋은건 의존성을 줄여라.

  • 관계가 있다는 것은 파라미터, 또는 인스턴스 변수등에서 전달되는 사이가 있다.
  • 관계에는 방향성이 있어야 한다.
  • 협력의 방향, 의존성의 방향
  • 상속관계랑 실체화 관계의 경우 명확하고 대부분이 연관, 의존 관계가 대부분
  • 데이터 흐름의 의존적일 수 있다.
  • 영구적인지, 일시적인지 여부에 따라서 의존관계 연관관계를 지정
  • 관계는 런타임에 어떤 연관을 가지는지에 따라 달라진다.

 

3.  연관관계 정의

  • 이 객체를 알면 다른 객체를 찾아갈수 있어요가 연관관계의 정의
  • 설계를 할때 의존관계를 손으로 그리고 코드를 작성한다. => 잘못된 패키지 참조가 발생되나?
  • 도메인에 나눠서 패키지를 구성하라.
  • 레이어 구현은 패키지
  • 패키지내부에 어떤 레이어로 나눌건지 패키지 구성
  • 의존성 역전 원칙
  • 패키지 싸이클은 절대 돌면 안된다.

 

대표적인 연관성 문제 해결방안

1. 객체 참조

  • 객체 참조로 List<B> b로 데이터를 가지고 있으면 메모리에 있을때 큰 이슈는 없으나 orm을 통해 데이터를 읽을 때 문제의 소지 발생 가능 (N+1 문제 등등)
  • 객체의 연관성이 너무 얽혀있음.
  • 객체 참조로 많은걸 수정하면 롱 트랜잭션이 생겨서 트랜잭션의 경계가 모호해짐.
  • 객체의 연관성이 높아지면 트랜잭션이 포인트가 커져서 트랜잭션의 문제가 발생
  • 객체 참조는 연관성이 너무 크기 때문에 필요하다면 다 끊어야한다.

  • 모든 객체 참조가 불 필요한 건 아니다. 함께 생성되고 함꼐 삭제되는 객체는 묵어라. 다시 말해 도메인 제약 사항을 공유하는 객체들은 묶어라. (도메인 관점으로 묶어라). 가능하면 분리하라 
  • 아래 같은 경우처럼 같은일은 하는 Order, OrderLineItem, OrderOptionGroup, OrderOption의 연관관계는 유지하고 Shop은 연관관계를 끊고 shopId를 보유하고 그 Id를 이용해서 데이터를 탐색하라.

  • 같은 객체와 연관관계가 있는 객체는 같은 트랜잭션 레벨에서 관리하면 된다. (같은 객체에 정보는 한번에 쿼리로 가져온면 된다.)

 

2. 객체지향보다 절차지향이 더 좋을때도 있다.

만약 연관관계가 있어서 사용하다가 연관관계를 끊으면서 문제가 생기면 그 부분을 별도의 로직으로 분리한다. 예를 들어 Order에서 validation을 밖으로 빼어 OrderValidatiuon을 만들면 Order에서 Shop, Menu등에 대한 연관관계가 필요없어진다. 그리고 Order에 별도의 validation 코드가 있다보면 응집도가 높은 코드들이 아닌 코드들이 같이 있기 때문에 통일성이 부족해진다. 꼭 객체안에 validation을 체크하는 로직이 같이 있을 필요는 없다.

 

3. 문제상황

의존성을 없애기 위해서 Shop객체가 사라지고 shopId가 생기면서 배달완료되고 배달료에 부여하는 코드에서 컴파일 오류가 발생된다.

이 부분을 validation처리 처럼 절차지향으로 변경하거나 도메인 이벤트 퍼블리싱으로 해결할 수 있다.

 

1) 절차지향 방식 사용

OrderDeliveredService를 사용해서 orderId를 받아서 Shop에 비용을 부과함으로써 Order에서 Shop의 연관성을 제거할 수 있다. 

하지만 DorderDeliveredService에 Order, Shop, Delivery를 사용하기 때문에 의존성이 다시 생긴다.

그래서 Interface를 이용해서 의존성을 해결해주면 된다. 

 

2) Domain Event를 이용한 의존성 제거

스프링에서 제공하는 Domain Event를 이용하여 특정 이벤트가 발생했을때 기능을 발생시키도록 하면 의존성없이 해결할 수 있다. 간단하게 AbstractAggregateRoot를 상속받아서 registerEvent를 통해 이벤트를 등록하면 핸들러가 처리한다. 실제로 배달의 민족의 대부분의 서비스가 다음과 같은 방식으로 이루어져있다고 한다.

이벤트가 발생하면 handler에서 이벤트를 받아서 비동기로 데이터를 처리하면 끝.!

 

직접 듣는게 아니라 녹화된 방송으로 들어서 정리하고 이해하면서 들을 수 있어서 더 좋았다. 다음에는 실제로 참석해서 들어보고 싶다. 개발하면서 지켜가면서 개발을 해봐야겠다.

Spring에서 초기 테이블과 데이터 관리를 위해서 data.sql과 schema.sql을 사용하였다. 하지만 테이블 스키마가 변경되거나 필수로 초기에 들어가야하는 데이터들이 추가되거나 수정되었을 때 히스토리 관리가 잘 되지 않았다. 

특히 서로 교류가 잘 되지 않은 경우에서는 컬럼이 추가되거나 무엇이 변경되었는지 알지 못해서 문제를 유발할 수 있기에 이를 관리 할 수 있는 무언가가 필요했다.

그래서 Redgate에서 제공하는 Flyway를 사용해보기로 했다. 우선 내 개인 프로젝트인 timeline에 적용시켜봤다.

 

데이터베이스 버전관리 Flyway

https://flywaydb.org/

동작 방식

Flyway가 버전관리를 하기위해서 테이블이 생성된다. Flyway가 버전관리는 이 테이블에 데이터베이스의 상태를 기록하면서 진행한다. 

Flyway가 시작되면 파일시스템 또는 마이그레이션 대상의 classpath를 스캔해서 Sql 또는 Java로 쓰여진 파일을 찾는다. 이 마이그레이션 작업은 파일에 적혀있는 version number대로 순서대로 진행된다. 그리고 현재 마이그레이션 해야할 파일의 버전과 테이블에 기록된 버전을 확인해보고 같으면 넘어간다.

Flyway에서 사용하는 테이블은 flyway_schema_history로 아래와 같이 구성되어있다.

CREATE TABLE `flyway_schema_history` (
  `installed_rank` int(11) NOT NULL,
  `version` varchar(50) DEFAULT NULL,
  `description` varchar(200) NOT NULL,
  `type` varchar(20) NOT NULL,
  `script` varchar(1000) NOT NULL,
  `checksum` int(11) DEFAULT NULL,
  `installed_by` varchar(100) NOT NULL,
  `installed_on` timestamp NOT NULL DEFAULT current_timestamp(),
  `execution_time` int(11) NOT NULL,
  `success` tinyint(1) NOT NULL,
  PRIMARY KEY (`installed_rank`),
  KEY `flyway_schema_history_s_idx` (`success`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
installed_rank 인덱스
version 버전명 (V나 R 뒤에 붙는 숫자)
description 설명
type SQL 또는 JDBC 
script 스크립트 이름 V1__kdjlkdf.sql
checksum checksum
installed_by 실행 주최자
installed_on 설치된 시간
execution_time 총 실행시간
success 성공여부

 

간단히 말해 변경된 데이터나 테이블 스키마를 적용하기 위해서는 마지막 버전보다 높은 파일을 만들어서 애플리케이션을 구동하면 된다.

 

애플리케이션에 적용

그럼 flyway를 적용하기 위해 gradle에 라이브러리부터 추가해보자.

dependency {
	compile group: "org.flywaydb", name: "flyway-core", version: '5.2.4'
}

그리고 application.yml을 설정하자.

spring:
  flyway:
    enabled: true
    baselineOnMigrate: true
    encoding: UTF-8

그리고 테이블과 데이터를 넣을 sql을 만들자. 

그리고 Springboot 애플리케이션을 실행시키면 해당 테이블에 버전 히스토리가 기록된다.

 

버전관리하기에 좋은거 같다.

자바 메모리 구조는 1.8 이후로 일부분 바뀌었다.

이 부분에 대한 정리를 다시 하고 싶었고 GC 알고리즘에 대한 종류와 상세 내용을 정리하고 싶었다. 그럼 이 두 가지 사항에 대해 가볍게 정리해보자.

Java 메모리 구조

Method Area

프로그램이 실행되는 도중에 아직 사용되지 않은 클래스들의 코드는 new를 통해 클래스의 인스턴스가 생성되면 JVM Method Area에 인스턴스 변수, 메스드 코드, 클래스 변수등을 저장한다. 해당영역은 모든 쓰레드 사이에서 공유되고 static 키워드로 생성된 변수 또한 저장을 Runtime Constant Pool 영역에 저장한다. 실 데이터를 저장하는 것이 아니라 레퍼런스만 저장하며 실제 데이터는 Heap 영역에 저장한다.

 

JVM Language stack

각 스레드들은 생성과 동시에 각자의 stack을 생성하게 되는데 이 영역에 메소드가 실행될 때 사용된 메스드의 데이터들을 저장한다. 그리고 메서드 실행이 끝나면 해당 stack영역은 사라진다.

 

PC Registers

각 스레드별로 PC Registers가 존재하며 JVM 머신이 가장 최근에 실행한 명령어의 주소를 저장한다.

 

Native Method Area

Native Library에 의존하는 native 코드들을 저장하는 곳이다. (JNI)

 

Permenent (Java8 metaspace 대체)

permenent 영역에 로드된 클래스의 메타 정보와 static한 변수들 정보들이 담겨져 있는데 그 중 static 영역과 상수 영역은 heap으로 옮겨졌다.

-XX:MetaspaceSize : JVM이 사용하는 네이티브 메모리 
-XX:MaxMetaspaceSize : metaspace의 최대 메모리 

 

Heap 영역

GC가 발생되는 대표적인 영역이며 new를 통해 인스턴스가 동적으로 생성된 데이터와 배열정보를 저장하는 공간으로 xms, xmx등의 옵션으로 기본 힙사이즈를 설정할 수 있다. 해당 힙사이즈도 모든 쓰레드 사이에서 공유된다.

-Xms : JVM 시작 시 힙 영역 크기
-Xmx : 최대 힙 영역 크기

힙 영역은 GC가 발생되는 방법에 따라 Young, Old영역으로 나뉘게 된다.

1. Young 영역 

Eden

새롭게 할당된 데이터가 쌓이는 곳으로 일정주기 동안 참조가 유지되면 Survivor로 옮겨진다. Survivor로 옮겨지지 못한 데이터는 GC에 의해 청소된다.

Survivor

Eden영역에서 넘어온 데이터가 1 또는 2영역으로 나눠서 저장된다. 참조가 살아있는 경우 주기에 맞춰서 다른 Survivor영역으로 이동하고 그렇지 못한 데이터들은 GC에 의해서 처리된다. 위와 같은 경우를 Minor GC라고 한다. 

-XX: NewRatio      : New영역과 Old 영역의 비율
-XX: NewSize       : New 영역의 크기
-XX: SurvivorRatio : Eden 영역과 Survivor 영역의 비율

 

2. Old 영역

Survivor 영역에서 오래 살아남은 데이터의 경우 Old 영역으로 넘어가게 된다. Old 영역은 이렇게 넘어온 데이터가 많기 때문에 Young크기 보다 더 크게 설계되며 이부분에서 발생된 GC를 Major GC라고 한다.

 

GC 알고리즘

YOUNG, OLD GC가 발생되는 알고리즘 종류에 대해 정리해보자. 우선 GC의 경우 mark > sweep > compaction 작업이 순서대로 동작한다. 우선 GC 대상을 고르는 mark 작업이 선행되고 실제 제거를 수행하는 sweep가 동작한다. 그리고 메모리의 파편화가 된 부분을 채워 나가는 Compaction 작업으로 마무리한다.

Serial GC

위에서 언급한 3가지 작업이 진행되는 간단한 GC 알고리즘이다. 이 GC의 경우 처리하는 쓰레드가 단 하나이기 때문에 처리하는 과정 동안에 발생하는 STW (Stop the world) pause 시간이 길다.

 

Parallel GC

Serial GC에서 동작하는 스레드가 하나였기 때문에 문제가 자주 발생하였는데, 여기에 작업을 진행하는 스레드를 추가하여 병렬로 작업을 진행한 알고리즘 이다.

왼쪽부터 Serial GC, Parallel SerialGC (https://www.oracle.com/technetwork/java/index.html)

CMS GC

기존에 사용되던 GC 알고리즘 보다 STW pause 시간을 줄이기 위해 고안된 방법으로 없애야 하는 데이터를 정확하게 선별하는 작업이 추가로 진행된다. 그만큼 연산작업이 추가되어 CPU같은 리소스 자원 사용이 증가하였다. 최초 GC 판단하는 initial Mark, initial mark때 선정된 객체를 참조 하는 객체의 GC 대상인지 판단하는  Concurrent Mark, 마지막 검증 작업을 하는 Remark작업을 통해 대상을 선정한 후 Concurrent Sweep 작업을 통해 데이터를 지운다. 그리고 기존 Serial GC와의 차이점은 데이터를 sweep한 후 compaction 작업을 자주 진행하지 않는다. 파편화된 메모리를 자주 매꾸면 그만큼 오버헤드가 많이 발생할 수 있기 때문에 심각한 파편화가 발생했을 때만 매꾼다.

 

G1GC

기존에 GC 알고리즘과는 다른 알고리즘이 나온게 G1GC이다. 기존에는 Eden, Survivor, Old, Permanent영역으로 정확하게 나누어져 있었다. 하지만 G1GC 사용할 경우 heap 영역을 2048개 region 영역으로 쪼개고 이 지역의 크기는 G1HeapRegionSize를 통해 32mb 까지 지정이 가능하다.

또한 새로운 형태의 상태값이 생겼는데 Humongous와 Available/Unused이다. Humongous는 region크기의 50%를 초과하는 큰 데이터를 저장하기 위한 곳이고 Available/Unused는 아직 사용하지 않는 region을 뜻 한다.

각 Region은 Eden, Survivor, Old, Permanent, Humongous, Available/Unused 상태로 지정이 가능하고 데이터가 가장 많이 찬 Region에서 GC가 발생된다. 

G1GC 사용 시 heap 영역 (https://c-guntur.github.io/java-gc/#/6)

 

 

출처

https://www.guru99.com/java-virtual-machine-jvm.html
https://d2.naver.com/helloworld/1329

  1. goodGid 2019.09.24 10:28

    JVM Language stack
    에서 오타가 있네요 ㅎㅎ
    실행될 때 사용된 메스드의 데이터들을 저장한다.
    --> 실행될 때 사용된 메소드의 데이터들을 저장한다.

Any vs Object vs Unknown 

- typescript에서 모든 타입은 any type의 서브 타입들이다. 그래서 어떠한 제약도 없는 모든 타입을 받아서 사용할 수 있다. 

- object non privitive type으로 undefined, string, boolean, symbol같은 primitive type을 허용하지 않는다. 대신 null은 허용된다. (null은 태생이 object)

- unknown 타입은 any 타입처럼 어떤값도 넣을 수 있다. 하지만 ts compiler는 unknown 타입에 대한 어떤 오퍼레이션도 허용하지 않는다. 게다가 unknown 타입은 오직 any 타입만 할당 할 수있다.

Unknown type operation 금지
Unknown은 any타입이외에 할당 불가

 

 

자료형 캐스팅 방법

- let strLength: number = (<string>someValue).length;

- let strLength: number = (someValue as string).length;

 

 

readonly vs const

- 두 가지 모두 기존 데이터를 수정할 수 없게 하는 예약어이지만 readonly는 클래스 변수에, const는 메소드 내 지역변수에서 사용된다.

 

 

symbol

- number, string의 non primitive 형태의 데이터를 primitive하게 해주는 역할

- 자바에서 new String(). new Integer()와 같은 것으로 두 개의 비교는 == 로 할 수없다. 왜냐하면 애초에 같은 값이여도 서로 다른 주소를 가리키는 객체가 되기 때문. 유니크한 형태의 primitive를 만들어 주는 것

 

 

declare

- 타입스크립트 컴파일러에게 해당 변수가 어딘가에 선언되어 있따고 알려주는 행위로써 전역변수를 사용할 떄도 사용되고 .d.ts 파일을 만들때 사용

https://stackoverflow.com/questions/35019987/what-does-declare-do-in-export-declare-class-actions

 

import vs require

- 둘다 같은 기능이나 require는 레거시 import 사용 권장

 

https://www.typescriptlang.org/docs/home.html

 

Documentation · TypeScript

Learn everything you need to know about TypeScript. New to TypeScript? # Already familiar with TypeScript? # Having trouble finding what you’re looking for? Tell us so we can better help you!

www.typescriptlang.org

 

'web > Typescript' 카테고리의 다른 글

Typescript 헷갈리는 부분 정리  (0) 2019.09.12

+ Recent posts