크리티컬 한 오류는 아니지만 IntelliJ에서 작동이 안 되길래 찾아본 오류입니다.

 

🤔 문제발생

@ConfigurationProperties(prefix = "spring.thymeleaf")를 사용하여 사용자 정의 프로퍼티(spring.thymeleaf3)를 만드는 코드를 작성했습니다.

물론 main 메서드가 있는 클래스에서 해당 프로퍼티를 스캔할 수 있도록 @ConfigurationPropertiesScan도 붙여줬습니다.

/*  앞 코드 생략  */

@Getter
@RequiredArgsConstructor
@ConstructorBinding
@ConfigurationProperties(prefix = "spring.thymeleaf3")
public static class Thymeleaf3Properties {
    /**
     * Thymeleaf 3 Decoupled Logic 기능의 활성화
     */
    private final boolean decoupledLogic;
}

/*  뒤 코드 생략  */

 

그리고 나서 사용자 정의 프로퍼티에 대한 지원을 해주는 의존성인 configuration-processor도 추가해 줬습니다.

// build.gradle

dependencies {
    /* 생략 */
    
    annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
}

 

위 의존성을 추가해 주고, gradle을 다시 build 했습니다. 그리고 나서 application.properties에 들어가서 사용자 정의한 "spring.thymeleaf3"가 자동완성되는지 확인했는데, 작동이 안 됐습니다.

 

 

 

🙆🏻‍♂️ 시도 & 해결

구글링에 들어갔습니다. 주요 검색 키워드는 configuration-processor is not working...

 

(1) Clean 하고 다시 Build를 해보자.

 

수도 없이 해봤지만 작동하지 않았습니다. IntelliJ 캐시 문제인가..?라는 생각이 들어서 캐시를 비우고, IDE를 재시작하고 여러 방법을 시도해 봤습니다.

 

이 방법도 근데 소용은 없었습니다. 그럼 캐시 문제도 아니고, 빌드의 문제도 아니라는 것입니다. 

 

 

 

(2) 사용자 정의 프로퍼티를 만들면 spring-configuration-metadata.json 파일이 생성된다는 글을 발견

어디에 생성되는지 찾아보니 아래와 같은 경로에 생성되었습니다.

build/classes/java/main/META-INF/spring-configuration-metadata.json

json 파일로 생성되고, 우리가 Java Doc으로 작성한 내용도 저장되며, 해당 프로퍼티에 대한 메타 데이터가 들어가 있는 파일이었습니다.

{
  "groups": [
    {
      "name": "spring.thymeleaf3",
      "type": "com.fastcampus.getinline.config.ThymeleafConfig$Thymeleaf3Properties",
      "sourceType": "com.fastcampus.getinline.config.ThymeleafConfig$Thymeleaf3Properties"
    }
  ],
  "properties": [
    {
      "name": "spring.thymeleaf3.decoupled-logic",
      "type": "java.lang.Boolean",
      "description": "Thymeleaf 3 Decoupled Logic 기능의 활성화",
      "sourceType": "com.fastcampus.getinline.config.ThymeleafConfig$Thymeleaf3Properties",
      "defaultValue": false
    }
  ],
  "hints": []
}

 

즉, Build는 정상적으로 되었고 메타 데이터도 정상적으로 들어왔다는 것이 확인되었습니다.

그럼 문제는 하나 IntelliJ에서 해당 메타 데이터를 인식하지 못한다는 점이었습니다.

 

 

 

(3) IntelliJ Project Setting 변경

그렇게 구글을 찾아서 돌아다니다가 글 하나를 발견했습니다. 

https://youtrack.jetbrains.com/issue/IDEA-191886

 

spring-configuration-metadata.json ignored in Gradle "build" directory : IDEA-191886

I am trying to get IDEA to recognize custom properties in my Spring Boot application. The required metadata file is generated by Gradle in "build/classes/java/main/META-INF/spring-configuration-metadata.json" but IDEA does not seem to recognise it. As soon

youtrack.jetbrains.com

제목부터가 "spring-configuration-metadata.json ignored in Gradle "build" directory"이어서 뭔가 내가 찾던 문제와 같은 문제를 겪고 있는 거 같았습니다.

The required metadata file is generated by Gradle in "build/classes/java/main/META-INF/spring-configuration-metadata.json" but IDEA does not seem to recognise it.

 

해당 글 중에서 제가 말했던 경로에 metadata.json이 만들어는 졌는데 IDEA가 그걸 인지하지 못한다는 질문이었습니다. 역시... 구글링은 영어로 하는 것이 정답인가... 영어를....

 

