OOM(Out Of Memory)이 발생하는 상황을 만들어보자

2025. 11. 13. 13:51·서버 개발(생각과 구현)/서버 생각
반응형

OOM(Out Of Memory) 실제로 메모리를 터트려보자 💥

들어가며

"메모리 관리 잘 해야 해요", "페이징 처리는 필수입니다" - 이런 말들을 수없이 들어왔다. 하지만 솔직히 말하면, 실제로 OOM이 발생하는 걸 직접 경험해본 적은 별로 없었다. 개발 환경은 메모리가 넉넉하고, 테스트 데이터는 항상 소량이니까.

그래서 오늘은 의도적으로 메모리를 터트려보기로 했다. 실제 프로덕션 환경에서 발생할 수 있는 다양한 OOM 시나리오를 직접 구현하고, 하나씩 실행해보면서 어떤 일이 벌어지는지 관찰해보았다.

⚠️ 주의: 이 글의 모든 코드는 학습 목적입니다. 절대 프로덕션 환경에서 사용하지 마세요!

테스트 환경 구축

먼저 OOM을 쉽게 재현하기 위해 JVM 힙 메모리를 제한했다.

java -Xmx512m -Xms128m \
     -XX:+HeapDumpOnOutOfMemoryError \
     -XX:HeapDumpPath=./heap_dump.hprof \
     -jar test-0.0.1-SNAPSHOT.jar
  • 최대 힙 메모리: 512MB (실제 서버보다 훨씬 적게)
  • OOM 발생 시 힙 덤프 자동 생성
  • 포트: 8080

그리고 편하게 테스트하기 위해 웹 대시보드도 만들었다. 버튼 클릭만으로 각 시나리오를 실행할 수 있고, 실시간으로 메모리 사용량을 모니터링할 수 있다.

1. 캐시 메모리 누수 - "캐시를 영원히 보관하면?"

테스트 코드

@GetMapping("/cache-leak")
fun cacheMemoryLeak(@RequestParam(defaultValue = "10000") count: Int): String {
    repeat(count) {
        val key = "cache_$it"
        val data = ByteArray(1024 * 1024) // 1MB
        globalCache[key] = data
    }
    return "캐시에 ${globalCache.size}개 항목 저장됨"
}

실행 결과

처음엔 count=500으로 가볍게 시작했다.

요청 전: 사용 메모리 50MB
요청 후: 사용 메모리 550MB
결과: "캐시에 500개 항목 저장됨 (약 500MB)"

메모리가 순식간에 500MB 증가했다. 문제는 이 메모리가 절대 해제되지 않는다는 것이다. globalCache는 static 변수라 GC 대상이 아니기 때문이다.

그래서 count=1000으로 다시 시도했고... 예상대로 OOM이 발생했다.

java.lang.OutOfMemoryError: Java heap space

실제 사례와 교훈

이건 실제로 자주 발생하는 패턴이다:

// ❌ 나쁜 예: TTL 없는 캐시
@Service
class CacheService {
    private val cache = ConcurrentHashMap<String, Data>()

    fun getData(key: String): Data {
        return cache.computeIfAbsent(key) { loadFromDB(key) }
    }
}

해결 방법:

  • Caffeine, Guava Cache 같은 라이브러리 사용 (자동 eviction)
  • TTL(Time To Live) 설정
  • 캐시 크기 제한
// ✅ 좋은 예: TTL이 있는 캐시
val cache: Cache<String, Data> = Caffeine.newBuilder()
    .maximumSize(10_000)
    .expireAfterWrite(10, TimeUnit.MINUTES)
    .build()

 

 

2. 세션 데이터 누수 - "로그아웃 안 하면?"

테스트 코드

@GetMapping("/session-leak")
fun sessionLeak(@RequestParam(defaultValue = "1000") userCount: Int): String {
    repeat(userCount) { userId ->
        val sessionData = mutableListOf<Any>()
        repeat(100) {
            sessionData.add(ByteArray(100 * 1024)) // 100KB
        }
        userSessions["user_$userId"] = sessionData
    }
    return "활성 세션: ${userSessions.size}개"
}

