1. JPA에서 지연 로딩(Lazy Loading)과 즉시 로딩(Eager Loading)의 차이는 무엇인가요? 어떤 상황에서 어떤 방식을 사용해야 하나요?

즉시 로딩(Eager Loading)과 지연 로딩(Lazy Loading)은 JPA에서 엔티티의 연관된 데이터를 언제 로딩할지 결정하는 방법입니다.

  1. 즉시 로딩(Eager Loading): 엔티티가 조회될 때 연관된 엔티티도 함께 조회합니다. 즉, 한 번의 쿼리로 필요한 모든 데이터를 미리 불러오는 방식입니다. JPA에서는 @ManyToOne과 @OneToOne 어노테이션에 기본적으로 적용되는 방식입니다. 즉시 로딩은 연관된 엔티티를 항상 사용할 것이 확실한 경우에 유용합니다. 하지만 불필요하게 많은 데이터를 조회하게 되면 성능 이슈를 초래할 수 있습니다. @ManyToOne(fetch = FetchType.EAGER)
  2. 지연 로딩(Lazy Loading): 엔티티를 조회할 때 연관된 엔티티는 로딩하지 않고, 실제로 연관된 엔티티를 사용할 때 로딩합니다. 즉, 실제로 사용될 때까지 데이터 로딩을 '지연'하는 방식입니다. JPA에서는 @OneToMany와 @ManyToMany 어노테이션에 기본적으로 적용되는 방식입니다. 지연 로딩은 연관된 엔티티를 항상 사용하는 것이 아닌 경우, 즉 필요한 경우에만 데이터를 조회하므로 성능 향상에 도움이 될 수 있습니다. @ManyToOne(getch = FetchType.LAZY)

  가지 방식은 각각의 상황에 따라 적절히 사용되어야 합니다. 연관된 엔티티를 항상 사용하게  경우에는 즉시 로딩을, 그렇지 않을 경우에는 지연 로딩을 사용하는 것이 일반적입니다.

더보기
  • 즉시 로딩 (Eager Loading): 엔티티가 데이터베이스에서 로드될 때, 연관된 엔티티들도 함께 로드되는 방식입니다. 이 방식을 사용하면 한 번의 쿼리로 필요한 모든 데이터를 가져올 수 있지만, 모든 연관 엔티티를 로드하므로 부담이 될 수 있습니다.
    즉시로딩은, 처음부터 모든 테이블에 조인을 걸어버리고 별도로 쿼리가 나가는 경우가 생기기에, 연관관계가 많고 복잡할수록 비용이 기하급수적으로 늘어나기에, 정확하게 이해하고 필요한 상황이 아니라면, 가급적으로 모두 지연로딩을 걸어두는게 일반적이기는 합니다. ( 쓸지 안쓸지 모르는데, 비용은 가장 많이 드는 작업일 수 있음. ) 
  • 지연 로딩 (Lazy Loading): 연관된 엔티티를 실제로 사용될 때까지 로드하지 않는 방식입니다.  방식을 사용하면 필요한 데이터만 로드하므로 메모리 사용량을 줄일  있지만, 필요한 데이터를 가져오기 위해 여러 번의 쿼리를 실행해야   있습니다.
    JPA 굳이 필요없는 DB 조회를 줄이면서 성능을 최적화한다.
    엔티티가 실제 사용될 때까지 데이터베이스 조회를 지연하는 방법을 제공하는데 이것을 지연 로딩이라 합니다. (지연 로딩 기능을 사용하려면 실제 엔티티 객체 대상에 데이터베이스 조회를 지연할  있는 가짜 객체가 필요한데 이것을 프록시 객체라고 합니다.)
    실제로 가짜 객체를 이용하면, 그때 별도의 쿼리가 나간다.특정 엔티티를 영속 상태로 만들  연관된 엔티티도 함께 영속 상태로 만들고 싶으면 영속성 전이기능을 사용하면 됩니다. JPA cascade 옵션으로 영속성 전이를 제공합니다.
  • 프록시 객체 (Proxy Object): Hibernate 나 JPA에서 많이 사용되는 개념으로, 데이터베이스에서 엔티티를 직접 가져오는 대신에 엔티티의 프록시를 생성하고, 이 프록시를 통해 엔티티에 접근합니다. 이렇게 하면 엔티티의 실제 사용 시점에만 데이터베이스에서 데이터를 가져옴으로써, 성능을 향상시킬 수 있습니다. 프록시 객체는 원래의 엔티티와 같은 인터페이스를 가지므로 사용자 코드는 원래의 엔티티와 프록시를 구분하지 않고 사용할 수 있습니다.

 

2. JPA에서 발생할 수 있는 N+1 문제는 무엇이며, 이를 해결하는 방법은 무엇인가요?

N+1 문제는 JPA나 ORM을 사용하면서 발생할 수 있는 성능 이슈입니다. N+1이라는 이름은 데이터베이스에 쿼리를 실행하는 횟수가 결과 집합의 수(N)에 비해 너무 많아진다는 것을 나타냅니다.

예를 들어, 부모 엔티티와 여러 개의 자식 엔티티가 있을 때, 부모 엔티티를 조회하는 쿼리 1번과 각 부모에 해당하는 자식 엔티티를 조회하는 쿼리 N번이 실행되면, 이를 합하면 총 N+1번의 쿼리가 실행된다는 뜻입니다. 이로 인해 성능 저하가 발생하게 됩니다.

N+1 문제를 해결하는 방법에는 주로 두 가지가 있습니다:

  1. Fetch Join: JPA의 JPQL에서 제공하는 Fetch Join을 사용하면 관련된 엔티티를 한 번의 쿼리로 함께 조회할 수 있습니다. 이를 통해 쿼리의 수를 줄일 수 있습니다.
  2. Batch Size 설정: @BatchSize 어노테이션을 사용하여 한 번의 쿼리로 가져올 엔티티의 수를 설정할 수 있습니다. 이렇게 하면 한 번의 쿼리로 더 많은 수의 엔티티를 한 번에 조회할 수 있어 N+1 문제를 완화할 수 있습니다.

 외에도 JPA 구현체(: Hibernate) 제공하는 특정 기능을 이용하는 방법도 있습니다. 예를 들어 Hibernate에서는 엔티티를 집합으로 조회하는 @Fetch(FetchMode.SUBSELECT) 같은 어노테이션을 제공합니다.

더보기

N+1 문제는 데이터를 가져올 때 발생하는 문제입니다. 만약 우리가 10명의 사용자와 그들이 쓴 게시글을 보려고 한다고 가정해 봅시다. 사용자를 먼저 찾는 쿼리를 한 번 실행하고, 각 사용자가 쓴 게시글을 찾기 위해 쿼리를 10번 더 실행한다면, 이를 모두 합치면 총 11번의 쿼리가 실행되는 것입니다. 이렇게 되면 데이터를 가져오는 데 필요한 쿼리의 횟수가 너무 많아져서 성능이 저하될 수 있습니다. 이게 바로 N+1 문제입니다.

이 문제를 해결하는 방법으로는 Fetch Join Batch Size 설정이 있습니다.

  1. Fetch Join:  방법은 사용자와 그들이  게시글을  번의 쿼리로 함께 가져오는 방법입니다. 이렇게 하면 쿼리의 횟수를 줄일  있습니다.
  2. Batch Size 설정:  방법은  번에 여러 개의 게시글을 가져오도록 설정하는 방법입니다. 예를 들어, Batch Size 5 설정하면,  번의 쿼리로 5개의 게시글을  번에 가져올  있습니다. 이렇게 하면 쿼리의 횟수를 줄일  있습니다.
  3. @Fetch(FetchMode.SUBSELECT)는 Hibernate에서 제공하는 기능으로, 특정 조건에 맞는 여러 개의 데이터(엔티티)를 한번에 가져오는 방법입니다.

    이를 좀 더 쉽게 이해하기 위해, 쇼핑몰에서 여러 개의 상품을 한 번에 볼 때를 생각해 볼까요? 각각의 상품을 하나씩 조회하는 대신, 특정 카테고리(예: '신발')의 모든 상품을 한 번에 보려고 할 것입니다. 이럴 때 @Fetch(FetchMode.SUBSELECT)를 사용하면, '신발' 카테고리의 모든 상품을 한 번의 쿼리로 가져올 수 있습니다. 이렇게 하면, 데이터베이스에 쿼리를 보내는 횟수를 줄여 성능을 향상시킬 수 있습니다.

 

 

2023.05.25 - [Spring] - JPA(Java Persistence API), ORM(Object-Relational Mapping) pt.2

 

 

 

1. 인증, 인가를 구분해서 설명해 보세요.

인증 (Authentication) : 해당 유저가 실제 유저인지 인증,인증은 사용자 신원을 검증하는 프로세스이다. 즉 로그인은 인증이다
인가 (Authorization) : 해당 유저가 특정 리소스에 접근이 가능한지 허가를 확인, 인가는 인증 이후의 프로세스이다. 사용자가 어떠한 권한이 있는지가 인가이다.

-> “인증 “인가 관리해주는 것이 바로 스프링 시큐리티(Spring Security)

 

2. Spring Security의 구조와 JWT 발급 과정에 대해 설명해주실 수 있을까요?

Spring Security는 애플리케이션의 보안 요구사항을 처리하기 위한 프레임워크로, 인증(Authentication)과 권한 부여(Authorization)에 대한 지원을 제공합니다.

Spring Security의 구조는 다음과 같습니다:

  • Authentication Manager: 인증 요청을 처리하는 인터페이스로, Provider Manager가 이를 구현합니다.
  • Authentication Provider: 실제 인증을 처리합니다. 사용자가 제공한 자격 증명을 검증하고, 해당 사용자에 대한 인증 객체를 반환합니다.
  • UserDetails Service: 사용자의 정보를 로드하는 역할을 합니다. 주로 사용자 이름을 통해 사용자를 찾아냅니다.
  • Security Context: 인증된 사용자의 정보를 저장하는 데 사용되는 곳입니다.

JWT 발급 과정은 대략 다음과 같습니다:

  1. 사용자는 자신의 자격 증명(예: 사용자 이름과 비밀번호)을 제출하여 인증을 요청합니다.
  2. 이 요청은 Spring Security에 의해 처리되며, 사용자 이름과 비밀번호를 확인합니다. 이때 UserDetails Service는 사용자 이름을 통해 데이터베이스에서 사용자를 찾습니다.
  3. 자격 증명이 유효하다면, Authentication Provider는 Authentication 객체를 생성하고 이를 Security Context에 저장합니다.
  4. 이후 JWT를 생성하는 컴포넌트에게 제어가 넘어가게 됩니다. 이 컴포넌트는 Authentication 객체에서 사용자 정보를 추출하고, 이를 사용하여 JWT를 생성합니다.
  5. 생성된 JWT는 응답 헤더나 바디에 담겨 사용자에게 전달됩니다. 이후 사용자는 이 토큰을 이용해 인증된 요청을 보낼 수 있습니다.

이러한 과정을 통해 Spring Security JWT 기반의 인증 시스템을 지원합니다. JWT 발급 과정에서 중요한 점은, JWT 'secret' 안전하게 보관되어야 한다는 것입니다. 'secret' 노출되면, JWT 위조될 위험이 있기 때문입니다.

 

Spring Security란 Spring 기반의 어플리케이션의 보안(인증과 권한, 인가 등)을 담당하는 스프링 하위 프레임워크 입니다. Spring Security는 '인증'과 '권한'에 대한 부분을 Filter 흐름에 따라 처리 하고 있습니다. Filter는 Dispatcher Servlet으로 가기 전에 적용되므로 가장 먼저 URL 요청을 받지만, Interceptor는 Dispatcher와 Controller 사이에 위치한다는 점에서 적용 시기의 차이가 있습니다. JWT가 발행되면, 발급된 JWT의 구성은 header에는 토큰타입,해시 암호화 알고리즘을 담고 Paylaod는 토큰의 정보를 담 고, Signature에는 시크릿 키를 담습니다. 사용자가 요청에 JWT를 보내면 서버에서 시크릿키 만을 활용해서 JWT 토큰의 유효성을 체크하고, 유효한 토큰이라면 사용자인증을 거칩니다. 이후 토큰의 만료기간을 체크하고, 토큰의 권한을 체크 합니다.

 

spring security는 세션-쿠키방식으로 인증한다. 1.유저가 로그인을 시도 (http request) 2.AuthenticationFilter 에서부터 위와같이 user DB까지 타고 들어감 3.DB에 있는 유저라면 UserDetails 로 꺼내서 유저의 session 생성 4.spring security의 인메모리 세션저장소인 SecurityContextHolder 에 저장 5.유저에게 session ID와 함께 응답을 내려줌 6.이후 요청에서는 요청쿠키에서 JSESSIONID를 까봐서 검증 후 유효하면 Authentication를 쥐어준다.  Jwt 발급과정 1. 클라이언트 로그인 요청 POST(id, pw) 2. 서버는 (id, pw)가 맞는지 확인 후 맞다면 JWT를 Secret Key로 생성 후 전달 3. 클라이언트는 Token을 로컬 쿠키에 저장 4. 클라이언트는 서버에 요청할 때 항상 헤더에 Token을 포함시킴 5. 서버는 요청을 받을 때마다 Secret Ky로 헤더에 포함된 JWT를 검증합니다. 6. 검증 결과가 유효하면, 요청한 작업을 처리하고, 그렇지 않으면 인증 에러 메시지를 반환합니다. 7. JWT가 만료되면, 클라이언트는 다시 로그인하거나 refresh token을 이용해 새로운 JWT를 요청해야 합니다.

이러한 방식은 Stateless한 HTTP 프로토콜을 활용하여 사용자 인증 상태를 유지하는 데 효율적입니다. 서버는 사용자의 상태를 저장하거나 세션을 유지할 필요가 없으며, 클라이언트는 각 요청에 JWT를 포함시킴으로써 자신을 인증할 수 있습니다.

 

3. JWT에 대해 설명해주실 수 있을까요? 구체적으로 JWT를 어디서 처리하는지, 어떠한 방식으로 검증하는지, 재발급 방식과 주기는 어떻게 처리하는지, 다른 API 서비스 호출 시 어떻게 잡아서 인증 처리하는지 말씀해주시면 좋습니다

JWT(Json Web Token)는 클라이언트와 서버간에 정보를 JSON 형태로 안전하게 전송하기 위한 개방형 표준입니다. 이 토큰은 서명되어 있어서, 정보가 조작되지 않았음을 검증할 수 있습니다.

JWT는 일반적으로 다음의 방식으로 처리합니다:

  1. 생성: 사용자가 로그인을 시도할 때, 사용자의 크리덴셜(보통 사용자 이름과 비밀번호)은 서버에서 검증됩니다. 크리덴셜이 유효하면 서버는 사용자의 ID, 만료 날짜, 등등과 같은 정보를 담은 JWT를 생성하고 이를 클라이언트에게 반환합니다. 이 토큰은 서버의 비밀 키로 서명되며, 이 서명을 통해 나중에 이 토큰의 내용이 변경되지 않았음을 검증할 수 있습니다.
  2. 사용: 클라이언트는 JWT를 저장하고, 서버로의 모든 후속 요청에 이를 포함시킵니다. 이 토큰은 보통 HTTP 요청 헤더의 Authorization 필드에 "Bearer" 스키마를 통해 포함시킵니다.
  3. 검증: 서버는 요청을 받을 때마다 헤더에서 JWT를 추출하고, 비밀 키를 사용하여 서명을 검증합니다. 서명이 유효하면, 토큰이 조작되지 않았음이 보장되고, 토큰의 payload에서 사용자 ID와 다른 정보를 추출하여 요청을 처리할 수 있습니다.
  4. 재발급: JWT는 만료 날짜를 가질 수 있습니다. 토큰이 만료되면, 클라이언트는 새 토큰을 얻기 위해 다시 로그인하거나 refresh token을 사용해야 합니다. Refresh token은 오랫동안 유효한 토큰이며, 새 access token을 발급받는데 사용됩니다.
  5. API 호출: 클라이언트가 다른 API 서비스를 호출할 때, 클라이언트는 이 서비스에 JWT를 전달할 수 있습니다. 이를 통해 해당 서비스는 JWT를 검증하고 요청을 처리할 수 있습니다. 이 방식은 클라이언트가 다른 서비스 간에 안전하게 인증 정보를 전달할 수 있도록 해줍니다.

이처럼 JWT 이용하는 방식은 서버에서 세션 상태를 유지하지 않아도 되므로, 서버의 부하를 줄이고 확장성을 향상시키는 장점이 있습니다.

 

JWT는 Json 포맷을 이용하여 사용자에 대한 속성을 저장하는 Claim 기반의 Web Token입니다. JWT는 토큰 자체를 정보로 사용하는 Self-Contained 방식으로 정보를 안전하게 전달합니다. 주로 회원 인증이나 정보 전달에 사용됩니다. JWT는 Header, Payload, Signature 3부분으로 이루어지며, json 형태의 각 부분은 Base64Url로 인코딩 되어 표현 됩니다. JWT 처리는 로그인을 하게되면 서버에서 회원의 정보와 시그니처의 시크릿 키를 이용해 access 토큰과 refresh 토큰을 발급한 후에 클라이언트에게 넘겨주고 리프레쉬는 암호화하여 db에 저장해줍니다. 검증은 이 토큰이 서버가 발급한 토큰이 맞는지 확인합니다. 재발급 주기는 평균적으로 access 토큰은 2시간 refresh 토큰은 2주입니다. api 통신을 하게되면 클라이언트가 header에서 엑세스 토큰을 담아서 보내고 서버에서 검증한 뒤 API 로직이 수행됩니다.

 

JWT의 작동방식은 사용자 로그인>서버는 로그인된 유저 정보를 JWT로 생성하여 클라이언트에 전달>클라이언트는 전달받은 JWT를 이용하여 인증이 필요한 요청에 사용 순으로 이루어진다. JSON Web Token은 Json 포맷을 이용하여 사용자에 대한 속성을 저장하는 Claim 기반의 Web Token이다. JWT는 토큰 자체를 정보로 사용하는 Self-Contained 방식으로 정보를 안전하게 전달한다. Header, Payload, Signature의 3 부분으로 이루어지며, Json 형태인 각 부분은 Base64Url로 인코딩 되어 표현된다. 또한 각각의 부분을 이어 주기 위해 . 구분자를 사용하여 구분한다. 추가로 Base64Url는 암호화된 문자열이 아니고, 같은 문자열에 대해 항상 같은 인코딩 문자열을 반환한다.

 

4. Spring Security를 적용했을 때 어떤 점이 도움이 되셨나요?

Spring Security는 웹 애플리케이션 보안에 효과적인 솔루션을 제공합니다. 그것이 제공하는 여러 기능 중 몇 가지를 제가 언급하겠습니다:

  1. 인증(Authentication)과 인가(Authorization): Spring Security는 강력한 인증 및 인가 메커니즘이 포함되어 있습니다. 사용자 이름과 비밀번호를 사용한 전통적인 방식 외에도, OAuth, JWT 등을 활용한 방식을 쉽게 구현할 수 있습니다. 또한, 메서드 수준에서 세부적인 인가 설정이 가능하여 보다 세밀한 권한 관리가 가능합니다.
  2. CSRF(Cross-Site Request Forgery) 방어: Spring Security는 CSRF 공격에 대한 보호 기능을 내장하고 있습니다. 이는 웹 사이트 간 요청 위조 공격을 방지해줍니다.
    더보기

    CSRF(사이트 간 요청 위조, Cross-site request forgery)

    공격자가 인증된 브라우저에 저장된 쿠키의 세션 정보를 활용하여 웹 서버에 사용자가 의도하지 않은 요청을 전달하는 것

    CSRF 설정이 되어있는 경우 html 에서 CSRF 토큰 값을 넘겨주어야 요청을 수신 가능

    쿠키 기반의 취약점을 이용한 공격 이기 때문에 REST 방식의 API에서는 disable 가능

    POST 요청마다 처리해 주는 대신 CSRF protection disable

  3. 세션 관리: Spring Security는 사용자 세션을 자동으로 생성하고, 세션 고정 공격을 방지하기 위해 세션 ID를 안전하게 관리합니다.
  4. 비밀번호 보안: Spring Security는 비밀번호를 안전하게 저장하고 관리하기 위한 다양한 인코딩 기능을 제공합니다.
  5. HTTPS 강제: 특정 URL이나 전체 사이트에 대해 HTTPS를 강제하는 기능을 제공하여 통신 보안을 높입니다.

따라서, Spring Security 적용하면 위와 같은 여러 보안 관련 요소를 편리하게 처리할 있어서 개발 시간을 단축하고 보안을 효과적으로 강화할 있습니다.

 

5. Spring Security를 사용하지 않는다면 어떻게 인증/인가를 효율적으로 처리할 수 있을까요?

Spring Security를 사용하지 않는다면, 사용자 인증과 인가를 처리하기 위한 많은 요소를 직접 구현해야 합니다. 이를 위한 몇 가지 방법은 다음과 같습니다.

  1. 커스텀 인증 필터: 서블릿 필터를 이용해 사용자의 요청을 가로채서 인증 과정을 직접 구현할 수 있습니다. 이 필터에서 사용자의 로그인 정보를 확인하고 이를 이용하여 사용자 인증을 진행합니다.
  2. JWT(JSON Web Tokens): JWT를 이용하면 클라이언트와 서버 간에 정보를 안전하게 전달할 수 있습니다. 인증 후 사용자 정보를 담은 JWT를 생성하고, 이후 요청에서 이 토큰을 검증하여 인증을 확인할 수 있습니다.
  3. OAuth 2.0, OpenID Connect: 이들 표준을 이용하면 외부 인증 제공자를 통해 사용자 인증을 처리할 수 있습니다. 이를 통해 사용자는 외부 계정 (Google, Facebook 등)을 이용해 로그인할 수 있고, 서비스는 별도의 인증 과정을 관리하지 않아도 됩니다.
  4. RBAC(Role-Based Access Control): RBAC은 사용자의 역할에 따라 접근 권한을 관리하는 방법입니다. 각 사용자는 하나 이상의 역할을 가지고, 각 역할은 특정 권한의 집합을 가집니다. 이를 통해 사용자의 역할에 따라 시스템의 리소스에 접근하는 것을 제어할 수 있습니다.

이러한 방법들을 사용하면 Spring Security 사용하지 않고도 사용자 인증과 인가를 처리할 있지만, 이들을 직접 구현하고 유지 보수하는 데는 많은 노력이 필요합니다. 따라서 가능한 경우 보안에 특화된 라이브러리나 프레임워크를 사용하는 것이 권장됩니다.

 

6. RefreshToken 적용에 대한 장/단점

장점:

  1. 긴 생명주기: Refresh Token은 액세스 토큰보다 긴 생명주기를 가집니다. 이를 통해 사용자가 자주 로그인하지 않아도 됩니다.
  2. 재발급 가능: 액세스 토큰이 만료되면 Refresh Token을 사용해 새 액세스 토큰을 받을 수 있습니다. 이를 통해 사용자는 서비스를 끊김없이 이용할 수 있습니다.
  3. 보안 강화: 만약 액세스 토큰이 유출되더라도 해당 토큰의 만료 시간이 짧기 때문에 보안 위협을 최소화할 수 있습니다. 이후에는 새로 발급받은 액세스 토큰으로 서비스를 이용하게 됩니다.

단점:

  1. 저장 관리: Refresh Token은 안전하게 저장하고 관리해야 합니다. 만약 유출되면 공격자가 새로운 액세스 토큰을 받아 사용자를 가장할 수 있습니다.
  2. 복잡성 증가: 액세스 토큰과 Refresh Token을 모두 관리해야 하므로 시스템의 복잡성이 증가합니다. 특히 토큰의 만료 시간과 재발급 등을 처리하는 로직이 필요합니다.
  3. 토큰 만료 관리: Refresh Token이 만료되면 사용자가 다시 로그인해야 합니다. 따라서 이를 사용자에게 알리고 처리하는 로직이 필요합니다.

, Refresh Token 사용자 경험과 보안 강화를 위한 좋은 방법이지만, 토큰의 저장과 관리, 만료와 재발급 등을 올바르게 처리해야 합니다. 이를 통해 사용자 인증 시스템의 안정성과 보안을 유지할 있습니다.

 

7. JWT를 사용하여 인증/인가를 구현 했을 때의 장점은 무엇일까요?반대로 JWT를 사용한 인증/인가의 한계점은 무엇일까요?

JWT(Json Web Token)를 사용하여 인증/인가를 구현할 때의 장점과 단점은 다음과 같습니다.

