메모리 누수를 방지하기 위해 JDBC 드라이버가 강제로 등록 해제되었습니다


325

웹 응용 프로그램을 실행할 때이 메시지가 나타납니다. 정상적으로 실행되지만 종료하는 동안이 메시지가 나타납니다.

심각 : 웹 응용 프로그램이 JBDC 드라이버 [oracle.jdbc.driver.OracleDriver]를 등록했지만 웹 응용 프로그램이 중지되었을 때 등록을 해제하지 못했습니다. 메모리 누수를 방지하기 위해 JDBC 드라이버가 강제로 등록 해제되었습니다.

도움을 주셔서 감사합니다.


답변:


301

버전 6.0.24 때문에,와 톰캣 선박 메모리 누수 탐지 웹 애플리케이션의에서 JDBC 4.0 호환 드라이버가있을 때 다시 경고 메시지의 종류로 이어질 수 기능을 /WEB-INF/lib하는 자동 등록 자체가 사용하는 웹 애플리케이션의 시작시 ServiceLoaderAPI를 하지만, 어떤 webapp 종료 중에 자동 등록 취소 하지 않았습니다 . 이 메시지는 순수한 비공식적이며 Tomcat은 이미 메모리 누수 방지 조치를 취했습니다.

당신은 무엇을 할 수 있나요?

  1. 이러한 경고는 무시하십시오. Tomcat이 올바르게 작업하고 있습니다. 실제 버그는 귀하의 코드가 아닌 다른 사람의 코드 (문제의 JDBC 드라이버)에 있습니다. Tomcat이 작업을 올바르게 수행하고 JDBC 드라이버 공급 업체가 수정 될 때까지 기다렸다가 드라이버를 업그레이드 할 수있게하십시오. 반면에, 당신은 webapp 's에서 JDBC 드라이버를 삭제하지 말고 /WEB-INF/libserver 's에서만 삭제해야합니다 /lib. 여전히 webapp 's에 보관하는 경우을 /WEB-INF/lib사용하여 수동으로 등록 및 등록 취소해야합니다 ServletContextListener.

  2. 이러한 경고가 발생하지 않도록 Tomcat 6.0.23 이상으로 다운 그레이드하십시오. 그러나 자동으로 메모리 누수를 유지합니다. 결국 그것이 좋은지 확실하지 않습니다. 메모리 누수의 이러한 종류의 뒤에 주요 원인 중 하나 OutOfMemoryError문제 톰캣 hotdeployments 동안.

  3. JDBC 드라이버를 Tomcat /lib폴더 로 이동하고 드라이버 를 관리하기 위해 연결 풀링 된 데이터 소스를 갖습니다. Tomcat의 내장 DBCP는 닫을 때 드라이버를 올바르게 등록 취소하지 않습니다. WONTFIX로 닫힌 버그 DBCP-322 도 참조하십시오 . 오히려 DBCP를 DBCP보다 더 잘 수행하는 다른 연결 풀로 교체하고 싶습니다. 예를 들어, HikariCP , BoneCP 또는 Tomcat JDBC Pool이 있습니다.


25
이것은 좋은 조언입니다. 메모리 누출을 경고하는 것이 아니라 Tomcat이 누출을 막기 위해 강제 조치를 취했다는 경고입니다.
matt b

49
옵션 (1)이 진행된다면 Tomcat은 왜 이것을 심각으로 기록합니까? 나에게 심각하다는 것은 "무시"가 아니라 "관리자 페이지"를 의미합니다.
Peter Becker

7
Tomcat이 기대하지 말고 직접 해보십시오. 제 생각에는 지저분한 코드를 정리하는 것이 Tomcat의 일이 아닙니다. 아래 답변을 참조하십시오.
sparkyspider

2
@ sproketboy : 응? HTTP 세션에 저장된 클래스의 필드로 JDBC 아티팩트를 지정 했습니까?
BalusC 2016 년

2
나는 보통 이유 3 (와 반대로 라이브러리에 wAR이 있음 lib)이라고 생각합니다.
lapo

160

서블릿 컨텍스트 리스너 contextDestroyed () 메소드에서 수동으로 드라이버를 등록 취소하십시오.

// This manually deregisters JDBC driver, which prevents Tomcat 7 from complaining about memory leaks wrto this class
Enumeration<Driver> drivers = DriverManager.getDrivers();
while (drivers.hasMoreElements()) {
    Driver driver = drivers.nextElement();
    try {
        DriverManager.deregisterDriver(driver);
        LOG.log(Level.INFO, String.format("deregistering jdbc driver: %s", driver));
    } catch (SQLException e) {
        LOG.log(Level.SEVERE, String.format("Error deregistering driver %s", driver), e);
    }
}