실행 결과

userCount=1000으로 테스트했다.

사용자 1000명 × 100개 객체 × 100KB = 약 10GB 필요
실제 메모리: 512MB
결과: OOM 발생! (당연하지만)

그래서 userCount=300으로 줄여서 실행했다.

요청 전: 사용 메모리 60MB
요청 후: 사용 메모리 360MB
결과: "활성 세션: 300개, 메모리 사용량: 약 3000MB"

실제 사례와 교훈

이건 특히 장시간 운영되는 서비스에서 문제가 된다. 사용자는 로그아웃을 잘 안 하니까.

// ❌ 나쁜 예: 세션을 메모리에만 저장
@RestController
class UserController {
    private val sessions = ConcurrentHashMap<String, UserSession>()

    @PostMapping("/login")
    fun login(user: User): String {
        val sessionId = UUID.randomUUID().toString()
        sessions[sessionId] = UserSession(user)
        return sessionId
    }
}

해결 방법:

  • Session timeout 설정
  • Redis 같은 외부 세션 저장소 사용
  • 주기적인 세션 정리 배치 작업
// ✅ 좋은 예: Session timeout 설정
@Configuration
class SessionConfig {
    @Bean
    fun configure(): SessionRepositoryFilter<*> {
        return SessionRepositoryFilter(
            RedisSessionRepository().apply {
                defaultMaxInactiveInterval = Duration.ofMinutes(30)
            }
        )
    }
}

 

 

3. 페이징 없는 대용량 조회 - "전체 조회의 함정"

테스트 코드

@GetMapping("/large-list")
fun largeList(@RequestParam(defaultValue = "10000000") size: Int): String {
    val largeList = mutableListOf<String>()
    repeat(size) {
        largeList.add("User{id=$it, name='User$it', email='user$it@example.com'...}")
    }
    return "리스트 크기: ${largeList.size}개"
}

실행 결과

야심차게 size=10,000,000 (천만 건)으로 시도했다.

실행 시간: 약 3초
메모리 사용량: 급증하다가...
결과: java.lang.OutOfMemoryError: Java heap space

당연히 터졌다. 천만 개의 문자열 객체를 메모리에 담을 수는 없다.

그래서 size=1,000,000 (백만 건)으로 줄였더니...

요청 전: 사용 메모리 80MB
실행 중: 200MB → 350MB → 480MB (계속 증가)
결과: 성공! "리스트 크기: 1000000개"
최종 메모리: 490MB (거의 한계)

아슬아슬하게 성공했지만, 서버는 거의 죽기 직전이었다.

실제 사례와 교훈

관리자 페이지에서 정말 자주 보는 패턴이다.

// ❌ 나쁜 예: 전체 조회
@GetMapping("/admin/users")
fun getAllUsers(): List<User> {
    return userRepository.findAll() // 수백만 건이 조회될 수 있음
}

이 코드가 개발 환경에서는 문제없어 보인다. 테스트 DB에는 사용자가 100명밖에 없으니까. 하지만 프로덕션에는 100만 명이 있다면?

해결 방법:

// ✅ 좋은 예: 페이징 필수
@GetMapping("/admin/users")
fun getUsers(
    @PageableDefault(size = 20, sort = ["createdAt"], direction = Sort.Direction.DESC) 
    pageable: Pageable
): Page<User> {
    return userRepository.findAll(pageable)
}

 

 

4. 문자열 연산의 비극 - "String + String + String..."

테스트 코드

@GetMapping("/string-concat")
fun stringConcat(@RequestParam(defaultValue = "1000000") iterations: Int): String {
    var result = ""
    repeat(iterations) {
        result += "This is line $it with some data\n"
    }
    return "문자열 길이: ${result.length}"
}