장점:

  1. 상태 유지(stateless): JWT는 자체 정보를 토큰 안에 가지고 있어 별도의 세션 저장소를 필요로 하지 않습니다. 이는 서버의 부하를 줄이고 확장성을 향상시킵니다.
  2. 분산 시스템 지원: JWT는 서버와 클라이언트 간에 인증 정보를 주고받는 방식이므로, 다수의 서버나 도메인 간에도 효과적으로 인증 정보를 전달할 수 있습니다.
  3. 사용성: JWT는 URL, POST 파라미터, HTTP 헤더 등에서 사용할 수 있어서 활용성이 높습니다.

단점:

  1. 토큰 크기: JWT는 페이로드에 많은 정보를 저장하므로, 토큰의 크기가 커질 수 있습니다. 이로 인해 네트워크 부하가 증가하거나 저장 공간이 부족할 수 있습니다.
  2. 보안 문제: JWT는 헤더와 페이로드가 Base64로 인코딩되므로 중간에 토큰이 노출되면 정보를 볼 수 있습니다. 따라서 민감한 정보는 토큰에 포함시키지 않아야 합니다.
  3. 토큰 관리: JWT는 상태를 유지하지 않기 때문에 한번 발급된 토큰을 서버 측에서 만료시키거나 취소하는 것이 어렵습니다. 이는 잠재적인 보안 문제를 야기할 수 있습니다.
  4. Secret Key 유출: Secret Key가 유출되면 JWT를 해독하거나 위조할 수 있습니다. 따라서 Secret Key를 안전하게 보관하고 정기적으로 변경하는 등의 관리가 필요합니다.

따라서 JWT 사용할 때는 이러한 장단점을 고려하여 설계하고, 특히 보안 문제에 대해서는 심도있게 고려해야 합니다.

 

11. JWT의 구조와 각 부분(header, payload, signature)의 역할에 대해 설명해주세요.

JWT(Json Web Token)은 세 부분으로 이루어져 있습니다: Header, Payload, 그리고 Signature.

Header: Header는 토큰의 타입과 사용된 암호화 알고리즘을 명시합니다. 일반적으로 JWT는 HMAC 알고리즘 혹은 RSA를 사용합니다. 예를 들어, Header는 다음과 같이 구성될 수 있습니다.

{ "alg": "HS256", "typ": "JWT" }

여기서 "alg"는 암호화 알고리즘을, "typ"는 토큰의 타입을 나타냅니다.

Payload: Payload 부분은 'Claim'이라고도 불리며, 토큰 자체에 포함될 실제 정보를 담고 있습니다. 이 정보는 이름(name), 만료 시간(exp), 주제(subject) 등 다양한 형태가 될 수 있습니다.

{ "sub": "1234567890", "name": "John Doe", "admin": true }

여기서 "sub"는 주제를, "name"은 이름을, "admin"은 관리자 권한 여부를 나타냅니다.

Signature: Signature는 Header와 Payload를 이용하여 생성되며, 이들이 변조되지 않았음을 확인하는데 사용됩니다. Header와 Payload를 각각 Base64로 인코딩한 뒤, Secret Key와 함께 지정된 암호화 알고리즘을 통해 해시를 생성합니다.

예를 들면, HMAC SHA256 알고리즘을 사용한다면, 다음과 같이 Signature를 생성할 수 있습니다.

HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)

이렇게 생성된 JWT는 각 부분을 점(.)으로 연결하여 사용합니다: encodedHeader.encodedPayload.encodedSignature. 이 형태의 문자열이 HTTP 요청의 Authorization 헤더에 "Bearer" 토큰으로 담겨 서버에 전송되며, 서버에서는 이를 검증하여 요청의 유효성을 판단합니다.

 

12. JWT는 어떻게 암호화되나요? 그리고 암호화된 JWT는 어떻게 복호화되나요?

JWT (JSON Web Token)는 두 가지 방법으로 보호할 수 있습니다: 서명 및 암호화.

  1. 서명(Signing): JWT의 가장 일반적인 보호 방식은 서명입니다. 서명은 JWT의 무결성을 보장합니다, 즉 토큰이 전송되는 동안 조작되지 않았음을 입증합니다. 서명은 토큰의 Header와 Payload를 각각 Base64Url 인코딩하고, 그 두 값을 연결한 후 ("header.payload") 시크릿 키와 함께 해시 알고리즘을 적용하여 생성합니다. 이렇게 생성된 서명은 토큰의 세 번째 부분을 형성합니다. 받는 측은 동일한 프로세스를 사용하여 서명을 검증할 수 있습니다: 동일한 헤더, 페이로드, 시크릿을 사용하여 새로운 서명을 생성하고, 이것이 받은 토큰의 서명과 일치하는지 확인합니다.
  2. 암호화(Encryption): JWT는 또한 기밀성을 보장하기 위해 암호화될 수 있습니다. 이는 토큰의 내용을 숨기는 데 사용되며, 이렇게 암호화된 토큰은 JWT를 받아들이는 측만 복호화하고 읽을 수 있습니다. JWT를 암호화하는 것은 상당히 복잡한 과정이며, 여러 가지 방식이 있습니다(JWE - JSON Web Encryption을 참조하세요).

이미 서명된 JWT는 다시 암호화될 수도 있습니다. 이러한 방식은 "nested JWT"라고 부르며, 먼저 JWT를 서명하고 그 결과를 암호화하여 결과적으로 무결성과 기밀성을 모두 보장하는 토큰을 생성합니다.

기억해야 할 중요한 점은, JWT 서명은 토큰의 무결성을 보장하지만 내용을 숨기지는 않는다는 것입니다. JWT 페이로드는 Base64로 인코딩되므로, 중요한 개인 정보가 포함된 경우 반드시 암호화를 적용해야 합니다.

 

13. JWT의 Secret Key가 유출되면 어떤 문제가 발생하나요? 이 문제를 어떻게 해결하나요?

JWT의 Secret Key는 토큰의 무결성을 보장하는 역할을 하기 때문에, 이 키가 유출되면 매우 심각한 보안 문제가 발생할 수 있습니다.

  1. 문제: Secret Key가 유출되면 공격자는 해당 Key를 사용하여 JWT를 만들 수 있습니다. 이는 공격자가 원하는 어떠한 데이터도 JWT에 넣을 수 있다는 것을 의미하며, 이 토큰은 서버에서 유효한 토큰으로 인식됩니다. 따라서 공격자는 다른 사용자의 권한을 얻거나 서버에서 금지된 작업을 수행할 수 있게 됩니다.
  2. 해결방안: Secret Key의 유출을 방지하거나 해결하기 위한 주요 방법은 다음과 같습니다:

만약 Secret Key가 이미 유출되었다면, 즉시 Key를 변경하고 모든 시스템에서 사용되는 해당 Key를 업데이트해야 합니다. 또한 공격자가 이 Key를 사용하여 수행한 모든 작업을 검토하고, 필요한 경우 이를 수정하거나 취소해야 합니다.

 

14. JWT의 보안 문제와 그에 대한 해결책에 대해 알고 계신가요?

JWT(Json Web Token)는 자체적인 정보를 가지고 있는 독립적인 토큰이지만, 이로 인해 보안 문제가 발생할 수 있습니다.

  1. Token을 탈취당한 경우: 만약 토큰이 탈취당하면 공격자는 사용자의 권한으로 시스템에 접근할 수 있습니다. 이를 방지하기 위해서는 HTTPS와 같은 안전한 프로토콜을 사용하여 토큰을 전송해야 합니다. 또한 민감한 작업을 수행할 때마다 사용자의 패스워드를 다시 확인하는 등의 추가적인 보안 조치를 취할 수 있습니다.
  2. Token이 만료되지 않는 경우: 기본적으로 JWT는 만료 기간이 없으며, 이로 인해 공격자가 토큰을 무제한으로 사용할 수 있습니다. 이를 방지하기 위해서는 JWT에 짧은 만료 기간을 설정해야 합니다. 토큰이 만료되면 사용자는 다시 로그인하여 새 토큰을 받아야 합니다.
  3. Token 내의 정보가 노출되는 경우: JWT의 정보는 기본적으로 암호화되지 않으며, base64 인코딩만으로 변환되므로 누구나 디코딩하여 정보를 볼 수 있습니다. 따라서 민감한 정보는 토큰에 포함시키지 않아야 합니다. 필요하다면 JWT를 암호화하는 추가적인 단계를 거칠 수 있습니다.
  4. Secret Key 유출: JWT는 서명 과정에서 Secret Key를 사용합니다. 이 Key가 유출되면 공격자는 원하는 어떤 데이터든지 토큰에 넣고 서명할 수 있습니다. 이러한 문제를 방지하기 위해 Secret Key는 안전한 곳에 보관되어야 하며, 주기적으로 변경되어야 합니다.

15. JWT의 'read only' 특성이란 무엇인가요?

JWT(Json Web Token)의 'read only' 특성은 JWT가 생성된 후 그 내용을 변경할 수 없다는 것을 의미합니다.

JWT는 세 부분으로 구성되어 있습니다: Header, Payload, Signature. 이 세 부분은 각각 base64로 인코딩된 후 '.'로 연결되어 하나의 문자열로 만들어집니다. Signature 부분은 Header와 Payload를 서버의 Secret Key로 해시한 결과입니다.

이렇게 생성된 JWT는 원래의 데이터를 그대로 읽을 수 있지만 (base64 디코딩을 통해), 이를 수정하려고 하면 Signature가 더 이상 유효하지 않게 됩니다. 왜냐하면 수정된 Header나 Payload는 원래의 Secret Key로 생성된 Signature와 다른 Signature를 생성하기 때문입니다.

따라서, JWT는 한번 생성되면 그 내용을 변경할 수 없는 'read only' 특성을 가지게 됩니다. 이는 JWT의 보안성을 높여주는 중요한 특성 중 하나입니다.

 

16. JWT를 사용하는 주요 이유와 장점은 무엇인가요?

  1. Stateless & Scalability: JWT는 상태를 유지하지 않는(stateless) 방식이기 때문에 서버는 클라이언트의 상태를 기억할 필요가 없습니다. 이로 인해 서버의 부하를 줄이고, 쉽게 확장성을 가질 수 있습니다.
  2. Decentralization: JWT는 클라이언트가 토큰을 가지고 있으며, 서버는 토큰의 유효성만을 확인하면 됩니다. 따라서 중앙 서버에서 세션 상태를 관리할 필요가 없어집니다.
  3. Cross-Domain / CORS: JWT는 CORS(Cross-Origin Resource Sharing) 문제를 해결하는 데 도움이 됩니다. 쿠키는 동일 출처 정책(Same-Origin Policy) 때문에 다른 도메인 간에 공유하기 어렵지만, JWT는 HTTP 헤더에 포함되어 전송되므로 도메인 간에 쉽게 전송할 수 있습니다.
  4. Self-contained: JWT는 필요한 모든 정보를 자체적으로 지니고 있습니다. 토큰 자체가 인증 정보를 가지고 있어서, 별도의 인증 저장소가 필요 없습니다.
  5. Performance: 세션 기반의 인증 시스템에서는 매번 사용자의 요청이 있을 때마다 세션 저장소를 조회해야 하지만, JWT에서는 그럴 필요가 없습니다. 이로 인해 성능 향상을 기대할 수 있습니다.

17. JWT의 보안상 문제나 단점은 무엇이며 이를 어떻게 극복할 수 있나요?

  1. 토큰의 노출: JWT는 클라이언트 측에서 저장하고 관리하기 때문에 토큰이 노출되거나 탈취당할 위험이 있습니다. 토큰이 탈취되면, 탈취당한 토큰으로 서버에 요청을 보낼 수 있기 때문에 심각한 보안 문제를 일으킬 수 있습니다.
    이를 극복하기 위해 HTTPS와 같은 안전한 채널을 통해 토큰을 전송하고, 보안이 강화된 클라이언트 측 저장소에 토큰을 저장해야 합니다. 또한, 토큰의 만료 시간을 짧게 설정하여 탈취당한 토큰이 오랫동안 사용되지 않도록 하는 것도 중요합니다.
  2. 토큰의 크기: JWT는 페이로드에 클레임 데이터를 포함하므로, 세션 ID와 비교해 크기가 큽니다. 큰 토큰은 클라이언트와 서버 사이의 요청에 부하를 줄 수 있습니다.
    이는 필요한 최소한의 데이터만 토큰에 포함하는 것으로 어느 정도 해결할 수 있습니다.
  3. 토큰의 만료 처리: JWT는 상태를 저장하지 않는 특성 상, 한 번 발급된 토큰을 서버 측에서 강제로 만료시키는 것이 어렵습니다.
    이 문제는 토큰의 만료 시간을 짧게 설정하고, 필요한 경우 새 토큰을 재발급하는 방식으로 해결할 수 있습니다. 또는 토큰의 블랙리스트를 관리하는 방법도 있습니다만, 이는 서버에서 상태를 관리해야 한다는 점에서 JWT의 stateless한 특성을 손상시킵니다.
  4. Secret Key의 보안: Secret Key는 서명을 생성하고 검증하는데 사용되므로, 이 Key가 유출되면 토큰이 위변조 될 수 있습니다.
    Secret Key의 안전한 관리가 필요하며, 주기적인 Key의 갱신도 중요합니다.
  5. 개인 정보의 암호화: JWT의 페이로드는 Base64로 인코딩되어 전송되므로, 중요한 개인 정보는 반드시 추가적으로 암호화해야 합니다.

18. 어떤 상황에서 JWT를 사용하면 안될까요?

JWT(Json Web Token)는 인증 정보를 안전하게 보호하기 위해 암호화를 사용합니다. JWT 토큰은 세 부분으로 이루어져 있습니다: Header, Payload, Signature.

  1. Header: 암호화에 사용될 알고리즘을 명시합니다.
  2. Payload: 인증 및 추가 데이터를 포함합니다.
  3. Signature: Header와 Payload, 그리고 비밀 키를 이용해 생성됩니다. 이 Signature는 JWT가 변경되지 않았음을 확인하는 데 사용됩니다.

JWT는 Header와 Payload를 각각 Base64Url로 인코딩하여, 이 둘을 합친 뒤 비밀 키를 이용해 Signature를 생성합니다. 생성된 JWT는 Header, Payload, Signature가 점(.)으로 연결된 형태로 제공됩니다.

즉, JWT 토큰은 이 세 부분을 암호화 및 인코딩하여 토큰의 무결성을 보장하고, 누군가 토큰을 수정하려 하면 Signature가 변경되어 토큰이 무효화되는 방식으로 안전성을 보장합니다. 그러나, Header와 Payload는 Base64로 인코딩되는 것이지, 암호화되는 것은 아닙니다. 따라서, 중요한 개인 정보는 JWT에 포함되지 않아야 합니다.

이처럼 JWT는 자체적으로 인증 정보를 안전하게 보호하며, 이는 서버와 클라이언트 사이에서 안전하게 인증 정보를 주고 받을 수 있게 합니다.

 

19. JWT는 어떻게 인증 정보를 안전하게 보호하나요?

JWT(Json Web Token)는 다음과 같은 방법으로 인증 정보를 보호합니다.

  1. 암호화된 서명: JWT는 Header, Payload, 그리고 Secret Key를 사용해 서명을 생성합니다. 이 서명은 JWT가 중간에 조작되지 않았음을 확인하는 데 사용되며, 만약 JWT가 변경되면, 서명이 더 이상 유효하지 않게 되므로 JWT가 무효화됩니다. 따라서 이 서명을 통해 인증 정보의 무결성을 보장합니다.
  2. 비공개 클레임: JWT의 Payload 부분에는 클레임이라는 여러 정보들이 들어갑니다. 이 클레임 중 비공개 클레임은 사용자의 인증에 관한 데이터를 포함하고 있어 인증 정보를 보호하는 데 사용됩니다.
  3. HTTPS의 사용: JWT 자체가 암호화된 형태는 아니기 때문에, JWT를 전송할 때는 HTTPS와 같은 보안 프로토콜을 통해 전송되어야 합니다. 이를 통해 중간에 JWT가 노출되는 것을 방지할 수 있습니다.
  4. Access Token과 Refresh Token의 사용: JWT를 이용한 인증 시스템에서는 일반적으로 Access Token과 Refresh Token 두 가지 토큰을 사용합니다. Access Token은 짧은 만료 시간을 가지고 있어, 토큰이 노출되더라도 노출 시간을 최소화할 수 있습니다. 만료된 Access Token은 Refresh Token을 사용해 갱신할 수 있습니다.

그러나, JWT는 데이터를 암호화하는 것이 아니므로 중요한 개인 정보는 포함되지 않아야 합니다. Header와 Payload는 Base64로 인코딩되는 것이지만, 이는 암호화된 형태가 아니므로, 이를 디코딩하면 원래의 데이터를 볼 수 있습니다. 따라서 민감한 정보를 안전하게 보호하려면 추가적인 암호화 measures가 필요합니다.

 

20. JWT에 저장할 수 있는 정보의 종류와 한계는 무엇인가요?

JWT에는 크게 세 가지 종류의 클레임(claims)이 저장될 수 있습니다:

  1. Registered Claims: 토큰의 발행자(iss), 주제(sub), 수신자(aud), 만료 시간(exp), 발행 시간(iat) 등과 같이 프리데파인된 set입니다. 이들은 선택적이지만 권장됩니다.
  2. Public Claims: 공개적으로 사용되는 클레임들로, 충돌을 방지하기 위해 IANA JSON Web Token Registry 또는 URI 형식의 이름을 사용해야 합니다.
  3. Private Claims: 두 파티 사이에서 합의한 클레임으로, 사용자 정의 클레임이라고도 합니다.

JWT에 저장할 수 있는 정보의 한계는 다음과 같습니다:

  • 보안: JWT는 토큰이 변경되지 않았음을 확인하는 서명을 제공하지만, 토큰 자체는 암호화되지 않습니다. 따라서 JWT에는 민감한 정보(예: 비밀번호)를 저장하지 않아야 합니다. 그렇지 않으면, 누군가가 토큰을 가로채어 그 내용을 읽을 수 있습니다.
  • 크기: JWT는 HTTP 헤더에 종종 포함되므로, 너무 큰 토큰은 네트워크 성능에 부정적인 영향을 줄 수 있습니다. 또한, 모든 웹 서버와 브라우저는 헤더 크기에 제한을 두고 있으므로, 이 제한을 초과하는 JWT는 전송되지 않을 수 있습니다. 이러한 이유로, JWT에 저장되는 정보는 최소한으로 유지해야 합니다.
  • 수명: JWT에는 만료 시간(exp)이 포함될 수 있지만, 한번 발행된 JWT는 중앙 서버가 만료를 강제할 방법이 없습니다. 따라서 JWT는 가능한 짧은 수명을 가져야 하며, 민감한 작업에는 사용되어서는 안됩니다.

21. 사용자 인증을 위해 세션 대신 JWT를 사용하는 이유는 무엇인가요?

JWT를 사용하는 주요 이유는 서버의 세션 상태를 유지하지 않아도 사용자 인증을 구현할 수 있기 때문입니다. 이는 "Stateless"한 아키텍처를 구현하는데 도움이 됩니다.

세션 기반 인증에서는 서버 메모리에 세션 상태를 유지해야 합니다. 사용자가 로그인하면 세션 ID가 생성되고, 이 ID는 쿠키를 통해 클라이언트에게 전달됩니다. 클라이언트는 이후 요청마다 이 세션 ID를 포함하여 서버에 전송하며, 서버는 이 세션 ID를 통해 해당 사용자를 식별합니다. 이 경우, 서버는 모든 활성 사용자에 대한 세션을 메모리에 유지해야 하므로, 세션 수가 많아질수록 서버의 부하가 증가합니다.

반면에, JWT 인증 방식에서는 사용자 상태를 서버 메모리에 저장하지 않습니다. 대신, 서버는 로그인 시 사용자 정보를 JWT 형태로 암호화하여 클라이언트에게 전달합니다. 클라이언트는 이후 요청마다 이 JWT를 포함하여 서버에 전송하고, 서버는 이 JWT를 해독하여 사용자를 인증합니다. 이렇게 하면 서버는 사용자 상태를 유지할 필요가 없으므로, 부하를 줄이고 확장성을 향상시킬 수 있습니다.

또한, JWT는 자체 포함적인(JWT 자체에 사용자 정보가 포함되어 있는) 특성 덕분에, 서버와 클라이언트 간에 상태 정보를 쉽게 주고받을 수 있으므로, 분산 시스템과 마이크로서비스 아키텍처에서 유용하게 사용될 수 있습니다.

 

22.  JWT와 OAuth의 차이점은 무엇인가요?

JWT(Json Web Token)와 OAuth는 모두 인증에 관련된 개념이지만, 다른 목적과 방식으로 사용됩니다.

  1. JWT(Json Web Token): JWT는 정보의 신뢰성을 확인할 수 있는 암호화된 문자열입니다. JWT는 토큰에 정보를 담아서 보내는 방식으로, 이 정보는 사용자 인증에 필요한 정보나 데이터 전송 등에 사용됩니다. JWT는 stateless한 인증 방식을 지원하며, 서버가 사용자의 상태를 기억하지 않아도 사용자를 인증할 수 있는 방식입니다.
  2. OAuth: 반면에 OAuth는 인증을 위한 open standard 프로토콜로, 사용자가 인터넷 애플리케이션들이 자신의 정보에 접근할 수 있는 권한을 부여하는데 사용됩니다. 즉, 사용자가 직접 ID와 비밀번호를 입력하지 않아도, Google이나 Facebook 등의 계정을 통해 다른 웹 서비스에 로그인하는 것을 가능하게 합니다.

이 두 기술은 서로 다른 목적을 가지고 있지만, 함께 사용될 수도 있습니다. 예를 들어, OAuth를 사용하여 사용자의 권한을 부여받은 후, 해당 권한을 JWT로 암호화하여 클라이언트와 서버 사이에 주고받을 수 있습니다. 이렇게 하면 클라이언트는 JWT를 사용하여 인증을 유지하면서, OAuth를 사용하여 새로운 인증을 받을 수 있습니다.

 

23. JWT는 어떤 식으로 로그아웃을 처리하나요? 그리고 이를 통해 어떤 문제가 발생할 수 있나요?

JWT (Json Web Token)는 자체적으로 로그아웃 기능을 제공하지 않습니다. JWT는 토큰이 만료되거나 클라이언트 측에서 토큰을 삭제할 때까지 유효하기 때문입니다.

로그아웃을 처리하기 위한 일반적인 방법은 클라이언트 측에서 JWT를 삭제하는 것입니다. 클라이언트에서 JWT를 저장하고 있는 공간(예: 쿠키, 로컬 스토리지 등)에서 토큰을 제거하면, 클라이언트는 더 이상 해당 토큰을 사용할 수 없으므로 인증도 해제되는 것과 같습니다.

하지만 이 방식은 중요한 보안 문제를 가지고 있습니다. JWT가 삭제되지 않고 남아있는 경우, 해당 토큰이 유효한 동안은 서버에서 계속 인증을 받게 됩니다. 이로 인해 토큰이 탈취당하면 공격자가 사용자를 가장할 수 있는 문제가 발생할 수 있습니다.

이 문제를 해결하기 위한 한 가지 방법은 서버 측에서 JWT의 블랙리스트를 유지하는 것입니다. 로그아웃 시, 클라이언트에서 JWT를 삭제하고, 해당 토큰을 서버의 블랙리스트에 추가합니다. 이후 이 토큰이 서버에 전송되면, 서버는 블랙리스트에 있는 토큰인지 확인하고, 있으면 인증을 거부합니다. 그러나 이 방법은 추가적인 서버 자원을 사용하게 되며, 일종의 상태를 유지하는 것이기 때문에 JWT가 목표로 하는 stateless한 특성을 상실하게 됩니다.

따라서 JWT 유효 시간을 짧게 설정하고 적절한 리프레시 토큰(refresh token) 전략을 사용하는 것이 중요합니다. 이렇게 하면 잠재적인 보안 위협을 줄일 있습니다.

 

24. 클라이언트가 JWT를 수정하려고 시도한다면 어떻게 대처하나요?

JWT는 페이로드와 헤더를 서명하는 방식으로 무결성을 보장합니다. 서명은 헤더, 페이로드, 그리고 비밀 키(secret key)를 사용하여 생성됩니다.

클라이언트가 JWT의 페이로드 또는 헤더를 변경하면, 서명이 더 이상 유효하지 않게 됩니다. 왜냐하면 변경된 페이로드 또는 헤더와 원래의 비밀 키를 사용하여 생성된 새로운 서명은 원래의 서명과 일치하지 않기 때문입니다.