해당 질문 댓글 중에서 해결책 하나가 쓰여있는 거 같아서 해봤습니다.

 

IntelliJ에서 Project Structrue에 들어갑니다. 맥에서는 {Command + ;}를 누르면 들어가집니다. 위 Tool bar를 통해서도 들어갈 수 있습니다.

 

Modules -> main -> Paths를 눌러보면 아래와 같은 화면이 나옵니다.

 

Output path를 보면 지금 out/production/classes로 되어 있는데, 이것을 Metadata.json이 있던 경로로 변경해 줬습니다.

 

out/production/classes -> build/classes/java/main으로 변경해 줬습니다. 

 

그리고 나서 Gradle clean을 하고 다시 build를 해주고 나서 application.properties에서 사용자 정의한 "spring.thymeleaf3"이 자동완성이 되는지 해봤습니다.

 

우와...됐다... 이전에는 자동완성이 나오지 않았었는데, spring.만 쳐도 thymeleaf3가 나오는 것을 볼 수 있습니다.

 

 

 

🙆🏻‍♂️ 알게 된 점 & 느낀 점

  • 구글링을 한글로 막 검색하다 보니 자료도 많이 안 나왔던 거 같습니다.
  • 해당 문제가 뭐 때문에 발생하는지 판단을 먼저 해야 정확한 구글링이 될 수 있다는 것을 느꼈습니다.
  • 저는 해당 문제가 Spring configuration processor의 문제라고 생각하고, 그쪽으로 검색을 엄청했었는데, IntelliJ의 문제일 수도 있겠다는 생각으로 검색을 하니 방법을 찾을 수 있었다고 생각합니다.

 

 

 

📘 참고자료

https://docs.spring.io/spring-boot/docs/current/reference/html/configuration-metadata.html#appendix.configuration-metadata.annotation-processor.automatic-metadata-generation

 

Configuration Metadata

Configuration metadata files are located inside jars under META-INF/spring-configuration-metadata.json. They use a JSON format with items categorized under either “groups” or “properties” and additional values hints categorized under "hints", as sh

docs.spring.io

 

평소에 늘 JDK11 버전을 사용해서 모르고 있었는데, 최근 JDK16으로 버전을 바꿔보면서 새롭게 사용해 본 기능이 있어서 기록해 볼까 합니다. 추가로 평소에 궁금했었던 불변 객체에 대해서도 조금 찾아서 기록해보려고 합니다.

 

 

📖 불변 객체(Immutable Object)란?

  • 생성 후 그 상태를 바꿀 수 없는 객체를 의미합니다.
  • 불변객체는 읽을 수만 있는 Read-Only 메서드만을 제공합니다.
  • Java의 대표적인 불변객체로는 String이 있습니다.

 

# String Class Description

 

그럼 String은 생성된 후 값을 변경할 수 없다는건가? 그럼 아래 코드는 무엇인가? 값을 변경하는 것이 아닌가?

public class Main {
    public static void main(String[] args) {
        String stringEx = "Hello";
        System.out.println(stringEx); // Hello
        
        stringEx = "World";
        System.out.println(stringEx); // World
    }
}
  • stringEx라는 String 객체의 값을 Hello에서 World로 바꾼 것이 아닌가?라는 생각이 들 수 있습니다.
  • 이는 값을 변경한 것이 아니고 기존에는 String Constant Pool에 있는 "Hello"를 참조하고 있다가 "World"로 참조 방향이 변경된 것 입니다. 즉, stringEx의 값이 변경된 것이 아닙니다.
  • String 포스팅이 아니기 때문에 해당 링크를 참고해주시면 이해가 쉬울 거 같습니다.

 

 

 

📖 불변 객체를 만드는 방법

1. final 키워드를 이용하자

  • Java에서는 불변성을 확보하기 위해서 final이라는 키워드를 제공합니다.
  • Java의 기본 필드는 가변적이지만 final 키워드를 앞에 붙여주면 객체 생성시 값 초기화는 가능하지만, 생성된 이후에는 변경이 불가능합니다.

 

# final 키워드를 이용한 필드 불변성 확보 시 문제점

  • 하지만 final 키워드를 사용하더라도 해당 객체 내부의 상태를 변경시키지 못하는 것은 아닙니다.
  • 아래의 예시는 List에 final 키워드를 붙여서 불변성을 확보했습니다. 그리고 List에 값을 추가해 보는 예시입니다.