실행 결과

iterations=100,000으로 시작했다.

실행 시간: 약 8초 (엄청 느림!)
메모리 사용량: 50MB → 150MB → 280MB → 420MB
CPU 사용률: 100% (코어 1개 풀가동)
결과: 성공... 하지만 너무 느림

왜 이렇게 느릴까? String concatenation은 매번 새로운 String 객체를 생성하기 때문이다.

"" + "line 1" → 새 String 생성
"line 1" + "line 2" → 또 새 String 생성
"line 1line 2" + "line 3" → 또 새 String 생성
...

10만 번 반복하면 10만 개의 String 객체가 생성된다!

해결 방법 비교

같은 작업을 StringBuilder로 다시 구현해봤다.

// StringBuilder 버전
fun stringConcatFixed(iterations: Int): String {
    val result = StringBuilder()
    repeat(iterations) {
        result.append("This is line $it with some data\n")
    }
    return "문자열 길이: ${result.length}"
}

 

 

방법 실행 시간 메모리 사용
String concatenation 8초 420MB
StringBuilder 0.3초 80MB

26배 빠르고, 5배 적은 메모리!

실제 사례

// ❌ 나쁜 예: CSV 생성
fun generateCsv(users: List<User>): String {
    var csv = "ID,Name,Email\n"
    users.forEach { user ->
        csv += "${user.id},${user.name},${user.email}\n"
    }
    return csv
}

// ✅ 좋은 예
fun generateCsv(users: List<User>): String {
    return buildString {
        append("ID,Name,Email\n")
        users.forEach { user ->
            append("${user.id},${user.name},${user.email}\n")
        }
    }
}

 

 

5. 파일 업로드의 함정 - "메모리에 다 올리면?"

테스트 코드

@GetMapping("/file-upload-simulation")
fun fileUploadSimulation(@RequestParam(defaultValue = "100") fileSizeMB: Int): String {
    // 파일을 메모리에 전부 로드
    val fileContent = ByteArray(fileSizeMB * 1024 * 1024)

    // 파일 처리 (복사본 생성)
    val processedContent = fileContent.copyOf()

    return "파일 처리 완료: ${fileSizeMB}MB"
}

실행 결과

fileSizeMB=100으로 테스트.

요청 전: 메모리 100MB
파일 로드: 100MB → 200MB
파일 복사: 200MB → 300MB
처리 완료: 300MB

결과: 성공

하지만 fileSizeMB=300으로 시도하자...

요청 전: 메모리 100MB
파일 로드: 100MB → 400MB
파일 복사 시도: OOM 발생!

300MB 파일을 처리하려면 최소 600MB가 필요한데, 우리는 512MB만 있으니 당연히 터진다.

더 무서운 시나리오: 동시 업로드

만약 3명이 동시에 200MB 파일을 업로드한다면?

사용자 A: 200MB 필요
사용자 B: 200MB 필요
사용자 C: 200MB 필요
합계: 600MB 필요

우리의 메모리: 512MB
결과: 💥

해결 방법

// ❌ 나쁜 예: 메모리에 전체 로드
@PostMapping("/upload")
fun upload(@RequestParam file: MultipartFile): String {
    val bytes = file.bytes // 전체를 메모리에!
    // 처리...
    return "success"
}

// ✅ 좋은 예: 스트리밍 방식
@PostMapping("/upload")
fun upload(@RequestParam file: MultipartFile): String {
    file.inputStream.use { input ->
        val output = FileOutputStream("upload/${file.originalFilename}")
        output.use { 
            input.copyTo(it, bufferSize = 8192) // 8KB씩 처리
        }
    }
    return "success"
}

 

 

6. 무한 객체 생성 - "GC가 따라올 수 있을까?"

테스트 코드