따라서 클라이언트가 JWT를 변경하려고 시도하면, 서버는 변경된 토큰의 서명이 유효하지 않다는 것을 알 수 있습니다. 이런 경우, 서버는 변경된 토큰을 거부하고, 그에 따른 적절한 에러 메시지를 반환할 수 있습니다. 이러한 방식으로 JWT는 무결성을 보장하며, 토큰의 정보가 중간에 변경되는 것을 방지합니다.

단, 이러한 보안 메커니즘이 정상적으로 작동하려면 비밀 키가 안전하게 보관되어야 합니다. 비밀 키가 유출되면 공격자가 유효한 서명을 생성할 수 있으므로, 비밀 키의 보안은 매우 중요합니다.

 

25. Refresh Token과 JWT는 어떤 관계가 있나요?

JWT (Json Web Token)과 Refresh Token은 모두 사용자 인증에 사용되지만, 각각 다른 목적을 가지고 있습니다.

JWT는 사용자의 인증 정보를 담고 있어, 이를 통해 사용자가 시스템의 어떤 자원에 접근할 수 있는지를 결정합니다. 하지만 보안상의 이유로 JWT의 만료 시간은 상대적으로 짧게 설정되는 경우가 많습니다. 따라서 사용자가 오랜 시간 동안 시스템을 이용하기 위해서는 JWT를 주기적으로 갱신해야 합니다.

이 때, 사용자가 매번 로그인을 통해 새로운 JWT를 받는 것은 불편할 수 있습니다. 이를 해결하기 위해 Refresh Token이 사용됩니다. Refresh Token은 장시간 동안 유효하며, 이를 사용하여 새로운 JWT를 발급받을 수 있습니다. 사용자는 로그인을 통해 JWT와 Refresh Token을 모두 받고, JWT가 만료될 때마다 Refresh Token을 사용해 새로운 JWT를 받습니다.

이렇게 하면 사용자는 로그인 없이도 안전하게 시스템을 오랜 시간 동안 이용할 있습니다. 만약 Refresh Token 유출되면 공격자가 JWT 계속 갱신할 있으므로, Refresh Token 보안도 중요합니다. 일반적으로 Refresh Token 보안이 강화된 서버에서 관리되며, 클라이언트에서는 접근할 없도록 합니다.

 

26. 토큰 기반 인증 시스템에서 JWT는 어떤 역할을 하나요?

토큰 기반 인증 시스템에서 JWT(Json Web Token)는 클라이언트와 서버 간의 인증을 담당하는 중요한 역할을 합니다.

  1. 인증 정보의 전달: 클라이언트가 처음으로 인증을 완료하면, 서버는 JWT를 생성하여 클라이언트에게 제공합니다. 이 JWT는 클라이언트의 인증 정보를 담고 있으며, 이를 클라이언트가 보관하게 됩니다.
  2. 요청 시 인증 정보의 확인: 클라이언트는 서버에 요청을 보낼 때마다 이 JWT를 함께 보냅니다. 서버는 이 JWT를 받아 복호화하고, 그 안에 있는 인증 정보를 확인합니다. 만약 JWT가 유효하다면, 요청을 처리하고 그 결과를 클라이언트에게 보내주게 됩니다.
  3. 인증 정보의 갱신: JWT는 일정 시간이 지나면 만료되므로, 클라이언트는 새로운 JWT가 필요할 수 있습니다. 이때는 보통 Refresh Token을 이용해서 새로운 JWT를 받게 됩니다.

따라서 JWT 토큰 기반 인증 시스템에서 인증 정보의 전달, 요청 인증 정보의 확인, 인증 정보의 갱신 등의 중요한 역할을 수행하게 됩니다.

 

27. JWT의 만료 시간(exp)은 어떻게 설정되나요?

JWT의 만료 시간(exp)은 생성 시점에 설정되며, 이는 JWT의 페이로드(Payload) 부분에 "exp"라는 필드로 저장됩니다. 이 "exp" 필드는 JWT가 언제 만료될지를 나타내는 Unix 시간 스탬프입니다.

JWT를 생성할 때, "exp" 필드에 특정 시간 값을 넣어주면 그 시간이 지나면 JWT는 자동으로 만료됩니다. JWT를 검증할 때, 현재 시간과 "exp" 필드의 시간을 비교하여 JWT가 아직 유효한지, 아니면 만료되었는지를 판단합니다.

이렇게 만료 시간을 설정함으로써, 서버는 장기간 사용되지 않는 토큰이 시스템을 오염시키는 것을 방지하고, 또한 만약 토큰이 도난당했다면 그 피해를 최소화할 수 있습니다.

"exp" 필드의 설정은 보통 JWT 생성하는 라이브러리나 프레임워크에서 제공하는 기능을 통해 이루어집니다. 개발자는 값을 직접 지정하거나, 예를 들어 "5 ", "24시간 " 같이 상대적인 시간을 설정할 수도 있습니다.

 

28. JWT와 쿠키/세션을 함께 사용하는 것이 가능한가요? 그렇다면 어떤 장점이 있나요?

JWT와 쿠키/세션을 함께 사용하는 것이 가능합니다. 사실, 종종 JWT는 쿠키를 통해 클라이언트와 서버 사이에 전달되곤 합니다. 이런 방식에는 몇 가지 장점이 있습니다:

  1. 보안성 강화: HTTPS를 사용하는 경우, Secure 쿠키 옵션을 활성화함으로써 JWT를 안전하게 전송할 수 있습니다. 또한, HttpOnly 쿠키 옵션을 사용하면, 쿠키가 JavaScript에서 접근할 수 없게 설정됩니다. 이를 통해 XSS(Cross-Site Scripting) 공격을 방지할 수 있습니다.
    HTTP 쿠키는 Secure 플래그와 HttpOnly 플래그를 사용하여 추가 보안을 제공합니다. Secure 플래그가 설정되면 쿠키는 HTTPS 연결을 통해서만 전송됩니다. 이로 인해 중간자 공격을 통한 토큰 유출을 방지할 있습니다. 또한, HttpOnly 플래그가 설정되면 JavaScript 통해 쿠키에 접근하는 것을 막습니다. 이는 크로스 사이트 스크립팅(XSS) 공격으로부터 쿠키를 보호합니다.
    상태 유지: 쿠키는 웹 브라우저가 자동으로 관리하므로, 클라이언트 측에서 별도로 상태를 유지할 필요가 없습니다. 반면, JWT가 Local Storage나 Session Storage에 저장되는 경우에는 클라이언트 애플리케이션이 명시적으로 이를 관리해야 합니다.
    JWT 사용하면 클라이언트의 상태를 서버에 저장하지 않고 클라이언트 측에 저장할 있습니다. 이는 서버가 클라이언트의 요청이 각각 독립적으로 처리될 있게 하는데 도움이 됩니다. 쿠키와 세션을 사용하면 클라이언트의 상태 정보를 서버에서 관리할 있으므로, JWT 쿠키에 저장하면 가지 접근법의 이점을 동시에 얻을 있습니다.
  2. 크로스 도메인 문제 해결: 일부 상황에서는 쿠키가 다른 도메인간에 공유되지 않는 문제가 있을 수 있습니다. 이 경우, JWT를 사용하면 도메인 간 인증을 쉽게 처리할 수 있습니다.
    JWT 도메인 간에 전송될 있습니다. 따라서, 클라이언트와 서버가 다른 도메인에 있거나, 서비스가 여러 도메인을 걸쳐서 제공되는 경우에도 인증 정보를 쉽게 전송할 있습니다. 이는 쿠키가 같은 도메인에서만 공유될 있다는 제한을 해결할 있습니다.

그러나 JWT 쿠키에 저장하는 것에는 주의해야 점도 있습니다. 만약 JWT 쿠키에 저장되면 CSRF(Cross-Site Request Forgery) 공격에 취약해질 있습니다. CSRF(Cross-Site Request Forgery)는 공격자가 사용자를 속여서 그들의 의도와는 무관하게 악성 요청을 보내는 공격입니다. 쿠키는 브라우저에서 자동으로 포함되므로, JWT가 쿠키에 있다면 CSRF 공격의 대상이 될 수 있습니다. 이를 방지하기 위해서는 CSRF 토큰과 같은 추가적인 보안 메커니즘을 사용해야 합니다.

결론적으로, JWT 쿠키/세션을 함께 사용하는 것은 애플리케이션의 보안성, 확장성, 유연성 등에 대한 균형을 맞추는 도움이 있습니다.

 

29. 각 종 세션 관리 방법(쿠키, 세션, JWT 등)을 효과적으로 사용하기 위한 가장 중요한 기준이나 전략은 무엇인가요?

1. 보안성:

어떤 세션 관리 방법을 선택하든지 간에, 보안은 항상 우선적으로 고려해야 합니다. 예를 들어, 세션 ID나 JWT는 반드시 안전하게 보관되어야 하며, 쿠키의 경우에는 HttpOnly와 Secure 플래그를 사용하여 클라이언트 측 스크립트나 네트워크 공격자로부터 보호해야 합니다.

2. 확장성:

시스템의 규모나 사용자 수가 증가함에 따라 세션 관리 방법도 이를 지원할 수 있도록 확장성이 고려되어야 합니다. 예를 들어, JWT는 상태를 클라이언트 측에 저장하므로 서버의 부하를 줄이고 확장성을 증가시키는데 유용합니다.

3. 사용 편의성과 유연성:

세션 관리 방법은 사용자의 경험에 영향을 미칩니다. 예를 들어, 사용자가 로그아웃하지 않고 브라우저를 닫을 경우 세션을 유지할 것인지, 아니면 다시 로그인을 요구할 것인지 결정해야 합니다. 이는 사용자의 편의성과 보안 간의 균형을 맞추는 문제입니다.

4. 성능:

세션 관리 방법은 애플리케이션의 성능에도 영향을 미칩니다. 서버 측에 상태 정보를 저장하는 세션은 서버의 메모리를 사용하므로, 많은 양의 동시 사용자를 처리하는 데 문제가 될 수 있습니다. 반면 JWT는 클라이언트 측에 상태를 저장하지만, 이는 네트워크 트래픽을 증가시킬 수 있습니다.

5. 유지보수성:

세션 관리 방법은 유지보수성을 고려하여 선택되어야 합니다. 예를 들어, JWT 경우 비밀 키를 관리하고 토큰을 갱신하는 등의 작업이 필요합니다.

 

2023.05.29 - [Web] - Cookie, Session, JWT(Json Web Token) pt.1

 

HTTP 는 상태를 저장하지 않습니다. ('Stateless' 하다)

더보기

 HTTP 기본적으로 상태 정보를 저장하지 않는 상태가 없는(Stateless) 프로토콜입니다. 이는 요청이 서로 독립적이라는 것을 의미하며, 서버가 클라이언트의 상태를 추적하거나 저장하지 않는다는 것을 의미합니다. 이러한 특징은 단순함, 빠른 속도, 나은 확장성을 제공하지만, 사용자 인증 같은 상태 정보가 필요한 작업을 처리하기 어렵게 만듭니다.

-> 쿠키와 세션 모두 HTTP 에 상태 정보를 유지(Stateful)하기 위해 사용됩니다. 즉, 쿠키와 세션을 통해, HTTP 이용한 어플리케이션은 상태 정보를 유지하고, 사용자 인증 인가, 사용자 설정 유지 등의 기능을 구현할 있게 됩니다.

 

  • 쿠키(Cookie): 쿠키는 서버가 사용자의 브라우저에 저장하는 작은 텍스트 파일입니다. 쿠키는 이름, , 만료일, 경로, 도메인 등의 정보를 가질 있습니다. 쿠키는 사용자가 사이트를 다시 방문할 때마다 해당 정보를 서버에 보내서 사용자를 식별하거나 설정을 유지합니다. 그러나 쿠키는 제한된 용량을 가지고 있으며, 사용자에게 보안적인 위험을 끌어낼 있습니다.
  • 세션(Session): 세션은 쿠키를 사용하여 클라이언트의 상태 정보를 서버에 저장하는 방법입니다. 서버는 클라이언트에 대해 유니크한 세션 ID 생성하고, ID 클라이언트의 쿠키에 저장합니다. 클라이언트가 다음 요청을 보낼 , 서버는 세션 ID 통해 해당 클라이언트의 세션 데이터에 접근할 있습니다. 방식은 상태 정보를 서버 측에 저장하므로, 쿠키보다 많은 정보를 저장할 있고, 보안성도 높습니다. 그러나 모든 세션 정보를 서버에 저장해야 하므로 서버의 부하를 증가시킬 있습니다.

  • JWT(Json Web Token) :  사용자에 대한 속성을 저장하는 Claim 기반의 Web Token입니다. Json 포맷을 이용하여 정보를 표현합니다. 이 토큰을 통해 로그인 정보를 서버에 저장하지 않고 클라이언트에 로그인 정보를 JWT로 암호화하여 저장할 수 있습니다. 따라서 JWT를 통해 사용자의 인증 및 인가를 할 수 있습니다.
  • JWT의 주요 장점은 다음과 같습니다:
  1. 동시 접속자가 많을 때 서버 부하를 낮출 수 있습니다.
  2. 클라이언트와 서버가 다른 도메인을 사용할 때 효과적입니다. 예를 들어, 카카오 OAuth2 로그인 시 JWT 토큰이 사용됩니다.
  • 하지만 JWT의 단점도 있습니다:
  1. 구현의 복잡도가 증가합니다.
  2. JWT에 담는 내용이 많아질수록 네트워크 비용이 증가합니다.
  3. 일단 생성된 JWT를 일부만 만료시킬 방법이 없습니다.
  4. Secret key가 유출되면 JWT를 조작할 수 있습니다.
  • JWT의 구조는 다음 세 부분으로 이루어집니다:
  1. Header: 토큰의 타입과 사용된 알고리즘을 지정합니다.
  2. { "alg": "HS256", "typ": "JWT" }
  3. Payload: 클레임 정보를 담고 있습니다. 클레임은 사용자에 대한 정보를 의미합니다.
  4. { "sub": "1234567890", "username": "카즈하", "admin": true }
  5. Signature: Header와 Payload를 합쳐서 Secret Key를 이용해 암호화한 결과입니다.
  6. HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
  • 모든 서버는 동일한 Secret Key를 소유하며, 이 Secret Key를 통해 암호화와 위조 검증을 진행합니다. 복호화 시에는 Secret Key가 필요하지 않으나, JWT 수정을 위해서는 Secret Key가 필요하므로 JWT는 Read only 데이터가 됩니다.

 

1. 쿠키와 세션의 차이점은 무엇인가요?

쿠키와 세션은 웹에서 상태 정보를 저장하고 관리하기 위해 사용되는 기술입니다. 하지만 두 기술은 저장 위치와 보안, 생명 주기 등에서 몇 가지 차이점을 가지고 있습니다.

  1. 저장 위치: 쿠키는 클라이언트의 웹 브라우저에 저장되는 반면, 세션은 서버 측에 저장됩니다.
  2. 보안: 세션은 서버에 저장되므로 쿠키에 비해 상대적으로 보안성이 높습니다. 반면 쿠키는 클라이언트에 저장되기 때문에 쿠키 정보가 노출되거나 조작될 위험이 있습니다.
  3. 생명 주기: 쿠키는 설정한 만료 시간이 될 때까지 브라우저에 저장되므로 사용자가 브라우저를 종료해도 정보가 유지됩니다. 반면, 세션은 사용자가 브라우저를 종료하면 세션이 종료되며, 서버에서 설정한 세션 타임아웃에 따라 세션이 종료될 수 있습니다.
  4. 용량: 쿠키는 총 4KB까지 저장할 수 있으며, 세션은 서버의 메모리를 사용하기 때문에 상대적으로 더 많은 데이터를 저장할 수 있습니다. 하지만 세션의 경우 많은 양의 데이터를 저장하면 서버의 부하가 증가할 수 있습니다.
  5. 데이터 타입: 쿠키는 문자열만 저장할 수 있는 반면, 세션은 객체와 같은 복잡한 데이터 타입도 저장할 수 있습니다.

따라서, 쿠키와 세션 어떤 것을 사용할지는 상황과 요구 사항에 따라 달라집니다. 일반적으로 민감한 정보를 다루는 경우에는 세션을, 개인화 설정과 같은 사용자 경험을 개선하는 데에는 쿠키를 사용하는 것이 일반적입니다.

 

2. 세션을 사용하면서 발생할 수 있는 보안 문제나 그에 대한 해결책은 무엇인가요?

세션 하이재킹: 이는 공격자가 사용자의 세션 ID를 가로채어 해당 사용자로서의 권한을 획득하는 것을 의미합니다. 세션 ID가 네트워크를 통해 전송될 때 이를 가로챌 수 있으므로, 통신이 암호화되지 않은 경우 특히 위험합니다. 이를 해결하기 위해 HTTPS와 같은 보안 프로토콜을 사용하여 통신을 암호화하는 것이 중요합니다.

세션 고정 공격: 이는 공격자가 특정 세션 ID를 사용자에게 강제로 할당하고, 사용자가 그 세션 ID를 사용하여 인증한 후에는 공격자가 그 세션 ID를 사용하여 사용자의 권한을 획득하는 공격입니다. 이를 방지하기 위해 로그인 시에 새로운 세션 ID를 할당하는 것이 중요합니다.

크로스 사이트 스크립팅(XSS) 공격: 이는 공격자가 사용자의 웹 브라우저에 악성 스크립트를 삽입하여 실행시키는 공격입니다. 이 스크립트는 사용자의 세션 ID를 가로챌 수 있습니다. 이를 방지하기 위해 입력 데이터를 적절히 검증하고 필터링하며, 적절한 HTTP 헤더를 사용하여 브라우저의 스크립트 실행을 제한하는 것이 중요합니다.

세션 타임아웃: 너무 길게 세션을 유지하면, 공격자에게 세션 ID를 탈취당할 시간을 제공하게 됩니다. 반대로 세션을 너무 짧게 설정하면 사용자 경험이 저하될 수 있습니다. 적절한 세션 타임아웃 설정이 중요하며, 민감한 작업을 수행하기 전에 사용자 재인증을 요구하는 것도 좋은 방법입니다.

크로스 사이트 요청 위조(CSRF) 공격: 이는 공격자가 사용자가 로그인한 상태에서 악성 웹페이지를 통해 서버에 요청을 보내게 하여, 사용자가 원하지 않는 행동을 서버에 요청하게 하는 공격입니다. 이를 방지하기 위해서는 CSRF 토큰을 사용하여 요청의 유효성을 검증하거나, 동일 출처 정책(Same-origin policy)를 준수하여 서로 다른 출처의 요청을 제한하는 것이 중요합니다.

브루트 포스 공격: 공격자가 무작위의 세션 ID 반복적으로 시도하여 유효한 세션 ID 찾아내는 공격입니다. 이를 방지하기 위해 세션 ID 충분히 길고 복잡하게 생성하며, 무작위의 시도에 대한 제한을 두는 것이 중요합니다.

 

3. 쿠키는 어떤 정보를 저장해야하고 어떤 정보를 저장해서는 안되나요?

쿠키는 사용자가 웹 사이트를 방문할 때 사용자의 기기에 저장되는 작은 텍스트 파일입니다. 쿠키는 일반적으로 사용자가 웹 사이트에 재방문했을 때 그 사이트가 사용자를 인식하도록 돕습니다.

쿠키에 저장해야하는 정보:

  1. 세션 정보: 쿠키는 사용자 세션을 추적하기 위해 자주 사용됩니다. 사용자가 로그인했을 때 쿠키에 세션 ID를 저장할 수 있으며, 이는 사용자가 사이트를 이동하면서 로그인 상태를 유지하게 합니다.
  2. 사용자 설정: 사이트에 대한 개인화된 사용자 설정을 저장하는데 쿠키를 사용할 수 있습니다. 예를 들어, 사용자가 선택한 언어 설정이나 테마 등을 저장할 수 있습니다.
  3. 사용자 행동과 선호도 추적: 쿠키는 사용자의 행동과 선호도를 추적하고 기록하는데 사용됩니다. 이는 개인화된 사용자 경험을 제공하거나 타겟팅 광고를 제공하는데 도움이 됩니다.

쿠키에 저장해서는 안되는 정보:

  1. 개인 식별 정보: 사용자의 신용카드 번호, 주민등록번호, 운전면허번호와 같은 민감한 개인 식별 정보는 절대 쿠키에 저장해서는 안됩니다. 쿠키는 클라이언트 측에서 쉽게 조작될 수 있으며, 이러한 정보가 유출되면 심각한 보안 문제가 발생할 수 있습니다.
  2. 비밀번호: 비밀번호 또한 쿠키에 저장해서는 안됩니다. 심지어 비밀번호를 해시처리한 후에도 저장해서는 안됩니다. 만약 쿠키가 탈취되면, 공격자가 사용자의 계정에 접근할 수 있게 됩니다.
  3. 대량의 정보: 쿠키는 텍스트 기반이며, 그 크기는 한정적입니다. 대량의 데이터를 쿠키에 저장하려고 하면 문제가 발생할 수 있습니다. 대량의 데이터는 보통 서버 측에서 관리하며 필요한 경우에만 클라이언트로 전송합니다.

4. 쿠키와 세션 이외에 클라이언트의 상태를 유지하는 다른 방법을 아는가요?

 

더보기
  1. Hidden Fields: 웹 페이지 내부의 숨겨진 필드를 사용하여 정보를 클라이언트에 저장할 수 있습니다. 이 정보는 서버로 다시 전송될 때 함께 전송됩니다. 이 방법은 주로 HTML 폼에서 사용되며, 사용자가 입력한 데이터를 유지하는 데 사용됩니다.
  2. LocalStorage, SessionStorage (Web Storage): HTML5의 Web Storage API를 사용하면 브라우저 내에 데이터를 저장할 수 있습니다. LocalStorage는 클라이언트가 웹사이트를 닫거나 재시작해도 데이터가 유지되는 반면, SessionStorage는 현재 세션 동안만 데이터를 유지합니다.
  3. IndexedDB: IndexedDB는 브라우저 내에 큰 데이터를 저장하기 위한 NoSQL 데이터베이스입니다. 웹 애플리케이션에서 클라이언트 측에서 작동하는 큰 데이터베이스가 필요할 때 사용됩니다.
  4. WebSockets, Server-Sent Events (SSE): 이 두 기술은 클라이언트와 서버 사이에 실시간 양방향 통신을 가능하게 합니다. 이를 통해 서버는 필요에 따라 클라이언트에 메시지를 푸시할 수 있습니다.
  5. HTTP/2 Server Push: HTTP/2 프로토콜에서, 서버는 클라이언트가 명시적으로 요청하지 않아도 미리 정의된 리소스를 클라이언트에게 푸시할 수 있습니다. 이를 통해 서버는 클라이언트의 다음 요청을 예측하고 미리 리소스를 전달하여 효율성을 높일 수 있습니다.

JWT (JSON Web Tokens)는 클라이언트의 상태를 유지하는 방법 중 하나입니다. JWT는 웹 서버가 클라이언트에게 JSON 객체를 반환하는데, 이 JSON 객체는 토큰으로 암호화됩니다.

클라이언트는 이 토큰을 다음 요청에 포함시켜 인증 정보를 전달하고, 서버는 이 토큰을 복호화하여 사용자 인증 및 데이터를 처리합니다. 이 토큰은 일반적으로 HTTP 헤더의 Authorization 필드에 포함됩니다.

JWT의 주요 장점은 상태를 저장하지 않는다는 점입니다. 이는 서버가 사용자 상태를 추적하거나 저장할 필요가 없다는 것을 의미하며, 이로 인해 서버 부하를 줄이고 확장성을 높일 수 있습니다. JWT는 또한 크로스 도메인 인증을 지원하므로, 여러 도메인이나 서비스에서 사용자를 인증하는 데 유용합니다.

더보기

JWT (JSON Web Tokens)는 상태를 저장하지 않는(stateless) 방식입니다.

"상태를 저장하지 않는다"는 것은 서버가 클라이언트의 정보를 유지하지 않는다는 의미입니다. 클라이언트가 요청을 보낼 때마다 해당 요청에 필요한 모든 정보가 포함되어야 하며, 서버는 이 정보를 바탕으로 요청을 처리하고 응답을 보냅니다. 다시 말해, 서버는 클라이언트의 이전 요청을 기억하지 않으며, 각 요청을 별도로 처리합니다.

JWT는 이러한 상태를 저장하지 않는 방식을 따릅니다. 클라이언트는 요청을 보낼 때마다 JWT를 포함시켜 서버에 인증 정보를 제공합니다. 이 토큰에는 사용자를 식별하는 데 필요한 모든 정보가 들어있으므로, 서버는 이 토큰을 디코딩하여 요청을 처리합니다. 따라서 서버는 세션 상태를 유지하거나 사용자 정보를 저장할 필요가 없습니다.