3
효과가있다! javabeat.net/servletcontextlistener-example는 서블릿 컨텍스트 리스너를 구현하는 데 도움이 될 수 있습니다
바딤 Zin4uk에게

17
사용 가능한 모든 JDBC 드라이버 를 등록 취소하지 않을 수 있으므로 공유 환경에서는 안전하지 않을 수 있습니다. 더 안전한 접근법에 대한 내 대답 을 참조하십시오 .
daiscog

85

Tomcat은 JDBC 드라이버를 강제로 등록 취소하지만 Tomcat이 수행하는 메모리 누수 방지 검사를 수행하지 않는 다른 서블릿 컨테이너로 이동할 경우 컨텍스트 삭제시 웹 애플리케이션에서 생성 한 모든 리소스를 정리하는 것이 좋습니다.

그러나 블랭킷 드라이버 등록 취소 방법은 위험합니다. DriverManager.getDrivers()메소드가 리턴 한 일부 드라이버 는 웹 애플리케이션 컨텍스트의 클래스 로더가 아닌 상위 클래스 로더 (예 : 서블릿 컨테이너의 클래스 로더)에 의해로드되었을 수 있습니다 (예 : 웹 애플리케이션이 아닌 컨테이너의 lib 폴더에있을 수 있으므로 전체 컨테이너에서 공유 됨) ). 이들을 등록 취소하면 사용중인 다른 웹앱 (또는 컨테이너 자체)에 영향을줍니다.

따라서 등록을 해제하기 전에 각 드라이버의 ClassLoader가 웹앱의 ClassLoader인지 확인해야합니다. 따라서 ContextListener의 contextDestroyed () 메소드에서

public final void contextDestroyed(ServletContextEvent sce) {
    // ... First close any background tasks which may be using the DB ...
    // ... Then close any DB connection pools ...

    // Now deregister JDBC drivers in this context's ClassLoader:
    // Get the webapp's ClassLoader
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    // Loop through all drivers
    Enumeration<Driver> drivers = DriverManager.getDrivers();
    while (drivers.hasMoreElements()) {
        Driver driver = drivers.nextElement();
        if (driver.getClass().getClassLoader() == cl) {
            // This driver was registered by the webapp's ClassLoader, so deregister it:
            try {
                log.info("Deregistering JDBC driver {}", driver);
                DriverManager.deregisterDriver(driver);
            } catch (SQLException ex) {
                log.error("Error deregistering JDBC driver {}", driver, ex);
            }
        } else {
            // driver was not registered by the webapp's ClassLoader and may be in use elsewhere
            log.trace("Not deregistering JDBC driver {} as it does not belong to this webapp's ClassLoader", driver);
        }
    }
}

해서는 안 if (cl.equals(driver.getClass().getClassLoader())) {됩니까?
user11153

6
@ user11153 아니요, 값이 같은 두 개의 별도 인스턴스가 아닌 정확히 동일한 ClassLoader 인스턴스인지 확인합니다.
daiscog

4
다른 문제는 문제의 문제를 올바르게 처리하는 것처럼 보이지만 war 파일을 삭제 한 다음 교체 할 때 문제가 발생합니다. 이 경우 드라이버 등록이 취소되고 반환되지 않습니다. Tomcat을 다시 시작해야만 해당 영역에서 벗어날 수 있습니다. 이 솔루션은 그 지옥을 피합니다.
OldCurmudgeon

여기에 대부분 동일하지만 추가 MySQL / MariaDB 처리 코드 github.com/spring-projects/spring-boot/issues/2612
gavenkoa

2
H2 또는 PostgreSQL을 사용하면 다시로드 할 때 드라이버가 다시 등록되지 않습니다. 두 드라이버는 내부 등록 상태를 유지하며 드라이버가에서 등록 해제 된 경우 지워지지 않습니다 DriverManager. github.com/spring-projects/spring-boot/issues/에
Marcel Stör

26

이 문제가 많이 발생합니다. 예, Tomcat 7은 자동으로 등록을 취소하지만 실제로 코드를 제어하고 좋은 코딩 방법을 사용합니까? 모든 객체를 닫고 데이터베이스 연결 풀 스레드를 종료하고 모든 경고를 제거 할 수있는 올바른 코드가 있는지 알고 싶습니다. 나는 확실히한다.

이것이 내가하는 방법입니다.

1 단계 : 리스너 등록

web.xml

<listener>
    <listener-class>com.mysite.MySpecialListener</listener-class>
</listener>

2 단계 : 리스너 구현

com.mysite.MySpecialListener.java

public class MySpecialListener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        // On Application Startup, please…

        // Usually I'll make a singleton in here, set up my pool, etc.
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        // On Application Shutdown, please…

        // 1. Go fetch that DataSource
        Context initContext = new InitialContext();
        Context envContext  = (Context)initContext.lookup("java:/comp/env");
        DataSource datasource = (DataSource)envContext.lookup("jdbc/database");

        // 2. Deregister Driver
        try {
            java.sql.Driver mySqlDriver = DriverManager.getDriver("jdbc:mysql://localhost:3306/");
            DriverManager.deregisterDriver(mySqlDriver);
        } catch (SQLException ex) {
            logger.info("Could not deregister driver:".concat(ex.getMessage()));
        } 

        // 3. For added safety, remove the reference to dataSource for GC to enjoy.
        dataSource = null;
    }

}