public class Ex {
    public static void main(String[] args) {
        WorkSpace workSpace = new WorkSpace("Test WorkSpace");
        workSpace.getWorkSpace().add(new Worker("윤씨", 27)); // 불변 필드의 내부 상태를 변경
        workSpace.getWorkSpace().add(new Worker("정씨", 29)); // 불변 필드의 내부 상태를 변경
        
        workSpace.getWorkers().forEach(worker -> {
            System.out.println(worker);
        });
        // [출력]
        // Worker[김씨, 26]
        // Worker[박씨, 30]
        // Worker[윤씨, 27]
        // Worker[정씨, 29]
    }
}

class WorkSpace {
    // 불변성을 확보하기 위해서 필드를 final로 선언
    private final List<Worker> workers;
    private final String workSpaceName;

    public WorkSpace(String workSpaceName) {
        this.workers = new ArrayList<>(List.of(new Worker("김씨", 26), new Worker("박씨", 30)));
        this.workSpaceName = workSpaceName;
    }

    // final 필드를 읽기만 하는 Read-Only 메서드 선언
    public List<Worker> getWorkers() {
        return this.workers;
    }
    
    public String getWorkSpaceName() {
        return this.workSpaceName;
    }
}

class Worker {
    private String name;
    private Integer age;

    public Worker(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
    
    @Override
    public String toString() {
        return "Worker[" + name + ", " + age + "]";
    }
}
  • 위 코드를 보면 알 수 있듯이 WorkSpace Class 내부의 workSpace 필드를 불변성 확보를 위해서 final 키워드로 선언했습니다.
  • 그리고 해당 필드를 읽을 수 있는 Getter 메서드를 선언해줬습니다.
  • 하지만 외부(Ex Class)에서 final로 불변성을 확보한 필드 내부에 새로운 객체가 계속 추가돼도 문제가 없는 상태가 되었습니다.
  • Java에서는 위와 같이 참조에 의해서 값이 변경될 수 있는 점을 주의해야 합니다. 그래서 완벽한 불변 객체를 만들기 위해서는 "불변 클래스"로 만들어야 합니다.

 

 

2. 불변 클래스로 만들자

  • 불변 클래스가 되기 위해서는 Class를 final 키워드를 사용해서 선언합니다.
  • Class의 모든 필드를 private final로 선언합니다.
  • 객체를 생성할 수 있는 생성자 또는 정적 팩토리 메서드(of)를 선언합니다.
  • 위에서 봤던 List와 같이 참조에 의해서 변경될 가능성이 있는 필드는 방어적 복사를 이용하여 제공합니다.

 

# WorkSpace를 완벽한 불변 클래스로 만드는 리펙토링

// final 키워드로 class를 선언
final class WorkSpace {
    // 불변성을 확보하기 위해서 필드를 private final로 선언
    private final List<Worker> workers;
    private final String workSpaceName;

    // 생성자를 내부적으로 호출할 수 있도록 private로 선언
    private WorkSpace(String workSpaceName) {
        this.workers = new ArrayList<>(List.of(new Worker("김씨", 26), new Worker("박씨", 30)));
        this.workSpaceName = workSpaceName;
    }

    // 정적 팩토리 메서드를 선언
    public static WorkSpace of(String workSpaceName) {
        return new WorkSpace(workSpaceName);
    }

    // 방어적 복사를 이용하여 workers를 제공
    public List<Worker> getWorkers() {
        return Collections.unmodifiableList(this.workers);
    }

    public String getWorkSpaceName() {
        return this.workSpaceName;
    }
}
  • 첫 번째로 final 키워드로 class를 선언하여 불변 클래스로 만들었습니다.
  • 두 번째로 모든 필드는 private final로 선언해 줬습니다.
  • 세 번째로 내부 생성자를 private로 선언하여 정적 팩토리 메서드를 통해서만 객체를 생성할 수 있도록 했습니다.

    • 이렇게 설정한 이유는 아무런 생성자 없이 정적 팩토리 메서드만 생성한다면 기본적으로 비어있는 내부 생성자를 컴파일러에서 자동으로 만들어줍니다. 그렇기 때문에 클래스 외부에서 어디서든 객체를 만들고 접근할 수 있기 때문입니다.
  • 마지막으로 workers List가 필요한 경우가 있을 수 있기 때문에 불변성을 확보하기 위해서 방어적 복사를 사용하여 List를 제공했습니다.

 

 

 

📖 Java의 Record

  • JDK14에서 preview로 소개하고, JDK16 버전부터 정식으로 사용할 수 있게 되었습니다.
  • 불변 데이터 객체를 쉽게 생성할 수 있도록 도와줍니다.

출처: https://www.baeldung.com/java-15-new

 

1. Record의 특징