이는 서버의 부하를 줄이고 확장성을 높이는 도움이 되며, 애플리케이션을 구축하는 사용되는 RESTful 아키텍처의 핵심 원칙 하나입니다.

하지만, JWT 가지 단점이 있습니다. 예를 들어, 토큰이 탈취되면 사용자의 데이터와 권한이 위협받을 있습니다. 이를 방지하기 위해 HTTPS 같은 안전한 프로토콜을 사용하거나 토큰의 유효기간을 짧게 설정하는 등의 방법이 있습니다. 또한 JWT 일반적으로 크기가 크므로, 이를 자주 전송하는 것은 네트워크 부하를 증가시킬 있습니다.

 

5. 쿠키와 세션을 사용할 때 성능에 미치는 영향은 어떻게 되나요?

쿠키(Cookie):

  • 쿠키는 클라이언트의 브라우저에 저장되는 작은 텍스트 파일입니다. 웹 사이트에서는 이를 사용하여 사용자의 정보나 상태를 저장하고, 이전 사용자의 활동을 추적할 수 있습니다.
  • 쿠키는 각 HTTP 요청과 함께 서버로 전송되므로, 큰 크기의 쿠키는 네트워크 대역폭을 사용하고, 이로 인해 애플리케이션의 응답 시간이 느려질 수 있습니다.
  • 또한, 쿠키에 저장할 수 있는 데이터는 제한적이며, 사용자가 쿠키를 삭제하거나 브라우저 설정에서 쿠키를 차단할 수 있으므로, 중요한 정보는 쿠키에 저장해서는 안 됩니다.

세션(Session):

  • 세션은 서버 측에서 클라이언트의 상태 정보를 유지합니다. 세션 ID는 클라이언트에게 전달되고, 이 ID를 사용하여 각 클라이언트의 세션 데이터에 접근할 수 있습니다.
  • 세션 데이터는 서버 메모리에 저장되므로, 많은 사용자가 접속하거나, 세션 데이터가 많을 경우 서버 메모리 부하가 증가할 수 있습니다. 이를 해결하기 위해선 세션 저장소로 데이터베이스나 캐시 서버(예: Redis)를 사용하는 등의 방법이 필요합니다.
  • 세션은 사용자가 로그아웃하거나 일정 시간 동안 활동이 없는 경우 만료되므로, 쿠키보다 보안에 더 안전할 수 있습니다.

6. 세션, 쿠키의 생명주기(Lifecycle)는 어떻게 되나요?

세션의 생명주기는 주로 다음과 같은 단계로 이루어집니다:

  1. 세션 생성: 사용자가 웹 애플리케이션에 처음으로 접속하면, 서버는 세션을 생성합니다. 이때 서버는 고유한 세션 ID를 생성하고, 이를 클라이언트에게 전달합니다. 일반적으로 이 세션 ID는 HTTP 응답의 쿠키를 통해 전달됩니다.
  2. 세션 활성화: 사용자가 애플리케이션을 사용하는 동안, 서버는 이 세션 ID를 사용하여 각 클라이언트의 세션 데이터를 관리합니다. 클라이언트가 요청을 보낼 때마다, 해당 요청에 포함된 세션 ID를 사용하여 사용자의 세션 데이터에 접근할 수 있습니다.
  3. 세션 업데이트: 사용자의 액션에 따라 세션 데이터를 업데이트할 수 있습니다. 예를 들어, 사용자가 상품을 장바구니에 추가하는 경우, 이 정보는 사용자의 세션 데이터에 추가될 수 있습니다.
  4. 세션 만료: 세션은 일정 시간 동안 사용자의 활동이 없을 경우 만료됩니다. 만료된 세션은 더 이상 유효하지 않으며, 이후 동일한 세션 ID로 들어오는 요청은 새로운 세션을 생성하게 됩니다. 또한, 사용자가 로그아웃하는 경우에도 세션을 명시적으로 만료시킬 수 있습니다.
  5. 세션 삭제: 세션 데이터는 세션이 만료되면 서버에서 삭제됩니다. 이는 서버의 메모리를 효율적으로 관리하기 위함입니다. 일부 시스템에서는 별도의 배경 프로세스가 주기적으로 만료된 세션을 청소합니다.

쿠키의 생명주기는 다음과 같은 단계로 구성됩니다:

  1. 쿠키 생성: 서버는 HTTP 응답 헤더에 'Set-Cookie' 포함시켜 클라이언트에게 쿠키를 생성하도록 지시합니다. 'Set-Cookie' 헤더에는 쿠키 이름, , 만료 날짜, 경로 쿠키의 속성이 포함됩니다.
  2. 쿠키 저장: 브라우저는 응답 헤더에서 받은 정보를 바탕으로 쿠키를 생성하고 저장합니다. 쿠키는 일반적으로 클라이언트의 하드 디스크에 저장되며, 정보는 다음에 서버에 요청을 보낼 때마다 함께 전송됩니다.
  3. 쿠키 전송: 클라이언트가 서버에 다시 요청을 보낼 , 브라우저는 쿠키 정보를 HTTP 요청 헤더에 포함시켜 서버에 전송합니다. 서버는 쿠키 정보를 사용하여 클라이언트를 식별하거나 세션을 유지하는 등의 작업을 수행합니다.
  4. 쿠키 만료: 쿠키에는 'Expires' 또는 'Max-Age' 속성이 있어, 이를 통해 쿠키의 생명주기를 지정할 있습니다. 만료 날짜를 지나거나 브라우저가 종료되면, 브라우저는 쿠키를 삭제합니다.
  5. 쿠키 삭제: 만료된 쿠키나 명시적으로 삭제 요청된 쿠키는 브라우저에서 삭제됩니다.

7.  HTTP가 Stateless한 프로토콜이라는 것의 의미는 무엇인가요? 그리고 이것이 웹 개발에 어떤 영향을 미치나요?

HTTP 프로토콜이 Stateless라는 것은 HTTP 요청 간에 서버가 클라이언트의 상태를 유지하지 않는다는 것을 의미합니다. 즉, 서버는 각 요청을 독립적으로 처리하며 이전 요청에 대한 정보를 기억하지 않습니다. 한 요청에서 다음 요청으로 상태를 전달하려면 명시적으로 데이터를 전달해야 합니다. 이러한 설계는 HTTP 프로토콜을 간단하게 만들고 서버의 부하를 줄이는 데 도움이 됩니다.

하지만 이러한 Stateless 설계는 웹 개발에 일부 제약을 가합니다. 예를 들어, 웹 사이트에서 사용자를 인증하고 사용자 세션을 유지하는 것은 HTTP가 Stateless하기 때문에 복잡해집니다. 이를 해결하기 위해 쿠키와 세션, JWT 같은 기술들이 사용됩니다.

쿠키는 클라이언트 측에 데이터를 저장하여 서버가 클라이언트를 인식하게 하고, 세션은 서버 측에 클라이언트의 상태를 저장하여 클라이언트 간에 상태를 유지하게 합니다. JWT는 클라이언트의 상태를 토큰에 저장하고 이를 서버에 보내어 상태를 유지하는 방식입니다.

이처럼 HTTP Stateless 프로토콜이라는 것은 개발에서 상태 관리를 위한 추가적인 메커니즘이 필요하다는 것을 의미합니다. 

 

8. 세션 하이재킹(Session Hijacking)이란 무엇인가요? 이를 방지하기 위한 방법은 무엇인가요?

세션 하이재킹, 또는 세션 탈취는 공격자가 사용자의 세션을 가로채서 그 사용자로서의 권한을 획득하는 공격을 말합니다. 이렇게 하면 공격자는 사용자의 세션을 통해 해당 사용자의 계정에 액세스하고, 해당 사용자의 데이터를 탈취하거나 서비스를 악용할 수 있게 됩니다.

세션 하이재킹을 방지하는 몇 가지 방법은 다음과 같습니다:

  1. 세션 ID의 안전한 관리: 사용자의 세션 ID를 쿠키로 관리할 때는 반드시 HttpOnly 플래그를 사용하여 자바스크립트를 통한 접근을 막아야 합니다. 이는 크로스 사이트 스크립팅(XSS) 공격을 통해 세션 ID를 탈취하는 것을 방지하는 데 도움이 됩니다.
  2. HTTPS 사용: HTTPS를 사용하면 세션 ID가 평문으로 네트워크를 통해 전송되는 것을 방지하여 네트워크를 통한 세션 ID의 탈취를 방지합니다.
  3. 세션 타임아웃 설정: 미사용 세션은 일정 시간 후에 만료되도록 설정하여 공격자가 탈취한 세션 ID의 사용을 제한합니다.
  4. 세션 재발급: 중요한 행동(예: 로그인, 개인 정보 변경 등)을 수행하기 전에 세션 ID를 재발급하여 공격자가 이전의 세션 ID를 사용하는 것을 방지합니다.
  5. CSRF 토큰 사용: 요청마다 새로운 CSRF 토큰을 발급하고 이를 검증하여 CSRF 공격을 방지합니다. CSRF 공격은 공격자가 사용자의 세션을 이용하여 의도치 않은 요청을 보내는 공격입니다.

이러한 방법들은 서버와 클라이언트 모두에서 적용되어야 효과적으로 세션 하이재킹을 방지할 수 있습니다.

 


 

9. JWT(Json Web Token)에 대해 설명해주실 수 있나요?

JSON Web Token(JWT) 간단히 말해 사이트와 서버간에 안전하게 정보를 주고받기 위한 방법 하나입니다

JWT 특정 사용자가 서비스에 접근할 권한이 있는지를 증명하는데 사용되며, 일반적으로 서버와 클라이언트 사이의 요청에 포함되어 전송됩니다.

JWT 크게 부분으로 나뉩니다: Header, Payload, Signature.

    1. Header: 토큰의 유형 (표준적으로 "JWT") 사용된 알고리즘 (: HS256 또는 RS256) 선언하는 JSON 객체입니다.
    2. Payload: 클레임(웹 사이트나 서버가 알아야 정보, 예를 들어, 사용자 ID)을 포함하는 JSON 객체입니다. 클레임은 추가적인 메타데이터를 가진 속성이며, 사용자에 대한 정보와 토큰의 유효성을 결정하는데 사용됩니다.
    3. Signature: 토큰의 무결성을 보증하기 위해 사용됩니다.(토큰이 변경되지 않았음을 확인하는  사용되는 일종의 디지털 서명) 이것은 header payload 합친 다음, 비밀 키를 사용하여 이것을 암호화한 결과입니다.

토큰이 생성되면, 부분 (header, payload, signature) Base64 인코딩되고 "." 구분되어 하나의 문자열로 결합됩니다.

JWT 주로 가지 방식으로 사용됩니다. 하나는 사용자 인증에 사용하고, 다른 하나는 안전하게 정보를 주고받는 사용합니다.

  1. Authentication: 클라이언트가 서버에 요청할 때마다 JWT를 함께 보내서 사용자가 누구인지 인증합니다. 서버는 토큰을 검증하여 요청이 유효한지 확인합니다.
  2. Information Exchange: 두 시스템 간에 안전하게 정보를 전달하기 위해 사용될 수 있습니다. 정보는 토큰에 포함되고, 토큰의 서명을 통해 무결성이 보장됩니다.

JWT의 주요 장점 중 하나는 상태를 유지하지 않는다는 것입니다. 이는 서버가 사용자의 상태를 기억할 필요가 없으며, 이로 인해 서버의 부하를 줄일 수 있습니다. 이는 특히 대규모 서비스에서 매우 유용하며, 확장성을 향상시킵니다.

그러나 JWT 무한정 사용할 있는 것은 아닙니다. 예를 들어, 발급된 JWT 취소가 불가능하기 때문에, 만료 시간을 짧게 설정하는 것이 일반적입니다. 또한 중요한 개인 정보가 포함된 경우, 토큰이 탈취될 위험이 있으므로 이를 방지하기 위한 추가적인 보안 조치가 필요합니다.

 

10. JWT는 어떤 경우에 사용하면 좋을까요? 그 이유는 무엇인가요?

  1. 분산 시스템: JWT 클라이언트와 서버 간에 정보를 안전하게 주고받을 있게 해줍니다. 이는 로드 밸런서가 여러 서버 간의 요청을 분산시키는 분산 시스템에서 특히 유용합니다. 세션 기반의 인증 시스템에서는 세션 정보를 모든 서버가 공유해야 하지만, JWT 사용하면 클라이언트가 요청을 보낼 때마다 토큰을 함께 보내므로 이러한 공유가 필요 없습니다.
  2. 마이크로서비스 아키텍처: 마이크로서비스 아키텍처에서 서비스는 자체적으로 Stateless 해야 합니다. JWT 사용하면 서비스가 독립적으로 인증을 수행할 있습니다.
  3. 모바일 애플리케이션: 모바일 환경에서는 서버 기반 세션은 상대적으로 많은 리소스를 사용하게 됩니다. 반면에 JWT 상태를 저장하지 않는 Stateless 통신이 가능하므로 리소스를 절약할 있습니다.
  4. 크로스 도메인 인증: 쿠키는 동일 출처 정책(Same Origin Policy) 따라 도메인 간에 공유될 없습니다. 반면에 JWT HTTP 헤더에 포함되어 전송되므로 다른 도메인 간에도 안전하게 정보를 주고받을 있습니다.
  5. 서드파티 인증: JWT OAuth, OpenID Connect 등의 서드파티 인증 프로토콜에서 사용되며, 이들 프로토콜을 사용하여 외부 서비스에 대한 인증을 제공할 있습니다.

 

2023.05.29 - [Web] - Cookie, Session, JWT(Json Web Token) pt.2

1. JVM 이란 무엇이고 왜 필요한지 설명해주실 수 있을까요?

Java Virtual Machine (JVM)은 자바 바이트코드(.class 파일)를 실행할 수 있는 가상의 실행 환경을 제공하는 스펙입니다. JVM이 있는 곳이라면 어떠한 운영체제에서도 자바 프로그램을 실행시킬 수 있습니다. 이런 특성 때문에 자바는 "한 번 작성하면, 어디에서나 실행할 수 있다(Write Once, Run Anywhere)"는 특징을 가지게 되었습니다.

JVM이 필요한 이유는 다음과 같습니다.

  1. 플랫폼 독립성: 자바 어플리케이션은 JVM 위에서 동작하며, JVM은 다양한 운영 체제에서 동일하게 동작합니다. 따라서 자바 어플리케이션은 한 번 작성하면 어떠한 운영 체제에서도 동작하는 플랫폼 독립적인 프로그램을 만들 수 있습니다.
  2. 메모리 관리: JVM은 가비지 컬렉션(Garbage Collection) 기능을 통해 프로그래머 대신 메모리를 관리해줍니다. 이로 인해 프로그래머는 메모리 관리에 신경 쓰지 않고, 비즈니스 로직에 집중할 수 있습니다.
  3. 보안: JVM은 클래스를 로드할 때 검증 과정을 거쳐 안전하지 않은 코드의 실행을 방지합니다.
    더보기

    JVM에는 몇 가지 보안 기능이 내장되어 있습니다. 일반적으로 이러한 기능은 Java의 "쓰기 한 번, 어디서나 실행"이라는 철학을 지원하면서도 악의적인 코드로부터 시스템을 보호하는 데 도움이 됩니다.

    1. 바이트코드 검증: JVM은 자바 애플리케이션을 실행하기 전에 클래스 로더를 통해 로드된 자바 바이트코드를 검사합니다. 이 검사 과정에서 바이트코드가 JVM 규칙을 준수하는지, 버퍼 오버플로우와 같은 악의적인 행동을 유발할 수 있는 코드가 없는지 확인합니다. 이것은 바이트코드가 안전하게 실행될 수 있도록 보장하는 첫 번째 방어선입니다.
    2. 클래스 로더: JVM의 클래스 로더는 애플리케이션에 필요한 클래스를 동적으로 로드합니다. 일반적으로 클래스 로더는 로컬 파일 시스템에서 클래스를 로드하지만 원격 소스에서 클래스를 로드하는 것도 가능합니다. 이러한 동적 로딩 방식은 보안상의 문제를 야기할 수 있지만, 클래스 로더의 구조는 서로 격리된 네임스페이스를 제공하여 이를 방지합니다. 이렇게 하면 악의적인 클래스가 기존 클래스를 오버라이드하거나 악의적인 방식으로 작동하는 것을 방지할 수 있습니다.
    3. 보안 매니저: Java의 보안 매니저는 애플리케이션이 수행하는 작업에 대한 접근 제어를 제공합니다. 보안 매니저는 파일 I/O, 네트워크 연결, 스레드 생성 등의 작업을 허용하거나 거부할 수 있습니다. 보안 정책에 따라 이러한 작업을 제한하거나 허용할 수 있습니다.

    이러한 기능들을 통해 JVM 실행하는 코드의 안전성을 보장하며, 시스템 리소스에 대한 접근을 제어합니다. 그러나 JVM 자체의 보안 기능만으로는 충분하지 않을 있으며, 보안은 어플리케이션 코드 작성, 데이터 보호, 네트워크 보안 여러 레벨에서 고려되어야 합니다.

JVM 자바 어플리케이션의 실행을 위한 핵심적인 요소입니다. JVM 스펙에 따라서 구현된 여러 가지 JVM 구현체 (Oracle HotSpot, OpenJDK ) 있으며, 이러한 구현체는 각각의 특성을 가지고 있습니다.

 

JVM이란 Java Virtual Machine 의 줄임말로, Java Byte Code 를 운영체제에 맞게 해석해주는 역할을 한다. 즉, 작성한 자바 프로그램의 실행 환경을 제공하는 자바 프로그램의 구동 엔진이다. Java compiler 는 .java 파일을 .class 라는 자바 바이트코드로 변환시켜주는데 Byte Code 는 기계어(Native Code)가 아니므로 OS 에서 바로 실행이 되지 않는다. 이때 JVM은 OS가 Byte Code 를 이해할 수 있도록 해석해주는 역할을 담당한다. JVM은 메모리 관리도 담당한다. 이를 '가비지 컬렉터'라고 하는데, 가비지 컬렉터는 Java7부터 힙 영역의 객체들을 관리하는 역할을 담당한다.

 

Java Virtual Machine의 줄임말로 OS에 종속받지 않고 Java를 실행시킬 수 있는 가상머신으로 JVM은 JAVA와 OS 사이에서 중개자 역할을 수행하여 JAVA가 OS에 관계없이 실행될 수 있게 해준다. 크게 세가지로 중요한 부분이 나뉜다. java 파일을 컴파일러가 자바 바이트 코드인 .class로 변환한다. 바이트 코드를 Class Loader 에서 Loading, Linking, Initialization 과정을 거치며 클래스를 실제 실행될때 사용하는 영역인 Runtime Data Area의 메모리로 로딩한다. 이때 메서드, 힙 ,스택 ,PC 레지스터,Native Method Stacks의 5개의 영역으로 구성된다. 위의 영역을 Execution Engine에서 실제로 실행한다. 이 때 반복되는 명령어는 JIT 컴파일러가 바로 실행시켜주며 가비지 컬렉터가 사용하지 않는 힙 영역에서 할당한 메모리를 주기적으로 삭제해준다.

 

JVM은 자바 가상 기계를 의미하며, 자바 소스 파일을 컴파일한 .class 바이트 코드 파일을 기계어로 번역 후 실행시켜줍니다. 자바 프로그램을 개발하기 위해서 자바 언어로 된 자바 소스파일(.java)을 생성한 뒤 컴파일하면 확장명이 .class인 바이트 코드 파일이 생성되는데, 해당 바이트 코드는 완전한 기계어가 아니므로 운영체제에서 바로 실행할 수 없습니다. 해당 과정에서 JVM이 바이트 코드 파일을 각 운영 체제에서 실행 가능한 기계어로 번역해줍니다. 그로 인해 다양한 운영체제에서 수정하지 않고도 바로 사용할 수 있습니다. 가령 예로 들자면 윈도우 운영체제에서 개발한 바이트 코드 파일을 리눅스로 옮겨도 바로 실행될 수 있게 해주는 경우와 같습니다.

 

2. JVM의 구조와 작동 원리에 대해 설명해주세요

Java Virtual Machine(JVM)은 자바 애플리케이션이 실행되는 런타임 환경입니다. 자바 프로그램은 바이트코드로 컴파일되고, 이 바이트코드는 JVM에서 실행됩니다.

JVM의 주요 기능은 메모리 관리와 가비지 컬렉션, 코드 최적화 등입니다. JVM의 작동 원리와 구조는 아래와 같습니다.

  1. 클래스 로더(Class Loader): 자바 애플리케이션은 여러 개의 클래스 파일로 구성되어 있습니다. 클래스 로더는 이러한 클래스 파일들을 로드하고 JVM 메모리에 배치하는 역할을 합니다.
  2. 런타임 데이터 영역(Runtime Data Area): JVM 메모리는 여러 개의 영역으로 구성되어 있습니다. 이러한 영역에는 객체, 메소드, 스레드 정보 등이 저장됩니다.
  3. 실행 엔진(Execution Engine): 바이트코드는 실행 엔진에 의해 해석되고 실행됩니다. JIT(Just-In-Time) 컴파일러는 여기에 포함되며, 자주 사용되는 바이트코드를 기계어로 변환하여 프로그램의 성능을 향상시킵니다.
  4. 네이티브 메소드 인터페이스(Native Method Interface): 자바 외의 언어로 작성된 함수를 실행하기 위한 인터페이스입니다.
  5. 가비지 컬렉션(Garbage Collection): JVM은 동적으로 할당된 메모리를 자동으로 관리합니다. 더 이상 참조되지 않는 객체를 자동으로 삭제하는 가비지 컬렉션 기능을 제공합니다.

이렇게 JVM 플랫폼 독립적인 바이트코드 실행 환경을 제공하여, 작성한 자바 프로그램이 다양한 플랫폼에서 실행될 있게 합니다.

 

3. JVM의 메모리 관리와 가비지 컬렉션에 대해 설명해주세요.

JVM(Java Virtual Machine)은 메모리 관리를 위해 몇 가지 특별한 영역을 정의하고 있습니다. 이 영역들은 다음과 같습니다.

  1. 힙(Heap) 영역: JVM의 가장 큰 메모리 영역으로, 모든 클래스 인스턴스와 배열이 이 영역에 할당됩니다. 힙 영역은 여러 스레드가 공유하며, 이 영역에서 가비지 컬렉션이 주로 발생합니다.
  2. 스택(Stack) 영역: 각 스레드마다 하나씩 생성되며, 메소드 호출과 로컬 변수를 위한 메모리 공간을 제공합니다. 메소드 호출이 발생할 때마다 스택 프레임이라는 블록이 스택에 추가되며, 메소드 호출이 종료되면 해당 스택 프레임이 제거됩니다.
  3. 메소드(Method) 영역: 모든 스레드가 공유하는 영역으로, 각 클래스의 구조정보를 담고 있습니다. 또한, 런타임 상수 풀, 필드와 메소드 데이터, 메소드와 생성자의 바이트코드 등도 이 영역에 저장됩니다.
  4. 네이티브 메소드 스택(Native Method Stack) 영역: JVM 외부의 네이티브 메소드를 위한 메모리 영역입니다.

JVM 메모리 관리의 핵심적인 부분은 가비지 컬렉션(Garbage Collection, GC)입니다. 이는 프로그램이 동적으로 할당한 메모리를 자동으로 회수하는 프로세스로, 이상 프로그램에 의해 참조되지 않는 객체를 탐지하고 제거합니다. 이런 방식으로, 자바 프로그래머는 메모리 해제 등에 대한 걱정 없이 코드를 작성할 있습니다. 다만 가비지 컬렉션은 CPU 리소스를 소모하기 때문에, 이를 효율적으로 관리하려면 GC 동작 방식을 이해하는 것이 중요합니다.

 

4. JIT 컴파일러란 무엇인가요? 그리고 그것이 왜 필요한지 설명해주세요.

JIT(Just-In-Time) 컴파일러는 JVM의 중요한 부분 중 하나로, 프로그램의 실행 속도를 향상시키는 데 사용됩니다.

일반적으로 자바 코드는 컴파일 단계에서 바이트코드로 변환되고, 이 바이트코드는 런타임에 JVM에 의해 기계어로 해석되어 실행됩니다. 이렇게 인터프리트 방식으로 코드를 실행하는 것은 단순하고 플랫폼 독립적이지만, 성능 면에서는 부족할 수 있습니다.