@GetMapping("/infinite-objects")
fun infiniteObjects(): String {
    val objects = mutableListOf<Any>()
    try {
        while (true) {
            objects.add(ByteArray(10 * 1024 * 1024)) // 10MB
            if (objects.size % 10 == 0) {
                println("생성된 객체 수: ${objects.size}")
            }
        }
    } catch (e: OutOfMemoryError) {
        return "OOM 발생! 생성된 객체 수: ${objects.size}"
    }
}

실행 결과

이건 진짜 위험한 테스트다. 실행 버튼을 누르자마자...

생성된 객체 수: 10 (100MB)
생성된 객체 수: 20 (200MB)
생성된 객체 수: 30 (300MB)
생성된 객체 수: 40 (400MB)
생성된 객체 수: 50 (500MB)
java.lang.OutOfMemoryError: Java heap space

약 5초 만에 서버가 죽었다. 😱

JConsole로 관찰해보니 재밌는 걸 발견했다:

Old Gen 메모리: 계속 증가
GC 활동: 계속 시도하지만 회수할 게 없음
GC 시간: 점점 길어짐 (1초 → 2초 → 3초)

GC는 열심히 돌아가지만, 모든 객체가 objects List에 참조되어 있어서 회수할 수가 없다. 결국 메모리는 계속 차고, OOM이 발생한다.

실제 사례: 이벤트 리스너 누적

// ❌ 나쁜 예: 리스너가 계속 쌓임
class EventService {
    private val listeners = mutableListOf<EventListener>()

    fun addEventListener(listener: EventListener) {
        listeners.add(listener)
        // 제거하는 로직이 없음!
    }
}

// ✅ 좋은 예: 제거 기능 제공
class EventService {
    private val listeners = CopyOnWriteArrayList<EventListener>()

    fun addEventListener(listener: EventListener): () -> Unit {
        listeners.add(listener)
        return { listeners.remove(listener) } // unsubscribe 함수 반환
    }
}

 

 

7. 실제 시나리오: 페이징 없는 관리자 페이지

이제 실제 웹 애플리케이션에서 발생할 수 있는 시나리오를 테스트해봤다.

테스트 코드

@GetMapping("/real-world/no-pagination")
fun noPagination(@RequestParam(defaultValue = "1000000") recordCount: Int): List<UserDto> {
    return (1..recordCount).map { id ->
        UserDto(
            id = id.toLong(),
            username = "user$id",
            email = "user$id@example.com",
            fullName = "Full Name $id",
            address = "Address Line 1, City, State $id",
            phoneNumber = "010-1234-$id",
            createdAt = LocalDateTime.now(),
            metadata = "Some metadata".repeat(10)
        )
    }
}

실행 결과

recordCount=100,000 (10만 건)으로 시작.

실행 시간: 약 5초
메모리 사용: 150MB → 450MB
응답 크기: 약 50MB (JSON)
결과: 성공... 하지만

성공은 했지만 문제가 많다:

  1. 5초 응답 시간 - 사용자는 이미 떠났다
  2. 50MB JSON - 브라우저가 렌더링할 수 있을까?
  3. 450MB 메모리 - 동시 사용자 2명만 와도 OOM

실제로 브라우저에서 확인해보니:

Chrome: 탭이 멈춤 (응답 없음)
Firefox: 5초 후 화면 렌더링, 하지만 스크롤이 버벅임
Safari: 페이지 크래시

교훈

개발 환경 테스트:

테스트 데이터: 100개
결과: 완벽하게 작동! ✅
개발자: "이거 완성!"

프로덕션:

실제 데이터: 100,000개
결과: 브라우저 크래시 💥
사용자: "뭐야 이거 왜 안돼"

항상 실제 데이터 규모로 테스트하자!

8. 이미지 처리 - "썸네일 100개 만들기"

테스트 코드

