|
|
난이도 : 중급
Lakshmi Shankar, Java Technology Center Development Team, IBM Hursley Labs Simon Burns, Java Technology Center Development Team, IBM Hursley Labs
2007 년 3 월 06 일
클래스 로딩 컴포넌트는 자바™ 가상 머신에 있어서 기본적인 것입니다. 개발자들이 클래스 로딩의 기초에 대해서 잘 이해하고 있더라도 문제가 생기면 이것을 진단하고 솔루션을 만드는데 어려움을 겪습니다. 네 편의 기술자료 시리즈에서 Lakshmi Shankar와 Simon Burns는 자바 개발 중에 발생할 수 있는 다양한 클래스 로딩 문제들을 설명하고 그 원인과 해결 방법을 설명합니다. NoClassDefFoundError 와 ClassNotFoundException 같은 일반적인 자바 예외와, 클래스 로더의 제약조건 위반 교착 상태 같은 보다 어려운 문제들 이해하고 해결하는데 도움이 될 것입니다. 이 첫 번째 글에서 자바 클래스 로딩의 작동 방법과 JVM에서 사용할 수 있는 툴을 사용하여 클래스 로딩 문제를 진단하는 방법을 설명합니다.
클래스 로더는 클래스들을 Java Virtual Machine (JVM)에 로딩하는 일을 담당한다. 단순한 애플리케이션들은 자바 플랫폼에 내장되어 있는 클래스 로딩 장치를 사용하여 클래스들을 로딩한다. 보다 복잡한 애플리케이션들은 고유의 클래스 로더를 정의하기도 한다. 어떤 종류의 클래스 로더를 사용하든지 간에, 클래스 로딩 과정 동안 문제가 생길 수 있다. 이 같은 문제를 피하려면, 클래스 로딩의 구조를 이해해야 하여야 하며, 이를 통해, 문제가 발생하면 진단 기능과 디버깅 기술이 문제 해결에 도움이 될 것이다.
본 기술자료 시리즈에서는 클래스 로딩 문제들을 설명한다. 첫 번째 기술자료에서는 클래스의 로딩의 기초를 설명하고, 두 번째 기술자료에서는 JVM 디버깅 기능들을 다룰 예정이다. 나머지 세 개의 기술자료에서는 클래스 로딩 예외를 해결하는 방법과, 보다 민감한 클래스 로딩 문제들을 설명할 것이다.
클래스 로딩의 기초
이 섹션에서는 클래스 로딩의 핵심 개념을 설명한다.
클래스 로더 위임
클래스 로더 델리게이션 모델(class loader delegation model)은 로딩 요청을 서로에게 전달하는 클래스 로더의 그래프이다. bootstrap 클래스 로더는 이 그래프의 루트(root)에 있다. 클래스 로더들은 하나의 delegation parent와 함께 생성되고 다음 위치에서 클래스를 찾는다.
클래스 로더가 처음 하는 일은 과거에도 같은 클래스를 로딩하도록 요청을 받았는지를 파악하는 것이다. 만약 그렇다면, 지난 번에 리턴했던(캐시에 저장된) 클래스를 리턴한다. 그렇지 않을 경우, 부모에게 클래스를 로딩 할 기회를 준다. 이러한 두 단계들이 반복되고 깊이 우선 방식으로(depth first) 탐색한다. 만약 부모가 null을 리턴하면(또는 ClassNotFoundException 을 던지면), 클래스 로더는 클래스의 소스에 대한 공유의 경로를 검색한다.
부모 클래스 로더는 클래스를 처음으로 로딩할 기회가 주어지기 때문에 이 클래스는 루트에 가장 가까운 클래스 로더에 의해 로딩된다. 모든 핵심적인 bootstrap 클래스들은 bootstrap 로더에 의해 로딩되고, 이 bootstrap 로더는 java.lang.Object 같은 정확한 클래스 버전이 로딩되었는지를 확인한다. 또한 클래스 로더가 그 자체 또는 부모 또는 조상에 의해서 로딩되었는지를 클래스가 볼 수 있도록 한다. 자식에 의해 로딩된 클래스들은 볼 수 없다.
그림 1은 세 가지 표준 클래스 로더들이다. 그림1. 클래스 로더 델리게이션 모델
다른 모든 클래스 로더들과는 달리 Bootstrap 클래스 로더(primordial 클래스 로더)는 자바 코드에 의해서 인스턴스화 될 수 없다. (VM의 일부로서 구현되기 때문이다.) 이 클래스 로더는 부트 classpath에서 핵심 시스템 클래스들을 로딩하는데, 이것은 일반적으로 jre/lib 디렉토리에 있는 JAR 파일이다. 하지만, -Xbootclasspath 명령어 옵션을 사용하여 classpath를 수정할 수 있다.
extension 클래스 로더(standard extensions 클래스 로더)는 Bootstrap 클래스 로더의 자식이다. 주로 jre/lib/ext 디렉토리에 위치한 Extension 디렉토리에서 클래스들을 로딩한다. 사용자의 classpath를 수정하지 않고 다양한 보안 확장 같은 새로운 확장으로 쉽게 갈 수 있는 기능을 제공한다.
system 클래스 로더(application 클래스 로더)는 CLASSPATH 환경 변수에서 지정된 경로에서 코드를 로딩한다. 기본적으로, 이 클래스 로더는 사용자가 생성한 모든 클래스 로더의 부모이다. ClassLoader.getSystemClassLoader() 메소드에서 리턴된 클래스 로더도 있다.
Classpath 옵션
표 1은 세 개의 표준 클래스 로더의 classpath를 설정하는 명령어 옵션을 요약한 것이다.
표 1. classpath 옵션
명령어 옵션 |
설명 |
사용되는 클래스 로더 |
-Xbootclasspath:<directories and zip/JAR files separated by ; or :> |
Bootstrap 클래스와 리소스용 검색 경로를 설정한다. |
Bootstrap |
-Xbootclasspath/a:<directories and zip/JAR files separated by ; or :> |
경로를 부트 classpath의 끝에 붙인다. |
Bootstrap |
-Xbootclasspath/p:<directories and zip/JAR files separated by ; or :> |
부트 classpath의 앞에 경로를 붙인다. |
Bootstrap |
-Dibm.jvm.bootclasspath=<directories and zip/JAR files separated by ; or :> |
이 프로퍼티의 값은 추가 검색 경로로서 사용되는데, -Xbootclasspath/p: 와 부트 classpath에 의해 정의된 값들 사이에 삽입된다. 부트 classpath는 기본이 될 수도 있고 -Xbootclasspath: 옵션으로 정의된 경로가 될 수도 있다. |
Bootstrap |
-Djava.ext.dirs=<directories and zip/JAR files separated by ; or :> |
Extension 클래스와 리소스에 대한 검색 경로를 지정한다. |
Extension |
-cp or -classpath <directories and zip/JAR files separated by ; or :> |
application 클래스와 리소스에 대한 검색 경로를 설정한다. |
System |
-Djava.class.path=<directories and zip/JAR files separated by ; or :> |
애션 클래스와 리소스에 대한 검색 경로를 설정한다. |
System |
클래스 로딩 단계
클래스 로딩은 로딩(loading), 링크(linking), 초기화(initializing) 단계로 나뉜다.
전부는 아니지만, 클래스 로딩과 관련한 대부분의 문제들이 이 세 단계 중 한 단계에서 발생한 문제들이다. 따라서, 각 단계를 이해하는 것이 클래스 로딩 문제를 진단하는데 도움이 된다. 그림 2에서는 각 단계들을 묘사하고 있다. 그림 2. 클래스 로딩 단계
로딩 단계는 필요한 클래스 파일을 배치하고(각각의 classpath를 통해 검색함) 바이트코드로 로딩하는 단계이다. JVM의 로딩 프로세스는 클래스 객체에 매우 기본적인 메모리 구조를 제공한다. 메소드, 필드, 기타 참조 클래스들이 이 단계에서는 다루어지지 않는다. 결국, 이러한 로딩 클래스는 사용할 수 없다.
링크(Linking)는 세 단계 중에서 가장 복잡한 단계이다. 다음과 같이 세 개의 주요 단계들로 나뉜다.
- 바이트코드 확인(Bytecode verification). 클래스 로더는 클래스의 바이트코드에 대해 수 많은 검사를 수행하여 잘 작동하는지를 확인한다.
- 클래스 준비(Class Preparation). 이 단계에서는 각 클래스에서 정의된 필드, 메소드, 인터페이스들을 나타내는 데이터 구조를 준비한다.
- 결정(Resolving). 이 단계에서, 클래스 로더는 특정 클래스에서 참조된 다른 모든 클래스들을 로딩한다. 클래스들은 다양한 방식을 통해 참조될 수 있다.
- 수퍼클래스(Superclass)
- 인터페이스
- 필드
- 메소드 시그니처
- 메소드에 사용된 로컬 변수
초기화(initialize) 단계 동안, 클래스 내에 포함된 정적 이니셜라이저(initializer)들이 실행된다. 이 단계의 끝에 가서는 정적 필드들이 기본 값으로 초기화 된다.
이 세 단계를 지나면, 클래스는 완전히 로딩되고 사용할 준비가 된다. 클래스 로딩은 레이지(lazy) 방식으로 수행될 수 있기 때문에 일부 클래스 로딩 프로세스는 로딩 시점 외에 처음 클래스를 사용할 때 수행될 수도 있다.
명시적(Explicit) 로딩 vs. 함축적(implicit) 로딩
클래스가 로딩되는 두 가지 방식, 명시적 로딩(explicitly) 또는 함축적 로딩(implicitly)이 있는데, 이 두 가지에는 미묘한 차이가 있다.
명시적 클래스 로딩은 클래스가 다음과 같은 메소드 호출들 중 하나를 사용하여 로딩될 때 발생한다.
cl.loadClass() ( cl 은 java.lang.ClassLoader 의 인스턴스이다.)
Class.forName() (시작 클래스 로더는 현재 클래스를 정의하는 클래스 로더이다.)
이러한 메소드들 중 하나가 호출되면 이름이 인자로 지정된 클래스가 클래스 로더에 의해서 로딩된다. 클래스가 이미 로딩되면 레퍼런스가 간단히 리턴된다. 그렇지 않을 경우, 로더는 클래스를 로딩하기 위해 델리게이션(delegation) 모델을 사용한다.
함축적 클래스 로딩은 클래스가 레퍼런스, 인스턴스화, 상속(명시적 메소드 호출을 사용하지 않음)의 결과로서 로딩된다. 각각의 경우, 로딩은 몰래 초기화 되고, JVM은 필요한 레퍼런스를 결정하고 클래스를 로딩한다. 명시적 클래스 로딩과 마찬가지로, 클래스가 이미 로딩되어 있으면 레퍼런스가 리턴된다. 그렇지 않을 경우, 로더는 델리게이션 모델을 사용하여 클래스를 로딩한다.
클래스는 명시적 로딩과 함축적 로딩을 결합해서 로딩되기도 한다. 예를 들어, 클래스 로더는 클래스를 명시적으로 로딩한 다음, 참조된 모든 클래스들을 몰래 로딩한다.
JVM의 디버깅 기능
이전 섹션에서는 클래스 로딩의 기본 원리를 설명했다 이 섹션에서는 디버깅에 사용되는 IBM JVM의 빌트인 기능을 설명하도록 하겠다. 다른 JVM들에도 비슷한 디버깅 기능들이 있다. 해당 문서를 참조하기 바란다.
Verbose 아웃풋
-verbose 명령어 옵션을 사용하여 IBM JVM의 Verbose 아웃풋을 실행할 수 있다. Verbose 아웃풋은 특정 이벤트가 발생할 때(예를 들어, 클래스가 로딩되었을 경우) 콘솔에 정보를 디스플레이 한다. 추가 클래스 로딩 정보의 경우 Verbose 클래스 아웃풋을 사용할 수 있다. 이것은 -verbose:class 옵션을 사용한다.
Verbose 결과 분석 Verbose 아웃풋은 열려있었던 모든 JAR 파일들을 리스팅하고, 그러한 JAR 파일들에 대한 전체 경로도 포함하고 있다. 다음은 예제이다.
...
[Opened D:\jre\lib\core.jar in 10 ms]
[Opened D:\jre\lib\graphics.jar in 10 ms]
...
|
로딩된 모든 클래스들은 JAR 파일 또는 이들이 로딩된 디렉토리와 함께 리스팅 된다.
...
[Loaded java.lang.NoClassDefFoundError from D:\jre\lib\core.jar]
[Loaded java.lang.Class from D:\jre\lib\core.jar]
[Loaded java.lang.Object from D:\jre\lib\core.jar]
...
|
Verbose 클래스 아웃풋은 수퍼클래스들이 로딩되는 때, 정적 이니셜라이저가 실행을 시작하는 시간 등 추가 정보를 보여준다. 다음은 아웃풋 예제이다.
...
[Loaded HelloWorld from file:/C:/myclasses/]
[Loading superclass and interfaces of HelloWorld]
[Loaded HelloWorldInterface from file:/C:/myclasses/]
[Loading superclass and interfaces of HelloWorldInterface]
[Preparing HelloWorldInterface]
[Preparing HelloWorld]
[Initializing HelloWorld]
[Running static initializer for HelloWorld]
... |
Verbose 아웃풋은 스택 트레이스(stack trace)를 포함하여 내부에서 생긴 예외도 보여준다.
-verbose를 사용하여 문제 해결하기 Verbose 아웃풋은 열리지 않은(그래서 classpath에 없는) JAR 파일, 잘못된 장소에서 로딩된 클래스 같은 classpath 문제를 해결하는데 도움이 된다.
IBM Verbose Class Loading
클래스 로더가 클래스를 찾는 장소와 어떤 클래스 로더가 특정 클래스를 로딩하는지를 알아두는 것도 유용하다. IBM Verbose Class Loading 명령어 옵션인 -Dibm.cl.verbose=<class name> 을 사용하여 이러한 정보를 얻을 수 있다. 정규식을 사용하여 클래스의 이름을 선언할 수도 있다. 예를 들어, Hello* 는 Hello 로 시작하는 이름을 가진 어떤 클래스라도 찾아낸다.
이 옵션은 사용자 정의 클래스 로더에도 적용된다. 단, 직접 또는 간접적으로 java.net.URLClassLoader 를 확장한 경우에 한한다.
IBM Verbose Class Loading 결과 분석 IBM Verbose Class Loading 아웃풋은 지정된 클래스를 로딩하려는 클래스 로더와 이들이 찾는 위치를 보여준다. 다음 명령어를 사용한다고 해보자.
java -Dibm.cl.verbose=ClassToTrace MainClass
|
여기에서, MainClass 는 메인 메소드의 ClassToTrace 를 참조한다. (결과는 여기를 참조)
이 클래스 로더들은 표준 델리게이션 모델이 작동하는 방식(Parents go first) 때문에 부모가 먼저 리스팅 된다.
클래스 로더에는 어떤 아웃풋도 없다. 아웃풋은 java.net.URLClassLoader 를 확장한 클래스 로더에만 생긴다. 클래스 로더들은 자신들의 클래스 이름 밑에 리스팅 된다. 클래스 로더에 두 개의 인스턴스가 있다면 이 두 가지를 구분하는 것은 불가능 하다.
IBM Verbose Class Loading을 사용한 문제 해결 IBM Verbose Class Loading 옵션은 모든 클래스 로더들에 어떤 classpath가 설정되었는지를 확인하기에 좋은 방법이다. 어떤 클래스 로더가 클래스를 로딩했고, 어디에서 로딩하는지를 알려준다. 정확한 클래스 버전이 로딩되는지를 확인하기 쉽다.
Javadump
Javadump (Javacore) 역시 유용한 IBM 진단 툴이다. IBM Diagnostics Guide를 참조하기 바란다. (참고자료) Javadump는 다음과 같은 이벤트가 발생할 때 JVM에서 생성된다.
- 심각한 예외가 발생할 때
- JVM이 힙 공간 외에서 실행될 때
- JVM에 신호가 보내질 때(Windows에서 Control-Break가 리눅스에서 Control-\이 입력되었을 때)
com.ibm.jvm.Dump.JavaDump() 메소드가 호출될 때
Javadump가 실행되는 순간, 상세 정보는 현재 실행 디렉토리에 저장된 텍스트 파일에 타임 스탬프와 함께 기록된다. 이러한 정보에는 쓰레드, 잠금, 스택 같은 데이터는 물론, 시스템의 클래스 로더 관련 정보들이 포함된다.
Javadump의 클래스 로딩 섹션 분석 Javadump 파일에서 제공되는 정확한 정보는 JVM이 실행되는 플랫폼에 의존한다. 클래스 로더 섹션에는 다음이 포함된다.
- 정의된 클래스 로더들과, 로더들의 관계
- 각 클래스 로더에 의해 로딩된 클래스의 리스트
다음은 Javadump에서 가져온 클래스 로더 정보이다.
CL subcomponent dump routine
============================
Classpath Z(D:\jre\lib\core.jar),...
Oldjava mode false
Bootstrapping false
Verbose class dependencies false
Class verification VERIFY_REMOTE
Namespace to classloader 0x00000000
Start of cache entry pool 0x44D85430
Start of free cache entries 0x44D86204
Location of method table 0x44C23AA0
Global namespace anchor 0x00266894
System classloader shadow 0x00376068
Classloader shadows 0x44D7BA60
Extension loader 0x00ADB830
System classloader 0x00ADB7B0
Classloader summaries
12345678: 1=primordial,2=extension,3=shareable,4=middleware,
5=system,6=trusted,7=application,8=delegating
-----ta- Loader sun/misc/Launcher$AppClassLoader(0x44D7BA60),
Shadow 0x00ADB7B0,
Parent sun/misc/Launcher$ExtClassLoader(0x00ADB830)
Number of loaded classes 1
Number of cached classes 260
Allocation used for loaded classes 1
Package owner 0x00ADB7B0
-xh-st-- Loader sun/misc/Launcher$ExtClassLoader(0x44D71288),
Shadow 0x00ADB830,
Parent *none*(0x00000000)
Number of loaded classes 0
Number of cached classes 0
Allocation used for loaded classes 3
Package owner 0x00ADB830
p-h-st-- Loader *System*(0x00376068), Shadow 0x00000000
Number of loaded classes 304
Number of cached classes 304
Allocation used for loaded classes 3
Package owner 0x00000000
ClassLoader loaded classes
Loader sun/misc/Launcher$AppClassLoader(0x44D7BA60)
HelloWorld(0x00ACF0E0)
Loader sun/misc/Launcher$ExtClassLoader(0x44D71288)
Loader *System*(0x00376068)
java/io/WinNTFileSystem(0x002CD118)
java/lang/Throwable(0x002C03A8)
java/lang/IndexOutOfBoundsException(0x44D45208)
java/lang/UnsatisfiedLinkError(0x44D42D38)
....................classes left out to save space........................
[Ljava/lang/Class;(0x002CA9E8)
java/io/InputStream(0x002C9818)
java/lang/Integer$1(0x002C83E8)
java/util/Dictionary(0x002C4298) |
이 예제에서는 단 세 개의 표준 클래스 로더만 있다.
- System 클래스 로더 (
sun/misc/Launcher$AppClass loader )
- Extension 클래스 로더 (
sun/misc/Launcher$ExtClass loader )
- Bootstrap 클래스 로더 (
*System* )
Classloader summaries 섹션은 클래스 로더의 유형을 포함하여 시스템에 있는 각 클래스 로더에 대한 상세를 제공한다. 본 기술자료 시리즈에서는 primordial, extension, system, application, delegation (리플렉션에 사용됨) 유형에 대해 설명 할 것이다. 기타 유형들(shareable, middleware, trusted)은 Persistent Reusable JVM에서 사용되는데 이 글에서는 설명하지 않겠다. (Persistent Reusable JVM User Guide 참조 - 참고자료) 요약 섹션에서는 부모 클래스 로더도 설명한다. 시스템 클래스 로더의 부모는 sun/misc/Launcher$ExtClass loader(0x00ADB830) )이다. 부모의 주소는 부모 클래스 로더(shadow)의 데이터 구조에 상응한다.
ClassLoader loaded classes 섹션은 각 클래스 로더에서 로딩된 클래스들을 리스팅 한다. 이 예제에서, System 클래스 로더는 단 하나의 클래스 HelloWorld (주소, 0x00ACF0E0 )를 갖고 있다.
Javadump를 사용하여 문제 해결하기 Javadump에서 제공된 정보를 사용하여 시스템 내에 어떤 클래스 로더들이 존재하는지를 파악할 수 있다. 여기에는 사용자 정의 클래스 로더들이 포함된다. 로딩된 클래스의 리스트에서, 어떤 클래스 로더가 특정 클래스를 로딩했는지를 찾을 수 있다. 클래스를 찾을 수 없다면 시스템에 있는 어떤 클래스 로더에 의해서도 로딩되지 않은 것이다. (ClassNotFoundException ).
Javadump를 사용해서 다른 유형의 문제들도 진단할 수 있다.
- 클래스 로더 네임스페이스 문제. 클래스 로더 네임스페이스(namespace)는 클래스 로더와 이 클래스 로더가 로딩했던 모든 클래스들의 결합이다. 예를 들어, 특정 클래스가 존재하지만 잘못된 클래스 로더에 의해 로딩되었다면(이는
NoClassDefFoundError 로 이어짐), 네임스페이스는 부정확하다. 다시 말해서, 이 클래스는 잘못된 classpath에 있는 것이다. 이 같은 문제를 해결하려면 정상 자바 classpath에 있는 다른 장소에 클래스를 놓고 이것이 System 클래스 로더에 의해 로딩되었는지를 확인한다.
- 클래스 로더 제약조건 문제. 이 문제는 본 시리즈의 마지막 기술자료에서 설명하도록 하겠다.
자바 메소드 트레이싱
IBM JVM에는 메소드 트레이싱(built-in method tracing) 장치가 내장되어 있다. 자바 코드에 있는 메소드들이(코어 시스템 클래스 포함) 코드를 수정하지 않고도 추적될 수 있다. 이 장치는 많은 양의 데이터를 제공할 수 있기 때문에 트레이스 레벨을 정해야 한다.
트레이스 실행 옵션은 JVM에 따라 다르다. 자세한 내용은 IBM Diagnostics Guides(참고자료)를 참조하라.
다음은 명령어 예제들이다.
IBM Java 1.4.2에서 HelloWorld 를 실행할 때 모든 java.lang.ClassLoader 메소드를 추적하려면,
java -Dibm.dg.trc.methods=java/lang/ClassLoader.*() -Dibm.dg.trc.print=mt HelloWorld
|
마찬가지로 IBM Java 1.4.2에서 loadClass() 의 ClassLoader 메소드와 HelloWorld 의 메소드를 추적하려면,
java -Dibm.dg.trc.methods=java/lang/ClassLoader.loadClass(),HelloWorld.*()
-Dibm.dg.trc.print=mt HelloWorld
|
메소드 트레이스 결과 분석 메소드 트레이스 아웃풋 샘플을 참조하라. (이전 섹션의 두 번째 명령어를 사용했음.)
각 트레이스 라인은 더 자세한 정보를 제공하고 있다. 한 라인을 자세히 분석해 보자.
12:57:41.277 0x002A23C0 04000D > java/lang/ClassLoader.loadClass Bytecode method,
This = 0x00D2B830, Signature: (Ljava/lang/String;Z)Ljava
/lang/Class;
|
트레이싱 결과,
12:57:41.277 : 메소드 시작 또는 종료 타임 스탬프
0x002A23C0 : 쓰레드 ID
04000D : 고급 진단 장치를 사용한 내부 JVM 트레이스 포인트
- 나머지 정보들은 메소드가 입력되었는지(
> ) 또는 끝났는지(< ) 여부를 나타내고, 메소드 상세를 보여주고 있다.
메소드 트레이스로 문제 해결하기 메소드 트레이싱은 다음과 같이 다양한 문제 해결에 사용된다.
- 성능 문제: 타임 스탬프를 사용하여 실행 시간이 많이 드는 메소드를 찾을 수 있다.
- 행(hang): 마지막 메소드 엔트리는 애플리케이션이 어디에서 중지되었는지를 알려주는 좋은 단서이다.
- 부정확한 객체: 어드레스를 사용하여 그 객체에 대한 컨스트럭터 호출에 대한 어드레스와 매칭하면서 원하는 객체에 대해 메소드 호출이 이루어졌는지를 확인할 수 있다.
- 예상 외의 코드 경로: 엔트리와 종료 포인트를 따라가면 프로그램에서 잡힌 예상치 못한 경로를 볼 수 있다.
- 기타 오류들: 마지막 메소드 엔트리는 오류가 어디에서 발생했는지를 알려주는 좋은 단서가 된다.
다음편 예고
JVM에서의 클래스 로딩의 기초와 IBM JVM에서 사용할 수 있는 디버깅 기능을 배웠다. 다음 글에서는 자바 애플리케이션을 실행할 때 주로 발생하는 클래스 로딩 문제들을 파악하고 해결하는 방법을 설명할 것이다. |