이런 성능 이슈를 해결하기 위해 JIT 컴파일러가 등장했습니다. JIT 컴파일러는 바이트코드를 런타임에 기계어로 컴파일하여 실행속도를 향상시킵니다. 더욱이, JIT 컴파일러는 동적으로 (즉, 실행 중에) 코드를 분석하므로, 인터프리트 방식보다 더 효율적인 최적화를 수행할 수 있습니다.

예를 들어, JIT 컴파일러는 반복적으로 호출되는 메소드(핫스팟이라고 불리는)를 발견하면 그 메소드를 특히 신경 써서 최적화합니다. 또한, JIT 컴파일러는 불필요한 계산을 제거하거나, 메모리 접근을 줄이는 등의 최적화도 수행합니다.

JIT 컴파일러의 도입으로 자바의 실행 속도는 크게 향상되었으며, 이로 인해 자바는 높은 수준의 플랫폼 독립성과 효율적인 성능을 동시에 제공할 있게 되었습니다.

 

5. JVM과 JRE, JDK의 차이에 대해 설명해주세요.

JVM (Java Virtual Machine):

  • JVM은 자바 바이트코드를 실행하는 런타임 환경입니다. JVM은 가상의 컴퓨터로 볼 수 있으며, 바이트코드를 해당 컴퓨터의 기계어로 해석하고 실행합니다. JVM은 플랫폼 독립적인 자바 코드를 가능하게 하는 중요한 역할을 합니다.

JRE (Java Runtime Environment):

  • JRE는 JVM과 함께 자바 프로그램을 실행하는데 필요한 라이브러리 파일들을 포함합니다. 즉, JRE는 JVM과 자바 클래스 라이브러리를 포함하여 자바 프로그램이 실행되는 환경을 제공합니다.

JDK (Java Development Kit):

  • JDK는 자바 개발자를 위한 통합 소프트웨어 환경으로, 자바 프로그램을 개발하고 컴파일하는 데 필요한 도구를 제공합니다. JDK는 JRE를 포함하며, 이는 개발된 프로그램을 실행하기 위해 필요합니다. JDK에는 컴파일러(javac), 디버거(jdb), 아카이버(jar) 등의 개발 도구가 포함되어 있습니다.

간단하게 요약하면, JDK 개발을 위한 환경이며 JRE 프로그램을 실행하는 필요한 환경이고, JVM 실제로 바이트코드를 해석하고 실행하는 역할을 합니다.

 

6. 자바 프로그램이 실행되는 과정을 설명해주세요.

  1. 코드 작성: 프로그래머는 .java 확장자의 파일에 자바 코드를 작성합니다. 이 파일을 소스 파일이라고 합니다.
  2. 컴파일: 작성된 자바 코드는 자바 컴파일러(javac)에 의해 컴파일됩니다. 이 과정에서 소스 파일(.java)은 바이트코드(.class) 파일로 변환됩니다. 바이트코드는 JVM이 이해할 수 있는 언어입니다.
  3. 로드: 컴파일된 바이트코드는 클래스 로더에 의해 JVM 메모리에 로드됩니다. 클래스 로더는 .class 파일들을 읽어들여 JVM이 이해할 수 있는 형태로 메모리에 로드합니다.
  4. 실행: 클래스 로더에 의해 메모리에 로드된 바이트코드는 실행 엔진에 의해 실행됩니다. 실행 엔진은 바이트코드를 한 번에 한 줄씩 읽어들여서 실행합니다. 이때 JIT(Just-In-Time) 컴파일러를 활용하여 성능을 향상시킬 수 있습니다.
  5. 가비지 컬렉션: 프로그램이 실행되는 동안, JVM은 더 이상 사용되지 않는 메모리를 해제하는 가비지 컬렉션을 수행합니다.
  6. 종료: 프로그램이 종료되면 JVM도 종료됩니다. 모든 자바 프로그램은 JVM 내에서 실행되므로, 프로그램이 종료되면 그에 따라 JVM도 종료됩니다.

위의 과정을 통해 자바는 '쓰기 쉽고, 실행하기 쉬운' 언어라는 목표를 달성합니다. 이를 가능하게 하는 핵심적인 요소가 JVM이며, 이로 인해 자바 프로그램은 다양한 플랫폼에서 작동할 있습니다.

 

7. Java가 컴파일되는 과정은 어떻게 되는지 설명해주실 수 있을까요?

  1. 소스 코드 작성: 프로그래머가 자바 언어로 코드를 작성하고 이를 .java 확장자를 가진 파일로 저장합니다. 이를 소스 코드 파일이라 합니다.
  2. 컴파일: 작성된 소스 코드는 자바 컴파일러(javac)에 의해 컴파일됩니다. 자바 컴파일러는 .java 파일을 읽어들이고, 이를 JVM이 이해할 수 있는 중간 언어인 바이트코드(.class 파일)로 변환합니다. 이 과정에서 문법 오류가 있는지 검사하고, 문법에 맞지 않는 코드가 있다면 컴파일 오류를 반환합니다.
  3. 바이트코드 실행: JVM은 컴파일러가 생성한 .class 파일을 읽어들입니다. 이 파일에는 바이트코드가 저장되어 있으며, JVM은 이 바이트코드를 해석하여 프로그램을 실행합니다. 이 과정에서 JIT(Just-In-Time) 컴파일러를 사용하여 바이트코드를 더 효율적으로 실행합니다.

JVM 바이트코드를 직접 해석하고 실행하는 방식은 자바가 " 작성하면 어디에서나 실행할 있다(Write Once, Run Anywhere)" 장점을 가질 있게 합니다. 이는 바이트코드가 플랫폼 독립적이며, JVM 설치되어 있다면 어떤 시스템에서든 실행할 있기 때문입니다.

개발자가 Java 소스코드를 작성하면 Java Compiler가 JVM이 읽을 수 있는 Bytecode로 컴파일 합니다. 컴파일된 Bytecode를 JVM의 class loader에 전달합니다. class loader는 dynamic loading을 통해 필요한 class 파일들을 로딩 및 링크하여 런타임 데이터 영역 즉, JVM의 메모리 영역에 올립니다. 그 다음 Java 언어 명세 및 JVM 명세에 명시된 대로 구성되어 있는지 검사하고 class의 필드, 메소드, 인터페이스 등으로 메모리를 할당합니다. class의 상수 풀 내 모든 symbolic reference를 direct reference로 변경하고 class 변수들을 적절한 값인 static 필드로 초기화 합니다. 이 모든 class loader의 세부 과정이 끝나면 실행 엔진은 JVM 메모리에 올라온 Bytecode들을 명령어 단위로 하나씩 가져와 인터프리터 또는 JIT 컴파일러로 변환하여 실행합니다.

1. 개발자가 자바 소스코드(.java)를 작성합니다. 2. 자바 컴파일러가 자바 소스코드(.java)파일을 읽어 바이트코드(.class)코드로 컴파일 합니다. 바이트코드(.class)파일은 아직 컴퓨터가 읽을 수 없는 JVM(자바 가상 머신)이 읽을 수 있는 코드입니다. (java - > class) 3. 컴파일된 바이트코드(.class)를 JVM의 클래스로더(Class Loader)에게 전달합니다. 4. 클래스 로더는 동적로딩(Dynamic Loading)을 통해 필요한 클래스들을 로딩 및 링크하여 런타임 데이터 영역(Runtime Data Area), 즉 JVM의 메모리에 올립니다. 5. 실행엔진(Execution Engine)은 JVM 메모리에 올라온 바이트 코드들을 명령어 단위로 하나씩 가져와서 실행합니다. 

 

8. JVM의 클래스 로더에 대해 설명해주세요.

클래스 로더(Class Loader)는 JVM 내부의 컴포넌트 중 하나로, .class 파일들 즉, 바이트코드를 메모리에 로드하는 역할을 합니다. 이 클래스 로더는 필요한 클래스를 찾아 메모리에 로드하고 이를 사용할 수 있도록 준비합니다.

클래스 로더의 동작 과정은 다음과 같습니다:

  1. 로딩(Loading): 클래스 로더는 필요한 .class 파일을 찾아서 JVM의 메모리 영역인 메서드 영역에 로드합니다. 이 때, 클래스 파일의 바이트코드를 읽어 클래스 또는 인터페이스의 바이너리 데이터를 생성하고 이를 메모리에 로드합니다.
  2. 연결(Linking): 로딩이 완료된 클래스들을 검증하고, 정적 변수에 대한 메모리를 할당하며, 해당 메모리를 기본값으로 초기화하는 과정을 포함합니다. 이 과정에서 JVM은 클래스의 바이트코드가 JVM의 규칙을 준수하는지, 예를 들어 final 클래스가 subclass를 갖는지 등을 검사합니다.
  3. 초기화(Initialization): 정적 변수들을 개발자가 지정한 초기값으로 설정합니다. 이 과정은 클래스의 static 블록이 실행되는 시점입니다.

JVM 클래스 로더는 위에서 언급한 기본적인 동작 외에도, 중요한 특징으로 "부모-자식 위임 모델" 있습니다. 이는 클래스 로더가 특정 클래스를 로드하려 , 먼저 부모 클래스 로더에게 위임하는 것을 말합니다. 모델은 자바의 보안 모델에 중요한 역할을 합니다. 이를 통해 JVM 클래스를 로드하는 과정에서 일관성을 유지하고, 악의적인 클래스로부터 보호될 있습니다.

 

9. JVM 언어가 무엇인지, 왜 사용하는지 설명해주세요.

JVM 언어는 Java Virtual Machine (JVM)에서 실행될 수 있는 프로그래밍 언어를 일컫습니다. 가장 대표적인 JVM 언어는 Java이지만, 이 외에도 Kotlin, Scala, Groovy, JRuby 등 다양한 언어들이 JVM에서 작동합니다.

JVM 언어를 사용하는 이유는 다음과 같습니다:

  1. 플랫폼 독립성: JVM 언어로 작성된 프로그램은 JVM이 설치되어 있는 모든 운영체제에서 실행될 수 있습니다. 이는 'Write Once, Run Anywhere'라는 자바의 핵심 철학을 구현한 것입니다.
  2. 성능 최적화: JVM은 Just-In-Time (JIT) 컴파일러를 포함하고 있어, 프로그램의 실행 속도를 높이는 데 도움을 줍니다.
  3. 메모리 관리: JVM은 가비지 컬렉션(Garbage Collection) 기능을 제공하여 개발자가 메모리 관리에 대한 부담을 덜 수 있게 도와줍니다.
  4. 보안: JVM은 프로그램 실행 중에 보안 문제를 체크하여 시스템을 보호합니다.
  5. 다양한 언어 지원: 다양한 JVM 언어들이 존재하기 때문에, 개발자는 자신의 목적에 맞는 언어를 선택하여 사용할 수 있습니다.

JVM 언어를 사용하면 JVM 생태계의 장점을 모두 활용할 수 있습니다. 이는 대규모 기업 환경에서는 매우 중요한 요소입니다. Java 라이브러리 및 프레임워크의 거대한 생태계, 훌륭한 성능 최적화, 플랫폼 간의 호환성 등을 활용할 수 있기 때문입니다.

그러나 JVM 언어를 사용할 때도 고려해야 단점이 있습니다. 예를 들어, JVM 애플리케이션의 시작 시간이 상대적으로 길고, 메모리 사용량이 크다는 점입니다. 이는 특히 마이크로서비스 아키텍처나 클라우드 기반 환경에서는 더욱 중요한 문제가 있습니다.

 

10. JVM의 스택과 힙메모리 영역에 대해 아는 만큼 설명해주실 수 있을까요?

JVM의 메모리는 크게 다섯 가지 영역으로 나눌 수 있습니다: 메서드 영역, 힙 영역, 스택 영역, 네이티브 메서드 스택, PC 레지스터. 이 중 힙과 스택에 대해 설명하겠습니다.

힙(Heap) 영역

힙 영역은 객체와 배열이 할당되는 곳입니다. 즉, new 키워드를 사용하여 생성된 인스턴스들이 저장되는 공간입니다. 또한, 힙 영역에 할당된 메모리는 가비지 컬렉션에 의해 관리됩니다.

힙 영역은 여러 스레드 간에 공유되므로 전역 변수와 같은 정적 변수를 저장하는 곳으로도 사용됩니다. 힙 영역의 메모리는 가비지 컬렉터가 자동으로 회수합니다. 따라서 개발자는 이 영역의 메모리를 직접 해제할 필요가 없습니다.

스택(Stack) 영역

스택 영역은 지역 변수와 메서드 호출에 대한 정보를 저장하는 공간입니다. 메서드가 호출될 때마다 해당 메서드에 대한 스택 프레임이 생성되고, 이 스택 프레임 안에는 지역 변수, 매개 변수, 메서드의 반환 값 등이 저장됩니다. 메서드 호출이 완료되면 해당 스택 프레임은 스택에서 제거(pop)됩니다.

스택 영역은 각 스레드마다 독립적으로 생성되며, 스레드 간에 데이터를 공유하지 않습니다. 따라서, 한 스레드에서 발생한 예외가 다른 스레드에 영향을 미치지 않습니다.

스택 영역의 메모리는 메서드 호출이 종료되면 자동으로 해제됩니다. 따라서 개발자가 직접 메모리를 관리할 필요가 없습니다. 하지만 스택 오버플로우와 같은 문제가 발생할 있으며, 이는 스택 영역의 메모리가 부족하게 되는 경우에 발생합니다. 이런 문제는 재귀 호출이 과도하게 이루어지는 경우 등에 발생할 있습니다.

더보기

메서드 영역(Method Area):

메서드 영역은 JVM이 읽어 들인 각 클래스와 인터페이스에 대한 런타임 상수 풀, 필드와 메서드 데이터, 메서드와 생성자 코드, 클래스, 인스턴스, 인터페이스 초기화 메서드 등을 저장합니다. 메서드 영역은 힙 영역과는 다르게 가비지 컬렉션의 대상이 아닙니다.

네이티브 메서드 스택(Native Method Stacks):

네이티브 메서드 스택은 자바 외의 언어로 작성된 네이티브 메서드를 위한 메모리 영역입니다. 네이티브 메서드는 보통 C나 C++ 등의 언어로 작성되며, 자바 네이티브 인터페이스(Java Native Interface, JNI)를 통해 호출됩니다.

PC 레지스터(Program Counter Register):

PC 레지스터는 현재 실행 중인 JVM 명령의 주소를 가지고 있습니다. 이것은 스레드마다 하나씩 존재하며, 스레드가 어떤 명령을 실행해야 할지 결정하는데 사용됩니다. 만약 실행 중인 명령이 있다면 PC 레지스터는 명령의 주소를 가리키고, 만약 실행 중인 명령이 없다면 (예를 들어, 해당 스레드가 네이티브 메서드를 실행 중일 경우) PC 레지스터는 undefined 값을 가집니다.

 

Stack의 경우에는 정적으로 할당된 메모리 영역이다. Stack에서는 Primitive 타입 (boolean, char, short, int, long, float, double) 의 데이터가 값이랑 같이할당이 되고, 또 Heap 영역에 생성된 Object 타입의 데이터의 참조 값이 할당 된다. 그리고 Stack 의 메모리는 Thread당 하나씩 할당 된다. 만약 새로운 스레드가 생성되면 해당 스레드에 대한 Stack이 새롭게 생성되고, 각 스레드 끼리는 Stack 영역을 접근할 수 가 없다. Heap의 경우에는 동적으로 할당된 메모리 영역이다. 힙 영역에서는 모든 Object 타입의 데이터가 할당이 된다. (참고로 모든 객체는 Object 타입을 상속받는다.) Heap 영역의 Object를 가리키는 참조변수가 Stack에 할당이 된다. 어플리케이션에서의 모든 메모리 중에서 Stack에 쌓이는 애들 빼고는 전부 이 Heap 쌓인다고 보면 된다.

 

JVM 스택 영역은 프로그램 실행과정에서 임시로 할당되었다가 메소드를 빠져나가면 바로 소멸되는 특성의 데이터를 저장하기 위한 영역이다. 각종 형태의 변수나 임시 데이터, 스레드나 메소드의 정보를 저장한다. 메소드 호출 시마다 각각의 스택 프레임(그 메서드만을 위한 공간)이 생성된다. 메서드 수행이 끝나면 프레임 별로 삭제를 한다. 메소드 안에서 사용되는 값들을 저장한다. 또 호출된 메소드의 매개변수, 지역변수, 리턴 값 및 연산 시 일어나는 값들을 임시로 저장한다. 힙 영역은 객체를 저장하는 가상메모리 공간. new 연산자로 생성되는 객체와 배열을 저장한다.Class Area(Static Area)에 올라온 클래스들만 객체로 생성할 수 있다.

 

스택은 정적 메모리가 저장되는 영역입니다. 원시타입의 데이터의 값과 힙 영역에서 생성된 Object 타입의 참조 값 등이 저장되며 함수가 호출될 때 사용되는 메모리입니다. 기능 수행이 끝나면 자동으로 반환되며, 이는 LIFO 방식으로 관리됩니다. 또한 쓰레드당 1개씩 존재합니다. 힙 영역은 동적 메모리가 저장되는 영역이며, 프로그램을 실행하면서 생성한 모든 인스턴스들을 저장합니다. 또한 모든 쓰레드가 공유하고 있으며, 스택과 연결되어 자동으로 관리되지 않는 메모리들은 GC가 관리하게 됩니다.

 

Stack 영역은 자료를 순차적으로 저장하며 LIFO구조로 정적 메모리 공간을 할당한다 그리고 기본타입 (정수타입, 실수타입, 논리타입)을 저장하며 메소드 작업이 종료되면 할당되었던 메모리 공간은 반환되어 비워진다 Heap 영역은 실행 시간 동안 사용할 메모리 공간을 할당하며 정적 메모리 공간 할당과 대조적이다 참조타입 (배열, 열거, 클래스, 인터페이스)를 저장하며 명시적 해제 혹은 garbage collect가 일어나기 전까지 유지된다

 

11. JVM의 성능 측정 방법에 대해 알고 있나요?

JVM의 성능 측정은 여러 가지 관점에서 볼 수 있습니다. 일반적으로 가장 중요한 성능 지표는 실행 시간, 메모리 사용량, CPU 사용률, 가비지 컬렉션(GC) 횟수 및 지속 시간 등입니다.

  1. 실행 시간: 프로그램이 작업을 완료하는 데 걸리는 시간을 측정합니다. 이는 종종 응답 시간이라고도 불리며, 사용자 대화형 애플리케이션에서 매우 중요한 지표입니다.
  2. 메모리 사용량: JVM이 얼마나 많은 메모리를 사용하고 있는지를 측정합니다. 힙 메모리 사용량이 너무 높으면 OutOfMemoryError가 발생할 수 있으므로 이를 모니터링하는 것이 중요합니다.
  3. CPU 사용률: JVM이 얼마나 많은 CPU 자원을 사용하고 있는지를 측정합니다. CPU 사용률이 높으면 시스템이 과부하 상태에 빠질 수 있습니다.
  4. 가비지 컬렉션(GC) 횟수 및 지속 시간: GC는 JVM의 효율성에 큰 영향을 미칩니다. GC 횟수가 너무 많거나 GC에 너무 많은 시간이 소요되면 애플리케이션 성능이 저하될 수 있습니다.

이러한 지표를 측정하려면 다양한 도구를 사용할 수 있습니다. JVM은 JMX(Java Management Extensions)를 통해 많은 성능 데이터를 제공합니다. 이를 통해 JVM의 동작을 모니터링하고 분석할 수 있습니다.

JConsole, VisualVM, Java Mission Control 등의 도구는 JVM의 성능 데이터를 시각적으로 표현하며, 이를 통해 JVM 성능을 모니터링하고 튜닝할 수 있습니다.

또한, GC 로그 분석 도구들을 사용해 가비지 컬렉션의 성능을 측정하고 이해하는 도움이 있습니다.

 

12. JVM의 튜닝과 최적화에 대해 어떤 경험이 있나요?

JVM 튜닝과 최적화는 자바 애플리케이션의 성능을 향상시키는 데 중요한 역할을 합니다. 여기에는 여러 가지 방법이 있습니다:

  1. 힙 메모리 설정: JVM의 힙 메모리 크기를 적절히 설정하는 것은 성능 향상에 중요합니다. 힙 메모리가 너무 작으면 가비지 컬렉션이 자주 발생하며, 너무 크면 가비지 컬렉션의 시간이 길어질 수 있습니다.
  2. 가비지 컬렉션 튜닝: JVM의 가비지 컬렉션 동작을 튜닝하여 성능을 개선할 수 있습니다. 가비지 컬렉터의 종류(G1GC, ParallelGC, CMS 등)를 선택하거나, 가비지 컬렉션 시간을 최소화하는 방법 등이 있습니다.
  3. JVM 옵션 최적화: JVM 옵션을 통해 자바 애플리케이션의 성능을 개선할 수 있습니다. 예를 들어, Just-In-Time 컴파일러의 동작을 조절하는 옵션, 시스템 프로퍼티를 설정하는 옵션 등이 있습니다.
  4. 프로파일링 도구 사용: JVM의 동작을 모니터링하고 분석하는 데는 다양한 프로파일링 도구(JVisualVM, JProfiler 등)를 사용할 수 있습니다. 이 도구들을 통해 JVM의 성능 문제를 파악하고, 이를 개선하는 방법을 찾을 수 있습니다.

그러나 JVM 튜닝과 최적화는 매우 복잡한 작업이며, 애플리케이션의 성능을 개선하기 위해 적절한 지식과 경험이 필요합니다. 특히 프로덕션 환경에서는 JVM 튜닝과 최적화 작업이 잘못되면 애플리케이션의 성능을 저하시키거나, 예상치 못한 문제를 발생시킬 있으므로 주의가 필요합니다.

 

13. 여러 JVM 구현체 중 Oracle의 HotSpot과 OpenJDK의 차이점에 대해 알고 있나요?

Oracle의 HotSpot과 OpenJDK는 모두 Java의 실행을 위한 가장 널리 사용되는 JVM 구현체입니다. 두 가지 모두 Java SE 스펙을 준수하며, Java 애플리케이션을 실행하기 위한 핵심 기능을 제공합니다.

  1. 라이선싱: OpenJDK GPL 라이센스 하에 배포되는 오픈 소스 프로젝트입니다. 이는 무료로 사용할 있으며 소스 코드를 자유롭게 있다는 것을 의미합니다. 한편, Oracle HotSpot JVM Oracle Binary Code License Agreement 따라 사용이 허가됩니다. 라이선스는 Oracle JDK 11 이후로 상업적인 목적의 사용에 대해 유료로 변경되었습니다.
  2. 기능과 플러그인: JVM 모두 Java SE 스펙을 준수하므로 기본적인 기능은 동일합니다. 그러나 Oracle JVM 추가적인 몇몇 상용 기능을 포함하고 있습니다. 예를 들어, Java Flight Recorder Java Mission Control 같은 고급 진단 도구들이 있습니다. OpenJDK에는 이런 추가적인 도구들이 포함되어 있지 않지만, 대부분의 경우 이런 도구들 없이도 충분히 사용할 있습니다.
  3. 성능: JVM 성능은 거의 동일하다고 있습니다. 고급 최적화 기술, JIT 컴파일, 그리고 세련된 가비지 컬렉션 알고리즘을 사용합니다. 그러나 이들 간의 성능 차이는 특정 애플리케이션의 특성에 따라 다소 다를 있습니다.
  4. 업데이트 주기와 지원: Oracle HotSpot 대해 주기적인 업데이트와 보안 패치를 제공하며, 이는 종종 유료 서비스로 제공됩니다. 반면에 OpenJDK 빈번하게 업데이트되며, 보안 패치도 커뮤니티에서 제공합니다. 이러한 차이는 OpenJDK Oracle JVM 선택하는 중요한 요소가 있습니다.

14. Garbage Collector의 역할, 원리, 알고리즘에 대해 아는 만큼 설명해주실 수 있을까요?(GC 동작 방식)

Garbage Collector(GC)의 기본적인 역할은 더 이상 사용되지 않는 메모리를 자동으로 회수하여 메모리 누수를 방지하고, 이로 인해 생길 수 있는 애플리케이션 성능 저하를 막는 것입니다.

Garbage Collector의 기본 원리는 'Reachability(접근성)'입니다. 즉, 어떤 객체가 루트로부터 연결되어 있지 않으면, 그 객체는 더 이상 사용되지 않는 것으로 간주되어 가비지 컬렉션의 대상이 됩니다. 여기서 루트는 GC가 추적을 시작하는 시작점으로, 일반적으로 스택에 위치한 지역 변수나 액티브 스레드, static 필드 등을 말합니다.