  • Record는 따로 적어주지 않아도 암묵적으로 final 클래스이기 때문에 상속이 불가능합니다.
  • 다른 클래스를 상속(extends) 받을 수는 없지만, 인터페이스를 구현(implements)은 할 수 있습니다.
  • 필드에 따로 private final 키워드를 붙여주지 않아도 됩니다.
  • 모든 필드를 파라미터로 갖는 생성자를 기본적으로 제공합니다.
  • 객체 생성 시 재정의 해줘야 하는 toString(), hashCode(), equals()를 기본적으로 제공합니다.

 

 

2. Class  vs Record (비교)

비교를 위해서 먼저 Record를 사용하지 않고, 불변 객체를 만드는 코드를 작성해 봅니다.

 

# 기존의 Class를 이용하여 불변 객체 Student를 생성

public final class Student {
    private final String studentId;
    private final String name;
    private final Integer age;
    
    public Student(String studentId, String name, Integer age) {
        this.studentId = studentId;
        this.name = name;
        this.age = age;
    }

    public String getStudentId() {
        return this.studentId;
    }

    public String getName() {
        return this.name;
    }

    public Integer getAge() {
        return this.age;
    }

    @Override
    public String toString() {
        return String.format("Student[studentId=%s, name=%s, age=%d]", studentId, name, age);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Student student = (Student) o;

        if (!Objects.equals(studentId, student.studentId)) return false;
        if (!Objects.equals(name, student.name)) return false;
        return Objects.equals(age, student.age);
    }

    @Override
    public int hashCode() {
        return Objects.hash(studentId, name, age);
    }
}

 

# Record를 이용하여 불변객체 Student 생성

public record Student(String studentId, String name, Integer age) {
   // 끝
}
  • 이렇게만 봐도 엄청난 기능을 가지고 있다고 볼 수 있습니다. 값을 가져올 수 있는 getter()도 모두 기본적으로 제공됩니다. 메서드의 이름은 만약 studentId를 가져오고 싶다면 그냥 studentId()라는 메서드로 사용하면 getter와 동일한 기능을 합니다.
  • @Override 해서 재정의해줬던 toString(), hashCode(), equals()도 모두 기본적으로 제공하기 때문에 따로 정의해주지 않아도 됩니다.
  • 모든 필드를 파라미터로 갖는 생성자도 기본적으로 제공해 주기 때문에 따로 정의해주지 않아도 됩니다.

 

 

3. Record의 추가적인 기능

추가적인 기능을 기존의 class와 비교하면서 설명드리겠습니다.

 

 

(1) 생성자로 들어오는 파라미터 유효성 검사하기

20살이 넘는 age 값이 들어왔을 때는 객체가 생성되지 않고, 예외를 던지도록 코드를 설정해 봅니다.

// Class 사용

public final class Student {
    private final String studentId;
    private final String name;
    private final Integer age;

    public Student(String studentId, String name, Integer age) {
        if (age >= 20) {
            throw new IllegalArgumentException("20살 이상은 학생이 아닙니다!");
        }
        this.studentId = studentId;
        this.name = name;
        this.age = age;
    }
}

 

// Record 사용

public record Student(String studentId, String name, Integer age) {

    public Student {
        if (age >= 20) {
            throw new IllegalArgumentException("20살 이상은 학생이 아닙니다!");
        }
    }
}

위 방법을 이용하면 Null에 대한 처리도 충분히 할 수 있을 거 같습니다.

 

 

(2) Person interface 구현하기

// Person Interface

public interface Person {
    void walk();
    void eat();
}
// Class 사용

public final class Student implements Person {
    /* 코드 생략 */
    
    @Override
    public void walk() {
        System.out.println(this.name + "가 걷습니다.");
    }

    @Override
    public void eat() {
        System.out.println(this.name + "가 밥을 먹습니다.");
    }
}
// Record 사용

public record Student(String studentId, String name, Integer age) implements Person {
    /* 코드 생략 */
    
    @Override
    public void walk() {
        System.out.println(this.name + "가 걷습니다.");
    }

    @Override
    public void eat() {
        System.out.println(this.name + "가 밥을 먹습니다.");
    }

}

Record는 Interface는 기존의 Class처럼 구현할 수 있습니다. 하지만 Class를 상속(extends) 받는 것은 불가능합니다.

 

Record에서 extends를 하려고 할 때

 

 

 

 

 

사실 Lombok 라이브러리나 IDE에서 toString(), hashCode(), equals()를 자동으로 Generate해주는 기능도 보면서 정말 편하게 사용하기 위해서 여러 가지 기능을 만들고 있구나라는 생각을 했는데, Record를 보고 나서 발전은 정말 끝이 없다는 것을 느꼈습니다. 나중에는 어떤 기능들이 점점 더 추가될지 기대되면서도 무섭다는 생각을 했습니다.

 

운영체제는 한정되어 있는 메모리를 극한으로 활용할 수 있도록 메모리를 관리하는 역할을 합니다.

 

 

📖 가상 메모리 (Virtual Memory)

(1) 가상 메모리란?