의견을 말하거나 추가하십시오 ...


4
그러나 방법 DataSource이 없습니다close
Jim

9
ApplicationContextListener를 확장하지 않고 javax.servlet.ServletContextListener를 구현해야합니까?
egaga

2
방법에서 해당 작업의 순서에 대한 이유가 contextDestroyed있습니까? 왜 2 단계, 수행하기 전에 1 단계합니까 initContext, envContext그리고 datasource전혀 언급되지 않습니다? 3 단계를 이해하지 못하기 때문에 묻습니다.
matthaeus

4
@ matthaeus 1 단계는 필요 lookup하지 않다고 생각합니다. 필요하지 않은 객체를 얻는 것 같습니다. 3 단계는 완전히 쓸모가 없습니다. 그것은 안전을 추가하지 않으며 초보자가 GC 작동 방식을 이해하지 못하는 사람처럼 보일 것입니다. stackoverflow.com/a/5315467/897024 와 함께 모든 드라이버를 제거합니다.
kapex

4
@kapep 일부 드라이버가 컨테이너에서 공유 될 수 있으므로 모든 드라이버를 제거하는 것은 위험합니다. webapp의 ClassLoader에 의해로드 된 드라이버 만 제거하는 접근법에 대한 내 대답 을 참조하십시오 .
daiscog

14

이것은 순수하게 mysql의 드라이버 또는 tomcats webapp-classloader의 드라이버 등록 / 파괴 문제입니다. MySQL 드라이버를 tomcats lib 폴더에 복사하십시오 (따라서 Tomcat이 아닌 jvm에 의해 직접로드 됨) 메시지가 사라집니다. 그러면 JVM 종료시에만 mysql jdbc 드라이버가 언로드되고 메모리 누수에 대해서는 신경 쓰지 않습니다.


2
작동하지 않습니다 ... jdbc 드라이버를 복사하려고했습니다 .TOMCAT_HOME / lib / postgresql-9.0-801.jdbc4.jar-Tomcat 7.0 \ lib \ postgresql-9.0-801.jdbc4.jar ...
FAjir

5
@Florito-웹앱 WEB-INF / lib에서도 제거해야합니다
Collin Peters

8

Maven 빌드 전쟁에서이 메시지를 받으면 JDBC 드라이버의 범위를 제공된 것으로 변경하고 사본을 lib 디렉토리에 두십시오. 이처럼 :

<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <version>5.1.18</version>
  <!-- put a copy in /usr/share/tomcat7/lib -->
  <scope>provided</scope>
</dependency>

8

앱별 배포 솔루션

이것은 문제를 해결하기 위해 작성한 리스너입니다. 드라이버가 자신을 등록했는지 여부를 자동 감지하고 그에 따라 행동합니다.

중요 : 드라이버 jar이 WEB-INF / lib에 배포 된 경우에만 사용하도록되어 있습니다 Tomcat / lib가 아닌 합니다. 각 응용 프로그램이 자체 드라이버를 처리하고 손대지 않은 Tomcat에서 실행할 수 있도록합니다. . 그것이 IMHO가되어야하는 방식입니다.

web.xml에서 리스너를 구성하기 만하면됩니다.

web.xml 상단 근처에 추가하십시오 .

<listener>
    <listener-class>utils.db.OjdbcDriverRegistrationListener</listener-class>    
</listener>

utils / db / OjdbcDriverRegistrationListener.java 로 저장 하십시오 .

package utils.db;

import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Enumeration;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

import oracle.jdbc.OracleDriver;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Registers and unregisters the Oracle JDBC driver.
 * 
 * Use only when the ojdbc jar is deployed inside the webapp (not as an
 * appserver lib)
 */