GC의 주요 알고리즘은 다음과 같습니다:

  1. Mark and Sweep: GC 먼저 모든 루트로부터 시작하여 사용되고 있는 객체를 찾아내 '마킹'합니다. 다음에, 마킹되지 않은 객체들을 메모리에서 '스위핑'하여 제거합니다. 알고리즘은 간단하고 효과적이지만, 스위핑 후에 메모리 공간이 분편화될 있다는 단점이 있습니다.
  2. Copying: 알고리즘은 메모리를 부분으로 나눕니다. 하나는 사용 중인 객체를 저장하고, 다른 하나는 비어 있습니다. GC 동작하면 사용 중인 객체 '살아 있는' 객체만 비어 있는 공간으로 복사됩니다. 과정이 끝나면, 이전에 사용하던 공간은 모두 비워집니다. 방식은 메모리 분편화 문제를 해결하지만, 항상 일정량의 메모리 공간이 비어있어야 한다는 단점이 있습니다.
  3. Generational: 알고리즘은 객체가 생존하는 시간에 따라 메모리를 분리합니다. 일반적으로 'young generation' 'old generation'으로 나누는데, 새로 생성된 객체는 young generation 위치하며, 오래동안 생존한 객체는 old generation으로 이동합니다. 대부분의 객체는 빠르게 비활성화되므로, young generation에서 자주 GC 수행하게 됩니다. 방식은 대부분의 모던 JVM에서 사용되는 기본 알고리즘입니다.
    더보기

     가비지 컬렉션(GC)의 알고리즘은 어떻게 메모리에서 불필요한 객체를 찾아내고 제거할지를 결정하는 규칙이나 절차를 말합니다. 이러한 알고리즘은 자원의 효율적인 관리를 위해 매우 중요하며, 프로그래밍 언어나 시스템의 메모리 관리 전략에 따라서 다르게 구현될 수 있습니다.

    앞서 언급한 'Mark and Sweep', 'Copying', 'Generational' 등의 알고리즘은 가비지 컬렉션의 기본적인 알고리즘입니다.

    • 'Mark and Sweep'은 사용되고 있는 객체를 '마킹'하고, 사용되지 않는 객체를 '스위핑'하는 방식을 사용합니다.
    • 'Copying'은 사용되는 객체와 사용되지 않는 객체를 분리하기 위해 메모리를 두 부분으로 나누는 방식을 사용합니다.
    • 'Generational'은 객체의 생존 시간에 따라 메모리를 관리하는 방식을 사용합니다.

    각각의 알고리즘은 자체로서 중요한 가치를 가지고 있지만, 실제로는 이러한 기본 알고리즘들이 조합되거나 변형되어서 사용되는 경우가 많습니다. 예를 들어, 'Generational' 알고리즘 내에서 'Mark and Sweep'이나 'Copying' 등의 방식이 적용될 있습니다. 이런 방식으로, GC 알고리즘은 주어진 상황에 가장 효과적인 메모리 관리를 있도록 설계되고 개선되어 왔습니다.

실제로 GC 동작 방식은 JVM의 종류와 버전, 그리고 설정에 따라 다소 달라질 수 있습니다. 각 GC 알고리즘은 특정 애플리케이션의 특성에 따라 더 잘 동작하거나 그렇지 않을 수 있습니다. 예를 들어, 사용자 응답 시간이 중요한 애플리케이션의 경우 'Stop-The-World' 시간을 최소화하는 GC 알고리즘을 선택할 수 있습니다. 'Stop-The-World'는 GC가 작동하는 동안 애플리케이션의 실행을 일시 중지하는 시간을 말합니다.Java 11에서는 ZGC(Z Garbage Collector)가 소개되었는데, ZGC는 'Stop-The-World' 시간을 거의 없애는 것을 목표로 하는 GC입니다. ZGC는 메모리의 분편화를 방지하기 위해 객체를 이동시킬 수 있으며, 이 과정을 병렬로 처리하여 GC 성능을 향상시킵니다. 다양한 GC 알고리즘에 대해 알아보고, 애플리케이션의 특성과 요구 사항에 가장 적합한 GC 알고리즘을 선택하고 튜닝하는 것은 JVM 성능 최적화의 중요한 부분입니다. Java 9부터 G1(Garbage-First) GC가 기본 GC로 사용됩니다. G1 GC는 메모리를 여러개의 동일한 크기의 Region으로 나누고, GC 동작 시에는 가장 가비지가 많이 쌓인 Region부터 처리하는 방식을 사용합니다. 이 방식을 통해 GC 효율을 높이고, 'Stop-The-World' 시간을 줄일 수 있습니다.

 

자바 가비지 컬렉터(GC)의 동작 방식

  1. 마킹(Marking): 가비지 컬렉터는 먼저 어떤 객체들이 "살아있는" 객체인지 파악하기 위해 모든 객체를 훑습니다. 이 과정에서 GC는 루트(root)라 불리는 객체들(로컬 변수와 입력 매개변수, 실행 중인 메소드, 네이티브 스택, 클래스 등)부터 시작해 참조를 따라가면서 마킹을 합니다.
  2. 삭제(Sweeping): 마킹 후에 GC 메모리에서 마킹되지 않은 객체들을 제거합니다. 이들은 프로그램에 의해 이상 참조되지 않는 객체들로서, 가비지라고 있습니다.
  3. 콤팩션(Compaction): 가비지 컬렉션 후에 메모리에는 여전히 사용 중인 객체와 이상 사용되지 않는 객체들이 섞여 있을 있습니다. 콤팩션은 이러한 메모리 공간을 재정렬하여 연속적인 공간을 만드는 과정입니다. 과정은 메모리 단편화를 방지하며, 새로운 객체들이 메모리에 할당될 있도록 합니다.

자바 가비지 컬렉션(GC) 알고리즘에는 여러 종류가 있습니다. 여기에는 Serial GC, Parallel GC, CMS (Concurrent Mark Sweep) GC, 그리고 G1(Garbage First) GC 등이 포함되며, 각각의 알고리즘은 특정 유형의 애플리케이션에 더 적합하다고 볼 수 있습니다.

  1. Serial GC - 이 GC는 단일 스레드에서 작동하며, 모든 GC 과정(Mark, Sweep, Compact)을 수행하는 동안 애플리케이션 스레드를 일시 중지합니다. 주로 단일 프로세서 시스템이나 소규모 애플리케이션에 사용됩니다.
  2. Parallel GC - 이 GC는 여러 GC 스레드를 사용하여 가비지 컬렉션 작업을 병렬로 수행합니다. 그러나 마찬가지로 GC 작업이 진행되는 동안에는 애플리케이션 스레드를 일시 중지합니다. 이 GC는 멀티 프로세서 시스템이나 CPU 리소스가 많은 시스템에 적합합니다.
  3. CMS (Concurrent Mark Sweep) GC - 이 GC는 애플리케이션 스레드와 병렬로 작동하여 가비지 컬렉션의 대부분 과정을 처리합니다. 이로 인해 애플리케이션의 중단 시간이 줄어들지만, CPU 리소스를 더 많이 소모합니다. 또한, 메모리 단편화 문제가 발생할 수 있습니다.
  4. G1 (Garbage First) GC - 이 GC는 메모리를 여러 개의 작은 영역으로 나누고, 이 영역들 중에서 가비지가 가장 많은 영역부터 수집하는 방식으로 작동합니다. 이로 인해 GC의 효율성과 성능을 개선하며, 중단 시간을 예측 가능하게 만들 수 있습니다.

위에서 언급한 GC 알고리즘 외에도 ZGC(Z Garbage Collector), Shenandoah 등과 같은 알고리즘들이 있으며, 이들은 특히 대용량 힙을 가진 애플리케이션에 사용됩니다. 이들 알고리즘은 GC 중단 시간을 최소화하려는 목적으로 설계되었습니다.

1. 동기와 비동기를 비교하여 설명해주실 수 있을까요?

"동기(synchronous)"와 "비동기(asynchronous)"라는 용어는 컴퓨터 프로그래밍에서 매우 중요한 개념으로, 함수의 호출 방식을 설명하는데 주로 사용됩니다.

  • 동기(synchronous) 방식은 호출된 함수의 결과를 기다리고, 그 함수가 작업을 완료하고 제어를 호출자에게 반환한 후에야 호출자가 다음 작업을 수행할 수 있습니다. 즉, 한 작업이 완료될 때까지 다음 작업이 대기 상태에 있어야 하는 방식입니다.
    예를 들어, 파일을 읽는 동기적인 작업에서는 파일이 완전히 읽히기 전까지 프로그램이 다른 작업을 수행하지 못하고 대기하게 됩니다. 이러한 방식은 구현이 비교적 간단하지만, 대기 시간 동안 프로그램이 멈춰 있어 효율성이 떨어질 수 있습니다.
  • 반면에, 비동기(asynchronous) 방식은 함수를 호출한 후 그 결과를 기다리지 않고 바로 다음 작업을 수행합니다. 호출된 함수는 작업이 완료되면 적절한 방식(예: 콜백 함수, 프로미스, 이벤트 등)으로 호출자에게 작업 완료를 알립니다.
    이 방식은 동시에 여러 작업을 수행할 수 있어 효율성이 높습니다. 그러나 비동기적인 작업의 제어 흐름을 관리하는 것이 복잡하고, 코드를 이해하고 디버깅하기 어려울 수 있습니다.

요약하면동기 방식은 작업의 순서를 보장하며 코드가 단순하고 이해하기 쉬우나대기 시간 동안 다른 작업을 수행할  없어 효율성이 떨어질  있습니다반면비동기 방식은 여러 작업을 동시에 수행할  있어 효율성이 높지만작업의 순서를 보장하기 어렵고 코드가 복잡해질  있습니다.

 

동기는 데이터의 요청과 결과가 한 자리에서 동시에 일어나는것을 말합니다. 사용자가 데이터를 서버에게 요청한다면 그 서버가 데이터 요청에 따른 응답을 사용자에게 다시 리턴해주기 전까지 사용자는 다른 활동을 할 수 없으며 기다려야만합니다. 비동기는 동시에 일어나지 않는다는 의미입니다. 서버에게 데이터를 요청한 후 요청에 따른 응답을 계속 기다리지 않아도되며 다른 외부 활동을 수행하여도되고 서버에게 다른 요청사항을 보내도 상관없습니다.

동기는 순차적으로 작업이 수행될 때 앞의 작업이 수행 중이면 다음 작업은 대기하게 되고, 요청에 대한 값이 반환되기 전까지는 blocking되어 있는 방식을 의미한다. 비동기는 요청에 대한 값이 반환되지 않더라도 non-blocking 상태로 이벤트 큐에 넣거나 백그라운드 스레드에게 해당 task를 위임하고 바로 다음 코드가 진행되는 작업이다.

 

동기는 요청과 그 결과가 동시에 일어나는 것을 의미하므로, 요청을 하면 시간이 얼마나 걸리든, 요청한 자리에서 결과가 주어져야 함을 의미합니다. 비동기는 요청과 그 결과가 동시에 일어나지 않는다는 의미입니다. 동기 방식은 설계가 매우 간단하고 직관적이지만, 결과가 주어질 때까지 아무것도 못하고 대기해야 한다는 단점이 존재하고, 비동기 방식은 동기보다 복잡하지만 결과가 주어지는데 시간이 걸리더라도 그 시간 동안 다른 작업을 함으로써 자원을 효율적으로 사용할 수 있다는 장점이 존재합니다.

 

동기적 처리(Synchronous)는 코드가 위에서부터 아래로 내려오면서 하나가 끝나면 다음 코드가 실행되는 방식을 말한다. 동기는 디자인이 비동기보다 간단하고 직관적이지만 결과가 주어질 때 까지 아무것도 못하고 대기해야하는 문제가 있다 비동기 처리(Asynchronous)는 동기적 코드가 전부 실행 되고나서 값을 반환한다. 비동기는 동기보다 복잡하지만 결과가 주어지는데 시간이 걸려도 그 시간동안 다른 작업을 할 수 있어서 보다 효율적일 수 있다.

 

2. 동기와 비동기의 개념을 설명해주세요.

동기(Synchronous)와 비동기(Asynchronous)는 작업의 실행 방식을 나타내는 개념입니다.

  • 동기(Synchronous) 방식은 한 작업이 완료될 때까지 기다렸다가 다음 작업을 실행하는 방식입니다. 이 방식을 사용하면 실행 순서를 예측하기 쉽지만, 앞의 작업이 끝나기를 기다려야 하기 때문에 시스템의 전체적인 효율이 떨어질 수 있습니다.
  • 비동기(Asynchronous) 방식은 작업이 끝나는 것을 기다리지 않고 다음 작업을 즉시 실행하는 방식입니다. 방식은 여러 작업을 동시에 진행할 있기 때문에 시스템의 효율성을 높일 있습니다. 하지만 작업의 완료 순서를 예측하기 어렵고, 동기 방식보다 복잡한 코드를 작성해야 있습니다.

3. 동기와 비동기 방식의 장단점은 무엇인가요?

동기 방식의 장점:

  1. 설계가 단순하고 이해하기 쉽습니다. 요청과 응답이 순차적으로 이루어지므로, 각 과정을 순서대로 파악하기 쉽습니다.
  2. 데이터의 정합성이 보장됩니다. 요청이 완료될 때까지 다른 작업을 수행하지 않으므로, 동시성 문제를 방지할 수 있습니다.

동기 방식의 단점:

  1. 요청이 완료될 때까지 시스템 자원이 대기 상태로 낭비될 수 있습니다. 이로 인해 전체 시스템의 성능이 저하될 수 있습니다.
  2. 하나의 요청이 지연될 경우 이후의 요청들도 모두 지연되는 병목 현상이 발생할 수 있습니다.

비동기 방식의 장점:

  1. 시스템 자원을 효율적으로 활용할 수 있습니다. 요청이 완료되는 것을 기다리지 않고 다음 작업을 수행할 수 있으므로, 시스템 자원을 놀리지 않고 사용할 수 있습니다.
  2. 여러 요청을 동시에 처리할 수 있으므로, 동기 방식에 비해 빠른 응답 시간을 제공할 수 있습니다.

비동기 방식의 단점:

  1. 설계가 복잡하고 디버깅이 어려울 있습니다. 비동기 방식은 동시에 여러 작업을 처리하므로, 데이터의 동기화 문제나 데드락 같은 동시성 문제가 발생할 가능성이 있습니다.
  2. 요청의 순서가 보장되지 않습니다. 이로 인해 결과가 예측과 다르게 나올 있으며, 이를 관리하기 위한 추가적인 로직이 필요할 있습니다.

4. 동기 방식과 비동기 방식을 사용하면 어떤 상황에서 어떤 차이가 발생하는지 예를 들어 설명해주세요.

동기 방식과 비동기 방식의 차이를 설명하는 가장 간단한 예는 웹서버에 데이터를 요청하는 상황입니다.

  1. 동기 방식의 예:
  2. 웹서버에 데이터를 요청하고, 해당 요청이 완료될 때까지 기다리는 것입니다. 즉, 사용자는 요청한 데이터가 도착하기 전까지는 다른 작업을 수행할 수 없습니다. 예를 들어, 웹 페이지에서 이미지를 로딩하는 동안 사용자는 다른 행동을 할 수 없고, 화면은 멈춘 상태가 됩니다.
  3. 비동기 방식의 예:
  4. 웹서버에 데이터를 요청하고, 요청이 완료되기를 기다리지 않고 바로 다음 작업을 진행하는 것입니다. 즉, 데이터가 도착하면 그때 처리하는 방식입니다. 예를 들어, 웹 페이지에서 이미지를 로딩하는 동안에도 사용자는 다른 작업을 계속 진행할 수 있으며, 이미지는 나중에 도착하면 그때 화면에 표시됩니다.

따라서 동기 방식은 간단하고 순차적인 작업에 적합하며, 비동기 방식은 복잡하고 시간이 걸리는 작업에 적합합니다. 동기 방식은 설계가 간단하지만, 전체 시스템의 효율을 떨어뜨릴 있습니다. 반면 비동기 방식은 설계가 복잡하지만, 시스템의 효율을 높이는 도움이 됩니다.

 

5. 동기적으로 처리해야 하는 작업을 비동기적으로 처리하면 어떤 문제가 발생할 수 있는지 설명해주세요.

동기적으로 처리해야 하는 작업을 비동기적으로 처리하게 되면, 데이터의 일관성과 순서 등이 보장되지 않을 수 있습니다. 이는 특히 작업의 순서가 중요한 경우나 다른 작업의 결과에 의존하는 경우에 문제가 될 수 있습니다.

예를 들어, 사용자 정보를 데이터베이스에 저장하고 바로 그 정보를 이용해 추가적인 작업을 수행하는 경우를 생각해보겠습니다. 만약 이 두 작업이 비동기적으로 처리된다면, 두 번째 작업이 첫 번째 작업이 완료되기 전에 시작될 수 있습니다. 결과적으로는 아직 데이터베이스에 저장되지 않은 사용자 정보를 참조하려는 상황이 발생할 수 있고, 이는 오류를 발생시키거나 부정확한 결과를 초래할 수 있습니다.

따라서 동기적으로 처리되어야 작업을 비동기적으로 처리하면, 의도치 않은 사이드 이펙트를 초래할 있으며, 이는 프로그램의 복잡도를 높이고 버그를 발생시킬 있습니다. 이러한 이유로, 작업의 성격과 요구사항에 따라 동기적인 처리와 비동기적인 처리를 적절하게 선택하고 사용해야 합니다.

 

6. 동기와 비동기 방식의 선택의 기준

  1. 작업의 성격: 작업이 순차적으로 실행되어야 하며 이전 작업의 결과에 다음 작업이 의존하는 경우, 동기 방식이 적합합니다. 반면, 여러 작업이 독립적으로 실행되고, 이들 간에 상호 의존성이 없는 경우 비동기 방식이 적합합니다.
  2. 자원의 효율적 사용: 비동기 방식은 IO-bound 작업(: 네트워크 요청, 디스크에 데이터 쓰기 ) 적합합니다. 이러한 작업은 대기 시간이 긴데, 동기 방식으로 처리하면 대기 시간 동안 CPU 일을 하지 않고 놀게 됩니다. 반면, 비동기 방식으로 처리하면 대기 시간 동안 다른 작업을 수행할 있어 자원을 효율적으로 활용할 있습니다.
  3. 복잡도와 버그: 동기 코드는 순차적으로 실행되기 때문에 이해하고 디버그하기 쉽습니다. 반면, 비동기 코드는 복잡도가 높아져 이해하거나 디버그하기 어려울 있습니다.
  4. 응답 시간과 사용자 경험: 사용자 인터페이스를 다루는 작업의 경우, 작업이 오래 걸리면 사용자 경험에 악영향을 있습니다. 이럴 때는 비동기 방식을 사용해 UI 멈추지 않도록 있습니다.

7. Java에서 동기 및 비동기 처리를 하는 방법

동기 처리:
동기 처리는 기본적으로 코드가 순차적으로 실행되는 방식입니다. 작업이 끝날 때까지 다음 작업은 기다려야 합니다.

void doSomething() {
    task1(); // task1이 완료될 때까지 기다림
    task2(); // task1이 완료되면 task2를 실행
}

비동기 처리:
Java에서 비동기 처리를 하는 방법은 여러 가지가 있습니다.

Thread 사용: Java에서는 Thread 클래스를 사용하여 비동기 처리를 있습니다.

Thread thread = new Thread(() -> {
    // 비동기로 처리할 작업
});
thread.start(); // 새로운 스레드에서 작업을 시작

ExecutorService 사용: ExecutorService 스레드 풀을 관리하는데 사용되며, 작업을 비동기로 실행할 있게 합니다.

ExecutorService executor = Executors.newFixedThreadPool(4); // 4개의 스레드를 가진 스레드풀 생성
executor.submit(() -> {
    // 비동기로 처리할 작업
});

CompletableFuture 사용: CompletableFuture Java 8 도입된 기능으로, 비동기 연산을 표현하고 컴포즈하는 사용됩니다.

CompletableFuture.supplyAsync(() -> {
    // 비동기로 처리할 작업
}).thenAccept(result -> {
    // 작업이 완료된 후 수행할 작업
});

8. 싱글 스레드와 멀티 스레드에서 동기와 비동기 처리 방식이 어떻게 다른지 설명해주세요.

1. 싱글 스레드 환경:

  • 동기 처리: 작업이 순차적으로 실행됩니다. 한 작업이 완료되어야만 다음 작업이 실행될 수 있습니다. 따라서 I/O 작업이나 네트워크 호출 등 블로킹 작업이 일어나면, 그 작업이 완료될 때까지 프로그램 전체가 대기 상태에 빠질 수 있습니다.
  • 비동기 처리: 비동기 처리를 사용하면, 블로킹 작업이 발생하더라도 해당 작업을 기다리는 동안 다른 작업을 실행할 수 있습니다. 블로킹 작업이 완료되면 콜백 함수를 통해 결과를 처리합니다.

2. 멀티 스레드 환경:

  • 동기 처리: 동기 처리 방식은 여전히 한 작업이 완료될 때까지 다음 작업이 기다려야 합니다. 하지만 멀티 스레드 환경에서는 여러 작업을 동시에 실행할 수 있으므로, 한 스레드가 블로킹 작업에 빠져 있더라도 다른 스레드는 계속 작업을 진행할 수 있습니다.
  • 비동기 처리: 스레드는 독립적으로 비동기 작업을 처리할 있습니다. 스레드에서 블로킹 작업이 발생하더라도, 스레드만 대기 상태가 되고, 다른 스레드는 계속해서 작업을 진행할 있습니다. 또한, 비동기 처리 방식을 사용하면, 블로킹 작업이 완료되는 시점에 콜백 함수를 통해 결과를 처리할 있습니다.

9. 비동기 프로그래밍을 구현할 때 어떤 문제점을 경험했고, 어떻게 해결했는지 설명해주세요.

1. Race Condition

비동기 프로그래밍에서 여러 스레드가 동시에 데이터에 접근하게 되면, 데이터의 일관성이 깨질 수 있는데 이를 '경쟁 상태(Race Condition)'라고 합니다.

해결방법: 이를 해결하기 위해 동기화(synchronization) 메커니즘이나 락(locking) 기법 등을 사용할 수 있습니다. 예를 들어, 자바에서는 synchronized 키워드를 이용해 특정 부분의 코드에 대한 동시 접근을 제한할 수 있습니다.

2. Deadlock

두 개 이상의 프로세스나 스레드가 서로 상대방의 작업이 끝나기만을 기다리고 있어 결과적으로 아무것도 완료되지 못하는 상태를 '데드락(Deadlock)'이라고 합니다.

해결방법: 데드락을 피하는 방법 중 하나는 자원에 대한 접근 순서를 정해주는 것입니다. 또는 '락 타임아웃'을 설정하여 일정 시간이 지나면 자동으로 락이 해제되게 하는 방법도 있습니다.

3. Callback Hell

비동기 프로그래밍에서 콜백 함수를 너무 많이 사용하면 코드의 가독성이 떨어지는 문제가 발생하는데 이를 '콜백 지옥(Callback Hell)'이라고 합니다.

해결방법: 자바 8부터 지원하는 CompletableFuture 연속적인 비동기 작업을 간결하게 표현할 있게 도와줍니다. 또는 함수형 프로그래밍 패러다임을 이용하면 콜백 지옥 문제를 해결할 있습니다.

 

10. 프로젝트에서 어떤 기능을 구현하면서 동기와 비동기 방식 중 어느 것을 선택했는지, 그 이유는 무엇인지 설명해주세요.

스프링 부트 프로젝트에서는 HTTP 요청을 처리하는 웹 애플리케이션을 개발하였습니다. 사용자의 요청을 처리하는 동안, 서버는 여러 외부 서비스에 데이터를 요청하거나 보내야하는 상황이 있었습니다. 이 경우, 동기적 방식으로 구현하면 사용자의 요청을 처리하는 동안 외부 서비스에 대한 요청을 기다려야하기 때문에 전체적인 응답 시간이 증가하게 됩니다.

따라서 이러한 상황에서는 비동기 방식을 선택했습니다. 외부 서비스로의 요청을 비동기적으로 처리하면, 서버는 다른 작업을 계속 처리할 수 있으며, 외부 서비스의 응답을 기다리는 동안 사용자의 요청을 더 빨리 처리할 수 있습니다.

Java에서는 CompletableFuture를 이용하여 비동기 처리를 할 수 있으며, 스프링에서는 @Async 어노테이션을 사용하여 비동기 메서드를 쉽게 구현할 수 있습니다. 이렇게 비동기적으로 처리하면 서버의 자원을 효율적으로 활용하면서 동시에 여러 요청을 빠르게 처리할 수 있습니다.