  • 메모리 관리 기법 중 하나로 프로세스 전체가 메모리에 올라오지 않더라도 실행이 가능하도록 해주는 기법입니다.
  • 실제의 물리 메모리 개념과 프로그래머의 논리 메모리 개념을 분리합니다.

 

(2) 가상 메모리를 이용하면 좋은 점

  • 물리 메모리(RAM)의 크기의 제약을 받지 않게 됩니다. (알고리즘에 집중할 수 있습니다)
  • 많은 프로그램을 동시에 실행할 수 있게 됩니다. 
  • 프로그램을 메모리에 올리고 Swap(스왑)을 하는데 필요한 입출력(IO) 횟수가 줄어들기 때문에 프로그램을 보다 더 빠르게 실행시킬 수 있습니다.

 

(3) 요구 페이징 (Demanding Paging)

  • 프로세스에서 필요한 것들만 골라서 메모리에 적재할 수 있도록 해주는 전략을 요구 페이징이라고 합니다.
  • 초기에는 필요한 부분만 메모리에 적재하여 프로세스를 실행하고, 필요할 때마다 요청하여 메모리에 적재하는 방법을 사용합니다.
  • 한 번도 접근하지 않은 *페이지는 물리 메모리에 적재되지 않습니다.
  • 해당 페이지가 물리 메모리에 존재하는지 안 하는지에 대한 여부는 유-무효(valid-invalid) 비트를 사용하여 표시합니다.
  • 가상 메모리 공간에는 존재하지만 물리 메모리 공간에는 없는 데이터에 접근했을 때는 페이지 폴트(Page Fault)가 발생합니다.
    • 페이지 폴트가 발생했을 때는 운영체제가 스와핑(Swaping)을 통해서 마치 페이지 폴트가 발생하지 않은 것처럼 프로그램을 작동시킵니다.
*페이지(Page): 가상 메모리를 사용하는 최소 크기 단위를 말합니다.

 

 

# 스와핑 (Swaping)

  • 페이지 폴트를 방지하기 위해서 당장 사용하지 않는 영역을 하드디스크(보조기억장치, 스왑영역)로 옮기고, 필요할 때 다시 하드디스크에서 RAM으로 불러오는 작업을 스와핑이라고 합니다.
  • 하드디스크에서 RAM으로 불러오는 것을 Swap-In이라고 하며, RAM에서 하드디스크로 내리는 작업을 Swap-Out이라고 합니다. 
  • Swap을 하는 데는 큰 디스크 전송시간이 요구되기 때문에 메모리가 부족한 경우에만 Swapping을 진행합니다.

 

 

# 요구 페이징을 통해 페이지 폴트를 처리하는 과정

*프레임(Frame): 물리 메모리를 사용하는 최소 크기 단위를 말합니다.

 

 

 

📖 스레싱 (Thrashing)

  • 너무 많은 프로세스가 동시에 올라오게 되면서 스와핑(Swaping)이 빈번하게 발생하여 생기는 문제가 스레싱입니다.
  • 즉, 페이지 폴드율이 높아짐에 따라서 발생하는 문제입니다. 스레싱은 컴퓨터의 심각한 성능 저하를 발생시킵니다.
  • 다시 정리해 보면 어떤 프로세스가 실행되는 시간보다 더 많은 시간이 페이징 하는 데 사용되고 있다면 스레싱이 발생했다고 할 수 있습니다.
  • OS가 평소에 CPU의 이용률을 감시합니다. 페이지 폴드율이 높아지면 그만큼 CPU의 대기 시간이 길어지기 때문에 CPU 이용률이 감소하게 됩니다. 그래서 OS는 CPU의 이용률이 낮기 때문에 여유롭다고 판단하고 가용성을 높이기 위해서 더 많은 프로세스를 메모리에 올리는 작업을 하게 됩니다.

 

(1) 스레싱을 해결할 수 있는 방법

  • 작업 세트, 작업 집합 (Working Set)
  • 페이지 폴트 빈도 (PFF, Page Fault Frequency)

 

# 작업 세트, 작업 집합 (Working Set)