public class OjdbcDriverRegistrationListener implements ServletContextListener {

    private static final Logger LOG = LoggerFactory
            .getLogger(OjdbcDriverRegistrationListener.class);

    private Driver driver = null;

    /**
     * Registers the Oracle JDBC driver
     */
    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        this.driver = new OracleDriver(); // load and instantiate the class
        boolean skipRegistration = false;
        Enumeration<Driver> drivers = DriverManager.getDrivers();
        while (drivers.hasMoreElements()) {
            Driver driver = drivers.nextElement();
            if (driver instanceof OracleDriver) {
                OracleDriver alreadyRegistered = (OracleDriver) driver;
                if (alreadyRegistered.getClass() == this.driver.getClass()) {
                    // same class in the VM already registered itself
                    skipRegistration = true;
                    this.driver = alreadyRegistered;
                    break;
                }
            }
        }

        try {
            if (!skipRegistration) {
                DriverManager.registerDriver(driver);
            } else {
                LOG.debug("driver was registered automatically");
            }
            LOG.info(String.format("registered jdbc driver: %s v%d.%d", driver,
                    driver.getMajorVersion(), driver.getMinorVersion()));
        } catch (SQLException e) {
            LOG.error(
                    "Error registering oracle driver: " + 
                            "database connectivity might be unavailable!",
                    e);
            throw new RuntimeException(e);
        }
    }

    /**
     * Deregisters JDBC driver
     * 
     * Prevents Tomcat 7 from complaining about memory leaks.
     */
    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {
        if (this.driver != null) {
            try {
                DriverManager.deregisterDriver(driver);
                LOG.info(String.format("deregistering jdbc driver: %s", driver));
            } catch (SQLException e) {
                LOG.warn(
                        String.format("Error deregistering driver %s", driver),
                        e);
            }
            this.driver = null;
        } else {
            LOG.warn("No driver to deregister");
        }

    }

}

팁 : Servlet 3.0부터 클래스에 주석을 달고 구성을 @WebListener생략 할 수 web.xml있습니다.
Basil Bourque

사실, 우선 순위가 높은 것으로 선택되어 전이나 후에 드라이버를 사용할 필요가 없도록하십시오.
Andrea Ratto

6

Spring 포럼에서 찾은 내용을 추가하겠습니다. JDBC 드라이버 jar을 웹 응용 프로그램과 함께 배포하는 대신 tomcat lib 폴더로 이동하면 경고가 사라지는 것 같습니다. 이것이 나를 위해 일한 것을 확인할 수 있습니다

http://forum.springsource.org/showthread.php?87335-Failure-to-unregister-the-MySQL-JDBC-Driver&p=334883#post334883


1
이것이 더 나은 솔루션을 제공한다고 생각합니다. DatasourceManager를 하위 클래스로 만들고 닫기 메소드를 재정 의하여 등록 취소를 추가하십시오. 그런 다음 Spring은 컨텍스트를 파괴 할 때 처리하고 Tomcat은 SEVERE 로그를 제공하지 않으며 JDBC 드라이버를 lib 디렉토리로 이동할 필요가 없습니다. 다음은 오래된 봄 포럼에서 온 오리지널 메시지입니다.
Adam

6

JDBC 드라이버를 등록 해제하기 위해 간단한 destroy () 메소드를 구현하면 잘 작동한다는 것을 알았습니다.

/**
 * Destroys the servlet cleanly by unloading JDBC drivers.
 * 
 * @see javax.servlet.GenericServlet#destroy()
 */
public void destroy() {
    String prefix = getClass().getSimpleName() +" destroy() ";
    ServletContext ctx = getServletContext();
    try {
        Enumeration<Driver> drivers = DriverManager.getDrivers();
        while(drivers.hasMoreElements()) {
            DriverManager.deregisterDriver(drivers.nextElement());
        }
    } catch(Exception e) {
        ctx.log(prefix + "Exception caught while deregistering JDBC drivers", e);
    }
    ctx.log(prefix + "complete");
}

5
사용 가능한 모든 JDBC 드라이버 를 등록 취소하지 않을 수 있으므로 공유 환경에서는 안전하지 않을 수 있습니다. 더 안전한 접근법에 대한 내 대답 을 참조하십시오 . 또한 JDBC 드라이버가 웹 응용 프로그램의 모든 서블릿에서 공유되므로 서블릿별로가 아닌 ServletContextListener에서 실제로 수행해야합니다.
daiscog

3