그러나, 모든 요청이 비동기적으로 처리되어야 하는 것은 아닙니다. 일부 요청에서는 외부 서비스로부터의 응답을 받아야 다음 작업을 수행할 있는 경우가 있을 있습니다. 이러한 경우에는 동기적 방식을 선택하여 요청과 응답이 순차적으로 이루어지도록 필요가 있습니다. 이처럼 동기와 비동기 방식의 선택은 구현하려는 기능의 특성과 요구사항에 따라 달라집니다.

 

11. 이벤트 루프(Event Loop)와 이벤트 드리븐(Event-Driven) 프로그래밍에 대해 설명해주세요.

12. Promise나 Async/Await와 같은 비동기 처리 패턴에 대해 설명해주세요

1. 프로세스와 스레드를 비교하여 설명해주실 수 있을까요?

프로세스와 스레드는 컴퓨터에서 프로그램을 실행하는데 사용되는 기본 단위입니다. 

프로세스(Process):

  • 프로세스는 실행 중인 프로그램의 인스턴스로, 컴퓨터에서 독립적으로 실행되는 프로그램 단위입니다.
  • 각 프로세스는 독립된 메모리 공간(코드, 데이터, 스택, 힙 영역 등)을 가지며, 프로세스는 이 메모리 공간을 접근하거나 수정할 수 있습니다. 프로세스는 하나 이상의 스레드를 가질 수 있으며, 이러한 스레드들은 프로세스의 메모리와 자원을 공유합니다.
  • 프로세스 간의 메모리 공간은 독립적이므로, 하나의 프로세스에서 발생한 오류가 다른 프로세스에 영향을 미치지 않습니다.

스레드(Thread):

  • 스레드는 프로세스 내에서 실행되는 하나의 실행 단위로, 프로세스의 메모리 공간을 공유합니다.
  • 이 공유된 메모리 공간 때문에, 스레드는 같은 프로세스 내의 다른 스레드와 정보를 쉽게 공유할 수 있습니다.
  • 하지만 이것은 스레드간 데이터 동기화를 주의깊게 관리해야한다는 단점도 생깁니다.
  • 또한 하나의 스레드에서 발생한 오류가 전체 프로세스에 영향을 미칠 수 있습니다.

결국, 프로세스와 스레드 모두 실행 중인 프로그램의 단위이지만, 스레드는 프로세스 내에서 실행되며 동일한 메모리 공간을 공유하는 반면, 프로세스는 독립적인 메모리 공간을 가지고 있습니다. 이러한 차이점은 멀티태스킹과 병렬 컴퓨팅 구현에서 중요한 역할을 합니다.

 

프로세스는 운영체제로부터 자원을 할당받은 작업의 단위입니다. 스레드는 프로세스가 할당받은 자원을 이용하는 실행 흐름의 단위입니다. 둘의 가장 큰 차이는 프로세스는 메모리에 올라갈 때 운영체제로부터 시스템 자원을 할당받는데 운영체제는 프로세스마다 각각 독립된 메모리 영역을, Code/Data/Stack/Heap의 형식으로 할당해준다. 그러나 스레드는 프로세스가 할당받은 메모리 영역 내에서 Stack 형식으로 할당된 메모리 영역은 따로 할당받고, 나머지 Code/Data/Heap 형식으로 할당된 메모리 영역을 공유한다. 따라서 각각의 스레드는 별도의 스택을 가지고 있지만 힙 메모리는 서로 읽고 쓸 수 있게 됩니다. 만약 한 프로세스가 실행하다가 오류가 발생하면 프로세스가 강제종료된다면 다른 프로세스에는 영향을 주지 않지만 스레드는 오류로 종료가 된다면 메모리 영역을 공유하고있는 스레드 모두 강제로 종료된다는 중요한 차이점이 존재합니다.

 

프로세스란 실행 중인 프로그램으로, 1개의 CPU가 한 번에 한 프로세스만을 실행할 수 있고 프로세스 간 데이터 접근이 불가하다는 특징이 있다. 스레드란 한 개의 프로세스 안에 여러 개의 프로세스를 동시에 생성 및 실행 가능한 구조다. 프로세스 안에서는 모든 스레드끼리 데이터에 접근이 가능하다.프로세스를 한꺼번에 여러 개 실행하는 것처럼 보이기 위해 CPU는 레지스터의 도움을 받아서 사용자가 느끼지 못할 정도로 빠른 속도로 프로세스를 바꾸어 가며 실행한다. 스레드는 그럴 필요가 없다. 한 프로세스 내에서 여러 프로그램을 실행 가능한 구조이기 때문이다.

 

어떤 작업을 위해 실행할 수 있는 파일인 프로그램이 메모리에 올라와 실행되고 있는 것을 프로세스라고 한다. 프로세스는 독립된 메모리 영역을 갖고 있으며 하나의 프로세스 내에서 실제로 작업을 수행하며 동시에 여러작업을 수행하도록 할 수 있게 만드는 주체가 바로 스레드이다. 이 때 프로세스와 스레드가 cpu를 나눠 쓰는 것을 멀티태스킹이라고 하며, 한 프로세스에 두 개 이상의 스레드가 존재하면 멀티스레딩, 사용하는 코어가 두 개 이상이면 멀티프로세싱 이라고 한다.

 

프로세스는 운영체제로부터 자원을 할당받는 작업의 단위입니다. 스레드는 할당 받은 자원을 이용하는 실행의 단위이며, 프로세스 내에 여러 개가 생길 수 있습니다. 어플리케이션은 하나의 프로세스이고, 그 안에서 처리되는 작업들이 스레드가 됩니다.

 

2. 프로세스와 스레드의 차이점을 설명해보세요.

프로세스(Process)와 스레드(Thread)는 모두 컴퓨터에서 실행되는 작업의 단위입니다. 그러나 둘 사이에는 몇 가지 주요한 차이점이 있습니다.

  1. 독립성: 프로세스는 운영 체제에서 독립적으로 실행되는 개별적인 작업 단위입니다. 각 프로세스는 고유한 메모리 공간과 자원을 할당받아 사용합니다. 반면에 스레드는 프로세스 내부에서 실행되는 작업의 단위로, 부모 프로세스의 메모리 공간과 자원을 공유하며 실행됩니다.
  2. 메모리 공유: 프로세스들은 각각 독립된 메모리 공간을 가지며, 한 프로세스의 메모리 공간은 다른 프로세스로부터 보호됩니다. 그러나 스레드는 같은 프로세스 내부에서 실행되므로, 스레드끼리 메모리 공간(Heap 영역 등)을 공유하게 됩니다.
  3. 생성과 종료: 프로세스를 생성하거나 종료하는 것은 상당히 많은 자원을 소모하는 작업입니다. 반면에 스레드의 생성 및 종료는 상대적으로 더 빠르고 자원을 적게 소모합니다.
  4. 컨텍스트 스위칭: 프로세스간 컨텍스트 스위칭은 많은 시간과 자원을 소모합니다. 그러나 스레드간 컨텍스트 스위칭은 더 빠르며 효율적입니다. 이는 스레드가 같은 프로세스 내부에서 메모리를 공유하기 때문입니다.
  5. 통신: 프로세스 간 통신(IPC)는 복잡한 메커니즘을 통해 이루어지며, 상대적으로 많은 자원을 소모합니다. 반면에 스레드 간 통신은 프로세스 내부에서 메모리를 공유하기 때문에 더 쉽고 빠릅니다.

따라서, 프로세스와 스레드의 선택은 실행하려는 작업의 특성과 요구 사항에 따라 달라집니다. 여러 작업이 서로 독립적이고 각각 다른 자원을 사용해야 하는 경우에는 프로세스를, 작업이 메모리와 자원을 공유하며 효율적으로 실행되어야 하는 경우에는 스레드를 사용하는 것이 좋습니다.

 

3. 멀티프로세싱과 멀티스레딩의 차이점에 대해 설명해주세요.

멀티프로세싱과 멀티스레딩은 컴퓨터 시스템에서 동시성을 구현하는 두 가지 주요한 방법입니다. 둘 다 여러 작업을 동시에 처리하도록 설계되어 있지만, 그 방식과 세부적인 특성에는 차이가 있습니다.

  1. 멀티프로세싱(Multiprocessing) 여러 프로세서(또는 다중 코어 시스템에서의 여러 코어) 동시에 여러 프로세스를 실행하도록 하는 방식입니다. 프로세스는 독립적인 메모리 공간을 가지며, 프로세서가 프로세스를 처리합니다. 멀티프로세싱은 프로세스가 독립적으로 실행되기 때문에 병렬성이 높고, 하나의 프로세스가 실패해도 다른 프로세스에 영향을 주지 않는 안정성을 제공합니다. 그러나 프로세스 간의 통신과 동기화는 복잡하며 자원을 많이 소모합니다.
  2. 멀티스레딩(Multithreading) 단일 프로세스 내에서 여러 스레드를 동시에 실행하도록 하는 방식입니다. 이들 스레드는 프로세스의 메모리와 자원을 공유하며, 스레드는 프로세스 내의 특정 작업을 담당합니다. 멀티스레딩은 스레드 생성, 컨텍스트 스위칭 등에 소비되는 오버헤드가 적으며, 스레드 간의 통신과 동기화도 상대적으로 간단합니다. 하지만 스레드들이 같은 메모리 공간을 공유하기 때문에, 하나의 스레드에서 발생한 문제가 다른 스레드에 영향을 미칠 있습니다.

다중 프로세스와 다중 스레드는 데이터 처리 장소에 차이가 있습니다.

다중 프로세스는 여러 개의 독립적인 프로세스가 동시에 실행되는 것을 의미합니다. 각 프로세스는 자체적인 주소 공간을 가지고 있으며, 운영체제는 각 프로세스를 독립적으로 관리합니다. 따라서 각 프로세스는 데이터를 자체적으로 보유하고 처리하는 독립된 공간을 가지고 있습니다. 이는 각 프로세스 간의 데이터 공유가 어렵고, 프로세스 간의 통신은 별도의 메커니즘을 사용하여 이루어집니다.

다중 스레드는 하나의 프로세스 내에서 여러 개의 스레드가 동시에 실행되는 것을 의미합니다. 스레드는 하나의 주소 공간을 공유하고, 각 스레드는 프로세스의 자원을 공유하여 작업을 수행합니다. 이는 스레드 간의 데이터 공유가 쉽고, 스레드 간의 통신은 메모리 공간을 통해 직접 이루어집니다. 따라서 스레드 간의 데이터 공유와 통신이 편리하며, 동시성 작업을 더 효율적으로 처리할 수 있습니다.

요약하면, 다중 프로세스는 프로세스가 독립된 공간을 가지고 데이터를 처리하고, 프로세스 간의 통신은 별도의 메커니즘을 사용합니다. 다중 스레드는 하나의 프로세스 내에서 스레드가 공유하는 주소 공간을 통해 데이터를 처리하고, 스레드 간의 통신은 메모리 공간을 직접 사용하여 이루어집니다.

https://www.youtube.com/watch?v=y60nIDJAyJQ&t=2s

 

4. 멀티프로그래밍을 구현할 때 CPU가 어떻게 콘텍스트 스위칭을 하는가

멀티프로그래밍에서 CPU는 프로세스 또는 스레드 간에 "콘텍스트 스위칭"을 수행하여 각각의 작업에 CPU 시간을 할당합니다. 이는 여러 프로세스 또는 스레드가 동시에 실행되는 것처럼 보이게 하는 주요 메커니즘입니다.

콘텍스트 스위칭은 다음과 같이 동작합니다:

  1. 현재 실행 중인 프로세스 또는 스레드의 상태(콘텍스트)를 저장합니다. 이 콘텍스트에는 프로그램 카운터, 레지스터 값, 스택 포인터 등의 정보가 포함됩니다.
  2. 이전에 중단된 다른 프로세스 또는 스레드의 콘텍스트를 CPU에 로드합니다. 이로써 해당 프로세스 또는 스레드는 중단되었던 지점부터 다시 실행을 시작할 수 있습니다.
  3. CPU는 새롭게 로드된 프로세스 또는 스레드를 실행합니다.

이런 식으로 CPU 여러 프로세스 또는 스레드 사이를 빠르게 전환하며, 프로세스 또는 스레드가 CPU 독점하고 있는 것처럼 보이게 합니다. 그러나 실제로는 프로세스 또는 스레드는 CPU 시간을 일정한 조각(타임 슬라이스)만큼만 할당받아 사용하게 됩니다. 이렇게 함으로써 CPU 여러 프로세스 또는 스레드를 동시에 실행하는 것처럼 보이게 됩니다.

 

5. 스레드의 문맥 교환(Context Switching)이란 무엇인지 설명해주세요.

문맥 교환(Context Switching)은 운영체제가 CPU의 제어권을 현재 실행 중인 스레드에서 다른 스레드로 넘기는 과정을 말합니다. 이는 운영체제의 스케줄러가 멀티태스킹을 지원하고, 여러 스레드가 동시에 실행될 수 있게 하기 위한 기본적인 메커니즘입니다.

스레드의 문맥 교환은 다음과 같이 이루어집니다:

  1. 현재 실행 중인 스레드의 상태를 저장합니다. 이 상태 정보는 프로그램 카운터, 레지스터 값, 스택 포인터 등이 포함된 스레드의 콘텍스트(Context)입니다.
  2. 다음으로 실행할 스레드의 콘텍스트를 CPU에 로드합니다.
  3. CPU는 새롭게 로드된 스레드를 실행합니다.

, 스레드의 문맥 교환은 하나의 스레드에서 다른 스레드로 제어를 넘기는 과정을 말합니다. 과정에서 스레드의 콘텍스트를 저장하고 로드하는 작업이 필요하며, 이로 인해 오버헤드가 발생할 있습니다. 따라서 문맥 교환은 효율적으로 이루어져야 시스템 성능을 최적화할 있습니다.

 

6. 프로세스와 스레드의 통신 방식에 대해 설명해주세요.

프로세스와 스레드의 통신 방식은 기본적으로 다릅니다.

  • 프로세스 간 통신 (Inter Process Communication, IPC): 프로세스 간 통신은 주로 운영체제가 제공하는 메커니즘을 사용합니다. 서로 독립적인 메모리 공간을 가진 프로세스들이 통신하기 위해서는 별도의 메커니즘을 통해야 합니다. 이러한 메커니즘에는 파이프라인, 메시지 큐, 공유 메모리, 소켓 등이 있습니다. 이러한 IPC 메커니즘을 통해 프로세스들은 데이터를 교환하거나 동기화할 수 있습니다.
    더보기

    "파이프라인", "메시지 큐", "공유 메모리", "소켓"은 모두 프로세스 간 통신(Inter-Process Communication, IPC)을 위한 방법입니다.

    1. 파이프라인 (Pipe): 파이프라인은 프로세스에서 다른 프로세스로 데이터를 전송하는 방법 하나입니다. 파이프라인은 일반적으로 '부모-자식' 관계에 있는 프로세스들 사이에서 사용됩니다. 파이프라인은 단방향 통신이며, 데이터는 FIFO(First-In-First-Out) 방식으로 전달됩니다.
    2. 메시지 (Message Queue): 메시지 큐는 프로세스 또는 스레드가 메시지의 형태로 데이터를 전송할 있도록 합니다. 메시지 큐는 파이프라인과 마찬가지로 FIFO 방식을 따르지만, 프로세스 간의 관계에 구애받지 않고 양방향 통신이 가능합니다.
    3. 공유 메모리 (Shared Memory): 공유 메모리는 이상의 프로세스가 동일한 메모리 공간을 공유하여 데이터를 주고받는 방식입니다. 공유 메모리를 통한 통신은 매우 빠르지만, 동시에 여러 프로세스가 메모리에 접근할 경우 데이터 일관성을 유지하기 위한 동기화가 필요합니다.
    4. 소켓 (Socket): 소켓은 네트워크 통신을 위한 인터페이스입니다. 프로세스 통신 뿐만 아니라, 다른 머신에 있는 프로세스와도 통신이 가능합니다. TCP/IP 프로토콜을 기반으로 데이터를 송수신합니다. 이는 인터넷을 통한 원격 프로세스 통신에 주로 사용됩니다.
  • 스레드 간 통신: 스레드는 같은 프로세스 내에서 동작하므로 동일한 메모리 영역을 공유합니다. 따라서 하나의 스레드가 변경한 메모리 상의 데이터는 다른 스레드에서도 접근 가능합니다. 이런 특성으로 인해 스레드 간의 통신은 메모리를 공유함으로써 이루어집니다. 하지만 이로 인해 동시성 문제가 발생할 수 있으며, 이를 해결하기 위해 뮤텍스(Mutex), 세마포어(Semaphore) 등의 동기화 기법을 사용합니다.
    더보기

    "뮤텍스(Mutex)"와 "세마포어(Semaphore)"는 여러 프로세스 또는 스레드가 공유 자원에 동시에 접근하는 것을 막는 동기화 기법입니다.

    1. 뮤텍스(Mutex, Mutual Exclusion): 뮤텍스는 한 번에 하나의 스레드만이 특정 코드 블록(임계 영역)을 실행할 수 있도록 하는 도구입니다. 만약 다른 스레드가 이미 뮤텍스를 소유하고 있다면, 다른 스레드는 그 스레드가 뮤텍스를 해제할 때까지 대기해야 합니다.
    2. 세마포어(Semaphore): 세마포어는 뮤텍스와 비슷하지만, 동시에 여러 스레드가 임계 영역을 실행할 수 있도록 허용하는 점이 다릅니다. 세마포어는 정수 값을 가지며, 이 값은 동시에 임계 영역에 접근할 수 있는 스레드의 최대 수를 의미합니다. 세마포어 값이 0이면, 모든 스레드는 세마포어 값이 증가할 때까지 대기해야 합니다.

    뮤텍스와 세마포어는 공유 자원에 대한 동시 접근을 제어하는 방식에서 차이가 있습니다.

    1. 뮤텍스(Mutex):

    뮤텍스는 바이너리 세마포어 또는 락(lock)으로 생각할 수 있으며, 오직 하나의 스레드만이 공유 자원을 점유할 수 있습니다.

    뮤텍스는 보통 단일 프로세스 내의 스레드들 사이의 동기화에 사용됩니다.

    예를 들어, 파일 시스템에 쓰는 작업이나 공유된 메모리에 쓰는 작업 등에 뮤텍스를 사용할 수 있습니다.

    1. 세마포어(Semaphore):

    세마포어는 뮤텍스와 다르게 하나 이상의 실행 스레드를 허용합니다. 세마포어의 값은 동시에 접근할 수 있는 최대 스레드 수를 나타냅니다.

    세마포어는 프로세스 간 동기화에도 사용될 수 있습니다.

    예를 들어, 공유 자원의 개수가 한정적인 경우(예: 공유 메모리 블록, 시스템에 있는 프린터나 파일 등)에 세마포어를 사용할 수 있습니다.

    기본적으로, 뮤텍스는 오직 하나의 스레드만이 공유 자원을 점유할 있게 하는 반면, 세마포어는 여러 스레드(또는 프로세스) 공유 자원에 동시에 접근할 있게 합니다.

따라서 프로세스 통신은 운영체제의 지원을 받아야 하며, 상대적으로 복잡하고 오버헤드가 발생할 있습니다. 반면 스레드 통신은 메모리를 공유하므로 빠르고 간단하지만, 동시성 제어가 필요합니다.

 

7. 프로세스와 스레드의 메모리 관리는 어떻게 이루어지는지 설명해주세요.

프로세스 메모리 관리:

  • 프로세스는 독립적인 메모리 공간을 가지고 있습니다. 이 메모리 공간은 일반적으로 텍스트(코드), 스택, 힙, 데이터 섹션 등으로 구성됩니다.
  • 각 프로세스는 서로의 메모리 공간에 직접 접근할 수 없습니다. 이는 프로세스 간에 메모리를 보호하고, 한 프로세스의 잘못된 동작이 다른 프로세스에 영향을 주지 않도록 합니다.
  • 프로세스 간에 데이터를 공유하려면 Inter-Process Communication(IPC) 기법을 사용해야 합니다. 예를 들면 파이프라인, 메시지 큐, 공유 메모리, 소켓 등이 있습니다.

스레드 메모리 관리:

  • 스레드는 프로세스 내에서 실행되는 가벼운 실행 단위로, 프로세스의 메모리 공간을 공유합니다.
  • 스레드는 자신만의 스택을 가지지만, 프로세스의 힙 메모리를 공유합니다. 이로 인해, 같은 프로세스에 속한 스레드 간에는 데이터를 공유하기 쉽습니다.
  • 그러나 이는 스레드 간에 동기화 문제를 야기할 수 있습니다. 한 스레드가 공유 데이터를 수정하는 동안 다른 스레드도 동일한 데이터에 접근하려고 시도하면 문제가 발생할 수 있습니다. 이런 문제를 방지하기 위해 뮤텍스, 세마포어 등의 동기화 기법을 사용합니다.

8. 프로세스와 스레드 중 어느 것을 사용해야 하는지 결정하는 기준은 무엇인가요?

  1. 자원 공유: 스레드는 같은 프로세스의 메모리 공간을 공유하기 때문에, 데이터를 공유해야 하는 작업에는 스레드가 적합합니다. 반면에 프로세스는 독립적인 메모리 공간을 가지므로, 작업이 서로 독립적으로 실행되어야 하거나 데이터 공유가 필요하지 않은 경우에 적합합니다.
  2. 오버헤드와 성능: 스레드는 프로세스보다 가볍기 때문에, 스레드를 생성하고 종료하는 드는 오버헤드가 작습니다. 따라서 빠른 반응성이 요구되는 작업에는 스레드가 적합할 있습니다. 그러나 공유 메모리 때문에 동기화 문제를 해결해야 필요가 있으며, 이는 추가적인 오버헤드를 초래할 있습니다.
  3. 안정성: 프로세스는 독립적인 메모리 공간을 가지므로, 프로세스에서 오류가 발생해도 다른 프로세스에는 영향을 주지 않습니다. 따라서 안정성이 중요한 작업에는 프로세스를 사용하는 것이 좋을 있습니다. 반면에 스레드에서는 스레드에서 오류가 발생하면 같은 프로세스의 다른 스레드에도 영향을 있습니다.
  4. 통신 비용: 프로세스 통신(IPC) 비교적 오버헤드가 크지만, 스레드 통신은 공유 메모리를 통해 쉽고 빠르게 이루어집니다.

9. 스레드 동기화란 무엇인지 설명해주세요. 그리고 이를 위해 어떤 방법들이 사용되는지 설명해주세요.

스레드 동기화는 여러 스레드가 공유 자원에 동시에 접근하는 것을 제어하는 방법입니다. 스레드 동기화가 없다면, 동시에 공유 자원을 수정하는 여러 스레드로 인해 데이터 불일치 문제가 발생할 수 있습니다. 이를 방지하기 위해 스레드 동기화 기법을 사용합니다.

다양한 스레드 동기화 기법들이 있습니다:

  1. 뮤텍스(Mutex): 뮤텍스는 Mutual Exclusion의 줄임말로, 한 번에 하나의 스레드만 공유 자원에 접근할 수 있도록 하는 도구입니다. 뮤텍스는 잠금(lock)과 잠금 해제(unlock) 두 가지 연산을 제공합니다. 스레드는 공유 자원에 접근하기 전에 뮤텍스를 잠그고, 사용 후에는 잠금을 해제합니다. 만약 다른 스레드가 뮤텍스를 잠그고 있다면, 그 스레드가 잠금을 해제할 때까지 기다려야 합니다.
  2. 세마포어(Semaphore): 세마포어는 뮤텍스와 비슷하게 동작하지만, 한 번에 여러 스레드가 공유 자원에 접근할 수 있도록 허용합니다. 세마포어는 카운터와 두 가지 연산(wait, signal)을 가지며, 카운터는 동시에 자원에 접근할 수 있는 스레드의 수를 의미합니다.
  3. 모니터(Monitor): 모니터는 객체 지향 프로그래밍에서 사용되는 동기화 메커니즘으로, 특정 객체에 대한 동시 접근을 제어합니다. 모니터 내의 메서드는 한 번에 하나의 스레드만 실행할 수 있습니다.
  4. 컨디션 변수(Condition Variable): 컨디션 변수는 스레드가 특정 조건을 기다리는 동안 그 스레드를 잠재워 다른 스레드가 실행될 수 있게 하는 도구입니다.
  5. 바캉트(Bakery Algorithm) 등의 알고리즘: 이러한 알고리즘은 스레드 간의 동기화를 위해 사용됩니다.

