OSSClient의 부적절한 사용으로 인해 발생한 OOM 문제 해결 프로세스를 기록합니다.

첫 번째 출시: 공개 계정 " Zhao Xiake "

머리말

최근 온라인에서 상대적으로 소외된 프로젝트에서 OOM이 발생했습니다. 다행히 이 프로젝트에서는 일부 오프라인 작업 처리만 수행합니다. OOM은 온라인 비즈니스에 영향을 미치지 않습니다. 다음은 문제 해결 과정에 대한 기록입니다.

덤프 로그 보기

프로젝트 구성의 주요 JVM 매개변수 설정은 다음과 같습니다.

-Xmx5120m -XX:+PreserveFramePointer -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/usr/local/update/heap_trace.log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/local/update/dump.log 

최대 힙 메모리는 5G로 주어지고, OOM 이후에 기록하고 메모리를 내보내도록 GC 로그가 구성됩니다. 먼저 OOM의 내보낸 메모리 스냅샷을 살펴보겠습니다. dump.log는 실제로 5GB입니다. 첫 번째 판단은 거기에 있다는 것입니다. 메모리 누수입니다.

그러다가 heap_trace.log의 GC 로그를 살펴보니 마지막 몇 개의 GC는 0.02초가 걸렸고 메모리를 많이 해제하지 않았는데 메모리 누수가 있는 것 같습니다.

2023-09-18T09:58:28.259+0800: 234057.213: [GC (Allocation Failure) [PSYoungGen: 438400K->7648K(441344K)] 763838K->333358K(961024K), 0.0140907 secs] [Times: user=0.04 sys=0.00, real=0.02 secs]  
2023-09-18T10:01:33.925+0800: 234242.879: [GC (Allocation Failure) [PSYoungGen: 436704K->7344K(441856K)] 762414K->333326K(961536K), 0.0134861 secs] [Times: user=0.04 sys=0.00, real=0.01 secs]  
2023-09-18T10:04:16.426+0800: 234405.380: [GC (Allocation Failure) [PSYoungGen: 437424K->8832K(441856K)] 763406K->335022K(961536K), 0.0147276 secs] [Times: user=0.05 sys=0.00, real=0.01 secs]  
2023-09-18T10:06:30.923+0800: 234539.877: [GC (Allocation Failure) [PSYoungGen: 438912K->11520K(442368K)] 765102K->338158K(962048K), 0.0202829 secs] [Times: user=0.06 sys=0.00, real=0.02 secs]  
2023-09-18T10:08:27.655+0800: 234656.609: [GC (Allocation Failure) [PSYoungGen: 442112K->12272K(442880K)] 768750K->340510K(962560K), 0.0216111 secs] [Times: user=0.06 sys=0.00, real=0.02 secs]  
2023-09-18T10:11:37.773+0800: 234846.727: [GC (Allocation Failure) [PSYoungGen: 442864K->12000K(445440K)] 771102K->340918K(965120K), 0.0243473 secs] [Times: user=0.06 sys=0.00, real=0.02 secs]  
2023-09-18T10:14:56.925+0800: 235045.879: [GC (Allocation Failure) [PSYoungGen: 443616K->8192K(445952K)] 772534K->337110K(965632K), 0.0152287 secs] [Times: user=0.04 sys=0.00, real=0.01 secs]  
2023-09-18T10:17:49.358+0800: 235218.312: [GC (Allocation Failure) [PSYoungGen: 439808K->8432K(445952K)] 768726K->337790K(965632K), 0.0151303 secs] [Times: user=0.05 sys=0.00, real=0.02 secs]  
2023-09-18T10:20:51.356+0800: 235400.310: [GC (Allocation Failure) [PSYoungGen: 441072K->8976K(446464K)] 770430K->338470K(966144K), 0.0159285 secs] [Times: user=0.05 sys=0.00, real=0.02 secs]  
2023-09-18T10:24:05.395+0800: 235594.349: [GC (Allocation Failure) [PSYoungGen: 441616K->9504K(446464K)] 771110K->339358K(966144K), 0.0219962 secs] [Times: user=0.05 sys=0.00, real=0.02 secs]  
2023-09-18T10:26:48.374+0800: 235757.328: [GC (Allocation Failure) [PSYoungGen: 443168K->11680K(446976K)] 773022K->341950K(966656K), 0.0195554 secs] [Times: user=0.05 sys=0.00, real=0.02 secs]

Jprofiler를 사용하여 덤프 파일 분석

JProFiler를 사용하여 덤프 파일을 열면 HashMap$Node에 실제로 1GB가 있음을 확인할 수 있습니다.

노드를 선택하고 3천만 개가 넘는 개체가 있음을 확인했습니다.

들어오는 참조 병합을 선택하고 Node 개체의 참조 체인을 단계별로 확장했으며 마침내 Node를 참조하는 OSSClient 개체가 있음을 발견했습니다.

이 업체가 파일업로드를 위해 알리바바 클라우드 OSS를 많이 사용하고 있다고 생각해서 OSSClient를 사용하는 코드를 찾았습니다.파일은 에이전트에서 매번 업로드되지만 매번 new OSSClient()새로운 로컬 변수가 있어도 메모리를 발생시키지 않아야 합니다. 누출?