이 메모리 누수를 방지하려면 컨텍스트 종료시 드라이버를 등록 취소하면됩니다.

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.mywebsite</groupId>
    <artifactId>emusicstore</artifactId>
    <version>1.0-SNAPSHOT</version>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.7.0</version>
                <configuration>
                    <source>1.9</source>
                    <target>1.9</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <!-- ... -->

        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>4.0.1.Final</version>
        </dependency>

        <dependency>
            <groupId>org.hibernate.javax.persistence</groupId>
            <artifactId>hibernate-jpa-2.0-api</artifactId>
            <version>1.0.1.Final</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.11</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/javax.servlet/servlet-api -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.5</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

</project>

MyWebAppContextListener.java

package com.emusicstore.utils;

import com.mysql.cj.jdbc.AbandonedConnectionCleanupThread;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Enumeration;

public class MyWebAppContextListener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        System.out.println("************** Starting up! **************");
    }

    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {
        System.out.println("************** Shutting down! **************");
        System.out.println("Destroying Context...");
        System.out.println("Calling MySQL AbandonedConnectionCleanupThread checkedShutdown");
        AbandonedConnectionCleanupThread.checkedShutdown();

        ClassLoader cl = Thread.currentThread().getContextClassLoader();

        Enumeration<Driver> drivers = DriverManager.getDrivers();
        while (drivers.hasMoreElements()) {
            Driver driver = drivers.nextElement();

            if (driver.getClass().getClassLoader() == cl) {
                try {
                    System.out.println("Deregistering JDBC driver {}");
                    DriverManager.deregisterDriver(driver);

                } catch (SQLException ex) {
                    System.out.println("Error deregistering JDBC driver {}");
                    ex.printStackTrace();
                }
            } else {
                System.out.println("Not deregistering JDBC driver {} as it does not belong to this webapp's ClassLoader");
            }
        }
    }

}

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                             http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <listener>
        <listener-class>com.emusicstore.utils.MyWebAppContextListener</listener-class>
    </listener>

<!-- ... -->

</web-app>

이 버그 수정에 영감을 준 소스 .


2

비슷한 문제가 있었지만 Tomcat 서버가 실행 중일 때 JSP 페이지를 수정 / 저장할 때마다 Java 힙 공간 오류가 발생하여 컨텍스트가 완전히 재충전되지 않았습니다.

내 버전은 Apache Tomcat 6.0.29 및 JDK 6u12입니다.

URL http://wiki.apache.org/tomcat/MemoryLeakProtection참조 섹션에 제안 된대로 JDK를 6u21 로 업그레이드 하면 JDBC 드라이버 오류가 계속 발생하더라도 Java 힙 공간 문제 (컨텍스트가 다시로드 됨)가 해결되었습니다.


0

Tomcat 버전 6.026에서 동일한 문제를 발견했습니다.

TOMCAT Lib뿐만 아니라 WebAPP 라이브러리에서도 Mysql JDBC.jar을 사용했습니다.

TOMCAT lib 폴더에서 Jar를 제거하여 위의 문제를 해결하십시오.

그래서 내가 이해하는 것은 TOMCAT이 JDBC 메모리 누출을 올바르게 처리하고 있다는 것입니다. 그러나 MYSQL Jdbc jar이 WebApp 및 Tomcat Lib에 복제 된 경우 Tomcat은 Tomcat Lib 폴더에있는 jar 만 처리 할 수 ​​있습니다.


0

Grails 애플리케이션을 AWS에 배포 할 때이 문제에 직면했습니다. 이것은 JDBC 기본 드라이버 org.h2 드라이버의 문제입니다 . 구성 폴더 안에있는 Datasource.groovy에서이를 확인할 수 있습니다. 아래에서 볼 수 있듯이 :

dataSource {
    pooled = true
    jmxExport = true
    driverClassName = "org.h2.Driver"   // make this one comment
    username = "sa"
    password = ""
}

해당 데이터베이스를 사용하지 않는 경우 datasource.groovy 파일에 org.h2.Driver 가 언급 된 곳에서 해당 행을 주석 처리 하십시오. 그렇지 않으면 해당 데이터베이스 jar 파일을 다운로드해야합니다.

감사 .


0

이 오류는 JTDS Driver 1.3.0 (SQL Server)이있는 Grails 응용 프로그램에서 발생했습니다. SQL Server에서 잘못된 로그인 문제였습니다. 이 문제를 해결 한 후 (SQL Server에서) 내 앱이 Tomcat에 올바르게 배포되었습니다. 팁 : stacktrace.log에서 오류를 보았습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.