@GetMapping("/real-world/image-processing")
fun imageProcessing(@RequestParam(defaultValue = "100") imageCount: Int): String {
    val images = mutableListOf<ByteArray>()

    repeat(imageCount) {
        val imageData = ByteArray(10 * 1024 * 1024) // 10MB 원본
        val thumbnail = imageData.copyOf(imageData.size / 4) // 2.5MB 썸네일
        val processed = imageData.copyOf() // 10MB 처리본

        images.add(imageData)
        images.add(thumbnail)
        images.add(processed)
    }

    return "이미지 ${imageCount}개 처리 완료"
}

실행 결과

imageCount=30으로 테스트.

이미지 1개당 필요 메모리: 10MB + 2.5MB + 10MB = 22.5MB
30개: 22.5MB × 30 = 675MB

우리의 메모리: 512MB
결과: OOM 발생!

imageCount=20으로 줄여서 재시도.

필요 메모리: 450MB
실제 사용: 480MB (약간의 오버헤드 포함)
결과: 성공! 하지만 아슬아슬

메모리 사용 패턴 관찰

JConsole로 보니 흥미로운 패턴이 보였다:

0초: 100MB
1초: 200MB (이미지 5개)
2초: 300MB (이미지 10개)
3초: 400MB (이미지 15개)
4초: 480MB (이미지 20개)
5초: GC 발동! → 480MB (변화 없음, 모두 참조 중)

GC가 열심히 돌아도 회수할 게 없다. 모든 이미지가 images List에 들어있기 때문이다.

실제 해결 방법

// ❌ 나쁜 예: 모든 이미지를 메모리에
fun processImages(files: List<File>) {
    val results = mutableListOf<ProcessedImage>()
    files.forEach { file ->
        val image = loadImage(file) // 메모리 로드
        val thumbnail = createThumbnail(image)
        results.add(ProcessedImage(image, thumbnail))
    }
    return results
}

// ✅ 좋은 예: 하나씩 처리하고 저장
fun processImages(files: List<File>) {
    files.forEach { file ->
        val image = loadImage(file)
        val thumbnail = createThumbnail(image)

        // 즉시 파일로 저장
        saveThumbnail(thumbnail)

        // 메모리에서 해제 (GC 대상)
        image.flush()
    }
}

 

 

9. Stream API 남용 - "중간 연산이 많으면?"

테스트 코드

@GetMapping("/real-world/stream-operations")
fun streamOperations(@RequestParam(defaultValue = "10000000") count: Int): String {
    val result = (1..count)
        .map { "Item $it with some data" }
        .filter { it.contains("Item") }
        .map { it.uppercase() }
        .map { "$it - PROCESSED" }
        .filter { it.length > 10 }
        .map { it.repeat(10) } // 데이터 10배 증가!
        .toList()

    return "처리 완료: ${result.size}개"
}

실행 결과

count=1,000,000으로 시도.

1단계 (map): 100만 개 문자열 생성
2단계 (filter): 100만 개 검사
3단계 (map): 100만 개 대문자 변환
4단계 (map): 100만 개 문자열 추가
5단계 (filter): 100만 개 검사
6단계 (map): 각 문자열을 10배로! (데이터 폭발 💥)

메모리: 100MB → 200MB → 350MB → OOM!

중간 단계에서 계속 새로운 컬렉션이 생성되고, 마지막에 repeat(10)으로 데이터가 10배로 늘어나면서 OOM이 발생했다.

Sequence vs List

Kotlin의 Sequence로 바꿔보면 어떨까?

// Sequence 버전 (Lazy Evaluation)
val result = (1..count).asSequence()
    .map { "Item $it" }
    .filter { it.contains("Item") }
    .map { it.uppercase() }
    .take(100) // 100개만
    .toList()

 

 

방식 메모리 사용 실행 시간
List (Eager) 500MB+ (OOM) -
Sequence (Lazy) 50MB 0.1초

Sequence는 필요한 만큼만 평가하기 때문에 훨씬 효율적이다!

10. 클로저 메모리 누수 - "람다가 너무 많이 캡처하면"

테스트 코드