그래서 DefaultServiceClient의 소스코드를 살펴보니 OSSClient를 생성할 때 호출되는 것을 알 수 있다.createHttpClientConnectionManager

    public DefaultServiceClient(ClientConfiguration config) {
    
    
        super(config);
        this.connectionManager = createHttpClientConnectionManager();
        this.httpClient = createHttpClient(this.connectionManager);
        RequestConfig.Builder requestConfigBuilder = RequestConfig.custom();    
        

현재 연결을 관리하는 createHttpClientConnectionManager사용됩니다 .IdleConnectionReaper

    protected HttpClientConnectionManager createHttpClientConnectionManager() {
    
    
        SSLContext sslContext = null;
      if (config.isUseReaper()) {
    
    
            IdleConnectionReaper.setIdleConnectionTime(config.getIdleConnectionTime());
            IdleConnectionReaper.registerConnectionManager(connectionManager);
        }
        return connectionManager;
    }

모든 HTTP 연결이 다음을 사용하여 저장되는 IdleConnectionReaper.registerConnectionManager것을 볼 수 있습니다 .ArrayList

public final class IdleConnectionReaper extends Thread {
    
    
    private static final int REAP_INTERVAL_MILLISECONDS = 5 * 1000;
    private static final ArrayList<HttpClientConnectionManager> connectionManagers = new ArrayList<HttpClientConnectionManager>();

    private static IdleConnectionReaper instance;

    private static long idleConnectionTime = 60 * 1000;

    private volatile boolean shuttingDown;

    private IdleConnectionReaper() {
    
    
        super("idle_connection_reaper");
        setDaemon(true);
    }

    public static synchronized boolean registerConnectionManager(HttpClientConnectionManager connectionManager) {
    
    
        if (instance == null) {
    
    
            instance = new IdleConnectionReaper();
            instance.start();
        }
        return connectionManagers.add(connectionManager);
    }

OSSClient가 메소드를 제공하는 것을 볼 수 있습니다 shutdown. 새로운 OSSClint가 더 이상 사용되지 않으면 shutdown을 호출하여 연결을 해제해야 합니다. 그러면 연결 관리자에서 연결된 연결이 제거됩니다. 음, 이는 실제로 잘못된 코드 사용으로 인해 발생하는 OOM입니다.

    @Override
    public void shutdown() {
    
    
        IdleConnectionReaper.removeConnectionManager(this.connectionManager);
        this.connectionManager.shutdown();
    }  
  public static synchronized boolean removeConnectionManager(HttpClientConnectionManager connectionManager) {
    
    
        boolean b = connectionManagers.remove(connectionManager);
        if (connectionManagers.isEmpty())
            shutdown();
        return b;
    }

Alibaba Cloud 공식 웹사이트에서도 동일한 문제를 발견했습니다.
이미지.png

해결책:

  1. 애플리케이션에서 OSSClient를 여러 번 인스턴스화하는 것을 방지하려면 OSSClient 인스턴스를 싱글톤 모드로 정의하세요.
  2. OSSClient.shutdown() 메서드를 사용하여 OSSClient 인스턴스를 닫고 리소스를 해제합니다.
  3. try-finally 블록을 사용하고 finally에서 OSSClient.shutdown() 메서드를 호출하세요.
  4. 애플리케이션에서 OSSClient를 사용하는 경우 완료 후 OSSClient 인스턴스를 닫아야 합니다.

국소 재생산

로컬 활성화 프로젝트는 Jprofiler를 사용하여 JVM에 연결합니다.

이 함수는 외부 세계와의 인터페이스를 제공하므로 이 인터페이스를 계속 요청하기 위해 for 루프를 작성하고 메모리 변화를 관찰했으며 6분 동안 실행한 후 사용 가능한 메모리가 0이 되었습니다.

서버에서 OOM도 보고했습니다.

문제를 풀다

팩토리 패턴을 사용하여 코드를 다시 작성합니다.

    public static final Map<String, OSSClient> map = new ConcurrentHashMap<>();
    public static OSSClient getClient(String endpoint, String accessKey, String accessSecret) {
        if (!map.containsKey(accessKey)) {
            OSSClient client = new OSSClient(endpoint, accessKey, accessSecret);
            map.put(accessKey, client);
        }
        return map.get(accessKey);
    }

원래 코드를 바꾸십시오.

  OSSClient client = AliyunUtil.getClient(endpoint, getAccessKey(), getAccessSecret());

다시 테스트한 결과, 각 GC가 메모리를 매우 잘 해제한 것으로 나타났으며, 6분 동안 실행한 후에도 메모리 사용량이 200M를 넘지 않아 문제가 완벽하게 해결되었습니다.

요약하다

이 기사에서는 Alibaba Cloud OSSClient의 부적절한 사용으로 인해 발생하는 온라인 OOM 프로세스 문제를 해결하기 위해 Jprofiler를 사용하는 방법을 소개합니다. 주로 코드 작성 시 OSSClient의 수동 종료에 주의를 기울이지 않아서 발생합니다. 다행히 핵심 비즈니스에서는 나타나지 않습니다. 시스템을 사용하지 않으면 결과가 더욱 번거로워질 것입니다. 앞으로 다른 사람이 제공한 도구를 사용할 때 공식이 해당 도구를 어떻게 사용하는지 자세히 읽어보고 소스 코드를 읽어 유사한 문제가 발생하지 않도록 해야 합니다.

Supongo que te gusta

Origin blog.csdn.net/whzhaochao/article/details/132992028
Recomendado
Clasificación