  • 프로세스가 실행이 될 때, 어떤 특정한 지역에서만 메모리를 참조하게 됩니다. 이것을 *지역성(Locality)라고 합니다.
  • 지역성 특징을 기반으로 하여 어느 일정 시간 동안 활발하게 사용되었던 페이지들을 이용하여 작업 집합을 만들어 미리 메모리에 로드하는 방법을 말합니다.
  • 이렇게 미리 메모리에 로드하게 되면 탐색에 드는 비용을 줄일 수 있고, 스와핑 횟수도 줄일 수 있습니다.
*지역성: 프로세스가 일정 시간 동안 집중적으로 특정 주소 영역을 참조하는 경향을 분석

 

# 페이지 폴트 빈도(PFF, Page Fault Frequency)

  • 페이지 폴트율의 상한과 하한을 지정하여 프로세스에 할당할 메모리의 크기를 동적으로 예측하고 조절하는 방법입니다.
  • 폴트율이 상한을 넘어서면 해당 프로세스에 필요한 프레임이 부족하다고 판단하여 늘려주고, 하한보다 떨어지면 해당 프로세스에 주어진 프레임이 많다고 판단하여 줄여주어 스레싱을 방지합니다.

 

 

 

📖 메모리 할당

(1) 연속 메모리 할당

  • 프로세스에 연속적인 메모리 공간을 할당하는 방법을 말합니다.
  • 연속 메모리 할당 방식에는 최초 적합, 최적 적합, 최악 적합 방식이 존재합니다.

# 최초 적합

  • 운영체제가 메모리 내에서 빈 공간을 순서대로 탐색하다가 적재가 가능한 공간을 발견하면 그 공간에 프로세스를 바로 적재하는 방식을 말합니다.
  • 검색 시간이 최소화되고, 빠르게 할당이 가능합니다.

 

# 최적 적합

  • 운영체제가 메모리의 비어있는 공간을 모두 검색한 뒤에 가장 작은 공간에 적재하는 방식을 말합니다.

 

# 최악 적합

  • 운영체제가 메모리의 비어있는 공간을 모두 검색한 뒤에 가장 큰 공간에 적재하는 방식을 말합니다.

 

 

(2) 연속 메모리 할당의 문제점

  • 연속 메모리 할당 방법은 메모리를 효율적으로 사용하는 방법이 아닙니다. 잠재적으로 외부 단편화가 발생할 수 있습니다.

# 외부 단편화

  • 프로세스들이 실행되고 종료되는 과정이 반복되며 메모리 사이사이에 비어있는 공간이 발생되고, 다음 프로세스를 할당하기 어려울 정도로 작아서 메모리가 낭비되는 문제를 말합니다. 아래 그림으로 간단하게 이해해봅시다.

# 메모리 압축 (Compaction) - 외부 단편화 해결

  • 비어있는 공간들을 하나로 모아서 하나의 큰 비어있는 공간으로 만드는 방법을 말합니다.
  • 여기저기 흩어져 있는 공간들을 합치는 과정은 효율이 떨어질 수 있는 방법입니다.

 

# 내부 단편화

  • 할당하기 위해서 나눴던 메모리 크기보다 프로세스에 필요한 메모리가 작아서 남는 메모리 공간이 생기는 것을 말합니다.

 

 

(3) 불연속 메모리 할당

  • 불연속 메모리 할당 방법은 현대 운영체제가 쓰는 방법인 페이징 기법이 존재합니다.
  • 페이징 기법은 메모리를 동일한 크기의 페이지로 나누고 프로그램(프로세스)마다 페이지 테이블을 두어 이를 통해 실제 메모리에 프로그램을 할당하는 방법입니다.
  • 페이징 기법 이외에도 세그멘테이션, 페이지드 세그멘테이션 방법이 존재합니다.

 

# 페이징 기법 (Paging)

  • 프로세스에게 연속적인 메모리를 할당하지 않고, 메모리를 페이지 단위로 나누어 서로 다른 위치에 프로세스를 할당합니다.
  • 논리적 메모리(가상 메모리)에서는 프로세스를 페이지 단위로 나누어서 관리하고, 물리적 메모리(RAM)는 프레임 단위로 나누어져 있습니다.
  • 하나의 프로세스가 사용하는 메모리 공간은 연속적이어야 한다는 제약을 깨버리는 메모리 할당 방법입니다.

 

# 세그멘테이션

  • 정해진 사이즈의 블록 크기 페이지 단위가 아닌 서로 사이즈가 다른 논리적인 세그먼트(Segment)로 나누는 방법을 말합니다.
  • 하나의 프로세스는 코드, 데이터, 스택, 힙으로 이루어져 있는데, 이를 기반으로 나눌 수도 있고, 함수 단위로 나눌 수도 있는 방법입니다.
  • 메모리에 비어있는 공간의 크기가 균일하지 않다는 문제가 발생될 수 있습니다.

 

 

 

📖 페이지 교체 알고리즘