@GetMapping("/real-world/closure-leak")
fun closureLeak(@RequestParam(defaultValue = "10000") count: Int): String {
    val callbacks = mutableListOf<() -> Unit>()

    repeat(count) { i ->
        val largeData = ByteArray(1024 * 1024) // 1MB

        // 클로저가 largeData를 캡처
        val callback = {
            println("Callback $i: ${largeData.size}")
        }

        callbacks.add(callback)
    }

    return "콜백 ${callbacks.size}개 등록"
}

실행 결과

count=5,000으로 테스트.

생성된 콜백: 5,000개
각 콜백이 캡처한 데이터: 1MB
총 메모리: 5,000MB 필요

우리의 메모리: 512MB
결과: OOM 발생!

문제는 각 람다가 largeData를 캡처하고 있어서, GC가 회수할 수 없다는 것이다.

val callback = {
    println("Callback $i: ${largeData.size}")
    // ↑ 이 부분 때문에 largeData가 메모리에 계속 남음
}

실제 사례: 이벤트 리스너

React 같은 프론트엔드에서 흔히 보는 패턴:

// ❌ 나쁜 예
class UserListView {
    fun render(users: List<User>) {
        users.forEach { user ->
            val button = Button()
            button.onClick = {
                // 클로저가 user 전체를 캡처
                showDetails(user)
            }
        }
    }
}

// ✅ 좋은 예
class UserListView {
    fun render(users: List<User>) {
        users.forEach { user ->
            val button = Button()
            val userId = user.id // 필요한 것만
            button.onClick = {
                // ID만 캡처 (훨씬 작음)
                showDetails(userId)
            }
        }
    }
}

 

 

테스트 결과 요약

총 19개의 OOM 시나리오를 테스트했고, 그 중 12개에서 실제로 OOM이 발생했다.

메모리 사용량 TOP 5

시나리오 메모리 사용 OOM 발생
무한 객체 생성 500MB+ ✅
이미지 처리 (50개) 450MB+ ✅
대용량 리스트 (100만) 490MB ❌ (아슬아슬)
캐시 누수 (1000개) 1000MB ✅
Stream 남용 500MB+ ✅

가장 위험한 패턴 TOP 3

  1. 캐시 TTL 없음 - 서서히 메모리가 차다가 어느 순간 폭발
  2. 페이징 없는 조회 - 데이터가 늘어날수록 위험
  3. 파일 메모리 로드 - 동시 사용자 증가 시 급격히 위험

교훈 및 Best Practices

1. 항상 메모리 제한을 의식하라

// ❌ 위험한 사고방식
"일단 List에 담고 보자"
"메모리는 충분하니까 괜찮겠지"
"개발 환경에서 잘 되는데?"

// ✅ 안전한 사고방식
"이 데이터가 얼마나 클 수 있을까?"
"동시에 몇 명이 사용할까?"
"프로덕션에서는 데이터가 얼마나 많을까?"

2. 페이징은 선택이 아닌 필수

// 절대적 진리
if (데이터가 100개 이상) {
    페이징 필수
}

if (데이터가 동적으로 증가) {
    페이징 필수
}

if (사용자가 선택할 수 있음) {
    페이징 필수
}

// 결론: 항상 페이징

3. 스트리밍 > 버퍼링

// ❌ 전체 로드
val data = file.readBytes() // 위험!

// ✅ 스트리밍
file.inputStream.use { input ->
    input.buffered().forEachLine { line ->
        process(line)
    }
}

4. StringBuilder는 친구

// 문자열 연산이 100번 이상 반복된다면?
// → StringBuilder 무조건 사용

5. 메모리 모니터링

프로덕션에서는 항상

  • Heap 사용량 모니터링
  • GC 로그 분석
  • OOM 발생 시 힙 덤프 자동 생성
java -Xmx2g \
     -XX:+HeapDumpOnOutOfMemoryError \
     -XX:HeapDumpPath=/var/dumps \
     -XX:+PrintGCDetails \
     -XX:+PrintGCTimeStamps \
     -Xloggc:/var/log/gc.log \
     -jar app.jar

 

 