스레드 동기화는 공유 자원에 대한 동시 접근 문제를 해결하는 데 매우 중요하지만, 사용하는 방법에 따라 성능에 큰 영향을 줄 수 있으므로 주의해야 합니다. 특히, 뮤텍스, 세마포어 등의 잠금 메커니즘이 과도하게 사용될 경우 데드락(deadlock)과 같은 문제가 발생할 수 있습니다. 데드락은 두 개 이상의 프로세스나 스레드가 서로 대기하는 상태로, 이러한 상태는 시스템의 성능을 저하시키고 자원을 낭비할 수 있습니다.

그 외에도 라이브락(livelock), 스타베이션(starvation) 등의 문제도 발생할 수 있습니다. 라이브락은 프로세스나 스레드가 진행하지 못하면서 계속 자원을 소모하는 상황을, 스타베이션은 특정 프로세스나 스레드가 계속해서 자원을 얻지 못하는 상황을 말합니다.

따라서, 스레드 동기화를 구현할 때는 이러한 문제를 주의하면서, 공유 자원에 대한 동시 접근을 제어하고, 시스템의 성능과 효율성을 유지하도록 설계해야 합니다.

 

10. 프로세스 스케줄링에 대해 설명해주세요. 그리고 이와 관련하여 CPU 스케줄링 알고리즘이 어떻게 작동하는지 설명해주세요.

프로세스 스케줄링은 운영체제가 여러 프로세스를 관리하면서 CPU 자원을 효율적으로 분배하는 방법입니다. 프로세스 스케줄러는 CPU가 할당되는 순서를 결정하며, 이는 시스템의 전반적인 성능과 효율성에 큰 영향을 미칩니다.

프로세스 스케줄링 알고리즘은 다양한 종류가 있습니다. 각 알고리즘은 다른 목표를 가지고 설계되며, 서로 다른 종류의 작업에 최적화되어 있습니다. 대표적인 CPU 스케줄링 알고리즘으로는 다음과 같은 것들이 있습니다:

  1. First-Come, First-Served (FCFS): 가장 간단한 스케줄링 알고리즘입니다. 프로세스가 요청된 순서대로 CPU 할당받습니다.
  2. Shortest Job Next (SJN) 또는 Shortest Job First (SJF): 가장 짧은 시간이 소요되는 작업부터 CPU 할당받습니다. 알고리즘은 평균 대기 시간을 최소화하는데 효과적이지만, 짧은 작업을 우선시하기 때문에 작업이 계속 대기해야 하는 "스타베이션" 문제가 발생할 있습니다.
  3. Priority Scheduling: 프로세스에 우선순위를 부여하고, 높은 우선순위를 가진 프로세스부터 CPU 할당합니다. 알고리즘은 중요한 작업을 빠르게 처리할 있지만, 낮은 우선순위의 작업이 계속 대기해야 하는 스타베이션 문제가 발생할 있습니다.
  4. Round Robin (RR): 프로세스에 동일한 시간 할당량(quantum) 부여하고, 순환하면서 CPU 할당합니다. 알고리즘은 공정성을 유지하면서 다수의 프로세스를 효과적으로 처리하는데 적합합니다.
  5. Multilevel Queue Scheduling: 프로세스를 여러 큐에 분류하고, 큐에 대해 다른 스케줄링 알고리즘을 적용합니다. 예를 들어, 시스템 프로세스를 위한 , 인터랙티브 작업을 위한 , 배치 작업을 위한 등을 따로 관리할 있습니다.

11. 동시성(Concurrency)과 병렬성(Parallelism)의 차이점을 설명하고, 이것이 프로세스와 스레드에 어떻게 영향을 미치는지 

동시성(Concurrency)와 병렬성(Parallelism)은 컴퓨팅 작업이 어떻게 실행되는지를 설명하는 두 개념입니다.

  • 동시성(Concurrency)은 두 개 이상의 작업이 겹쳐서 실행되는 것을 의미합니다. 이 경우, 각 작업은 서로 다른 시간에 시작되고 종료될 수 있지만, 시간적으로 겹치는 부분이 있습니다. 이는 단일 프로세서에서 멀티스레딩을 이용해 여러 작업을 번갈아가며 실행하는 것과 같습니다. 즉, 동시에 진행되는 것처럼 보이지만 실제로는 한 번에 하나의 작업만 처리합니다.
  • 병렬성(Parallelism)은 두 개 이상의 작업이 동시에 실행되는 것을 의미합니다. 이 경우, 각 작업은 동시에 시작되고 종료될 수 있습니다. 이는 여러 프로세서를 사용하여 동시에 여러 작업을 실행하는 것과 같습니다.

프로세스와 스레드의 관점에서 볼 때, 이 두 개념은 아래와 같이 적용됩니다.

  • 동시성은 주로 하나의 CPU 코어에서 여러 스레드를 동시에 실행할 수 있는 멀티스레딩 환경에서 사용됩니다. 이 경우, CPU는 각 스레드 간에 빠르게 전환하면서 작업을 수행함으로써 동시에 실행되는 것처럼 보이게 합니다. 이를 통해 각 스레드는 독립적으로 실행되면서도 서로 협력하여 복잡한 작업을 수행할 수 있습니다.
  • 병렬성은 여러 CPU 코어를 사용하는 멀티프로세싱 환경에서 사용됩니다. 이 경우, 각 프로세스(또는 스레드)는 독립적인 CPU 코어에서 동시에 실행되므로, 더 빠른 작업 처리가 가능합니다. 병렬 처리는 대량의 데이터를 처리하거나 복잡한 계산을 수행하는 등의 상황에서 효율적입니다.

개념은 상황에 따라 선택적으로 사용되며, 종종 함께 사용되기도 합니다. 예를 들어, 여러 코어를 가진 시스템에서는 코어에서 여러 스레드를 동시에 실행함으로써 동시성과 병렬성을 동시에 활용할 있습니다.

동시성은 적어도 두 개의 스레드가 진행 중일 때 존재하는 조건이며, 가상 병렬 처리의 한 형태로 시간 분할(time-slicing)을 포함합니다. 우리가 흔히 ‘동시’라고 이야기 하지만 컴퓨터(코어)는 한번에 하나의 명령어만 처리할 수 있다. 즉, 두개 이상의 알고리즘이 하나의 코어내에서 스레드간에 빠르게 교차되면서 실행되기 때문에 ‘동시’라고 느끼는 것입니다. 병렬성을 이야기하려면 적어도 2개 이상의 코어가 있어야 합니다. 병렬성도 동시성을 의미하지만 동시성과의 차이는 각 코어내의 스레드가 실제로 동시에 명령어를 실행할 수 있음을 말합니다. 그러므로 두개의 알고리즘이 정확히 같은 시점에 실행될 때 이를 병렬적이라고 말할 수 있습니다.

동시성은 여러개의 스레드를 하나의 코어에서 빠르게 교차시키면서 실행하여 동시에 실행되는 것 처럼 보이도록 동작시키는 방식이다. 스레드가 교차하며 Context Switching을 계속해서 일으키고 다중 스레드들이 실행된다. 반면 병렬성은 멀티 코어프로세서 에서 멀티 스레드를 실행 시키는 방법으로 실제로 다중의 코어에서 각자의 스레드가 명령어를 실행하는 것을 말한다.

 

12. 실제 프로젝트에서 멀티프로세싱 혹은 멀티스레딩을 사용한 경험이 있다면 설명해주세요.

더보기

예를 들어 웹 서버를 구축하는 프로젝트를 진행했다고 가정해보겠습니다. 웹 서버는 일반적으로 다수의 클라이언트 요청을 동시에 처리해야 합니다. 이때, 단일 스레드에서 모든 요청을 처리하려고 하면, 한 번에 하나의 요청만 처리할 수 있으므로 성능 저하가 발생할 수 있습니다. 따라서 웹 서버는 일반적으로 멀티스레딩을 사용하여 동시에 여러 요청을 처리합니다.

각 요청을 처리하는 독립적인 스레드를 생성하면, 요청을 병렬로 처리할 수 있어 성능 향상을 기대할 수 있습니다. 스레드는 프로세스 내에서 자원을 공유하므로, 다른 스레드와 통신하는 데 비용이 적게 들며, 스레드 생성과 스위칭 비용도 프로세스에 비해 상대적으로 적습니다. 따라서 병렬로 처리해야 하는 작업이 많은 웹 서버와 같은 환경에서는 효과적입니다.

그러나 이러한 멀티스레딩 환경에서는 스레드 간 동기화 문제를 신경 써야 합니다. 여러 스레드가 동시에 공유 자원에 액세스하려고 하면 경쟁 상태가 발생할 수 있으므로, 적절한 동기화 메커니즘을 사용해야 합니다. Java에서는 synchronized 키워드를 사용하여 이를 관리할 수 있습니다.

이처럼 멀티스레딩은 프로젝트의 효율성을 크게 향상시키지만, 동기화 등의 복잡한 이슈를 관리해야 하는 단점도 있습니다. 따라서 멀티스레딩을 적용할 때는 프로젝트의 요구 사항과 특성을 고려하여 결정해야 합니다.

 

1. Base64 인코딩이란 무엇인가요?

Base64는 바이너리 데이터를 ASCII 문자열 형태로 인코딩하는 방법 중 하나입니다.

인터넷에서 사용하는 대부분의 프로토콜들은 텍스트 기반으로 설계되어 있기 때문에, 바이너리 데이터를 직접 전송하면 이들 프로토콜이 처리할 수 없는 경우가 있습니다. 이러한 이유로, 바이너리 데이터를 텍스트 형태로 변환할 필요가 있습니다.

Base64 인코딩은 바이너리 데이터를 64개의 프린트 가능한 문자로 변환합니다. 이 64개의 문자는 대문자 A-Z, 소문자 a-z, 숫자 0-9, 그리고 '+'와 '/'를 포함합니다. 이렇게 인코딩된 데이터는 대부분의 텍스트 기반 시스템에서 안전하게 전송되고 저장될 수 있습니다.

하지만 Base64 암호화 방법이 아닙니다. 인코딩된 데이터는 쉽게 원래의 형태로 디코딩될 있으며, 보안적인 면에서는 아무런 보호도 제공하지 않습니다. 그래서 Base64 데이터를 안전하게 전송하거나 저장하는 목적으로 사용되지만, 데이터를 보호하거나 숨기는 목적으로는 사용되지 않습니다.

 

3바이트 데이터를 4문자로 표현하는 방식으로 현재 전자 우편 첨부파일 전송에 많이 사용됩니다. 보통 1글자를 표현할 때 8비트를 사용하는데, base64는 6비트를 사용해, 효율이 좋습니다.

 

Base 64는 8비트 바이너리 데이터를 문자 코드에 영향을 받지 않는 ASCII 영역의 문자들로만 이루어진 일련의 문자열로 바꾸는 인코딩 방식을 가리키는 개념이다. 24비트의 버퍼를 만든 뒤 6비트 단위로 잘라 Base64 테이블의 ASCII 문자로 변환한다.문자열을 ASCII 값으로 변경한뒤 나열하여 6bit단위로 자른 뒤 숫자를 base64의 변환 표에 맞는 값으로 변환한다. 이때 총 비트 수가 6의 배수가 아니면 해당 비트의 마지막을 0으로 채운 뒤 남은 비트를 base64의 =에 해당되는 값으로 채운다. 확장된 아스키 코드는 언어 및 국가에 따라서 일부 제어 문자가 시스템 별로 다른 코드값을 가지기 때문에 일부 특수문자를 포함한 64개의 안전한 출력 문자를 사용하는 Base64를 사용하여 통신의 동일성을 보장할 수 있다.

 

Base64를 글자 그대로 직역하면 64진법이라는 뜻이고, Base64 인코딩은 Binary Data를 ASCII 문자로만 이루어진 Text로 바꾸는 인코딩입니다.

 

2. Base64 인코딩이 왜 필요한지 설명해보세요.

  1. 데이터 전송 안전성 보장: 인터넷 프로토콜이나 여러 시스템들은 텍스트 기반입니다. 때문에 바이너리 데이터를 그대로 전송하면 오류가 발생하거나 데이터가 손상될 있습니다. Base64 인코딩은 이런 문제를 방지하며, 데이터 전송을 안전하게 만들어 줍니다.
  2. 문자열로의 표현: 몇몇 경우에서, 바이너리 데이터를 사람이 읽을 있는 문자열 형태로 표현해야 필요가 있습니다. 이때 Base64 인코딩을 사용하면 바이너리 데이터를 ASCII 문자열로 변환할 있습니다. 이런 경우는 이메일 첨부파일 전송, 웹에서 이미지 데이터를 전송하는 등에서 있습니다.

3. Base64 인코딩의 작동 원리에 대해 설명해주세요.

Base64 인코딩의 작동 원리는 바이너리 데이터를 64개의 프린트 가능한 문자로 변환하는 것입니다. 이 프로세스는 다음 단계를 따릅니다.

  1. 데이터 분할: 원본 바이너리 데이터는 8비트씩 나누어집니다. 이는 바이트를 이진수 형태로 나타내는 것을 의미합니다.
  2. 그룹화: 분할된 데이터는 3바이트씩 그룹화됩니다. , 24비트의 데이터 그룹이 만들어집니다. 만약 데이터가 부족하여 24비트를 채울 없다면, 남는 부분은 0으로 채워집니다.
  3. 인코딩: 24비트 그룹은 4개의 6비트 그룹으로 나누어집니다. 6비트 그룹은 Base64 테이블에서 해당하는 문자로 변환됩니다. Base64 테이블은 A-Z, a-z, 0-9, '+', '/' 64개의 프린트 가능한 문자를 포함하고 있습니다.
  4. 패딩: 만약 원본 데이터가 3바이트를 완전히 채우지 못했다면, 해당 부분은 패딩 문자인 '=' 채워집니다. 이는 인코딩된 문자열이 항상 4 배수가 되도록 합니다.

4. Base64 인코딩은 어떤 상황에서 사용되나요?

  1. 이메일: 이메일 메시지는 일반적으로 ASCII 문자만을 전송할 있으므로, 바이너리 파일(: 이미지, 문서 ) Base64 인코딩된 후에 전송됩니다. 이는 MIME(Multipurpose Internet Mail Extensions) 표준의 일부로서 구현되어 있습니다.
  2. 데이터: 페이지에서 이미지, CSS, JavaScript 파일 등을 내장하는 Base64 인코딩이 사용될 있습니다. 방법을 사용하면, 서버는 별도의 HTTP 요청 없이 필요한 모든 데이터를 번에 보낼 있습니다.
  3. URL 인코딩: URL 일반적으로 ASCII 문자만을 포함할 있으므로, 바이너리 데이터를 URL 포함시키려면 Base64 인코딩을 사용해야 합니다.
  4. 데이터 저장: 바이너리 데이터를 텍스트 형태로 저장하거나 전송해야 Base64 인코딩이 사용될 있습니다. 이는 로그 파일, 데이터베이스, XML 문서 등에서 흔히 있습니다.

5. Base64 인코딩이 안전한 방법이라고 생각하나요? 그 이유는 무엇인가요?

Base64 인코딩은 데이터의 안전한 전송과 저장에 유용하지만, 데이터의 보안을 제공하는 것은 아닙니다.

안전성과 보안성은 다른 개념입니다. Base64 인코딩이 "안전한" 방법이라는 것은, Base64 인코딩을 통해 변환된 데이터가 전송 중에 변경되거나 손상되지 않을 것이라는 뜻입니다. 즉, 인코딩 후의 데이터가 원래의 데이터로 안전하게 복원될 수 있다는 것을 의미합니다.

그러나, Base64 인코딩은 암호화 방법이 아니므로, 인코딩된 데이터는 쉽게 원래의 형태로 디코딩될 수 있습니다. 따라서, Base64 인코딩은 데이터를 보호하거나 숨기는 데 적합하지 않습니다. 만약 데이터의 보안이 중요한 경우, 암호화 알고리즘을 사용해야 합니다.

요약하면, Base64 인코딩은 데이터의 안전한 전송과 저장에는 유용하지만, 데이터의 보안에는 적합하지 않습니다.

 

6. Base64 인코딩과 URL 인코딩의 차이점은 무엇인가요?

Base64 인코딩과 URL 인코딩은 둘 다 데이터를 안전하게 전송하기 위해 사용되는 방법이지만, 사용 목적과 처리 방식에 있어서 몇 가지 중요한 차이점이 있습니다.

  1. 사용 목적:
  • Base64 인코딩은 바이너리 데이터를 ASCII 문자열로 변환하는데 주로 사용됩니다. 이는 이메일과 같은 텍스트 기반 프로토콜에서 바이너리 데이터를 안전하게 전송하기 위해 필요한 과정입니다.
  • URL 인코딩은 웹에서 안전하게 URL을 전송하기 위해 사용됩니다. URL은 특정 문자를 사용할 수 없으며, 이러한 문자를 안전하게 전송하기 위해 URL 인코딩이 필요합니다.
  1. 처리 방식:
  • Base64 인코딩은 입력 데이터를 6비트 블록으로 나누고, 각 블록을 특정 문자로 대체하여 인코딩합니다. 인코딩된 데이터는 원래 데이터의 약 4/3 크기가 됩니다.
  • URL 인코딩은 특정 문자를 '%'와 16진수 두 자리로 이루어진 코드로 대체합니다. 예를 들어, 공백은 '%20'으로 인코딩됩니다.

따라서, Base64 인코딩과 URL 인코딩은 각각 바이너리 데이터의 안전한 전송과 URL 안전한 전송에 사용되며, 가지 목적에 따라 서로 다른 방식으로 데이터를 처리합니다.

 

7. Base64 인코딩은 어떤 종류의 데이터에 적합한가요?

Base64 인코딩은 바이너리 데이터를 ASCII 문자열로 변환하는데 적합합니다.

이는 바이너리 데이터가 텍스트 기반 프로토콜에서 안전하게 전송되야 할 경우 유용합니다. 예를 들어, 이메일은 텍스트를 기반으로 하는 프로토콜인데, 이메일을 통해 이미지나 음악 파일과 같은 바이너리 데이터를 보내려면 이 데이터를 텍스트 형태로 변환해야 합니다. 이 경우에 Base64 인코딩이 사용됩니다.

또한, Base64는 웹에서 데이터를 URL이나 HTML에서 안전하게 인코딩하고 전송하는 데에도 사용될 수 있습니다. 예를 들어, 웹 애플리케이션은 사용자의 프로필 사진을 Base64로 인코딩하여 HTML 코드 내에 직접 삽입할 수 있습니다. 이렇게 하면 별도의 이미지 파일을 서버에서 불러올 필요가 없어집니다.

그러나 Base64 인코딩은 원래 데이터의 크기를 33% 증가시키는 단점이 있습니다. 따라서 대량의 데이터를 처리해야 때는 다른 방법을 고려하는 것이 좋습니다.

 

8. Base64 인코딩이 바이너리 데이터를 효율적으로 처리하나요? 그 이유는 무엇인가요?

Base64 인코딩은 바이너리 데이터를 ASCII 문자열로 변환하는데는 효율적입니다. 하지만 이는 "효율성"에 대한 정의에 따라 달라집니다.

Base64 인코딩의 주된 목적은 텍스트로만 구성된 프로토콜 또는 시스템에서 바이너리 데이터를 안전하게 전송하거나 저장하는 것입니다. 예를 들어, 이메일이나 HTTP와 같은 텍스트 기반 프로토콜은 바이너리 데이터를 바로 처리할 수 없습니다. 따라서 이러한 경우에 바이너리 데이터를 ASCII 문자열로 변환하는 Base64 인코딩은 매우 유용합니다.

그러나 Base64 인코딩은 데이터의 크기를 약 33% 증가시킵니다. 즉, 3 바이트의 바이너리 데이터가 4 바이트의 문자열로 변환됩니다. 따라서 대량의 데이터를 전송하거나 저장해야 하는 경우, Base64 인코딩은 저장 공간이나 네트워크 대역폭에 대한 비효율성을 초래할 수 있습니다.

또한, Base64 인코딩과 디코딩에는 CPU 리소스가 필요합니다. 따라서 성능이 중요한 시스템에서는 이런 추가적인 계산 부담을 고려해야 합니다.

결론적으로, Base64 인코딩은 바이너리 데이터를 텍스트 기반 시스템에서 안전하게 처리하는 데는 효율적이지만, 데이터의 크기, 네트워크 대역폭 사용, CPU 리소스 사용 등을 고려할 항상 최선의 선택이라고는 없습니다.

 

9. Base64 인코딩된 문자열을 디코딩하는 과정을 설명해주세요.

Base64 인코딩된 문자열을 디코딩하는 과정은 인코딩 과정의 반대입니다. 아래는 그 단계입니다:

  1. 인코딩된 Base64 문자열을 입력받습니다. 이 문자열은 Base64 알파벳에 속하는 문자들로 이루어져 있어야 합니다. Base64 알파벳은 A-Z, a-z, 0-9, '+', '/'와 패딩을 위한 '='으로 구성됩니다.
  2. Base64 문자열의 각 문자를 Base64 알파벳에 대응하는 6비트 이진 값으로 변환합니다. 예를 들어, 'A'는 000000, 'B'는 000001, 'Z'는 110101, 'a'는 110110, 'z'는 111101, '0'는 111110, '9'는 111111, '+'는 111000, '/'는 111001로 매핑됩니다.
  3. 변환된 이진 값들을 연결하여 새로운 이진 문자열을 만듭니다.
  4. 이 새로운 이진 문자열을 8비트 단위로 분할합니다. 각 8비트 단위는 원래의 바이너리 데이터를 나타내는 바이트입니다.
  5. 마지막으로, 각 8비트 바이트를 해당하는 ASCII 문자로 변환하여 원래의 텍스트 또는 데이터를 복원합니다.

과정에서 주의해야 점은 인코딩 추가된 패딩 '=' 문자입니다. 패딩 문자는 원래 데이터의 바이트 수가 3 배수가 아닐 추가되므로, 디코딩 이를 고려하여 제거해야 합니다.

 

10. Base64 인코딩은 암호화 방법이라고 생각하나요? 그렇지 않다면, 왜 그런지 설명해주세요.

 

Base64 인코딩은 암호화 방법이 아닙니다. 

  1. 공개적으로 알려진 방법: Base64 인코딩과 디코딩 알고리즘은 완전히 공개되어 있습니다. 이는 누구나 인코딩된 문자열을 쉽게 원래 데이터로 변환할 수 있다는 것을 의미합니다.
  2. 역변환 가능: Base64 인코딩은 손실 없이 원래의 데이터로 역변환이 가능합니다. 이는 단순히 데이터 형식을 변환하는 과정으로, 암호화에서는 보통 역변환이 어렵거나 불가능하게 만드는 방법을 사용합니다.
  3. 보안 제공하지 않음: Base64 인코딩 자체는 어떠한 보안적인 장치도 제공하지 않습니다. 이는 데이터의 안전성, 기밀성, 무결성 등을 보장하지 않습니다.

따라서, Base64 인코딩은 데이터를 텍스트 형태로 안전하게 전송하거나 저장하는 용도로 사용될 수 있지만, 보안 목적으로는 적합하지 않습니다. 보안을 위해서는 실제 암호화 알고리즘을 사용해야 합니다.

 

11. 프로젝트에서 Base64 인코딩을 어떻게 사용했는지 설명해주세요.

  1. 이미지나 파일 업로드: 클라이언트에서 서버로 이미지나 파일을 업로드할 때, 이 데이터는 HTTP 요청의 일부로 전송되어야 합니다. 그러나 HTTP는 텍스트 기반의 프로토콜이므로, 바이너리 데이터를 안전하게 전송하기 위해 Base64 인코딩이 사용됩니다. 이렇게 인코딩된 데이터는 서버에서 디코딩하여 사용하거나 저장합니다.
  2. 데이터베이스 저장: 일부 경우에, 바이너리 데이터를 데이터베이스에 직접 저장해야 할 필요가 있을 수 있습니다. 이때 Base64 인코딩을 사용하면, 바이너리 데이터를 텍스트 형식으로 변환하여 데이터베이스에 저장할 수 있습니다.
  3. 인증 토큰 생성: 인증 토큰(예: JWT)을 생성할 때, 페이로드는 종종 Base64 인코딩이 사용됩니다. 이렇게 하면 토큰의 페이로드를 안전하게 URL이나 HTTP 헤더에 포함시킬 수 있습니다.

Spring Boot에서 Base64 인코딩과 디코딩은 java.util.Base64 클래스를 사용하여 수행할 있습니다. 클래스는 Base64 인코딩과 디코딩을 위한 메서드를 제공합니다.