  • 메모리는 한정되어 있어서 스와핑이 빈번하게 일어납니다.
  • 스와핑이 최대한 적게 일어날 수 있는 구조로 설계되어야 합니다.
  • 페이지 교체 알고리즘을 기반으로 스와핑이 일어나게 됩니다.

 

# 오프라인 알고리즘 (Offline Algorithm)

  • 먼 미래에 참조되는 페이지와 현재 할당하려는 페이지를 바꾸는 알고리즘을 말합니다.
  • 먼 미래에 사용되는 프로세스를 알 수 있는 방법이 존재하지 않기 때문에 사용할 수 없는 알고리즘입니다.
  • 알고리즘 간의 성능 비교에 대한 기준으로 사용됩니다.

 

# FIFO (First In First Out)

  • 페이지 교체 시점이 오면 먼저 메모리에 할당되었던 순서대로 먼저 나가게 되는 간단한 알고리즘입니다.
  • 가장 직관적인 간단한 알고리즘으로 구현하기 쉽다는 장점이 있습니다.
  • 가장 활발한 페이지를 먼저 교체해서 페이지의 부재율을 높이는 부작용이 발생할 수 있습니다.

 

# LRU (Least Recentle Used) : 기간

  • 페이지 교체 시점이 오면 가장 오랫동안 사용되지 않는 페이지를 선택하여 교체합니다.
  • 대체적으로 FIFO 알고리즘보다는 우수한 성능을 갖습니다.
  • 가장 오래됐는지 파악하기 위해서 각 페이지마다 계수기, 스택을 두어야 하는 문제점이 있습니다.

 

# LFU (Least Frequency Used) : 횟수

  • 페이지 교체 시점이 오면 가장 참조 횟수가 적은 페이지를 선택하여 교체합니다.
  • 활발한 페이지는 참조 횟수도 많을 것이라는 가정하에 만들어진 알고리즘입니다.

 

# 최적 페이지 교체 (Optimal Page Replacement, OPR)

  • 앞으로 오랫동안 사용되지 않을 페이지를 선택하여 교체합니다.
  • 프로세스가 앞으로 사용할 페이지를 미리 알아야 한다는 조건이 필요합니다.
  • 앞으로 사용할 페이지를 아는 것은 불가능하기 때문에 구현이 불가능한 알고리즘입니다.
  • 알고리즘 구현의 목적보다는 알고리즘 연구 비교의 목적으로 사용되는 알고리즘입니다.

IntelliJ 사용 중 아래와 같이 계속 주석에 밑줄이 생겼다.

 

 

문법적인 오류로 발생한건지... 영어랑 한글이랑 저렇게 붙여서 쓰면 계속 밑줄이 생겨서 거슬린다. 제거해 버리자.

 

 

1. Setting으로 들어가기

맥북은 Command + , 을 누르면 들어가진다. 윈도우는 찾아봐야 할 거 같지만, 단축키 없이도 들어갈 수 있다.

 

 

 

 

2. "Inspection" 검색

 

 

 

3. "Proofreading" 검색

 

이렇게 Proofreading을 검색하고 들어가서 "Typo"를 누르면 오른쪽에 체크박스가 나옵니다.

그중에서 Process comments의 체크박스를 풀어주면 됩니다.

 

 

 

4. 사라졌는지 확인

 

밑줄이 사라진 것을 볼 수 있다. 계속 거슬렸는데 이렇게 설정해서 없애니깐 속이 시원해졌습니다. 😤

 

근데 인텔리제이의 설정은 프로젝트마다 설정이 존재하는 것으로 보입니다.

다른 프로젝트로 변경하여 설정에 들어가서 동일하게 Inspection -> Proofreading -> Typo를 들어가 보면 아래와 같이 나옵니다.

 

다른 프로젝트로 들어가서 다시 확인해봤을 때의 모습

 

결론은 번거로워서 자주 해주는 설정은 아니지만, 오랫동안 봐야 하는 프로젝트를 할 때마다 설정 방법을 까먹어서 이렇게 기록해 두고 쓰려고 합니다.

 

밑줄 아무렇지 않아 보이지만 코드 작성할 때마다 계속 신경 쓰일 겁니다 😈

🤔 문제 발생

Request 요청을 위한 DTO로 사용하기 위한 클래스를 생성한 후 해당 객체를 ObjectMapper의 writeValueAsString() 메서드에 넣어 serialize 해서 json 데이터로 mvc에 content에 담아 POST 요청을 하는 테스트를 진행했습니다.

 

# PlaceRequest.java 코드

@AllArgsConstructor
public class PlaceRequest {
    private PlaceType placeType;
    private String placeName;
    private String address;
    private String phoneNumber;
    private Integer capacity;
    private String memo;