실전 체크리스트

코드 리뷰 시 반드시 체크할 것들:

  • DB 조회에 페이징 적용되어 있는가?
  • 파일 처리를 스트리밍 방식으로 하는가?
  • 캐시에 TTL이나 크기 제한이 있는가?
  • 반복적인 문자열 연산에 StringBuilder를 사용하는가?
  • List에 담을 데이터의 최대 크기를 예상했는가?
  • 동시 사용자를 고려했는가?
  • 메모리 누수 가능성이 있는 패턴은 없는가?

마치며

솔직히 이번 테스트를 하기 전까지는 "OOM이 그렇게 쉽게 발생하나?" 하는 생각이 있었다. 하지만 직접 테스트해보니 생각보다 훨씬 쉽게 발생한다는 걸 알게 되었다.

특히 무서운 건, 개발 환경에서는 전혀 문제가 없어 보인다는 것이다. 테스트 데이터가 적고, 동시 사용자가 없고, 메모리가 넉넉하니까. 하지만 프로덕션에서는...

테스트 데이터: 100개 → 프로덕션: 100만 개
동시 사용자: 1명 → 프로덕션: 100명
메모리: 8GB → 프로덕션: 2GB (컨테이너)

이 차이를 항상 염두에 두고 개발해야 한다.

가장 중요한 교훈

"지금 작동한다고 해서 안전한 코드는 아니다"

메모리 관리는 예방이 최선이다. 문제가 발생한 후에는 이미 늦다. 디버깅도 어렵고, 힙 덤프 분석도 복잡하고, 무엇보다 사용자는 이미 불편을 겪었다.

그러니 처음부터 안전하게 코드를 작성하자.

참고 자료

  • Java Memory Management 공식 문서
  • Kotlin Collections vs Sequences
  • Spring Data JPA Pagination

 

반응형

'서버 개발(생각과 구현) > 서버 생각' 카테고리의 다른 글

나는 검색도 같이 한다.  (0) 2025.11.13
보여지는 시간대(타임존)은 누구의 책임인가  (0) 2025.01.03
단일 프로젝트 구조와 멀티 모듈 구조  (0) 2025.01.01
Custom Swagger를 통해 협업, 업무 효율성 동시에 잡기  (0) 2024.12.27
JPA N+1의 의도와 문제 해결  (0) 2024.12.26
'서버 개발(생각과 구현)/서버 생각' 카테고리의 다른 글
  • 나는 검색도 같이 한다.
  • 보여지는 시간대(타임존)은 누구의 책임인가
  • 단일 프로젝트 구조와 멀티 모듈 구조
  • Custom Swagger를 통해 협업, 업무 효율성 동시에 잡기
dev.JJ
dev.JJ
Software Programming
  • dev.JJ
    끊임없이 생각하자
    dev.JJ
  • 전체
    오늘
    어제
    • 분류 전체보기 (45)
      • 서버 개발(생각과 구현) (16)
        • 코틀린 (0)
        • 서버 생각 (16)
      • 머신러닝 (24)
        • 신경망 첫걸음 (2)
        • 모두를 위한 딥러닝 (12)
        • 딥러닝을 이용한 자연어처리 입문 (9)
        • AI 프로젝트 (1)
      • 수학 (5)
        • 기초 선형대수학 (4)
        • 기초수학 (1)
  • 블로그 메뉴

    • 홈
    • 태그
    • 미디어로그
    • 위치로그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    ML
    머신러닝
    ai
    수학
    프로젝트
    벡터
    코로나 바이러스 예측
    자연어처리
  • 최근 댓글

  • 최근 글

  • 반응형
  • hELLO· Designed By정상우.v4.10.3
dev.JJ
OOM(Out Of Memory)이 발생하는 상황을 만들어보자
상단으로

티스토리툴바