    public static PlaceRequest of(
            PlaceType placeType,
            String placeName,
            String address,
            String phoneNumber,
            Integer capacity,
            String memo
    ) {
        return new PlaceRequest(placeType, placeName, address, phoneNumber, capacity, memo);
    }
}

위 코드의 static 메서드인 of()로 만든 객체를 ObjectMapper.writeValueAsString()에 넣어 serialize를 진행했고, 아래와 같은 오류가 발생했습니다.

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class

 

 

 

🙆🏻‍♂️ 시도 / 해결

1. PlaceRequest 클래스에 빈 생성자가 존재하지 않으면 Serializer가 작동하지 않는다.

@NoArgsConstructor
@AllArgsConstructor
public class PlaceRequest {
    /* 코드생략 */
}

@NoArgsConstructor 애노테이션을 붙여 비어있는 생성자를 만들어줬습니다. 그리고 다시 실행했을 때의 결과는 동일한 예외가 발생되었습니다.

 

JackSon에서 Deserialize(역직렬화, json -> Object)를 할 때는 기본 생성자로 객체를 생성한 후 필드 값을 찾아서 바인딩해준다고 합니다. 하지만 우리가 하려는 과정은 Deserialize가 아닌 Serialize였기 때문에 사실상 지금은 @NoArgsConstructor가 아무런 효과가 없었습니다.

 

 

2. serialize를 하는 과정에서 PlaceRequest 클래스의 필드에 접근하게 되는데, 이때 public 접근제한자 또는 getter를 통해서 접근하게 된다.

여기서 2가지 방법을 시도해볼 수 있었습니다.

 

(1) 필드를 모두 public으로 바꿔보자

@AllArgsConstructor
public class PlaceRequest {
    public PlaceType placeType;
    public String placeName;
    public String address;
    public String phoneNumber;
    public Integer capacity;
    public String memo;
    
    /* 코드 생략 */
}

테스트를 돌려보니 통과하는 것을 볼 수 있습니다.

 

 

(2) 필드는 그대로 private로 두고, Getter를 만들어보자

@Getter
@AllArgsConstructor
public class PlaceRequest {
    private PlaceType placeType;
    private String placeName;
    private String address;
    private String phoneNumber;
    private Integer capacity;
    private String memo;
}

이 방법도 동일하게 테스트가 통과되는 것을 볼 수 있습니다. 저는 private 필드를 사용하기 위해서 두 번째 방법을 사용해서 코드를 작성했습니다.

 

 

 

💡 알게된 점

JackSon을 사용해서 Serialize(Object -> json)할 때는 필드를 public으로 지정하거나 private으로 해두었다면 JackSon이 해당 필드에 접근할 수 있도록 Getter를 만들어주어야 합니다.

 

반대로 Deserialize(json -> Object)를 할 때는 비어있는 기본 생성자(NoArgsConstructor)로 객체를 생성하기 때문에 반드시 기본 생성자를 클래스 안에 만들어줘야 합니다.

 

사실 해결 방법에는 @JsonProperty를 필드에 붙여주는 방법도 찾았던 거 같은데, 이 부분은 나중에 따로 시간을 내서 알아보고자 합니다. 

알면 편리한 기능인 것 같아서 짧게 기록해두려 합니다.

 

아래 코드가 있습니다.

 

"$.data~~"에서 data뒤에 배열의 인덱스를 나타내는 [0]을 추가하고 싶습니다.

 

단축키를 찾아보니 "Shift + 아래키"라고 쓰여있어서 했는데, 아래와 같이 모든 줄이 선택됩니다.

 

 

이때 "Command + Shift + 8"을 입력하면 인텔리제이 오른쪽 밑 부분에 Column이라는 글자가 나타나면서 모드가 변경됩니다.

 

 

# 단축키 누르기 전

 

# 단축키를 누른 후

 

Column이라는 글자가 생긴 것을 볼 수 있습니다.

이때 다시 "Shift + 아래키"를 눌러 봅시다.

 

 

오호 성공입니다. 만약 커서를 다시 원상태로 복구하고 싶다면 Esc를 눌러주시면 됩니다.

 

이제 동시에 코드를 추가해 보겠습니다.

 

 

모든 수정이 끝나면 다시 "Command + Shift + 8"을 눌러주면 기존 모드로 변경되어 "Shift + 아래키"를 누르면 다중 문장이 선택됩니다.

 

하나씩 변경해 줘도 상관없지만 한 번 수정에 1초면 5초를 Save 했습니다. 😀

 

+ Recent posts