You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@dubbo.apache.org by al...@apache.org on 2021/10/19 02:12:10 UTC

[dubbo] branch 3.0 updated: [3.0] Manage global resources and executor services, fix zk client connections (#9033)

This is an automated email from the ASF dual-hosted git repository.

albumenj pushed a commit to branch 3.0
in repository https://gitbox.apache.org/repos/asf/dubbo.git


The following commit(s) were added to refs/heads/3.0 by this push:
     new 1bdf359  [3.0] Manage global resources and executor services, fix zk client connections (#9033)
1bdf359 is described below

commit 1bdf359b1a4c50e260960fd35c2896b184e1954b
Author: Gong Dewei <ky...@qq.com>
AuthorDate: Tue Oct 19 10:11:53 2021 +0800

    [3.0] Manage global resources and executor services, fix zk client connections (#9033)
    
    * shutdown scheduledExecutors and executorServiceRing
    
    * fix NPE of configuration
    
    * Fix notify loop after executor service is shutdown
    
    * improve registry center in tests
    
    * polish unregister shutdown hook log
    
    * Fix systemConfiguration NPE
    
    * enable surefire reuseForks for fast testing
    
    * Fix checking DubboShutdownHook is alive
    
    * fix SPI/Bean instantiation constructor matching
    
    * shutdown share executor
    
    * release zk client
    
    * Improve application destroy processing
    
    * Add GlobalResourcesRepository to manager static resources, including ExecutorService and HashedWheelTimer
    
    * revert zk client address mapping
    
    * Fix zk client early close problem
    
    * improve executor service
    
    * improve both start by module and by application
    
    * release sharedScheduledExecutor and fix AccessLogFilter
    
    * Fix tests
    
    * checking zk registry if destroyed
    
    * fix testSystemPropertyOverrideReferenceConfig config error
    
    * improve KeepRunningOnSpringClosedTest
    
    * SPI extension/scope bean prefers parameterized constructor instead of default constructor
    
    * Support compatible usage of ZookeeperRegistryFactory
    
    * Improve the application/module destroy process, the deployer destruction is divided into pre-destroy and post-destroy
    
    * Destroy NacosDynamicConfiguration
    
    * Destroy MetadataReportFactory
    
    * Recreate adaptive classes
    
    * Destroy global resources on dubbo shutdown
    
    * Fix zk thread leaks when create client failed
    
    * Improve protocol destroying
    
    * Improve thread name of protocol port
    
    * Manage global EventLoopGroup, HashedWheelTimer, etc.
    
    * Protected metadata info in memory
    
    * Support checking unclosed threads when finish test class
    
    * Improve application check started
    
    * add test log4j.properties
    
    * add pom.xml of dubbo-test-check
    
    * fix CodecSupport.checkSerialization error
    
    * Fix ReferenceCountExchangeClientTest log msg checking
    
    * Fix ServiceInstanceMetadataUtilsTest and MigrationInvokerTest
    
    * Fix protocol destroy problem
    
    * rename test check jvm args
    
    * cancel asyncMetadataFuture and unregisterServiceInstance in pre-destroy application
    
    * Ignore refresh metadata if application is stopping
    
    * Remove unused destroyDynamicConfigurations method
    
    * Change ZookeeperTransporter to application scope
    
    * Add testMultiProviderApplicationsStopOneByOne
    
    * Destroy ZookeeperTransporter in RemotingScopeModelInitializer
    
    * support specify test check report file
---
 dubbo-cluster/pom.xml                              |   8 +-
 .../loadbalance/ShortestResponseLoadBalance.java   |  14 +-
 .../rpc/cluster/support/ForkingClusterInvoker.java |   5 +-
 dubbo-common/pom.xml                               |   1 -
 .../src/main/java/org/apache/dubbo/common/URL.java |   5 +
 .../beans/support/InstantiationStrategy.java       |  32 +-
 .../common/concurrent/CallableSafeInitializer.java | 101 ++++++
 .../apache/dubbo/common/config/Environment.java    |   9 +-
 .../dubbo/common/config/ModuleEnvironment.java     |   1 +
 .../configcenter/nop/NopDynamicConfiguration.java  |   9 +-
 .../wrapper/CompositeDynamicConfiguration.java     |  16 +
 .../dubbo/common/deploy/ApplicationDeployer.java   |  20 +-
 .../apache/dubbo/common/deploy/DeployState.java    |  35 ++-
 .../org/apache/dubbo/common/deploy/Deployer.java   |   5 +-
 .../apache/dubbo/common/deploy/ModuleDeployer.java |   8 +-
 .../extension/AdaptiveClassCodeGenerator.java      |  12 +
 .../resource/Disposable.java}                      |  11 +-
 .../common/resource/GlobalResourceInitializer.java |  65 ++++
 .../common/resource/GlobalResourcesRepository.java | 130 ++++++++
 .../manager/DefaultExecutorRepository.java         | 125 ++++----
 .../threadpool/manager/ExecutorRepository.java     |   8 +-
 .../support/cached/CachedThreadPool.java           |   2 +-
 .../threadpool/support/eager/EagerThreadPool.java  |   2 +-
 .../threadpool/support/fixed/FixedThreadPool.java  |   2 +-
 .../support/limited/LimitedThreadPool.java         |   2 +-
 .../common/utils/ClassLoaderResourceLoader.java    |  23 +-
 .../org/apache/dubbo/config/ServiceConfigBase.java |   9 +-
 .../apache/dubbo/rpc/model/ApplicationModel.java   |  38 ++-
 .../org/apache/dubbo/rpc/model/FrameworkModel.java |  33 +-
 .../org/apache/dubbo/rpc/model/ModuleModel.java    |  15 +-
 .../org/apache/dubbo/rpc/model/ScopeModel.java     |  12 +-
 .../dubbo/rpc/model/ScopeModelDestroyListener.java |   4 +-
 .../file/FileSystemDynamicConfigurationTest.java   |  12 +-
 .../threadpool/manager/ExecutorRepositoryTest.java |  57 +++-
 dubbo-compatible/pom.xml                           |   6 +
 .../dubbo/config/ConfigScopeModelInitializer.java  |   3 +-
 .../org/apache/dubbo/config/DubboShutdownHook.java |   4 +-
 .../org/apache/dubbo/config/ServiceConfig.java     |  24 +-
 .../dubbo/config/bootstrap/DubboBootstrap.java     |  46 +--
 .../config/deploy/DefaultApplicationDeployer.java  | 343 ++++++++++++++-------
 .../dubbo/config/deploy/DefaultModuleDeployer.java |  29 +-
 .../dubbo/config/deploy/FrameworkModelCleaner.java |  64 ++++
 .../dubbo/config/AbstractMethodConfigTest.java     |   8 +
 .../dubbo/config/AbstractReferenceConfigTest.java  |   7 +
 .../dubbo/config/ConfigCenterConfigTest.java       |   7 +
 .../apache/dubbo/config/ProtocolConfigTest.java    |  15 +-
 .../apache/dubbo/config/ProviderConfigTest.java    |   1 +
 .../bootstrap/DubboBootstrapMultiInstanceTest.java | 315 ++++++++++++++++++-
 .../context/DubboDeployApplicationListener.java    |   4 +-
 ...est.java => KeepRunningOnSpringClosedTest.java} |  40 ++-
 .../javaconfig/JavaConfigReferenceBeanTest.java    | 213 +++++++------
 dubbo-config/pom.xml                               |   9 +
 .../support/apollo/ApolloDynamicConfiguration.java |  23 +-
 .../support/nacos/NacosConfigServiceWrapper.java   |   4 +
 .../support/nacos/NacosDynamicConfiguration.java   |  26 +-
 .../zookeeper/ZookeeperDynamicConfiguration.java   |   9 +-
 .../ZookeeperDynamicConfigurationFactory.java      |   8 +-
 dubbo-configcenter/pom.xml                         |   9 +
 dubbo-filter/pom.xml                               |   9 +
 .../org/apache/dubbo/metadata/MetadataInfo.java    |  11 +-
 .../metadata/report/MetadataReportFactory.java     |   3 +
 .../report/support/AbstractMetadataReport.java     |  14 +-
 .../support/AbstractMetadataReportFactory.java     |  30 +-
 .../store/zookeeper/ZookeeperMetadataReport.java   |  12 +-
 .../zookeeper/ZookeeperMetadataReportFactory.java  |   8 +-
 .../zookeeper/ZookeeperMetadataReportTest.java     |  15 +-
 dubbo-metadata/pom.xml                             |   8 +
 dubbo-metrics/pom.xml                              |   9 +
 .../apache/dubbo/monitor/dubbo/DubboMonitor.java   |   5 +-
 .../src/test/resources/log4j.properties            |   7 +
 dubbo-monitor/pom.xml                              |  10 +
 dubbo-native/pom.xml                               |   7 +-
 .../common/serialize/Serialization$Adaptive.java   |  14 +-
 .../report/MetadataReportFactory$Adaptive.java     |   3 +
 .../zookeeper/ZookeeperTransporter$Adaptive.java   |  28 --
 .../org/apache/dubbo/rpc/Protocol$Adaptive.java    |  10 +-
 .../apache/dubbo/rpc/ProxyFactory$Adaptive.java    |  18 +-
 .../apache/dubbo/rpc/cluster/Cluster$Adaptive.java |  12 +-
 .../java/org/apache/dubbo/utils/CodeGenerator.java |   3 +-
 .../dubbo/qos/command/impl/ShutdownTelnet.java     |   4 +-
 .../dubbo/qos/command/impl/ChangeTelnetTest.java   |  17 +-
 .../dubbo/qos/command/impl/CountTelnetTest.java    |   6 +-
 .../dubbo/qos/command/impl/InvokeTelnetTest.java   |   2 -
 .../dubbo/qos/command/impl/PortTelnetTest.java     |  12 +-
 .../dubbo/qos/command/impl/PwdTelnetTest.java      |   9 +-
 .../dubbo/qos/command/impl/SelectTelnetTest.java   |   9 +-
 .../dubbo/qos/command/impl/ShutdownTelnetTest.java |   6 +-
 .../dubbo/qos/command/util/CommandHelperTest.java  |   1 +
 .../dubbo/qos/legacy/ChangeTelnetHandlerTest.java  |   4 +-
 .../org/apache/dubbo/qos/legacy/ProtocolUtils.java |  17 +-
 .../dubbo/qos/legacy/TraceTelnetHandlerTest.java   |   4 +-
 dubbo-plugin/pom.xml                               |   8 +
 .../store/InMemoryWritableMetadataService.java     |  19 +-
 .../client/migration/MigrationRuleListener.java    |   2 +-
 .../registry/integration/RegistryProtocol.java     |  22 +-
 .../dubbo/registry/support/AbstractRegistry.java   |   6 +-
 .../metadata/ServiceInstanceMetadataUtilsTest.java |   8 +-
 .../client/migration/MigrationInvokerTest.java     |   9 +-
 .../src/test/resources/log4j.xml                   |   4 +-
 .../registry/zookeeper/ZookeeperRegistry.java      |  21 +-
 .../zookeeper/ZookeeperRegistryFactory.java        |  11 +-
 .../zookeeper/ZookeeperServiceDiscovery.java       |   1 +
 .../registry/zookeeper/ZookeeperRegistryTest.java  |   7 +-
 dubbo-registry/pom.xml                             |   9 +
 .../remoting/RemotingScopeModelInitializer.java    |  44 +--
 .../org/apache/dubbo/remoting/api/Connection.java  |   2 +-
 .../dubbo/remoting/api/NettyEventLoopFactory.java  |   9 +-
 .../remoting/exchange/support/DefaultFuture.java   |  16 +-
 .../remoting/exchange/support/DefaultFuture2.java  |  18 +-
 .../support/header/HeaderExchangeClient.java       |  32 +-
 .../support/header/HeaderExchangeServer.java       |  17 +-
 .../dispatcher/WrappedChannelHandler.java          |  12 +-
 .../zookeeper/AbstractZookeeperClient.java         |   5 +-
 .../zookeeper/AbstractZookeeperTransporter.java    |  10 +
 .../remoting/zookeeper/ZookeeperTransporter.java   |  11 +-
 ...rg.apache.dubbo.rpc.model.ScopeModelInitializer |   1 +
 .../remoting/transport/netty4/NettyClient.java     |  18 +-
 .../curator5/Curator5ZookeeperClient.java          |  20 +-
 .../zookeeper/curator/CuratorZookeeperClient.java  |  21 +-
 dubbo-remoting/pom.xml                             |   9 +
 .../apache/dubbo/rpc/filter/AccessLogFilter.java   |  25 +-
 .../dubbo/rpc/protocol/AbstractProtocol.java       |   9 +-
 .../dubbo/rpc/filter/AccessLogFilterTest.java      |   7 +-
 .../dubbo/rpc/protocol/dubbo/DubboProtocol.java    |  21 ++
 .../dubbo/decode/DubboTelnetDecodeTest.java        |  11 +
 .../rpc/protocol/dubbo/support/ProtocolUtils.java  |  21 +-
 .../dubbo-rpc-dubbo/src/test/resources/log4j.xml   |   8 +
 .../dubbo/rpc/protocol/grpc/GrpcProtocol.java      |  16 +-
 .../dubbo/rpc/protocol/rest/RestProtocol.java      |   4 +-
 .../dubbo/rpc/protocol/tri/TripleProtocol.java     |  23 +-
 dubbo-rpc/pom.xml                                  |   9 +
 dubbo-serialization/pom.xml                        |   9 +
 .../event/AwaitingNonWebApplicationListener.java   |  12 +-
 dubbo-spring-boot/pom.xml                          |   6 +
 dubbo-test/{ => dubbo-test-check}/pom.xml          |  41 ++-
 .../apache/dubbo/test/check/DubboTestChecker.java  | 309 +++++++++++++++++++
 ...g.junit.platform.launcher.TestExecutionListener |   1 +
 dubbo-test/dubbo-test-common/pom.xml               |   6 +
 dubbo-test/pom.xml                                 |   1 +
 139 files changed, 2470 insertions(+), 816 deletions(-)

diff --git a/dubbo-cluster/pom.xml b/dubbo-cluster/pom.xml
index 26b1295..fbb0d17 100644
--- a/dubbo-cluster/pom.xml
+++ b/dubbo-cluster/pom.xml
@@ -60,5 +60,11 @@
             <version>${project.parent.version}</version>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-test-check</artifactId>
+            <version>${project.parent.version}</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
-</project>
\ No newline at end of file
+</project>
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/loadbalance/ShortestResponseLoadBalance.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/loadbalance/ShortestResponseLoadBalance.java
index a898c84..1f06079 100644
--- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/loadbalance/ShortestResponseLoadBalance.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/loadbalance/ShortestResponseLoadBalance.java
@@ -17,7 +17,6 @@
 package org.apache.dubbo.rpc.cluster.loadbalance;
 
 import org.apache.dubbo.common.URL;
-import org.apache.dubbo.common.utils.NamedThreadFactory;
 import org.apache.dubbo.rpc.Invocation;
 import org.apache.dubbo.rpc.Invoker;
 import org.apache.dubbo.rpc.RpcStatus;
@@ -29,7 +28,6 @@ import java.util.List;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
 import java.util.concurrent.ThreadLocalRandom;
 import java.util.concurrent.atomic.AtomicBoolean;
 
@@ -46,7 +44,7 @@ public class ShortestResponseLoadBalance extends AbstractLoadBalance implements
 
     public static final String NAME = "shortestresponse";
 
-    private int SLIDE_PERIOD = 30_000;
+    private int slidePeriod = 30_000;
 
     private ConcurrentMap<RpcStatus, SlideWindowData> methodMap = new ConcurrentHashMap<>();
 
@@ -54,13 +52,15 @@ public class ShortestResponseLoadBalance extends AbstractLoadBalance implements
 
     private volatile long lastUpdateTime = System.currentTimeMillis();
 
+    private ExecutorService executorService;
+
     @Override
     public void setApplicationModel(ApplicationModel applicationModel) {
-        SLIDE_PERIOD = applicationModel.getModelEnvironment().getConfiguration().getInt(Constants.SHORTEST_RESPONSE_SLIDE_PERIOD, 30_000);
+        slidePeriod = applicationModel.getModelEnvironment().getConfiguration().getInt(Constants.SHORTEST_RESPONSE_SLIDE_PERIOD, 30_000);
+        executorService = applicationModel.getApplicationExecutorRepository().getSharedExecutor();
     }
 
     protected static class SlideWindowData {
-        private final static ExecutorService EXECUTOR_SERVICE = Executors.newSingleThreadExecutor((new NamedThreadFactory("Dubbo-slidePeriod-reset")));
 
         private long succeededOffset;
         private long succeededElapsedOffset;
@@ -138,10 +138,10 @@ public class ShortestResponseLoadBalance extends AbstractLoadBalance implements
             }
         }
 
-        if (System.currentTimeMillis() - lastUpdateTime > SLIDE_PERIOD
+        if (System.currentTimeMillis() - lastUpdateTime > slidePeriod
             && onResetSlideWindow.compareAndSet(false, true)) {
             //reset slideWindowData in async way
-            SlideWindowData.EXECUTOR_SERVICE.execute(() -> {
+            executorService.execute(() -> {
                 methodMap.values().forEach(SlideWindowData::reset);
                 lastUpdateTime = System.currentTimeMillis();
                 onResetSlideWindow.set(false);
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/ForkingClusterInvoker.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/ForkingClusterInvoker.java
index 8980b34..6439c46 100644
--- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/ForkingClusterInvoker.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/ForkingClusterInvoker.java
@@ -30,7 +30,6 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -53,11 +52,11 @@ public class ForkingClusterInvoker<T> extends AbstractClusterInvoker<T> {
      * Use {@link NamedInternalThreadFactory} to produce {@link org.apache.dubbo.common.threadlocal.InternalThread}
      * which with the use of {@link org.apache.dubbo.common.threadlocal.InternalThreadLocal} in {@link RpcContext}.
      */
-    private final ExecutorService executor = Executors.newCachedThreadPool(
-        new NamedInternalThreadFactory("forking-cluster-timer", true));
+    private final ExecutorService executor;
 
     public ForkingClusterInvoker(Directory<T> directory) {
         super(directory);
+        executor = directory.getUrl().getOrDefaultApplicationModel().getApplicationExecutorRepository().getSharedExecutor();
     }
 
     @Override
diff --git a/dubbo-common/pom.xml b/dubbo-common/pom.xml
index 1e2d18a..0c8d768 100644
--- a/dubbo-common/pom.xml
+++ b/dubbo-common/pom.xml
@@ -94,7 +94,6 @@
             <scope>test</scope>
         </dependency>
 
-
     </dependencies>
 
 </project>
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/URL.java b/dubbo-common/src/main/java/org/apache/dubbo/common/URL.java
index 2a4cdd0..848d599 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/URL.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/URL.java
@@ -1577,6 +1577,11 @@ class URL implements Serializable {
         return attributes.get(key);
     }
 
+    public Object getAttribute(String key, Object defaultValue) {
+        Object val = attributes.get(key);
+        return val != null ? val : defaultValue;
+    }
+
     public URL putAttribute(String key, Object obj) {
         attributes.put(key, obj);
         return this;
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/beans/support/InstantiationStrategy.java b/dubbo-common/src/main/java/org/apache/dubbo/common/beans/support/InstantiationStrategy.java
index b1b67ce..02e4063 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/beans/support/InstantiationStrategy.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/beans/support/InstantiationStrategy.java
@@ -33,7 +33,6 @@ import java.util.List;
  */
 public class InstantiationStrategy {
 
-    private boolean supportConstructorWithArguments;
     private ScopeModelAccessor scopeModelAccessor;
 
     public InstantiationStrategy() {
@@ -42,19 +41,17 @@ public class InstantiationStrategy {
 
     public InstantiationStrategy(ScopeModelAccessor scopeModelAccessor) {
         this.scopeModelAccessor = scopeModelAccessor;
-        this.supportConstructorWithArguments = (this.scopeModelAccessor != null);
     }
 
     public <T> T instantiate(Class<T> type) throws ReflectiveOperationException {
 
-        // 1. try default constructor
+        // should not use default constructor directly, maybe also has another constructor matched scope model arguments
+        // 1. try get default constructor
+        Constructor<T> defaultConstructor = null;
         try {
-            return type.getConstructor().newInstance();
+            defaultConstructor = type.getConstructor();
         } catch (NoSuchMethodException e) {
             // ignore no default constructor
-            if (!supportConstructorWithArguments) {
-                throw new IllegalArgumentException("Default constructor was not found for type: " + type.getName());
-            }
         }
 
         // 2. use matched constructor if found
@@ -65,22 +62,35 @@ public class InstantiationStrategy {
                 matchedConstructors.add(constructor);
             }
         }
+        // remove default constructor from matchedConstructors
+        if (defaultConstructor != null) {
+            matchedConstructors.remove(defaultConstructor);
+        }
+
+        // match order:
+        // 1. the only matched constructor with parameters
+        // 2. default constructor if absent
+
+        Constructor targetConstructor;
         if (matchedConstructors.size() > 1) {
             throw new IllegalArgumentException("Expect only one but found " +
                 matchedConstructors.size() + " matched constructors for type: " + type.getName() +
                 ", matched constructors: " + matchedConstructors);
-        } else if (matchedConstructors.size() == 0) {
+        } else if (matchedConstructors.size() == 1) {
+            targetConstructor = matchedConstructors.get(0);
+        } else if (defaultConstructor != null) {
+            targetConstructor = defaultConstructor;
+        } else {
             throw new IllegalArgumentException("None matched constructor was found for type: " + type.getName());
         }
 
         // create instance with arguments
-        Constructor constructor = matchedConstructors.get(0);
-        Class[] parameterTypes = constructor.getParameterTypes();
+        Class[] parameterTypes = targetConstructor.getParameterTypes();
         Object[] args = new Object[parameterTypes.length];
         for (int i = 0; i < parameterTypes.length; i++) {
             args[i] = getArgumentValueForType(parameterTypes[i]);
         }
-        return (T) constructor.newInstance(args);
+        return (T) targetConstructor.newInstance(args);
     }
 
     private boolean isMatched(Constructor<?> constructor) {
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/concurrent/CallableSafeInitializer.java b/dubbo-common/src/main/java/org/apache/dubbo/common/concurrent/CallableSafeInitializer.java
new file mode 100644
index 0000000..092ec41
--- /dev/null
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/concurrent/CallableSafeInitializer.java
@@ -0,0 +1,101 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.common.concurrent;
+
+import org.apache.dubbo.common.resource.Disposable;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
+
+/**
+ * <p>
+ * A safe and lazy and removable initializer implementation that wraps a
+ * {@code Callable} object.
+ * </p>
+ * @see org.apache.commons.lang3.concurrent.AtomicSafeInitializer
+ */
+public class CallableSafeInitializer<T> {
+    /** A guard which ensures that initialize() is called only once. */
+    private final AtomicReference<CallableSafeInitializer<T>> factory =
+            new AtomicReference<>();
+
+    /** Holds the reference to the managed object. */
+    private final AtomicReference<T> reference = new AtomicReference<>();
+
+    /** The Callable to be executed. */
+    private final Callable<T> callable;
+
+    public CallableSafeInitializer(Callable<T> callable) {
+        this.callable = callable;
+    }
+
+    /**
+     * Get (and initialize, if not initialized yet) the required object
+     *
+     * @return lazily initialized object
+     * exception
+     */
+    //@Override
+    public final T get() {
+        T result;
+
+        while ((result = reference.get()) == null) {
+            if (factory.compareAndSet(null, this)) {
+                reference.set(initialize());
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * Creates and initializes the object managed by this
+     * {@code AtomicInitializer}. This method is called by {@link #get()} when
+     * the managed object is not available yet. An implementation can focus on
+     * the creation of the object. No synchronization is needed, as this is
+     * already handled by {@code get()}. This method is guaranteed to be called
+     * only once.
+     *
+     * @return the managed data object
+     */
+    protected T initialize() {
+        try {
+            return callable.call();
+        } catch (Exception e) {
+            throw new RuntimeException(e.getMessage(), e);
+        }
+    }
+
+    public T remove() {
+        return this.remove(null);
+    }
+
+    public T remove(Consumer<? super T> action) {
+        // release
+        T value = reference.getAndSet(null);
+        if (value != null && action != null) {
+            if (action != null) {
+                action.accept(value);
+            } else if (value instanceof Disposable) {
+                ((Disposable) value).destroy();
+            }
+        }
+        factory.set(null);
+        return value;
+    }
+}
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/config/Environment.java b/dubbo-common/src/main/java/org/apache/dubbo/common/config/Environment.java
index 6df25f1..67ecf61 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/config/Environment.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/config/Environment.java
@@ -246,7 +246,14 @@ public class Environment extends LifecycleAdapter implements ApplicationExt {
         globalConfiguration = null;
         globalConfigurationMaps = null;
         defaultDynamicGlobalConfiguration = null;
-        defaultDynamicConfiguration = null;
+        if (defaultDynamicConfiguration != null) {
+            try {
+                defaultDynamicConfiguration.close();
+            } catch (Exception e) {
+                logger.warn("close dynamic configuration failed: " + e.getMessage(), e);
+            }
+            defaultDynamicConfiguration = null;
+        }
     }
 
     /**
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/config/ModuleEnvironment.java b/dubbo-common/src/main/java/org/apache/dubbo/common/config/ModuleEnvironment.java
index 5aad696..c177f51 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/config/ModuleEnvironment.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/config/ModuleEnvironment.java
@@ -119,6 +119,7 @@ public class ModuleEnvironment extends Environment implements ModuleExt {
 
     @Override
     public void destroy() throws IllegalStateException {
+        super.destroy();
         this.orderedPropertiesConfiguration = null;
         this.globalConfiguration = null;
         this.dynamicGlobalConfiguration = null;
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/config/configcenter/nop/NopDynamicConfiguration.java b/dubbo-common/src/main/java/org/apache/dubbo/common/config/configcenter/nop/NopDynamicConfiguration.java
index 213e8c2..0263999 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/config/configcenter/nop/NopDynamicConfiguration.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/config/configcenter/nop/NopDynamicConfiguration.java
@@ -20,10 +20,10 @@ import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.config.configcenter.ConfigurationListener;
 import org.apache.dubbo.common.config.configcenter.DynamicConfiguration;
 
-import static java.util.Collections.emptySortedSet;
-
 import java.util.SortedSet;
 
+import static java.util.Collections.emptySortedSet;
+
 /**
  * The default extension of {@link DynamicConfiguration}. If user does not specify a config center, or specifies one
  * that is not a valid extension, it will default to this one.
@@ -71,4 +71,9 @@ public class NopDynamicConfiguration implements DynamicConfiguration {
     public SortedSet<String> getConfigKeys(String group) {
         return emptySortedSet();
     }
+
+    @Override
+    public void close() throws Exception {
+        // no-op
+    }
 }
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/config/configcenter/wrapper/CompositeDynamicConfiguration.java b/dubbo-common/src/main/java/org/apache/dubbo/common/config/configcenter/wrapper/CompositeDynamicConfiguration.java
index 0613854..a2e8dea 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/config/configcenter/wrapper/CompositeDynamicConfiguration.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/config/configcenter/wrapper/CompositeDynamicConfiguration.java
@@ -18,6 +18,8 @@ package org.apache.dubbo.common.config.configcenter.wrapper;
 
 import org.apache.dubbo.common.config.configcenter.ConfigurationListener;
 import org.apache.dubbo.common.config.configcenter.DynamicConfiguration;
+import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.LoggerFactory;
 
 import java.util.HashSet;
 import java.util.Set;
@@ -32,6 +34,8 @@ public class CompositeDynamicConfiguration implements DynamicConfiguration {
 
     public static final String NAME = "COMPOSITE";
 
+    private static final Logger logger = LoggerFactory.getLogger(CompositeDynamicConfiguration.class);
+
     private Set<DynamicConfiguration> configurations = new HashSet<>();
 
     public void addConfiguration(DynamicConfiguration configuration) {
@@ -87,6 +91,18 @@ public class CompositeDynamicConfiguration implements DynamicConfiguration {
         return (SortedSet<String>) iterateConfigOperation(configuration -> configuration.getConfigKeys(group));
     }
 
+    @Override
+    public void close() throws Exception {
+        for (DynamicConfiguration configuration : configurations) {
+            try {
+                configuration.close();
+            } catch (Exception e) {
+                logger.warn("close dynamic configuration failed: " + e.getMessage(), e);
+            }
+        }
+        configurations.clear();
+    }
+
     private void iterateListenerOperation(Consumer<DynamicConfiguration> consumer) {
         for (DynamicConfiguration configuration : configurations) {
             consumer.accept(configuration);
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/deploy/ApplicationDeployer.java b/dubbo-common/src/main/java/org/apache/dubbo/common/deploy/ApplicationDeployer.java
index 0a61208..5673224 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/deploy/ApplicationDeployer.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/deploy/ApplicationDeployer.java
@@ -19,7 +19,7 @@ package org.apache.dubbo.common.deploy;
 import org.apache.dubbo.common.config.ReferenceCache;
 import org.apache.dubbo.rpc.model.ApplicationModel;
 
-import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Future;
 
 /**
  * initialize and start application instance
@@ -33,17 +33,29 @@ public interface ApplicationDeployer extends Deployer<ApplicationModel> {
 
     /**
      * Starts the component.
+     * @return
      */
-    CompletableFuture start() throws IllegalStateException;
+    Future start() throws IllegalStateException;
 
     /**
      * Stops the component.
      */
     void stop() throws IllegalStateException;
 
+    /**
+     * Register application instance and start internal services
+     */
     void prepareApplicationInstance();
 
-    void destroy();
+    /**
+     * Pre-processing before destroy model
+     */
+    void preDestroy();
+
+    /**
+     * Post-processing after destroy model
+     */
+    void postDestroy();
 
     /**
      * Indicates that the Application is initialized or not.
@@ -61,6 +73,6 @@ public interface ApplicationDeployer extends Deployer<ApplicationModel> {
 
     void checkStarting();
 
-    void checkStarted(CompletableFuture checkerStartFuture);
+    void checkStarted();
 
 }
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/deploy/DeployState.java b/dubbo-common/src/main/java/org/apache/dubbo/common/deploy/DeployState.java
index eda92c4..2790639 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/deploy/DeployState.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/deploy/DeployState.java
@@ -20,5 +20,38 @@ package org.apache.dubbo.common.deploy;
  * Deploy state enum
  */
 public enum DeployState {
-    PENDING, STARTING, STARTED, STOPPING, STOPPED, FAILED
+    /**
+     * Unknown state
+     */
+    UNKNOWN,
+
+    /**
+     * Pending, wait for start
+     */
+    PENDING,
+
+    /**
+     * Starting
+     */
+    STARTING,
+
+    /**
+     * Started
+     */
+    STARTED,
+
+    /**
+     * Stopping
+     */
+    STOPPING,
+
+    /**
+     * Stopped
+     */
+    STOPPED,
+
+    /**
+     * Failed
+     */
+    FAILED
 }
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/deploy/Deployer.java b/dubbo-common/src/main/java/org/apache/dubbo/common/deploy/Deployer.java
index b4d0704..568dda2 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/deploy/Deployer.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/deploy/Deployer.java
@@ -18,7 +18,7 @@ package org.apache.dubbo.common.deploy;
 
 import org.apache.dubbo.rpc.model.ScopeModel;
 
-import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Future;
 
 /**
  */
@@ -31,8 +31,9 @@ public interface Deployer<E extends ScopeModel> {
 
     /**
      * Starts the component.
+     * @return
      */
-    CompletableFuture start() throws IllegalStateException;
+    Future start() throws IllegalStateException;
 
     /**
      * Stops the component.
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/deploy/ModuleDeployer.java b/dubbo-common/src/main/java/org/apache/dubbo/common/deploy/ModuleDeployer.java
index 45894c6..6c32be8 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/deploy/ModuleDeployer.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/deploy/ModuleDeployer.java
@@ -20,7 +20,7 @@ import org.apache.dubbo.common.config.ReferenceCache;
 import org.apache.dubbo.config.ServiceConfigBase;
 import org.apache.dubbo.rpc.model.ModuleModel;
 
-import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Future;
 
 /**
  * Export/refer services of module
@@ -29,11 +29,13 @@ public interface ModuleDeployer extends Deployer<ModuleModel> {
 
     void initialize() throws IllegalStateException;
 
-    CompletableFuture start() throws IllegalStateException;
+    Future start() throws IllegalStateException;
 
     void stop() throws IllegalStateException;
 
-    void destroy() throws IllegalStateException;
+    void preDestroy() throws IllegalStateException;
+
+    void postDestroy() throws IllegalStateException;
 
     boolean isInitialized();
 
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/extension/AdaptiveClassCodeGenerator.java b/dubbo-common/src/main/java/org/apache/dubbo/common/extension/AdaptiveClassCodeGenerator.java
index b19f8c2..61a9a8d 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/extension/AdaptiveClassCodeGenerator.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/extension/AdaptiveClassCodeGenerator.java
@@ -26,6 +26,7 @@ import org.apache.dubbo.rpc.model.ScopeModelUtil;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
 import java.util.Arrays;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.stream.Collectors;
@@ -90,6 +91,14 @@ public class AdaptiveClassCodeGenerator {
      * generate and return class code
      */
     public String generate() {
+        return this.generate(false);
+    }
+
+    /**
+     * generate and return class code
+     * @param sort - whether sort methods
+     */
+    public String generate(boolean sort) {
         // no need to generate adaptive class since there's no adaptive method found.
         if (!hasAdaptiveMethod()) {
             throw new IllegalStateException("No adaptive method exist on extension " + type.getName() + ", refuse to create the adaptive class!");
@@ -101,6 +110,9 @@ public class AdaptiveClassCodeGenerator {
         code.append(generateClassDeclaration());
 
         Method[] methods = type.getMethods();
+        if (sort) {
+            Arrays.sort(methods, Comparator.comparing(Method::toString));
+        }
         for (Method method : methods) {
             code.append(generateMethod(method));
         }
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/ScopeModelDestroyListener.java b/dubbo-common/src/main/java/org/apache/dubbo/common/resource/Disposable.java
similarity index 84%
copy from dubbo-common/src/main/java/org/apache/dubbo/rpc/model/ScopeModelDestroyListener.java
copy to dubbo-common/src/main/java/org/apache/dubbo/common/resource/Disposable.java
index 93f6e84..78c075c 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/ScopeModelDestroyListener.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/resource/Disposable.java
@@ -14,8 +14,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.rpc.model;
+package org.apache.dubbo.common.resource;
+
+/**
+ * An interface for destroying resources
+ */
+public interface Disposable {
+
+    void destroy();
 
-public interface ScopeModelDestroyListener {
-    void onDestroy(ScopeModel scopeModel);
 }
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/resource/GlobalResourceInitializer.java b/dubbo-common/src/main/java/org/apache/dubbo/common/resource/GlobalResourceInitializer.java
new file mode 100644
index 0000000..80c467e
--- /dev/null
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/resource/GlobalResourceInitializer.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.common.resource;
+
+import org.apache.dubbo.common.concurrent.CallableSafeInitializer;
+
+import java.util.concurrent.Callable;
+import java.util.function.Consumer;
+
+/**
+ * A initializer to release resource automatically on dubbo shutdown
+ */
+public class GlobalResourceInitializer<T> extends CallableSafeInitializer<T> {
+
+    /**
+     * The Dispose action to be executed on shutdown.
+     */
+    private Consumer<T> disposeAction;
+
+    private Disposable disposable;
+
+    public GlobalResourceInitializer(Callable<T> initializer) {
+        super(initializer);
+    }
+
+    public GlobalResourceInitializer(Callable initializer, Consumer<T> disposeAction) {
+        super(initializer);
+        this.disposeAction = disposeAction;
+    }
+
+    public GlobalResourceInitializer(Callable<T> callable, Disposable disposable) {
+        super(callable);
+        this.disposable = disposable;
+    }
+
+    @Override
+    protected T initialize() {
+        T value = super.initialize();
+        // register disposable to release automatically
+        if (this.disposable != null) {
+            GlobalResourcesRepository.getInstance().registerDisposable(this.disposable);
+        } else {
+            GlobalResourcesRepository.getInstance().registerDisposable(() -> this.remove(disposeAction));
+        }
+        return value;
+    }
+
+    public interface DestroyHandler<T> {
+        void dispose(GlobalResourceInitializer<T> initializer);
+    }
+}
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/resource/GlobalResourcesRepository.java b/dubbo-common/src/main/java/org/apache/dubbo/common/resource/GlobalResourcesRepository.java
new file mode 100644
index 0000000..8e60e5d
--- /dev/null
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/resource/GlobalResourcesRepository.java
@@ -0,0 +1,130 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.common.resource;
+
+import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.common.utils.NamedThreadFactory;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * Global resources repository between all framework models.
+ * It will be destroyed only after all framework model is destroyed.
+ */
+public class GlobalResourcesRepository {
+
+    private static final Logger logger = LoggerFactory.getLogger(GlobalResourcesRepository.class);
+
+    private volatile static GlobalResourcesRepository instance;
+    private volatile ExecutorService executorService;
+    private final List<Disposable> oneoffDisposables = Collections.synchronizedList(new ArrayList<>());
+    private final List<Disposable> reusedDisposables = Collections.synchronizedList(new ArrayList<>());
+
+    private GlobalResourcesRepository() {
+    }
+
+    public static GlobalResourcesRepository getInstance() {
+        if (instance == null) {
+            synchronized (GlobalResourcesRepository.class) {
+                if (instance == null) {
+                    instance = new GlobalResourcesRepository();
+                }
+            }
+        }
+        return instance;
+    }
+
+    public static ExecutorService getGlobalExecutorService() {
+        return getInstance().getExecutorService();
+    }
+
+    public ExecutorService getExecutorService() {
+        if (executorService == null || executorService.isShutdown()) {
+            synchronized (this) {
+                if (executorService == null || executorService.isShutdown()) {
+                    executorService = Executors.newCachedThreadPool(new NamedThreadFactory("Dubbo-global-shared-handler", true));
+                }
+            }
+        }
+        return executorService;
+    }
+
+    public void destroy() {
+        if (logger.isInfoEnabled()) {
+            logger.info("Destroying global resources ...");
+        }
+        if (executorService != null) {
+            executorService.shutdownNow();
+            executorService = null;
+        }
+
+        // notify disposables
+        // NOTE: don't clear reused disposables for reuse purpose
+        for (Disposable disposable : new ArrayList<>(reusedDisposables)) {
+            try {
+                disposable.destroy();
+            } catch (Exception e) {
+                logger.warn("destroy resources failed: " + e.getMessage(), e);
+            }
+        }
+
+        for (Disposable disposable : new ArrayList<>(oneoffDisposables)) {
+            try {
+                disposable.destroy();
+            } catch (Exception e) {
+                logger.warn("destroy resources failed: " + e.getMessage(), e);
+            }
+        }
+        // clear one-off disposable
+        oneoffDisposables.clear();
+
+        if (logger.isInfoEnabled()) {
+            logger.info("Dubbo is completely destroyed");
+        }
+    }
+
+    /**
+     * Register a one-off disposable, the disposable is removed automatically on first shutdown.
+     * @param disposable
+     */
+    public void registerDisposable(Disposable disposable) {
+        this.registerDisposable(disposable, false);
+    }
+
+    /**
+     * Register a disposable
+     * @param disposable
+     * @param reused true - the disposable is keep and reused. false - the disposable is removed automatically on first shutdown
+     */
+    public void registerDisposable(Disposable disposable, boolean reused) {
+        List<Disposable> disposables = reused ? reusedDisposables : oneoffDisposables;
+        if (!disposables.contains(disposable)) {
+            disposables.add(disposable);
+        }
+    }
+
+    public void removeDisposable(Disposable disposable) {
+        this.reusedDisposables.remove(disposable);
+        this.oneoffDisposables.remove(disposable);
+    }
+
+}
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/threadpool/manager/DefaultExecutorRepository.java b/dubbo-common/src/main/java/org/apache/dubbo/common/threadpool/manager/DefaultExecutorRepository.java
index 731fbd4..0ed4ac5 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/threadpool/manager/DefaultExecutorRepository.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/threadpool/manager/DefaultExecutorRepository.java
@@ -32,6 +32,7 @@ import org.apache.dubbo.rpc.model.ApplicationModel;
 import org.apache.dubbo.rpc.model.ModuleModel;
 import org.apache.dubbo.rpc.model.ScopeModelAware;
 
+import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
@@ -48,6 +49,7 @@ import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_REFER_TH
 import static org.apache.dubbo.common.constants.CommonConstants.EXECUTOR_SERVICE_COMPONENT_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.SIDE_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.THREADS_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.THREAD_NAME_KEY;
 
 /**
  * Consider implementing {@code Licycle} to enable executors shutdown when the process stops.
@@ -57,17 +59,18 @@ public class DefaultExecutorRepository implements ExecutorRepository, ExtensionA
 
     private int DEFAULT_SCHEDULER_SIZE = Runtime.getRuntime().availableProcessors();
 
-    private final ExecutorService SHARED_EXECUTOR = Executors.newCachedThreadPool(new NamedThreadFactory("DubboSharedHandler", true));
+    private final ExecutorService sharedExecutor;
+    private final ScheduledExecutorService sharedScheduledExecutor;
 
     private Ring<ScheduledExecutorService> scheduledExecutors = new Ring<>();
 
-    private volatile ExecutorService serviceExportExecutor;
+    private volatile ScheduledExecutorService serviceExportExecutor;
 
     private volatile ExecutorService serviceReferExecutor;
 
     private ScheduledExecutorService reconnectScheduledExecutor;
 
-    public  Ring<ScheduledExecutorService> registryNotificationExecutorRing = new Ring<>();
+    public Ring<ScheduledExecutorService> registryNotificationExecutorRing = new Ring<>();
 
     private Ring<ScheduledExecutorService> serviceDiscoveryAddressNotificationExecutorRing = new Ring<>();
 
@@ -77,25 +80,28 @@ public class DefaultExecutorRepository implements ExecutorRepository, ExtensionA
 
     private ExecutorService poolRouterExecutor;
 
-    private static Ring<ExecutorService> executorServiceRing = new Ring<ExecutorService>();
+    private Ring<ExecutorService> executorServiceRing = new Ring<ExecutorService>();
 
-    private static final Object LOCK = new Object();
+    private final Object LOCK = new Object();
     private ExtensionAccessor extensionAccessor;
 
     private ApplicationModel applicationModel;
 
     public DefaultExecutorRepository() {
+        sharedExecutor = Executors.newCachedThreadPool(new NamedThreadFactory("Dubbo-shared-handler", true));
+        sharedScheduledExecutor = Executors.newScheduledThreadPool(8, new NamedThreadFactory("Dubbo-shared-scheduler", true));
+
         for (int i = 0; i < DEFAULT_SCHEDULER_SIZE; i++) {
             ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(
-                new NamedThreadFactory("Dubbo-framework-scheduler", true));
+                new NamedThreadFactory("Dubbo-framework-scheduler-" + i, true));
             scheduledExecutors.addItem(scheduler);
 
             executorServiceRing.addItem(new ThreadPoolExecutor(1, 1,
                 0L, TimeUnit.MILLISECONDS,
-                new LinkedBlockingQueue<Runnable>(1024), new NamedInternalThreadFactory("Dubbo-state-router-loop", true)
+                new LinkedBlockingQueue<Runnable>(1024), new NamedInternalThreadFactory("Dubbo-state-router-loop-" + i, true)
                 , new ThreadPoolExecutor.AbortPolicy()));
         }
-//
+
 //        reconnectScheduledExecutor = Executors.newSingleThreadScheduledExecutor(new NamedThreadFactory("Dubbo-reconnect-scheduler"));
         poolRouterExecutor = new ThreadPoolExecutor(1, 10, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(1024),
             new NamedInternalThreadFactory("Dubbo-state-router-pool-router", true), new ThreadPoolExecutor.AbortPolicy());
@@ -123,7 +129,11 @@ public class DefaultExecutorRepository implements ExecutorRepository, ExtensionA
         Map<Integer, ExecutorService> executors = data.computeIfAbsent(EXECUTOR_SERVICE_COMPONENT_KEY, k -> new ConcurrentHashMap<>());
         // Consumer's executor is sharing globally, key=Integer.MAX_VALUE. Provider's executor is sharing by protocol.
         Integer portKey = CONSUMER_SIDE.equalsIgnoreCase(url.getParameter(SIDE_KEY)) ? Integer.MAX_VALUE : url.getPort();
-        ExecutorService executor = executors.computeIfAbsent(portKey, k -> createExecutor(url));
+        if (url.getParameter(THREAD_NAME_KEY) == null) {
+            url = url.putAttribute(THREAD_NAME_KEY, "Dubbo-protocol-"+portKey);
+        }
+        URL finalUrl = url;
+        ExecutorService executor = executors.computeIfAbsent(portKey, k -> createExecutor(finalUrl));
         // If executor has been shut down, create a new one
         if (executor.isShutdown() || executor.isTerminated()) {
             executors.remove(portKey);
@@ -156,7 +166,7 @@ public class DefaultExecutorRepository implements ExecutorRepository, ExtensionA
             logger.info("Executor for " + url + " is shutdown.");
         }
         if (executor == null) {
-            return SHARED_EXECUTOR;
+            return sharedExecutor;
         } else {
             return executor;
         }
@@ -201,17 +211,16 @@ public class DefaultExecutorRepository implements ExecutorRepository, ExtensionA
     }
 
     @Override
-    public ExecutorService getServiceExportExecutor() {
+    public ScheduledExecutorService getServiceExportExecutor() {
         if (serviceExportExecutor == null) {
             synchronized (LOCK) {
                 if (serviceExportExecutor == null) {
                     int coreSize = getExportThreadNum();
-                    serviceExportExecutor = Executors.newFixedThreadPool(coreSize,
+                    serviceExportExecutor = Executors.newScheduledThreadPool(coreSize,
                         new NamedThreadFactory("Dubbo-service-export", true));
                 }
             }
         }
-
         return serviceExportExecutor;
     }
 
@@ -219,14 +228,13 @@ public class DefaultExecutorRepository implements ExecutorRepository, ExtensionA
     public void shutdownServiceExportExecutor() {
         synchronized (LOCK) {
             if (serviceExportExecutor != null && !serviceExportExecutor.isShutdown()) {
-                try{
+                try {
                     serviceExportExecutor.shutdown();
-                }catch (Throwable ignored){
+                } catch (Throwable ignored) {
                     // ignored
-                    logger.warn(ignored.getMessage(),ignored);
+                    logger.warn(ignored.getMessage(), ignored);
                 }
             }
-
             serviceExportExecutor = null;
         }
     }
@@ -242,7 +250,6 @@ public class DefaultExecutorRepository implements ExecutorRepository, ExtensionA
                 }
             }
         }
-
         return serviceReferExecutor;
     }
 
@@ -250,13 +257,12 @@ public class DefaultExecutorRepository implements ExecutorRepository, ExtensionA
     public void shutdownServiceReferExecutor() {
         synchronized (LOCK) {
             if (serviceReferExecutor != null && !serviceReferExecutor.isShutdown()) {
-                try{
+                try {
                     serviceReferExecutor.shutdown();
-                }catch (Throwable ignored){
-                    logger.warn(ignored.getMessage(),ignored);
+                } catch (Throwable ignored) {
+                    logger.warn(ignored.getMessage(), ignored);
                 }
             }
-
             serviceReferExecutor = null;
         }
     }
@@ -341,7 +347,12 @@ public class DefaultExecutorRepository implements ExecutorRepository, ExtensionA
 
     @Override
     public ExecutorService getSharedExecutor() {
-        return SHARED_EXECUTOR;
+        return sharedExecutor;
+    }
+
+    @Override
+    public ScheduledExecutorService getSharedScheduledExecutor() {
+        return sharedScheduledExecutor;
     }
 
     private ExecutorService createExecutor(URL url) {
@@ -355,20 +366,10 @@ public class DefaultExecutorRepository implements ExecutorRepository, ExtensionA
 
     @Override
     public void destroyAll() {
-        try{
-            poolRouterExecutor.shutdown();
-        }catch (Throwable ignored){
-            // ignored
-            logger.warn(ignored.getMessage(),ignored);
-        }
-//        serviceDiscoveryAddressNotificationExecutor.shutdown();
-//        registryNotificationExecutor.shutdown();
-        try{
-            metadataRetryExecutor.shutdown();
-        }catch (Throwable ignored){
-            // ignored
-            logger.warn(ignored.getMessage(),ignored);
-        }
+        logger.info("destroying executor repository ..");
+        shutdownExecutorService(poolRouterExecutor, "poolRouterExecutor");
+        shutdownExecutorService(metadataRetryExecutor, "metadataRetryExecutor");
+
         shutdownServiceExportExecutor();
         shutdownServiceReferExecutor();
 
@@ -376,32 +377,50 @@ public class DefaultExecutorRepository implements ExecutorRepository, ExtensionA
             if (executors != null) {
                 executors.values().forEach(executor -> {
                     if (executor != null && !executor.isShutdown()) {
-                        try{
+                        try {
                             ExecutorUtil.shutdownNow(executor, 100);
-                        }catch (Throwable ignored){
+                        } catch (Throwable ignored) {
                             // ignored
-                            logger.warn(ignored.getMessage(),ignored);
+                            logger.warn(ignored.getMessage(), ignored);
                         }
                     }
                 });
             }
         });
+        data.clear();
 
-        // shutdown all executor services
-        for (ScheduledExecutorService executorService : scheduledExecutors.listItems()) {
-            try {
-                executorService.shutdown();
-            } catch (Exception e) {
-                logger.warn("shutdown scheduledExecutors failed: " + e.getMessage(), e);
-            }
+        // scheduledExecutors
+        shutdownExecutorServices(scheduledExecutors.listItems(), "scheduledExecutors");
+
+        // executorServiceRing
+        shutdownExecutorServices(executorServiceRing.listItems(), "executorServiceRing");
+
+        // shutdown share executor
+        shutdownExecutorService(sharedExecutor, "sharedExecutor");
+        shutdownExecutorService(sharedScheduledExecutor, "sharedScheduledExecutor");
+
+        // serviceDiscoveryAddressNotificationExecutorRing
+        shutdownExecutorServices(serviceDiscoveryAddressNotificationExecutorRing.listItems(),
+            "serviceDiscoveryAddressNotificationExecutorRing");
+
+        // registryNotificationExecutorRing
+        shutdownExecutorServices(registryNotificationExecutorRing.listItems(),
+            "registryNotificationExecutorRing");
+
+    }
+
+    private void shutdownExecutorServices(List<? extends ExecutorService> executorServices, String msg) {
+        for (ExecutorService executorService : executorServices) {
+            shutdownExecutorService(executorService, msg);
         }
+    }
 
-        for (ExecutorService executorService : executorServiceRing.listItems()) {
-            try {
-                executorService.shutdown();
-            } catch (Exception e) {
-                logger.warn("shutdown executorServiceRing failed: " + e.getMessage(), e);
-            }
+    private void shutdownExecutorService(ExecutorService executorService, String name) {
+        try {
+            executorService.shutdownNow();
+        } catch (Exception e) {
+            String msg = "shutdown executor service [" + name + "] failed: ";
+            logger.warn(msg + e.getMessage(), e);
         }
     }
 
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/threadpool/manager/ExecutorRepository.java b/dubbo-common/src/main/java/org/apache/dubbo/common/threadpool/manager/ExecutorRepository.java
index dabd581..4e78ea8 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/threadpool/manager/ExecutorRepository.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/threadpool/manager/ExecutorRepository.java
@@ -58,7 +58,7 @@ public interface ExecutorRepository {
 
     ExecutorService nextExecutorExecutor();
 
-    ExecutorService getServiceExportExecutor();
+    ScheduledExecutorService getServiceExportExecutor();
 
     /**
      * The executor only used in bootstrap currently, we should call this method to release the resource
@@ -92,6 +92,12 @@ public interface ExecutorRepository {
      */
     ExecutorService getSharedExecutor();
 
+    /**
+     * Get the shared schedule executor
+     * @return
+     */
+    ScheduledExecutorService getSharedScheduledExecutor();
+
     ExecutorService getPoolRouterExecutor();
 
     /**
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/threadpool/support/cached/CachedThreadPool.java b/dubbo-common/src/main/java/org/apache/dubbo/common/threadpool/support/cached/CachedThreadPool.java
index 02d3bb6..eb14fba 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/threadpool/support/cached/CachedThreadPool.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/threadpool/support/cached/CachedThreadPool.java
@@ -47,7 +47,7 @@ public class CachedThreadPool implements ThreadPool {
 
     @Override
     public Executor getExecutor(URL url) {
-        String name = url.getParameter(THREAD_NAME_KEY, DEFAULT_THREAD_NAME);
+        String name = url.getParameter(THREAD_NAME_KEY, (String) url.getAttribute(THREAD_NAME_KEY, DEFAULT_THREAD_NAME));
         int cores = url.getParameter(CORE_THREADS_KEY, DEFAULT_CORE_THREADS);
         int threads = url.getParameter(THREADS_KEY, Integer.MAX_VALUE);
         int queues = url.getParameter(QUEUES_KEY, DEFAULT_QUEUES);
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/threadpool/support/eager/EagerThreadPool.java b/dubbo-common/src/main/java/org/apache/dubbo/common/threadpool/support/eager/EagerThreadPool.java
index 6d12689..8aa8fac 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/threadpool/support/eager/EagerThreadPool.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/threadpool/support/eager/EagerThreadPool.java
@@ -44,7 +44,7 @@ public class EagerThreadPool implements ThreadPool {
 
     @Override
     public Executor getExecutor(URL url) {
-        String name = url.getParameter(THREAD_NAME_KEY, DEFAULT_THREAD_NAME);
+        String name = url.getParameter(THREAD_NAME_KEY, (String) url.getAttribute(THREAD_NAME_KEY, DEFAULT_THREAD_NAME));
         int cores = url.getParameter(CORE_THREADS_KEY, DEFAULT_CORE_THREADS);
         int threads = url.getParameter(THREADS_KEY, Integer.MAX_VALUE);
         int queues = url.getParameter(QUEUES_KEY, DEFAULT_QUEUES);
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/threadpool/support/fixed/FixedThreadPool.java b/dubbo-common/src/main/java/org/apache/dubbo/common/threadpool/support/fixed/FixedThreadPool.java
index bcf12f4..71ee074 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/threadpool/support/fixed/FixedThreadPool.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/threadpool/support/fixed/FixedThreadPool.java
@@ -43,7 +43,7 @@ public class FixedThreadPool implements ThreadPool {
 
     @Override
     public Executor getExecutor(URL url) {
-        String name = url.getParameter(THREAD_NAME_KEY, DEFAULT_THREAD_NAME);
+        String name = url.getParameter(THREAD_NAME_KEY, (String) url.getAttribute(THREAD_NAME_KEY, DEFAULT_THREAD_NAME));
         int threads = url.getParameter(THREADS_KEY, DEFAULT_THREADS);
         int queues = url.getParameter(QUEUES_KEY, DEFAULT_QUEUES);
         return new ThreadPoolExecutor(threads, threads, 0, TimeUnit.MILLISECONDS,
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/threadpool/support/limited/LimitedThreadPool.java b/dubbo-common/src/main/java/org/apache/dubbo/common/threadpool/support/limited/LimitedThreadPool.java
index 87c46d3..25e3003 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/threadpool/support/limited/LimitedThreadPool.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/threadpool/support/limited/LimitedThreadPool.java
@@ -45,7 +45,7 @@ public class LimitedThreadPool implements ThreadPool {
 
     @Override
     public Executor getExecutor(URL url) {
-        String name = url.getParameter(THREAD_NAME_KEY, DEFAULT_THREAD_NAME);
+        String name = url.getParameter(THREAD_NAME_KEY, (String) url.getAttribute(THREAD_NAME_KEY, DEFAULT_THREAD_NAME));
         int cores = url.getParameter(CORE_THREADS_KEY, DEFAULT_CORE_THREADS);
         int threads = url.getParameter(THREADS_KEY, DEFAULT_THREADS);
         int queues = url.getParameter(QUEUES_KEY, DEFAULT_QUEUES);
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/ClassLoaderResourceLoader.java b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/ClassLoaderResourceLoader.java
index ee149c4..be2a561 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/ClassLoaderResourceLoader.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/ClassLoaderResourceLoader.java
@@ -16,6 +16,8 @@
  */
 package org.apache.dubbo.common.utils;
 
+import org.apache.dubbo.common.resource.GlobalResourcesRepository;
+
 import java.io.IOException;
 import java.lang.ref.SoftReference;
 import java.lang.reflect.Field;
@@ -30,25 +32,21 @@ import java.util.Set;
 import java.util.UUID;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.SynchronousQueue;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
 
 public class ClassLoaderResourceLoader {
-    private final static ExecutorService executorService =
-        new ThreadPoolExecutor(0, Integer.MAX_VALUE,
-            60L, TimeUnit.SECONDS,
-            new SynchronousQueue<>(),
-            new NamedThreadFactory("DubboClassLoaderResourceLoader", true));
 
     private static SoftReference<Map<ClassLoader, Map<String, Set<URL>>>> classLoaderResourcesCache = null;
 
+    static {
+        // register resources destroy listener
+        GlobalResourcesRepository.getInstance().registerDisposable(()-> destroy(), true);
+    }
+
     public static Map<ClassLoader, Set<java.net.URL>> loadResources(String fileName, List<ClassLoader> classLoaders) {
         Map<ClassLoader, Set<java.net.URL>> resources = new ConcurrentHashMap<>();
         CountDownLatch countDownLatch = new CountDownLatch(classLoaders.size());
         for (ClassLoader classLoader : classLoaders) {
-            executorService.submit(()->{
+            GlobalResourcesRepository.getGlobalExecutorService().submit(() -> {
                 resources.put(classLoader, loadResources(fileName, classLoader));
                 countDownLatch.countDown();
             });
@@ -99,6 +97,11 @@ public class ClassLoaderResourceLoader {
         return urlCache.get(fileName);
     }
 
+    public static void destroy() {
+        if (classLoaderResourcesCache != null) {
+            classLoaderResourcesCache.clear();
+        }
+    }
 
     private static void setRef(URL url) {
         try {
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/ServiceConfigBase.java b/dubbo-common/src/main/java/org/apache/dubbo/config/ServiceConfigBase.java
index 41d3dc7..0d4ce7f 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/config/ServiceConfigBase.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/config/ServiceConfigBase.java
@@ -185,13 +185,14 @@ public abstract class ServiceConfigBase<T> extends AbstractServiceConfig {
         }
         if (!interfaceClass.isInstance(ref)) {
             throw new IllegalStateException("The class "
-                + ref.getClass().getName() + getClassloaderDesc(ref.getClass()) + " unimplemented interface "
-                + interfaceClass + getClassloaderDesc(interfaceClass) + "!");
+                + getClassDesc(ref.getClass()) + " unimplemented interface "
+                + getClassDesc(interfaceClass) + "!");
         }
     }
 
-    private String getClassloaderDesc(Class clazz) {
-        return "[classloader=" + clazz.getClassLoader().getClass().getName() + "@" + clazz.getClassLoader().hashCode() + "]";
+    private String getClassDesc(Class clazz) {
+        ClassLoader classLoader = clazz.getClassLoader();
+        return clazz.getName() + "[classloader=" + classLoader.getClass().getName() + "@" + classLoader.hashCode() + "]";
     }
 
     public Optional<String> getContextPath(ProtocolConfig protocolConfig) {
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/ApplicationModel.java b/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/ApplicationModel.java
index 1d9f18f..8fd6f0c 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/ApplicationModel.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/ApplicationModel.java
@@ -201,13 +201,20 @@ public class ApplicationModel extends ScopeModel {
     }
 
     @Override
-    public void onDestroy() {
-        // TODO destroy application resources
-        for (ModuleModel moduleModel : new ArrayList<>(moduleModels)) {
-            moduleModel.destroy();
+    protected void onDestroy() {
+
+        if (deployer != null) {
+            deployer.preDestroy();
         }
 
-        notifyDestroy();
+        // destroy application resources
+        for (ModuleModel moduleModel : new ArrayList<>(moduleModels)) {
+            if (moduleModel != internalModule) {
+                moduleModel.destroy();
+            }
+        }
+        // destroy internal module later
+        internalModule.destroy();
 
         if (defaultInstance == this) {
             synchronized (ApplicationModel.class) {
@@ -219,10 +226,12 @@ public class ApplicationModel extends ScopeModel {
         }
 
         if (deployer != null) {
-            deployer.destroy();
-            deployer = null;
+            deployer.postDestroy();
         }
 
+        // destroy other resources (e.g. ZookeeperTransporter )
+        notifyDestroy();
+
         if (environment != null) {
             environment.destroy();
             environment = null;
@@ -235,6 +244,8 @@ public class ApplicationModel extends ScopeModel {
             serviceRepository.destroy();
             serviceRepository = null;
         }
+        // try destroy framework if no any application
+        frameworkModel.tryDestroy();
     }
 
     public FrameworkModel getFrameworkModel() {
@@ -301,12 +312,13 @@ public class ApplicationModel extends ScopeModel {
             if (moduleModel == defaultModule) {
                 defaultModule = findDefaultModule();
             }
-            if (this.moduleModels.size() == 1 && this.moduleModels.get(0) == internalModule) {
-                this.internalModule.destroy();
-            }
-            if (this.moduleModels.isEmpty()) {
-                destroy();
-            }
+        }
+    }
+
+    void tryDestroy() {
+        if (this.moduleModels.isEmpty()
+            || (this.moduleModels.size() == 1 && this.moduleModels.get(0) == internalModule)) {
+            destroy();
         }
     }
 
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/FrameworkModel.java b/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/FrameworkModel.java
index 6ed1917..d979e28 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/FrameworkModel.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/FrameworkModel.java
@@ -21,6 +21,7 @@ import org.apache.dubbo.common.extension.ExtensionLoader;
 import org.apache.dubbo.common.extension.ExtensionScope;
 import org.apache.dubbo.common.logger.Logger;
 import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.common.resource.GlobalResourcesRepository;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -49,7 +50,6 @@ public class FrameworkModel extends ScopeModel {
     private FrameworkServiceRepository serviceRepository;
 
 
-
     public FrameworkModel() {
         super(null, ExtensionScope.FRAMEWORK);
         initialize();
@@ -70,20 +70,38 @@ public class FrameworkModel extends ScopeModel {
     }
 
     @Override
-    public void onDestroy() {
-        //TODO destroy framework model
+    protected void onDestroy() {
+        // destroy all application model
         for (ApplicationModel applicationModel : new ArrayList<>(applicationModels)) {
             applicationModel.destroy();
         }
 
-        allInstances.remove(this);
-        if (defaultInstance == this) {
-            synchronized (FrameworkModel.class) {
+        if (LOGGER.isInfoEnabled()) {
+            LOGGER.info("Dubbo framework[" + getInternalId() + "] is destroying ...");
+        }
+        synchronized (FrameworkModel.class) {
+            allInstances.remove(this);
+            if (defaultInstance == this) {
                 defaultInstance = null;
             }
         }
 
+        // notify destroy and clean framework resources
+        // see org.apache.dubbo.config.deploy.FrameworkModelCleaner
         notifyDestroy();
+
+        if (LOGGER.isInfoEnabled()) {
+            LOGGER.info("Dubbo framework[" + getInternalId() + "] is destroyed");
+        }
+
+        // if all FrameworkModels are destroyed, clean global static resources, shutdown dubbo completely
+        if (allInstances.isEmpty()) {
+            destroyGlobalResources();
+        }
+    }
+
+    private void destroyGlobalResources() {
+        GlobalResourcesRepository.getInstance().destroy();
     }
 
     public static FrameworkModel defaultModel() {
@@ -120,6 +138,9 @@ public class FrameworkModel extends ScopeModel {
 
     synchronized void removeApplication(ApplicationModel model) {
         this.applicationModels.remove(model);
+    }
+
+    synchronized void tryDestroy() {
         if (applicationModels.size() == 0) {
             destroy();
         }
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/ModuleModel.java b/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/ModuleModel.java
index e11c1b5..936261e 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/ModuleModel.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/ModuleModel.java
@@ -81,14 +81,20 @@ public class ModuleModel extends ScopeModel {
     }
 
     @Override
-    public void onDestroy() {
-        notifyDestroy();
+    protected void onDestroy() {
+        if (deployer != null) {
+            deployer.preDestroy();
+        }
+
         applicationModel.removeModule(this);
 
         if (deployer != null) {
-            deployer.destroy();
-            deployer = null;
+            deployer.postDestroy();
         }
+
+        // destroy other resources
+        notifyDestroy();
+
         if (serviceRepository != null) {
             serviceRepository.destroy();
             serviceRepository = null;
@@ -98,6 +104,7 @@ public class ModuleModel extends ScopeModel {
             moduleEnvironment.destroy();
             moduleEnvironment = null;
         }
+        applicationModel.tryDestroy();
     }
 
     public ApplicationModel getApplicationModel() {
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/ScopeModel.java b/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/ScopeModel.java
index 1cc4712..3e4cd9e 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/ScopeModel.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/ScopeModel.java
@@ -69,7 +69,6 @@ public abstract class ScopeModel implements ExtensionAccessor {
 
     private Map<String, Object> attributes;
     private AtomicBoolean destroyed = new AtomicBoolean(false);
-    private volatile boolean stopping;
 
     public ScopeModel(ScopeModel parent, ExtensionScope scope) {
         this.parent = parent;
@@ -92,7 +91,6 @@ public abstract class ScopeModel implements ExtensionAccessor {
         this.destroyListeners = new LinkedList<>();
         this.attributes = new ConcurrentHashMap<>();
         this.classLoaders = new ConcurrentHashSet<>();
-        this.stopping = false;
 
         // Add Framework's ClassLoader by default
         ClassLoader dubboClassLoader = ScopeModel.class.getClassLoader();
@@ -119,21 +117,13 @@ public abstract class ScopeModel implements ExtensionAccessor {
         return destroyed.get();
     }
 
-    public void setStopping() {
-        stopping = true;
-    }
-
-    public boolean isStopping() {
-        return stopping;
-    }
-
     protected void notifyDestroy() {
         for (ScopeModelDestroyListener destroyListener : destroyListeners) {
             destroyListener.onDestroy(this);
         }
     }
 
-    public abstract void onDestroy();
+    protected abstract void onDestroy();
 
     public final void addDestroyListener(ScopeModelDestroyListener listener) {
         destroyListeners.add(listener);
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/ScopeModelDestroyListener.java b/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/ScopeModelDestroyListener.java
index 93f6e84..5b73ede 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/ScopeModelDestroyListener.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/ScopeModelDestroyListener.java
@@ -16,6 +16,6 @@
  */
 package org.apache.dubbo.rpc.model;
 
-public interface ScopeModelDestroyListener {
-    void onDestroy(ScopeModel scopeModel);
+public interface ScopeModelDestroyListener<T extends ScopeModel> {
+    void onDestroy(T scopeModel);
 }
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/common/config/configcenter/file/FileSystemDynamicConfigurationTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/config/configcenter/file/FileSystemDynamicConfigurationTest.java
index 20e127a..97f55d2 100644
--- a/dubbo-common/src/test/java/org/apache/dubbo/common/config/configcenter/file/FileSystemDynamicConfigurationTest.java
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/config/configcenter/file/FileSystemDynamicConfigurationTest.java
@@ -16,23 +16,22 @@
  */
 package org.apache.dubbo.common.config.configcenter.file;
 
+import org.apache.commons.io.FileUtils;
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.logger.Logger;
 import org.apache.dubbo.common.logger.LoggerFactory;
-
-import org.apache.commons.io.FileUtils;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable;
 
 import java.io.File;
+import java.io.IOException;
 import java.util.TreeSet;
 import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 import static java.util.Arrays.asList;
-import static org.apache.commons.io.FileUtils.deleteQuietly;
 import static org.apache.dubbo.common.URL.valueOf;
 import static org.apache.dubbo.common.config.configcenter.DynamicConfiguration.DEFAULT_GROUP;
 import static org.apache.dubbo.common.config.configcenter.file.FileSystemDynamicConfiguration.CONFIG_CENTER_DIR_PARAM_NAME;
@@ -60,13 +59,18 @@ public class FileSystemDynamicConfigurationTest {
     public void init() {
         File rootDirectory = new File(getClassPath(), "config-center");
         rootDirectory.mkdirs();
+        try {
+            FileUtils.cleanDirectory(rootDirectory);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
         URL url = valueOf("dubbo://127.0.0.1:20880").addParameter(CONFIG_CENTER_DIR_PARAM_NAME, rootDirectory.getAbsolutePath());
         configuration = new FileSystemDynamicConfiguration(url);
     }
 
     @AfterEach
     public void destroy() throws Exception {
-        deleteQuietly(configuration.getRootDirectory());
+        FileUtils.deleteQuietly(configuration.getRootDirectory());
         configuration.close();
     }
 
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/common/threadpool/manager/ExecutorRepositoryTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/threadpool/manager/ExecutorRepositoryTest.java
index 82202da..33ab9fb 100644
--- a/dubbo-common/src/test/java/org/apache/dubbo/common/threadpool/manager/ExecutorRepositoryTest.java
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/threadpool/manager/ExecutorRepositoryTest.java
@@ -23,16 +23,12 @@ import org.junit.jupiter.api.Test;
 
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 public class ExecutorRepositoryTest {
     private ExecutorRepository executorRepository = ApplicationModel.defaultModel().getExtensionLoader(ExecutorRepository.class).getDefaultExtension();
 
     @Test
-    public void test() {
-        testGetExecutor();
-        testUpdateExecutor();
-    }
-
     public void testGetExecutor() {
         testGet(URL.valueOf("dubbo://127.0.0.1:23456"));
         testGet(URL.valueOf("dubbo://127.0.0.1:23456?side=consumer"));
@@ -54,6 +50,7 @@ public class ExecutorRepositoryTest {
         Assertions.assertNotEquals(executorService, executorRepository.getExecutor(url));
     }
 
+    @Test
     public void testUpdateExecutor() {
         URL url = URL.valueOf("dubbo://127.0.0.1:23456?threads=5");
         ThreadPoolExecutor executorService = (ThreadPoolExecutor) executorRepository.createExecutorIfAbsent(url);
@@ -75,7 +72,57 @@ public class ExecutorRepositoryTest {
 
         executorService.setCorePoolSize(5);
         executorRepository.updateThreadpool(url, executorService);
+    }
 
+    @Test
+    public void testSharedExecutor() throws Exception {
+        ExecutorService sharedExecutor = executorRepository.getSharedExecutor();
+        MockTask task1 = new MockTask(2000);
+        MockTask task2 = new MockTask(100);
+        MockTask task3 = new MockTask(200);
+        sharedExecutor.execute(task1);
+        sharedExecutor.execute(task2);
+        sharedExecutor.submit(task3);
+
+        Thread.sleep(150);
+        Assertions.assertTrue(task1.isRunning());
+        Assertions.assertFalse(task1.isDone());
+        Assertions.assertTrue(task2.isRunning());
+        Assertions.assertTrue(task2.isDone());
+        Assertions.assertTrue(task3.isRunning());
+        Assertions.assertFalse(task3.isDone());
+
+        Thread.sleep(200);
+        Assertions.assertTrue(task3.isDone());
+        Assertions.assertFalse(task1.isDone());
+    }
 
+    private static class MockTask implements Runnable {
+        private long waitTimeMS;
+        private AtomicBoolean running = new AtomicBoolean();
+        private AtomicBoolean done = new AtomicBoolean();
+
+        public MockTask(long waitTimeMS) {
+            this.waitTimeMS = waitTimeMS;
+        }
+
+        @Override
+        public void run() {
+            running.set(true);
+            try {
+                Thread.sleep(waitTimeMS);
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+            done.set(true);
+        }
+
+        public boolean isDone() {
+            return done.get();
+        }
+
+        public boolean isRunning() {
+            return running.get();
+        }
     }
 }
diff --git a/dubbo-compatible/pom.xml b/dubbo-compatible/pom.xml
index d12ddd9..8da57cc 100644
--- a/dubbo-compatible/pom.xml
+++ b/dubbo-compatible/pom.xml
@@ -98,5 +98,11 @@
             <artifactId>fastjson</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-test-check</artifactId>
+            <version>${project.parent.version}</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 </project>
diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ConfigScopeModelInitializer.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ConfigScopeModelInitializer.java
index 8639cc2..955da26 100644
--- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ConfigScopeModelInitializer.java
+++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ConfigScopeModelInitializer.java
@@ -21,6 +21,7 @@ import org.apache.dubbo.common.deploy.ApplicationDeployer;
 import org.apache.dubbo.common.deploy.ModuleDeployer;
 import org.apache.dubbo.config.deploy.DefaultApplicationDeployer;
 import org.apache.dubbo.config.deploy.DefaultModuleDeployer;
+import org.apache.dubbo.config.deploy.FrameworkModelCleaner;
 import org.apache.dubbo.config.utils.DefaultConfigValidator;
 import org.apache.dubbo.rpc.model.ApplicationModel;
 import org.apache.dubbo.rpc.model.FrameworkModel;
@@ -31,7 +32,7 @@ public class ConfigScopeModelInitializer implements ScopeModelInitializer {
 
     @Override
     public void initializeFrameworkModel(FrameworkModel frameworkModel) {
-
+        frameworkModel.addDestroyListener(new FrameworkModelCleaner());
     }
 
     @Override
diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/DubboShutdownHook.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/DubboShutdownHook.java
index 2ecba74..9fa94b2 100644
--- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/DubboShutdownHook.java
+++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/DubboShutdownHook.java
@@ -87,8 +87,8 @@ public class DubboShutdownHook extends Thread {
      */
     public void unregister() {
         if (registered.compareAndSet(true, false)) {
-            if (this.isAlive() || destroyed.get()) {
-                // DubboShutdownHook is running
+            if (this.isAlive()) {
+                // DubboShutdownHook thread is running
                 return;
             }
             try {
diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ServiceConfig.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ServiceConfig.java
index be1d486..663836a 100644
--- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ServiceConfig.java
+++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ServiceConfig.java
@@ -23,11 +23,11 @@ import org.apache.dubbo.common.deploy.ModuleDeployer;
 import org.apache.dubbo.common.extension.ExtensionLoader;
 import org.apache.dubbo.common.logger.Logger;
 import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.common.threadpool.manager.ExecutorRepository;
 import org.apache.dubbo.common.url.component.ServiceConfigURL;
 import org.apache.dubbo.common.utils.ClassUtils;
 import org.apache.dubbo.common.utils.CollectionUtils;
 import org.apache.dubbo.common.utils.ConfigUtils;
-import org.apache.dubbo.common.utils.NamedThreadFactory;
 import org.apache.dubbo.common.utils.StringUtils;
 import org.apache.dubbo.config.annotation.Service;
 import org.apache.dubbo.config.invoker.DelegateProviderMetaDataInvoker;
@@ -57,8 +57,6 @@ import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.UUID;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 
@@ -108,11 +106,6 @@ public class ServiceConfig<T> extends ServiceConfigBase<T> {
      */
     private static final Map<String, Integer> RANDOM_PORT_MAP = new HashMap<String, Integer>();
 
-    /**
-     * A delayed exposure service timer
-     */
-    private static final ScheduledExecutorService DELAY_EXPORT_EXECUTOR = Executors.newSingleThreadScheduledExecutor(new NamedThreadFactory("DubboServiceDelayExporter", true));
-
     private Protocol protocolSPI;
 
     /**
@@ -259,13 +252,14 @@ public class ServiceConfig<T> extends ServiceConfigBase<T> {
     }
 
     protected void doDelayExport() {
-        DELAY_EXPORT_EXECUTOR.schedule(() -> {
-            try {
-                doExport();
-            } catch (Exception e) {
-                logger.error("Failed to export service config: " + interfaceName, e);
-            }
-        }, getDelay(), TimeUnit.MILLISECONDS);
+        getScopeModel().getDefaultExtension(ExecutorRepository.class).getServiceExportExecutor()
+            .schedule(() -> {
+                try {
+                    doExport();
+                } catch (Exception e) {
+                    logger.error("Failed to export service config: " + interfaceName, e);
+                }
+            }, getDelay(), TimeUnit.MILLISECONDS);
     }
 
     protected void exported() {
diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/bootstrap/DubboBootstrap.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/bootstrap/DubboBootstrap.java
index 4477b41..201c7d3 100644
--- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/bootstrap/DubboBootstrap.java
+++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/bootstrap/DubboBootstrap.java
@@ -46,16 +46,14 @@ import org.apache.dubbo.config.bootstrap.builders.ReferenceBuilder;
 import org.apache.dubbo.config.bootstrap.builders.RegistryBuilder;
 import org.apache.dubbo.config.bootstrap.builders.ServiceBuilder;
 import org.apache.dubbo.config.context.ConfigManager;
-import org.apache.dubbo.metadata.report.support.AbstractMetadataReportFactory;
-import org.apache.dubbo.rpc.Protocol;
 import org.apache.dubbo.rpc.model.ApplicationModel;
 import org.apache.dubbo.rpc.model.FrameworkModel;
 import org.apache.dubbo.rpc.model.ModuleModel;
 
 import java.util.List;
 import java.util.Map;
-import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Future;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.locks.Condition;
 import java.util.concurrent.locks.Lock;
@@ -127,10 +125,6 @@ public final class DubboBootstrap {
         return getInstance(frameworkModel.newApplication());
     }
 
-    public static DubboBootstrap newInstance(ApplicationModel applicationModel) {
-        return getInstance(applicationModel);
-    }
-
     /**
      * Try reset dubbo status for new instance.
      *
@@ -153,8 +147,6 @@ public final class DubboBootstrap {
                 instance.destroy();
                 instance = null;
             }
-            AbstractMetadataReportFactory.destroy();
-            destroyAllProtocols();
             FrameworkModel.destroyAll();
         } else {
             instance = null;
@@ -227,7 +219,7 @@ public final class DubboBootstrap {
      * @return
      */
     public DubboBootstrap start(boolean wait) {
-        CompletableFuture future = applicationDeployer.start();
+        Future future = applicationDeployer.start();
         if (wait) {
             try {
                 future.get();
@@ -239,6 +231,14 @@ public final class DubboBootstrap {
     }
 
     /**
+     * Start dubbo application but no wait for finish.
+     * @return the future object
+     */
+    public Future asyncStart() {
+        return applicationDeployer.start();
+    }
+
+    /**
      * Stop dubbo application
      * @return
      * @throws IllegalStateException
@@ -249,7 +249,7 @@ public final class DubboBootstrap {
     }
 
     public void destroy() {
-        applicationDeployer.destroy();
+        applicationModel.destroy();
     }
 
     public boolean isInitialized() {
@@ -330,30 +330,6 @@ public final class DubboBootstrap {
         return applicationDeployer.getReferenceCache();
     }
 
-    /**
-     * Destroy all the protocols.
-     */
-    private static void destroyProtocols(FrameworkModel frameworkModel) {
-        //TODO destroy protocol in framework scope
-        ExtensionLoader<Protocol> loader = frameworkModel.getExtensionLoader(Protocol.class);
-        for (String protocolName : loader.getLoadedExtensions()) {
-            try {
-                Protocol protocol = loader.getLoadedExtension(protocolName);
-                if (protocol != null) {
-                    protocol.destroy();
-                }
-            } catch (Throwable t) {
-                logger.warn(t.getMessage(), t);
-            }
-        }
-    }
-
-    private static void destroyAllProtocols() {
-        for (FrameworkModel frameworkModel : FrameworkModel.getAllInstances()) {
-            destroyProtocols(frameworkModel);
-        }
-    }
-
     private void executeMutually(Runnable runnable) {
         try {
             lock.lock();
diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/deploy/DefaultApplicationDeployer.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/deploy/DefaultApplicationDeployer.java
index 61ac94a..81c718f 100644
--- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/deploy/DefaultApplicationDeployer.java
+++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/deploy/DefaultApplicationDeployer.java
@@ -26,6 +26,7 @@ import org.apache.dubbo.common.config.configcenter.wrapper.CompositeDynamicConfi
 import org.apache.dubbo.common.deploy.AbstractDeployer;
 import org.apache.dubbo.common.deploy.ApplicationDeployListener;
 import org.apache.dubbo.common.deploy.ApplicationDeployer;
+import org.apache.dubbo.common.deploy.DeployState;
 import org.apache.dubbo.common.deploy.ModuleDeployer;
 import org.apache.dubbo.common.extension.ExtensionLoader;
 import org.apache.dubbo.common.lang.ShutdownHookCallbacks;
@@ -48,16 +49,13 @@ import org.apache.dubbo.metadata.MetadataServiceExporter;
 import org.apache.dubbo.metadata.WritableMetadataService;
 import org.apache.dubbo.metadata.report.MetadataReportFactory;
 import org.apache.dubbo.metadata.report.MetadataReportInstance;
-import org.apache.dubbo.metadata.report.support.AbstractMetadataReportFactory;
 import org.apache.dubbo.registry.client.DefaultServiceInstance;
 import org.apache.dubbo.registry.client.ServiceInstance;
 import org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils;
 import org.apache.dubbo.registry.client.metadata.store.InMemoryWritableMetadataService;
 import org.apache.dubbo.registry.client.metadata.store.RemoteMetadataServiceImpl;
 import org.apache.dubbo.registry.support.RegistryManager;
-import org.apache.dubbo.rpc.Protocol;
 import org.apache.dubbo.rpc.model.ApplicationModel;
-import org.apache.dubbo.rpc.model.FrameworkModel;
 import org.apache.dubbo.rpc.model.ModuleModel;
 import org.apache.dubbo.rpc.model.ScopeModel;
 import org.apache.dubbo.rpc.model.ScopeModelUtil;
@@ -70,6 +68,7 @@ import java.util.List;
 import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Future;
 import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -115,8 +114,9 @@ public class DefaultApplicationDeployer extends AbstractDeployer<ApplicationMode
 
     private ScheduledFuture<?> asyncMetadataFuture;
     private String identifier;
-    private CompletableFuture startFuture;
+    private volatile CompletableFuture startFuture;
     private DubboShutdownHook dubboShutdownHook;
+    private Object startedLock = new Object();
 
     public DefaultApplicationDeployer(ApplicationModel applicationModel) {
         super(applicationModel);
@@ -512,25 +512,26 @@ public class DefaultApplicationDeployer extends AbstractDeployer<ApplicationMode
      * @return
      */
     @Override
-    public synchronized CompletableFuture start() {
+    public synchronized Future start() {
+        CompletableFuture startFuture = getStartFuture();
+
+        // maybe call start again after add new module, check if any new module
+        boolean hasPendingModule = hasPendingModule();
+
         if (isStarting()) {
+            // currently is starting, maybe both start by module and application
+            // if has new modules, start them
+            if (hasPendingModule) {
+                startModules();
+            }
+            // if is starting, reuse previous startFuture
             return startFuture;
         }
-        startFuture = new CompletableFuture();
-        if (isStarted()) {
-            // maybe call start again after add new module, check if any new module
-            boolean hasNewModule = false;
-            for (ModuleModel moduleModel : applicationModel.getModuleModels()) {
-                if (moduleModel.getDeployer().isPending()) {
-                    hasNewModule = true;
-                    break;
-                }
-            }
-            // if no new module, just return
-            if (!hasNewModule) {
-                startFuture.complete(false);
-                return startFuture;
-            }
+
+        // if is started and no new module, just return
+        if (isStarted() && !hasPendingModule) {
+            completeStartFuture(false);
+            return startFuture;
         }
 
         onStarting();
@@ -542,35 +543,69 @@ public class DefaultApplicationDeployer extends AbstractDeployer<ApplicationMode
         return startFuture;
     }
 
-    private void doStart() {
-        // copy current modules, ignore new module during starting
-        List<ModuleModel> moduleModels = new ArrayList<>(applicationModel.getModuleModels());
-        List<CompletableFuture> futures = new ArrayList<>(moduleModels.size());
-
-        for (ModuleModel moduleModel : moduleModels) {
-            // export services in module
+    private boolean hasPendingModule() {
+        boolean found = false;
+        for (ModuleModel moduleModel : applicationModel.getModuleModels()) {
             if (moduleModel.getDeployer().isPending()) {
-                CompletableFuture moduleFuture = moduleModel.getDeployer().start();
-                futures.add(moduleFuture);
+                found = true;
+                break;
+            }
+        }
+        return found;
+    }
+
+    private CompletableFuture getStartFuture() {
+        if (startFuture == null) {
+            synchronized (this) {
+                if (startFuture == null) {
+                    startFuture = new CompletableFuture();
+                }
             }
         }
+        return startFuture;
+    }
+
+
+    private void doStart() {
+        startModules();
 
         // prepare application instance
         prepareApplicationInstance();
 
-        // notify on each module started
-//        executorRepository.getSharedExecutor().submit(()-> {
-//            awaitDeployFinished(futures);
-//            onStarted();
-//        });
+        executorRepository.getSharedExecutor().submit(() -> {
+            while (true) {
+                // notify on each module started
+                synchronized (startedLock) {
+                    try {
+                        startedLock.wait(500);
+                    } catch (InterruptedException e) {
+                        // ignore
+                    }
+                }
+
+                // if has new module, do start again
+                if (hasPendingModule()) {
+                    startModules();
+                    continue;
+                }
+
+                DeployState newState = checkState();
+                if (!(newState == DeployState.STARTING || newState == DeployState.PENDING)) {
+                    // start finished or error
+                    break;
+                }
+            }
+        });
     }
 
-    private void awaitDeployFinished(List<CompletableFuture> futures) {
-        try {
-            CompletableFuture mergedFuture = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
-            mergedFuture.get();
-        } catch (Exception e) {
-            logger.error(getIdentifier() + " await deploy finished failed", e);
+    private void startModules() {
+        // copy current modules, ignore new module during starting
+        List<ModuleModel> moduleModels = new ArrayList<>(applicationModel.getModuleModels());
+        for (ModuleModel moduleModel : moduleModels) {
+            // export services in module
+            if (moduleModel.getDeployer().isPending()) {
+                moduleModel.getDeployer().start();
+            }
         }
     }
 
@@ -595,8 +630,14 @@ public class DefaultApplicationDeployer extends AbstractDeployer<ApplicationMode
         exportMetadataService();
         // start internal module
         ModuleDeployer internalModuleDeployer = applicationModel.getInternalModule().getDeployer();
-        if (!internalModuleDeployer.isRunning()) {
-            internalModuleDeployer.start();
+        if (!internalModuleDeployer.isStarted()) {
+            Future future = internalModuleDeployer.start();
+            // wait for internal module start finished
+            try {
+                future.get();
+            } catch (Exception e) {
+                logger.warn("wait for internal module started failed: " + e.getMessage(), e);
+            }
         }
     }
 
@@ -707,15 +748,23 @@ public class DefaultApplicationDeployer extends AbstractDeployer<ApplicationMode
         }
         if (registered) {
             // scheduled task for updating Metadata and ServiceInstance
-            asyncMetadataFuture = executorRepository.nextScheduledExecutor().scheduleAtFixedRate(() -> {
-                InMemoryWritableMetadataService localMetadataService = (InMemoryWritableMetadataService) WritableMetadataService.getDefaultExtension(applicationModel);
-                if (!applicationModel.getDeployer().isStopping() || !applicationModel.getDeployer().isStopped()) {
-                    localMetadataService.blockUntilUpdated();
+            asyncMetadataFuture = executorRepository.getSharedScheduledExecutor().scheduleAtFixedRate(() -> {
+
+                // ignore refresh metadata on stopping
+                if (applicationModel.isDestroyed()) {
+                    return;
                 }
+
+                InMemoryWritableMetadataService localMetadataService = (InMemoryWritableMetadataService) WritableMetadataService.getDefaultExtension(applicationModel);
+                localMetadataService.blockUntilUpdated();
                 try {
-                    ServiceInstanceMetadataUtils.refreshMetadataAndInstance(serviceInstance);
+                    if (!applicationModel.isDestroyed()) {
+                        ServiceInstanceMetadataUtils.refreshMetadataAndInstance(serviceInstance);
+                    }
                 } catch (Exception e) {
-                    logger.error("Refresh instance and metadata error", e);
+                    if (!applicationModel.isDestroyed()) {
+                        logger.error("Refresh instance and metadata error", e);
+                    }
                 } finally {
                     localMetadataService.releaseBlock();
                 }
@@ -780,36 +829,45 @@ public class DefaultApplicationDeployer extends AbstractDeployer<ApplicationMode
 
     @Override
     public void stop() {
-        destroy();
+        applicationModel.destroy();
     }
 
     @Override
-    public synchronized void destroy() {
+    public void preDestroy() {
         if (isStopping() || isStopped()) {
             return;
         }
-        try {
-            onStopping();
-            unRegisterShutdownHook();
-            unregisterServiceInstance();
-            unexportMetadataService();
-            if (asyncMetadataFuture != null) {
-                asyncMetadataFuture.cancel(true);
-            }
+        onStopping();
 
-            executeShutdownCallbacks();
+        unRegisterShutdownHook();
+        if (asyncMetadataFuture != null) {
+            asyncMetadataFuture.cancel(true);
+        }
+        unregisterServiceInstance();
+        unexportMetadataService();
 
-            applicationModel.destroy();
+    }
 
-            destroyProtocols();
+    @Override
+    public synchronized void postDestroy() {
+        // expect application model is destroyed before here
+        if (isStopped()) {
+            return;
+        }
+        try {
+            executeShutdownCallbacks();
 
             destroyRegistries();
             destroyServiceDiscoveries();
             destroyMetadataReports();
 
-            destroyServiceDiscoveries();
+            // TODO should we close unused protocol server which only used by this application?
+            // protocol server will be closed on all applications of same framework are stopped currently, but no associate to application
+            // see org.apache.dubbo.config.deploy.FrameworkModelCleaner#destroyProtocols
+            // see org.apache.dubbo.config.bootstrap.DubboBootstrapMultiInstanceTest#testMultiProviderApplicationStopOneByOne
+
+            // destroy all executor services
             destroyExecutorRepository();
-            destroyDynamicConfigurations();
 
             onStopped();
         } catch (Throwable ex) {
@@ -828,44 +886,129 @@ public class DefaultApplicationDeployer extends AbstractDeployer<ApplicationMode
         if (isStarting()) {
             return;
         }
-        onStarting();
+        for (ModuleModel moduleModel : applicationModel.getModuleModels()) {
+            if (moduleModel.getDeployer().isStarting()) {
+                onStarting();
+                break;
+            }
+        }
     }
 
     @Override
-    public void checkStarted(CompletableFuture checkerStartFuture) {
-        for (ModuleModel moduleModel : applicationModel.getModuleModels()) {
-            if (moduleModel.getDeployer().isPending()) {
+    public void checkStarted() {
+        // TODO improve newState checking
+        DeployState newState = checkState();
+        switch (newState) {
+            case STARTED:
+                onStarted();
+                break;
+            case STARTING:
+                onStarting();
+                break;
+            case PENDING:
                 setPending();
-            } else if (moduleModel.getDeployer().isStarting()) {
-                return;
+                break;
+        }
+
+        // notify started
+        synchronized (startedLock) {
+            startedLock.notifyAll();
+        }
+    }
+
+    private DeployState checkState() {
+        DeployState newState = DeployState.UNKNOWN;
+        int pending = 0, starting = 0, started = 0, stopping = 0, stopped = 0;
+        for (ModuleModel moduleModel : applicationModel.getModuleModels()) {
+            ModuleDeployer deployer = moduleModel.getDeployer();
+            if (deployer.isPending()) {
+                pending++;
+            } else if (deployer.isStarting()) {
+                starting++;
+            } else if (deployer.isStarted()) {
+                started++;
+            } else if (deployer.isStopping()) {
+                stopping++;
+            } else if (deployer.isStopped()) {
+                stopped++;
             }
         }
-        // all modules has been started
-        onStarted(checkerStartFuture);
+
+        if (started > 0) {
+            if (pending + starting + stopping + stopped == 0) {
+                // all modules have been started
+                newState = DeployState.STARTED;
+            } else if (pending + starting > 0) {
+                // some module is pending and some is started
+                newState = DeployState.STARTING;
+            } else if (stopping + stopped > 0) {
+                newState = DeployState.STOPPING;
+            }
+        } else if (starting > 0) {
+            // any module is starting
+            newState = DeployState.STARTING;
+        } else if (pending > 0) {
+            if (starting + starting + stopping + stopped == 0) {
+                // all modules have not starting or started
+                newState = DeployState.PENDING;
+            } else if (stopping + stopped > 0) {
+                // some is pending and some is stopping or stopped
+                newState = DeployState.STOPPING;
+            }
+        } else if (stopping > 0) {
+            // some is stopping and some stopped
+            newState = DeployState.STOPPING;
+        } else if (stopped > 0) {
+            // all modules are stopped
+            newState = DeployState.STOPPED;
+        }
+        return newState;
     }
 
     private void onStarting() {
+        if (isStarting()) {
+            return;
+        }
         setStarting();
         if (logger.isInfoEnabled()) {
             logger.info(getIdentifier() + " is starting.");
         }
     }
 
-    private void onStarted(CompletableFuture checkerStartFuture) {
+    private void onStarted() {
+        if (isStarted()) {
+            return;
+        }
         setStarted();
         if (logger.isInfoEnabled()) {
             logger.info(getIdentifier() + " is ready.");
         }
-        if (startFuture != null) {
-            startFuture.complete(true);
+        // refresh metadata
+        try {
+            if (serviceInstance != null) {
+                ServiceInstanceMetadataUtils.refreshMetadataAndInstance(serviceInstance);
+            }
+        } catch (Exception e) {
+            logger.error("refresh metadata failed: " + e.getMessage(), e);
         }
-        if (checkerStartFuture != null) {
-            checkerStartFuture.complete(true);
+        // complete future
+        completeStartFuture(true);
+        // shutdown export/refer executor after started
+        executorRepository.shutdownServiceExportExecutor();
+        executorRepository.shutdownServiceReferExecutor();
+    }
+
+    private void completeStartFuture(boolean success) {
+        if (startFuture != null) {
+            startFuture.complete(success);
+            startFuture = null;
         }
     }
 
     private void onStopping() {
-        applicationModel.setStopping();
+        if (isStopping()) {
+            return;
+        }
         setStopping();
         if (logger.isInfoEnabled()) {
             logger.info(getIdentifier() + " is stopping.");
@@ -873,6 +1016,9 @@ public class DefaultApplicationDeployer extends AbstractDeployer<ApplicationMode
     }
 
     private void onStopped() {
+        if (isStopped()) {
+            return;
+        }
         setStopped();
         if (logger.isInfoEnabled()) {
             logger.info(getIdentifier() + " has stopped.");
@@ -888,27 +1034,6 @@ public class DefaultApplicationDeployer extends AbstractDeployer<ApplicationMode
         RegistryManager.getInstance(applicationModel).destroyAll();
     }
 
-    /**
-     * Destroy all the protocols.
-     */
-    private void destroyProtocols() {
-        FrameworkModel frameworkModel = applicationModel.getFrameworkModel();
-        if (frameworkModel.getApplicationModels().isEmpty()) {
-            //TODO destroy protocol in framework scope
-            ExtensionLoader<Protocol> loader = frameworkModel.getExtensionLoader(Protocol.class);
-            for (String protocolName : loader.getLoadedExtensions()) {
-                try {
-                    Protocol protocol = loader.getLoadedExtension(protocolName);
-                    if (protocol != null) {
-                        protocol.destroy();
-                    }
-                } catch (Throwable t) {
-                    logger.warn(t.getMessage(), t);
-                }
-            }
-        }
-    }
-
     private void destroyServiceDiscoveries() {
         RegistryManager.getInstance(applicationModel).getServiceDiscoveries().forEach(serviceDiscovery -> {
             try {
@@ -923,15 +1048,11 @@ public class DefaultApplicationDeployer extends AbstractDeployer<ApplicationMode
     }
 
     private void destroyMetadataReports() {
-        // TODO only destroy MetadataReport of this application
-        AbstractMetadataReportFactory.destroy();
-    }
-
-    private void destroyDynamicConfigurations() {
-        // TODO only destroy DynamicConfiguration of this application
-        // DynamicConfiguration may be cached somewhere, and maybe used during destroy
-        // destroy them may cause some troubles, so just clear instances cache
-        // ExtensionLoader.resetExtensionLoader(DynamicConfigurationFactory.class);
+        // only destroy MetadataReport of this application
+        List<MetadataReportFactory> metadataReportFactories = getExtensionLoader(MetadataReportFactory.class).getLoadedExtensionInstances();
+        for (MetadataReportFactory metadataReportFactory : metadataReportFactories) {
+            metadataReportFactory.destroy();
+        }
     }
 
     private ApplicationConfig getApplication() {
@@ -940,10 +1061,10 @@ public class DefaultApplicationDeployer extends AbstractDeployer<ApplicationMode
 
     private String getIdentifier() {
         if (identifier == null) {
-            if (applicationModel.getModelName() != null && !StringUtils.isEquals(applicationModel.getModelName(), applicationModel.getInternalName())) {
-                identifier = applicationModel.getModelName() + "[" + applicationModel.getInternalId() + "]";
-            } else {
-                identifier = "Dubbo Application" + "[" + applicationModel.getInternalId() + "]";
+            identifier = "Dubbo application[" + applicationModel.getInternalId() + "]";
+            if (applicationModel.getModelName() != null
+                && !StringUtils.isEquals(applicationModel.getModelName(), applicationModel.getInternalName())) {
+                identifier += "(" + applicationModel.getModelName() + ")";
             }
         }
         return identifier;
diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/deploy/DefaultModuleDeployer.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/deploy/DefaultModuleDeployer.java
index c8b8bac..530c1ba 100644
--- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/deploy/DefaultModuleDeployer.java
+++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/deploy/DefaultModuleDeployer.java
@@ -43,6 +43,7 @@ import java.util.List;
 import java.util.Set;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
 
 /**
  * Export/refer services of module
@@ -120,7 +121,7 @@ public class DefaultModuleDeployer extends AbstractDeployer<ModuleModel> impleme
     }
 
     @Override
-    public synchronized CompletableFuture start() throws IllegalStateException {
+    public synchronized Future start() throws IllegalStateException {
         if (isStarting() || isStarted()) {
             return startFuture;
         }
@@ -164,15 +165,22 @@ public class DefaultModuleDeployer extends AbstractDeployer<ModuleModel> impleme
 
     @Override
     public void stop() throws IllegalStateException {
-        destroy();
+        moduleModel.destroy();
     }
 
     @Override
-    public synchronized void destroy() throws IllegalStateException {
+    public void preDestroy() throws IllegalStateException {
         if (isStopping() || isStopped()) {
             return;
         }
         onModuleStopping();
+    }
+
+    @Override
+    public synchronized void postDestroy() throws IllegalStateException {
+        if (isStopped()) {
+            return;
+        }
         unexportServices();
         unreferServices();
 
@@ -206,7 +214,6 @@ public class DefaultModuleDeployer extends AbstractDeployer<ModuleModel> impleme
             }
             serviceRepository.destroy();
         }
-        moduleModel.destroy();
         onModuleStopped();
     }
 
@@ -219,7 +226,9 @@ public class DefaultModuleDeployer extends AbstractDeployer<ModuleModel> impleme
     private void onModuleStarted(CompletableFuture startFuture) {
         setStarted();
         logger.info(getIdentifier() + " has started.");
-        applicationDeployer.checkStarted(startFuture);
+        applicationDeployer.checkStarted();
+        // complete module start future after application state changed, fix #9012 ?
+        startFuture.complete(true);
     }
 
     private void onModuleStopping() {
@@ -345,7 +354,6 @@ public class DefaultModuleDeployer extends AbstractDeployer<ModuleModel> impleme
         } catch (Exception e) {
             logger.warn(getIdentifier() + " export services occurred an exception.");
         } finally {
-            executorRepository.shutdownServiceExportExecutor();
             logger.info(getIdentifier() + " export services finished.");
             asyncExportingFutures.clear();
         }
@@ -359,7 +367,6 @@ public class DefaultModuleDeployer extends AbstractDeployer<ModuleModel> impleme
         } catch (Exception e) {
             logger.warn(getIdentifier() + " refer services occurred an exception.");
         } finally {
-            executorRepository.shutdownServiceReferExecutor();
             logger.info(getIdentifier() + " refer services finished.");
             asyncReferringFutures.clear();
         }
@@ -390,10 +397,10 @@ public class DefaultModuleDeployer extends AbstractDeployer<ModuleModel> impleme
 
     private String getIdentifier() {
         if (identifier == null) {
-            if (moduleModel.getModelName() != null && !StringUtils.isEquals(moduleModel.getModelName(), moduleModel.getInternalName())) {
-                identifier = moduleModel.getModelName() + "[" + moduleModel.getInternalId() + "]";
-            } else {
-                identifier = "Dubbo Module" + "[" + moduleModel.getInternalId() + "]";
+            identifier = "Dubbo module[" + moduleModel.getInternalId() + "]";
+            if (moduleModel.getModelName() != null
+                && !StringUtils.isEquals(moduleModel.getModelName(), moduleModel.getInternalName())) {
+                identifier += "(" + moduleModel.getModelName() + ")";
             }
         }
         return identifier;
diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/deploy/FrameworkModelCleaner.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/deploy/FrameworkModelCleaner.java
new file mode 100644
index 0000000..cf926d0
--- /dev/null
+++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/deploy/FrameworkModelCleaner.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.config.deploy;
+
+import org.apache.dubbo.common.extension.ExtensionLoader;
+import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.rpc.Protocol;
+import org.apache.dubbo.rpc.model.FrameworkModel;
+import org.apache.dubbo.rpc.model.ScopeModelDestroyListener;
+
+/**
+ * A cleaner to release resources of framework model
+ */
+public class FrameworkModelCleaner implements ScopeModelDestroyListener<FrameworkModel> {
+
+    private static final Logger logger = LoggerFactory.getLogger(FrameworkModelCleaner.class);
+
+    @Override
+    public void onDestroy(FrameworkModel frameworkModel) {
+        destroyFrameworkResources(frameworkModel);
+    }
+
+    /**
+     * Destroy all framework resources.
+     */
+    private void destroyFrameworkResources(FrameworkModel frameworkModel) {
+        // destroy protocol in framework scope
+        destroyProtocols(frameworkModel);
+    }
+
+    /**
+     * Destroy all the protocols.
+     */
+    private void destroyProtocols(FrameworkModel frameworkModel) {
+        ExtensionLoader<Protocol> loader = frameworkModel.getExtensionLoader(Protocol.class);
+        for (String protocolName : loader.getLoadedExtensions()) {
+            try {
+                Protocol protocol = loader.getLoadedExtension(protocolName);
+                if (protocol != null) {
+                    protocol.destroy();
+                }
+            } catch (Throwable t) {
+                logger.warn(t.getMessage(), t);
+            }
+        }
+    }
+
+
+}
diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/AbstractMethodConfigTest.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/AbstractMethodConfigTest.java
index 5b82bc8..ceb63d5 100644
--- a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/AbstractMethodConfigTest.java
+++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/AbstractMethodConfigTest.java
@@ -16,6 +16,8 @@
  */
 package org.apache.dubbo.config;
 
+import org.apache.dubbo.config.bootstrap.DubboBootstrap;
+import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.Test;
 
 import java.util.HashMap;
@@ -28,6 +30,12 @@ import static org.hamcrest.Matchers.isEmptyOrNullString;
 import static org.hamcrest.Matchers.sameInstance;
 
 public class AbstractMethodConfigTest {
+
+    @AfterAll
+    public static void afterAll() {
+        DubboBootstrap.reset();
+    }
+
     @Test
     public void testTimeout() throws Exception {
         MethodConfig methodConfig = new MethodConfig();
diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/AbstractReferenceConfigTest.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/AbstractReferenceConfigTest.java
index 2966322..f4dd078 100644
--- a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/AbstractReferenceConfigTest.java
+++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/AbstractReferenceConfigTest.java
@@ -24,6 +24,8 @@ import org.apache.dubbo.rpc.cluster.RouterFactory;
 import org.apache.dubbo.rpc.cluster.router.condition.ConditionRouterFactory;
 import org.apache.dubbo.rpc.cluster.router.condition.config.AppRouterFactory;
 import org.apache.dubbo.rpc.cluster.router.tag.TagRouterFactory;
+import org.apache.dubbo.rpc.model.FrameworkModel;
+import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 
@@ -47,6 +49,11 @@ import static org.mockito.Mockito.when;
 
 public class AbstractReferenceConfigTest {
 
+    @AfterAll
+    public static void afterAll() throws Exception {
+        FrameworkModel.destroyAll();
+    }
+
     @Test
     public void testCheck() throws Exception {
         ReferenceConfig referenceConfig = new ReferenceConfig();
diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/ConfigCenterConfigTest.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/ConfigCenterConfigTest.java
index 5ee50cc..d006c0f 100644
--- a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/ConfigCenterConfigTest.java
+++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/ConfigCenterConfigTest.java
@@ -91,6 +91,7 @@ public class ConfigCenterConfigTest {
                         .initialize();
             } catch (Exception e) {
                 // ignore
+                e.printStackTrace();
             }
 
             Collection<ConfigCenterConfig> configCenters = ApplicationModel.defaultModel().getApplicationConfigManager().getConfigCenters();
@@ -100,6 +101,7 @@ public class ConfigCenterConfigTest {
             Assertions.assertEquals(false, configCenter.isCheck());
         } finally {
             SysProps.clear();
+            DubboBootstrap.getInstance().stop();
         }
     }
 
@@ -128,6 +130,7 @@ public class ConfigCenterConfigTest {
             Assertions.assertEquals(false, configCenter.isCheck());
         } finally {
             SysProps.clear();
+            DubboBootstrap.getInstance().stop();
         }
     }
 
@@ -155,6 +158,7 @@ public class ConfigCenterConfigTest {
             Assertions.assertEquals(false, configCenter.isCheck());
         } finally {
             SysProps.clear();
+            DubboBootstrap.getInstance().stop();
         }
     }
 
@@ -183,6 +187,7 @@ public class ConfigCenterConfigTest {
             Assertions.assertEquals(false, configCenter.isCheck());
         } finally {
             ApplicationModel.defaultModel().getModelEnvironment().getPropertiesConfiguration().refresh();
+            DubboBootstrap.getInstance().stop();
         }
     }
 
@@ -211,6 +216,7 @@ public class ConfigCenterConfigTest {
             Assertions.assertEquals(false, configCenter.isCheck());
         } finally {
             SysProps.clear();
+            DubboBootstrap.getInstance().stop();
         }
     }
 
@@ -240,6 +246,7 @@ public class ConfigCenterConfigTest {
             Assertions.assertEquals(false, configCenter.isCheck());
         } finally {
             ApplicationModel.defaultModel().getModelEnvironment().getPropertiesConfiguration().refresh();
+            DubboBootstrap.getInstance().stop();
         }
     }
 
diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/ProtocolConfigTest.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/ProtocolConfigTest.java
index 11aea9d..b9fe550 100644
--- a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/ProtocolConfigTest.java
+++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/ProtocolConfigTest.java
@@ -20,6 +20,7 @@ package org.apache.dubbo.config;
 import org.apache.dubbo.common.utils.NetUtils;
 import org.apache.dubbo.config.bootstrap.DubboBootstrap;
 import org.apache.dubbo.config.context.ConfigManager;
+import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeEach;
@@ -31,7 +32,9 @@ import java.util.HashMap;
 import java.util.Map;
 
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.*;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.hasEntry;
+import static org.hamcrest.Matchers.is;
 
 public class ProtocolConfigTest {
 
@@ -45,6 +48,11 @@ public class ProtocolConfigTest {
         SysProps.clear();
     }
 
+    @AfterAll
+    public static void afterAll() {
+        DubboBootstrap.reset();
+    }
+
     @Test
     public void testName() throws Exception {
         ProtocolConfig protocol = new ProtocolConfig();
@@ -245,6 +253,7 @@ public class ProtocolConfigTest {
             Assertions.assertEquals("rest", protocolConfig.getName());
             Assertions.assertEquals(port, protocolConfig.getPort());
         } finally {
+            DubboBootstrap.getInstance().stop();
         }
     }
 
@@ -265,6 +274,7 @@ public class ProtocolConfigTest {
             Assertions.assertEquals("rest", protocolConfig.getName());
             Assertions.assertEquals(port, protocolConfig.getPort());
         } finally {
+            DubboBootstrap.getInstance().stop();
         }
     }
 
@@ -287,6 +297,7 @@ public class ProtocolConfigTest {
             Assertions.assertEquals("rest", protocolConfig.getName());
             Assertions.assertEquals(port, protocolConfig.getPort());
         } finally {
+            DubboBootstrap.getInstance().stop();
         }
     }
 
@@ -314,6 +325,7 @@ public class ProtocolConfigTest {
             Assertions.assertEquals("rest", protocol.getName());
             Assertions.assertEquals(port1, protocol.getPort());
         } finally {
+            DubboBootstrap.getInstance().stop();
         }
     }
 
@@ -340,6 +352,7 @@ public class ProtocolConfigTest {
             Assertions.assertEquals("rest", protocol.getName());
             Assertions.assertEquals(port1, protocol.getPort());
         } finally {
+            DubboBootstrap.getInstance().stop();
         }
     }
 
diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/ProviderConfigTest.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/ProviderConfigTest.java
index 30143ed..78585cc 100644
--- a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/ProviderConfigTest.java
+++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/ProviderConfigTest.java
@@ -31,6 +31,7 @@ import static org.hamcrest.Matchers.is;
 import static org.hamcrest.Matchers.not;
 
 public class ProviderConfigTest {
+
     @Test
     public void testProtocol() throws Exception {
         ProviderConfig provider = new ProviderConfig();
diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/DubboBootstrapMultiInstanceTest.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/DubboBootstrapMultiInstanceTest.java
index 12b2796..125737f 100644
--- a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/DubboBootstrapMultiInstanceTest.java
+++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/DubboBootstrapMultiInstanceTest.java
@@ -16,6 +16,8 @@
  */
 package org.apache.dubbo.config.bootstrap;
 
+import org.apache.dubbo.common.deploy.ApplicationDeployer;
+import org.apache.dubbo.common.deploy.DeployState;
 import org.apache.dubbo.common.deploy.ModuleDeployer;
 import org.apache.dubbo.common.utils.NetUtils;
 import org.apache.dubbo.common.utils.StringUtils;
@@ -34,6 +36,7 @@ import org.apache.dubbo.rpc.model.ApplicationModel;
 import org.apache.dubbo.rpc.model.FrameworkModel;
 import org.apache.dubbo.rpc.model.FrameworkServiceRepository;
 import org.apache.dubbo.rpc.model.ModuleModel;
+import org.apache.dubbo.test.check.DubboTestChecker;
 import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Assertions;
@@ -41,9 +44,12 @@ import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
+import java.io.IOException;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
-import static org.apache.dubbo.metadata.MetadataConstants.METADATA_PUBLISH_DELAY_KEY;
+import static org.apache.dubbo.remoting.Constants.EVENT_LOOP_BOSS_POOL_NAME;
 
 public class DubboBootstrapMultiInstanceTest {
 
@@ -51,8 +57,12 @@ public class DubboBootstrapMultiInstanceTest {
 
     private static RegistryConfig registryConfig;
 
+    private static DubboTestChecker testChecker;
+    private static String testClassName;
+
     @BeforeAll
     public static void beforeAll() {
+        FrameworkModel.destroyAll();
         registryCenter = new ZookeeperSingleRegistryCenter(NetUtils.getAvailablePort());
         registryCenter.startup();
         RegistryCenter.Instance instance = registryCenter.getRegistryCenterInstance().get(0);
@@ -60,11 +70,45 @@ public class DubboBootstrapMultiInstanceTest {
             instance.getType(),
             instance.getHostname(),
             instance.getPort()));
+
+        // pre-check threads
+        //precheckUnclosedThreads();
     }
 
     @AfterAll
-    public static void afterAll() {
+    public static void afterAll() throws Exception {
         registryCenter.shutdown();
+        FrameworkModel.destroyAll();
+
+        // check threads
+        //checkUnclosedThreads();
+    }
+
+    private static Map<Thread, StackTraceElement[]> precheckUnclosedThreads() throws IOException {
+        // create a special DubboTestChecker
+        if (testChecker == null) {
+            testChecker = new DubboTestChecker();
+            testChecker.init(null);
+            testClassName = DubboBootstrapMultiInstanceTest.class.getName();
+        }
+        return testChecker.checkUnclosedThreads(testClassName, 0);
+    }
+
+    private static void checkUnclosedThreads() {
+        Map<Thread, StackTraceElement[]> unclosedThreadMap = testChecker.checkUnclosedThreads(testClassName, 3000);
+        if (unclosedThreadMap.size() > 0) {
+            String str = getStackTraceString(unclosedThreadMap);
+            Assertions.fail("Found unclosed threads: " + unclosedThreadMap.size()+"\n" + str);
+        }
+    }
+
+    private static String getStackTraceString(Map<Thread, StackTraceElement[]> unclosedThreadMap) {
+        StringBuilder sb = new StringBuilder();
+        for (Thread thread : unclosedThreadMap.keySet()) {
+            sb.append(DubboTestChecker.getFullStacktrace(thread, unclosedThreadMap.get(thread)));
+            sb.append("\n");
+        }
+        return sb.toString();
     }
 
     @BeforeEach
@@ -173,7 +217,7 @@ public class DubboBootstrapMultiInstanceTest {
     @Test
     public void testMultiModuleApplication() throws InterruptedException {
 
-        SysProps.setProperty(METADATA_PUBLISH_DELAY_KEY, "1");
+        //SysProps.setProperty(METADATA_PUBLISH_DELAY_KEY, "100");
         String version1 = "1.0";
         String version2 = "2.0";
         String version3 = "3.0";
@@ -224,7 +268,7 @@ public class DubboBootstrapMultiInstanceTest {
 
             providerBootstrap.start();
 
-            Thread.sleep(100);
+            //Thread.sleep(200);
 
             // consumer app
             consumerBootstrap = DubboBootstrap.newInstance();
@@ -257,7 +301,134 @@ public class DubboBootstrapMultiInstanceTest {
                 consumerBootstrap.destroy();
             }
         }
+    }
+
+    @Test
+    public void testMultiProviderApplicationsStopOneByOne() {
+
+        String version1 = "1.0";
+        String version2 = "2.0";
+
+        DubboBootstrap providerBootstrap1 = null;
+        DubboBootstrap providerBootstrap2 = null;
+        ZookeeperSingleRegistryCenter registryCenter2 = null;
+
+        try {
+
+            // save threads before provider app 1
+            Map<Thread, StackTraceElement[]> stackTraces0 = Thread.getAllStackTraces();
+
+            // start provider app 1
+            ServiceConfig serviceConfig1 = new ServiceConfig();
+            serviceConfig1.setInterface(DemoService.class);
+            serviceConfig1.setRef(new DemoServiceImpl());
+            serviceConfig1.setVersion(version1);
+
+            ProtocolConfig protocolConfig1 = new ProtocolConfig("dubbo", NetUtils.getAvailablePort());
+
+            providerBootstrap1 = DubboBootstrap.getInstance();
+            providerBootstrap1.application("provider1")
+                .registry(registryConfig)
+                .service(serviceConfig1)
+                .protocol(protocolConfig1)
+                .start();
+
+            // save threads of provider app 1
+            Map<Thread, StackTraceElement[]> lastAllThreadStackTraces = Thread.getAllStackTraces();
+            Map<Thread, StackTraceElement[]> stackTraces1 = findNewThreads(lastAllThreadStackTraces, stackTraces0);
+            Assertions.assertTrue(stackTraces1.size() > 0, "Get threads of provider app 1 failed");
+
+            // start zk server 2
+            registryCenter2 = new ZookeeperSingleRegistryCenter(NetUtils.getAvailablePort());
+            registryCenter2.startup();
+            RegistryCenter.Instance instance = registryCenter2.getRegistryCenterInstance().get(0);
+            RegistryConfig registryConfig2 = new RegistryConfig(String.format("%s://%s:%s",
+                instance.getType(),
+                instance.getHostname(),
+                instance.getPort()));
+
+            // start provider app 2 use a difference zk server 2
+            ServiceConfig serviceConfig2 = new ServiceConfig();
+            serviceConfig2.setInterface(DemoService.class);
+            serviceConfig2.setRef(new DemoServiceImpl());
+            serviceConfig2.setVersion(version2);
+
+            ProtocolConfig protocolConfig2 = new ProtocolConfig("dubbo", NetUtils.getAvailablePort());
+
+            providerBootstrap2 = DubboBootstrap.newInstance();
+            providerBootstrap2.application("provider2")
+                .registry(registryConfig2)
+                .service(serviceConfig2)
+                .protocol(protocolConfig2)
+                .start();
+
+            // save threads of provider app 2
+            Map<Thread, StackTraceElement[]> stackTraces2 = findNewThreads(Thread.getAllStackTraces(), stackTraces0);
+            Assertions.assertTrue(stackTraces2.size() > 0, "Get threads of provider app 2 failed");
+
+            // stop provider app 1 and check threads
+            providerBootstrap1.stop();
+
+            // TODO Remove ignore thread prefix of NettyServerBoss if supporting close protocol server only used by one application
+            // see org.apache.dubbo.config.deploy.DefaultApplicationDeployer.postDestroy
+            // NettyServer will close when all applications are shutdown, but not close if any application of the framework is alive, just ignore it currently
+            checkUnclosedThreadsOfApp(stackTraces1, "Found unclosed threads of app 1: ", new String[]{EVENT_LOOP_BOSS_POOL_NAME, "Dubbo-global-shared-handler"});
+
+
+            // stop provider app 2 and check threads
+            providerBootstrap2.stop();
+            // shutdown register center after dubbo application to avoid unregister services blocking
+            registryCenter2.shutdown();
+            checkUnclosedThreadsOfApp(stackTraces2, "Found unclosed threads of app 2: ", null);
+
+        } finally {
+            if (providerBootstrap1 != null) {
+                providerBootstrap1.stop();
+            }
+            if (providerBootstrap2 != null) {
+                providerBootstrap2.stop();
+            }
+            if (registryCenter2 != null) {
+                registryCenter2.shutdown();
+            }
+        }
+    }
+
+    private Map<Thread, StackTraceElement[]> findNewThreads(Map<Thread, StackTraceElement[]> newAllThreadMap, Map<Thread, StackTraceElement[]> prevThreadMap) {
+        Map<Thread, StackTraceElement[]> deltaThreadMap = new HashMap<>(newAllThreadMap);
+        deltaThreadMap.keySet().removeAll(prevThreadMap.keySet());
+        // expect deltaThreadMap not contains any elements of prevThreadMap
+        Assertions.assertFalse(deltaThreadMap.keySet().stream().filter(thread -> prevThreadMap.containsKey(thread)).findAny().isPresent());
+        return deltaThreadMap;
+    }
+
+    private void checkUnclosedThreadsOfApp(Map<Thread, StackTraceElement[]> stackTraces1, String msg, String[] ignoredThreadPrefixes) {
+        int waitTimeMs = 5000;
+        System.out.println("Wait "+waitTimeMs+"ms to check threads of app ...");
+        try {
+            Thread.sleep(waitTimeMs);
+        } catch (InterruptedException e) {
+        }
+        HashMap<Thread, StackTraceElement[]> unclosedThreadMap1 = new HashMap<>(stackTraces1);
+        unclosedThreadMap1.keySet().removeIf(thread -> !thread.isAlive());
+        if (ignoredThreadPrefixes!= null && ignoredThreadPrefixes.length > 0) {
+            unclosedThreadMap1.keySet().removeIf(thread -> isIgnoredThread(thread.getName(), ignoredThreadPrefixes));
+        }
+        if (unclosedThreadMap1.size() > 0) {
+            String str = getStackTraceString(unclosedThreadMap1);
+            Assertions.fail(msg + unclosedThreadMap1.size()+"\n" + str);
+        }
+    }
 
+    private boolean isIgnoredThread(String name, String[] ignoredThreadPrefixes) {
+        if (ignoredThreadPrefixes!= null && ignoredThreadPrefixes.length > 0) {
+            for (String prefix : ignoredThreadPrefixes) {
+                if (name.startsWith(prefix)) {
+                    return true;
+                }
+            }
+        }
+        return false;
     }
 
     @Test
@@ -402,6 +573,142 @@ public class DubboBootstrapMultiInstanceTest {
         }
     }
 
+    @Test
+    public void testBothStartByModuleAndByApplication() throws Exception {
+        String version1 = "1.0";
+        String version2 = "2.0";
+        String version3 = "3.0";
+
+        String serviceKey1 = DemoService.class.getName() + ":" + version1;
+        String serviceKey2 = DemoService.class.getName() + ":" + version2;
+        String serviceKey3 = DemoService.class.getName() + ":" + version3;
+
+        // provider app
+        DubboBootstrap providerBootstrap = null;
+        try {
+            providerBootstrap = DubboBootstrap.newInstance();
+
+            ServiceConfig serviceConfig1 = new ServiceConfig();
+            serviceConfig1.setInterface(DemoService.class);
+            serviceConfig1.setRef(new DemoServiceImpl());
+            serviceConfig1.setVersion(version1);
+
+            //provider module 1
+            providerBootstrap
+                .application("provider-app")
+                .registry(registryConfig)
+                .protocol(new ProtocolConfig("dubbo", -1))
+                .service(builder -> builder
+                    .interfaceClass(Greeting.class)
+                    .ref(new GreetingLocal2()))
+                .newModule()
+                .service(serviceConfig1)
+                .endModule();
+
+            // 1. start module1
+            ModuleDeployer moduleDeployer1 = serviceConfig1.getScopeModel().getDeployer();
+            moduleDeployer1.start().get();
+            Assertions.assertEquals(DeployState.STARTED, moduleDeployer1.getState());
+
+            ApplicationModel applicationModel = providerBootstrap.getApplicationModel();
+            ApplicationDeployer applicationDeployer = applicationModel.getDeployer();
+            Assertions.assertEquals(DeployState.STARTING, applicationDeployer.getState());
+            ModuleModel defaultModule = applicationModel.getDefaultModule();
+            Assertions.assertEquals(DeployState.PENDING, defaultModule.getDeployer().getState());
+
+            // 2. start application after module1 is started
+            providerBootstrap.start();
+            Assertions.assertEquals(DeployState.STARTED, applicationDeployer.getState());
+            Assertions.assertEquals(DeployState.STARTED, defaultModule.getDeployer().getState());
+            
+            // 3. add module2 and re-start application
+            ServiceConfig serviceConfig2 = new ServiceConfig();
+            serviceConfig2.setInterface(DemoService.class);
+            serviceConfig2.setRef(new DemoServiceImpl());
+            serviceConfig2.setVersion(version2);
+            ModuleModel moduleModel2 = providerBootstrap.newModule()
+                .service(serviceConfig2)
+                .getModuleModel();
+            providerBootstrap.start();
+            Assertions.assertEquals(DeployState.STARTED, applicationDeployer.getState());
+            Assertions.assertEquals(DeployState.STARTED, moduleModel2.getDeployer().getState());
+            
+            // 4. add module3 and start module3
+            ServiceConfig serviceConfig3 = new ServiceConfig();
+            serviceConfig3.setInterface(DemoService.class);
+            serviceConfig3.setRef(new DemoServiceImpl());
+            serviceConfig3.setVersion(version3);
+            ModuleModel moduleModel3 = providerBootstrap.newModule()
+                .service(serviceConfig3)
+                .getModuleModel();
+            moduleModel3.getDeployer().start().get();
+            Assertions.assertEquals(DeployState.STARTED, applicationDeployer.getState());
+            Assertions.assertEquals(DeployState.STARTED, moduleModel3.getDeployer().getState());
+
+        } finally {
+            if (providerBootstrap != null) {
+                providerBootstrap.stop();
+            }
+        }
+    }
+
+
+    @Test
+    public void testBothStartByModuleAndByApplication2() throws Exception {
+        String version1 = "1.0";
+        String version2 = "2.0";
+        String version3 = "3.0";
+
+        String serviceKey1 = DemoService.class.getName() + ":" + version1;
+        String serviceKey2 = DemoService.class.getName() + ":" + version2;
+        String serviceKey3 = DemoService.class.getName() + ":" + version3;
+
+        // provider app
+        DubboBootstrap providerBootstrap = null;
+        try {
+            providerBootstrap = DubboBootstrap.newInstance();
+
+            ServiceConfig serviceConfig1 = new ServiceConfig();
+            serviceConfig1.setInterface(DemoService.class);
+            serviceConfig1.setRef(new DemoServiceImpl());
+            serviceConfig1.setVersion(version1);
+
+            //provider module 1
+            providerBootstrap
+                .application("provider-app")
+                .registry(registryConfig)
+                .protocol(new ProtocolConfig("dubbo", -1))
+                .service(builder -> builder
+                    .interfaceClass(Greeting.class)
+                    .ref(new GreetingLocal2()))
+                .newModule()
+                .service(serviceConfig1)
+                .endModule();
+
+            // 1. start module1 but no wait
+            ModuleDeployer moduleDeployer1 = serviceConfig1.getScopeModel().getDeployer();
+            moduleDeployer1.start();
+            Assertions.assertEquals(DeployState.STARTING, moduleDeployer1.getState());
+
+            ApplicationModel applicationModel = providerBootstrap.getApplicationModel();
+            ApplicationDeployer applicationDeployer = applicationModel.getDeployer();
+            Assertions.assertEquals(DeployState.STARTING, applicationDeployer.getState());
+            ModuleModel defaultModule = applicationModel.getDefaultModule();
+            Assertions.assertEquals(DeployState.PENDING, defaultModule.getDeployer().getState());
+
+            // 2. start application after module1 is starting
+            providerBootstrap.start();
+            Assertions.assertEquals(DeployState.STARTED, applicationDeployer.getState());
+            Assertions.assertEquals(DeployState.STARTED, moduleDeployer1.getState());
+            Assertions.assertEquals(DeployState.STARTED, defaultModule.getDeployer().getState());
+
+        } finally {
+            if (providerBootstrap != null) {
+                providerBootstrap.stop();
+            }
+        }
+    }
+
 
     private DubboBootstrap configConsumerApp(DubboBootstrap dubboBootstrap) {
         ReferenceConfig<DemoService> referenceConfig = new ReferenceConfig<>();
diff --git a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/context/DubboDeployApplicationListener.java b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/context/DubboDeployApplicationListener.java
index 249c2e5..918e0b6 100644
--- a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/context/DubboDeployApplicationListener.java
+++ b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/context/DubboDeployApplicationListener.java
@@ -36,7 +36,7 @@ import org.springframework.context.event.ContextClosedEvent;
 import org.springframework.context.event.ContextRefreshedEvent;
 import org.springframework.core.Ordered;
 
-import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Future;
 
 /**
  * An ApplicationListener to control Dubbo application.
@@ -105,7 +105,7 @@ public class DubboDeployApplicationListener implements ApplicationListener<Appli
         ModuleDeployer deployer = moduleModel.getDeployer();
         Assert.notNull(deployer, "Module deployer is null");
         // start module
-        CompletableFuture future = deployer.start();
+        Future future = deployer.start();
 
         // if the module does not start in background, await finish
         if (!deployer.isBackground()) {
diff --git a/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/ShutdownHookTest.java b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/KeepRunningOnSpringClosedTest.java
similarity index 57%
rename from dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/ShutdownHookTest.java
rename to dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/KeepRunningOnSpringClosedTest.java
index 2516b51..28bdee2 100644
--- a/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/ShutdownHookTest.java
+++ b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/KeepRunningOnSpringClosedTest.java
@@ -16,7 +16,9 @@
  */
 package org.apache.dubbo.config.spring;
 
+import org.apache.dubbo.common.deploy.ApplicationDeployer;
 import org.apache.dubbo.common.deploy.DeployState;
+import org.apache.dubbo.common.deploy.ModuleDeployer;
 import org.apache.dubbo.config.bootstrap.DubboBootstrap;
 import org.apache.dubbo.config.spring.context.DubboSpringInitCustomizerHolder;
 import org.apache.dubbo.rpc.model.ModuleModel;
@@ -24,46 +26,52 @@ import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 import org.springframework.context.support.ClassPathXmlApplicationContext;
 
-public class ShutdownHookTest {
+public class KeepRunningOnSpringClosedTest {
 
     @Test
-    public void testDisableShutdownHook() throws InterruptedException {
+    public void test(){
 
         // set KeepRunningOnSpringClosed flag for next spring context
         DubboSpringInitCustomizerHolder.get().addCustomizer(context-> {
             context.setKeepRunningOnSpringClosed(true);
         });
 
+        ClassPathXmlApplicationContext providerContext = null;
         try {
-            ClassPathXmlApplicationContext providerContext;
             String resourcePath = "org/apache/dubbo/config/spring";
             providerContext = new ClassPathXmlApplicationContext(
                 resourcePath + "/demo-provider.xml",
                 resourcePath + "/demo-provider-properties.xml");
             providerContext.start();
 
-            DubboStateListener listener = providerContext.getBean(DubboStateListener.class);
-            for (int i = 0; i < 10; i++) {
-                if (DeployState.STARTED.equals(listener.getState())) {
-                    break;
-                }
-                Thread.sleep(100);
-            }
+            // Expect 1: dubbo application state is STARTED after spring context start finish.
+            // No need check and wait
+
+            DubboStateListener dubboStateListener = providerContext.getBean(DubboStateListener.class);
+            Assertions.assertEquals(DeployState.STARTED, dubboStateListener.getState());
 
             ModuleModel moduleModel = providerContext.getBean(ModuleModel.class);
-            Assertions.assertTrue(moduleModel.getDeployer().isStarted());
-            Assertions.assertEquals(true, DubboBootstrap.getInstance().isStarted());
-            Assertions.assertEquals(false, DubboBootstrap.getInstance().isStopped());
+            ModuleDeployer moduleDeployer = moduleModel.getDeployer();
+            Assertions.assertTrue(moduleDeployer.isStarted());
+
+            ApplicationDeployer applicationDeployer = moduleModel.getApplicationModel().getDeployer();
+            Assertions.assertEquals(DeployState.STARTED, applicationDeployer.getState());
+            Assertions.assertEquals(true, applicationDeployer.isStarted());
+            Assertions.assertEquals(false, applicationDeployer.isStopped());
 
             // close spring context
             providerContext.close();
 
-            // expect dubbo bootstrap will not be destroyed after closing spring context
-            Assertions.assertEquals(true, DubboBootstrap.getInstance().isStarted());
-            Assertions.assertEquals(false, DubboBootstrap.getInstance().isStopped());
+            // Expect 2: dubbo application will not be destroyed after closing spring context cause setKeepRunningOnSpringClosed(true)
+            Assertions.assertEquals(DeployState.STARTED, applicationDeployer.getState());
+            Assertions.assertEquals(true, applicationDeployer.isStarted());
+            Assertions.assertEquals(false, applicationDeployer.isStopped());
         } finally {
             DubboBootstrap.getInstance().stop();
             SysProps.clear();
+            if (providerContext != null) {
+                providerContext.close();
+            }
         }
     }
 }
diff --git a/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/reference/javaconfig/JavaConfigReferenceBeanTest.java b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/reference/javaconfig/JavaConfigReferenceBeanTest.java
index ad17975..a4ec239 100644
--- a/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/reference/javaconfig/JavaConfigReferenceBeanTest.java
+++ b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/reference/javaconfig/JavaConfigReferenceBeanTest.java
@@ -16,6 +16,7 @@
  */
 package org.apache.dubbo.config.spring.reference.javaconfig;
 
+import com.sun.management.HotSpotDiagnosticMXBean;
 import org.apache.dubbo.config.annotation.DubboReference;
 import org.apache.dubbo.config.annotation.DubboService;
 import org.apache.dubbo.config.annotation.Reference;
@@ -42,8 +43,10 @@ import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.PropertySource;
 
+import javax.management.MBeanServer;
 import java.io.PrintWriter;
 import java.io.StringWriter;
+import java.lang.management.ManagementFactory;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
@@ -59,8 +62,33 @@ public class JavaConfigReferenceBeanTest {
     }
 
     @AfterAll
-    public static void afterAll() {
+    public static void afterAll() throws Exception {
         multipleRegistryCenter.shutdown();
+        multipleRegistryCenter = null;
+
+        //Thread.sleep(10000);
+        //dumpHeap("/Users/gongdewei/work/dump/dubbo/", true);
+    }
+
+    public static void dumpHeap(String dirpath, boolean live) throws Exception {
+
+        java.lang.management.RuntimeMXBean runtime =
+            java.lang.management.ManagementFactory.getRuntimeMXBean();
+        java.lang.reflect.Field jvm = runtime.getClass().getDeclaredField("jvm");
+        jvm.setAccessible(true);
+        sun.management.VMManagement mgmt =
+            (sun.management.VMManagement) jvm.get(runtime);
+        java.lang.reflect.Method pid_method =
+            mgmt.getClass().getDeclaredMethod("getProcessId");
+        pid_method.setAccessible(true);
+        int pid = (Integer) pid_method.invoke(mgmt);
+
+        MBeanServer server = ManagementFactory.getPlatformMBeanServer();
+        HotSpotDiagnosticMXBean mxBean = ManagementFactory.newPlatformMXBeanProxy(
+            server, "com.sun.management:type=HotSpotDiagnostic", HotSpotDiagnosticMXBean.class);
+        String filepath = dirpath + pid + ".hprof";
+        mxBean.dumpHeap(filepath, live);
+        System.out.println("Dump heap to file: " + filepath);
     }
 
     @BeforeEach
@@ -78,19 +106,21 @@ public class JavaConfigReferenceBeanTest {
         AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(CommonConfig.class,
             AnnotationAtFieldConfiguration.class);
 
-        Map<String, HelloService> helloServiceMap = context.getBeansOfType(HelloService.class);
-        Assertions.assertEquals(2, helloServiceMap.size());
-        Assertions.assertNotNull(helloServiceMap.get("helloService"));
-        Assertions.assertNotNull(helloServiceMap.get("helloServiceImpl"));
-
-        Map<String, ReferenceBean> referenceBeanMap = context.getBeansOfType(ReferenceBean.class);
-        Assertions.assertEquals(1, referenceBeanMap.size());
-        ReferenceBean referenceBean = referenceBeanMap.get("&helloService");
-        Assertions.assertEquals("demo", referenceBean.getGroup());
-        Assertions.assertEquals(HelloService.class, referenceBean.getInterfaceClass());
-        Assertions.assertEquals(HelloService.class.getName(), referenceBean.getServiceInterface());
-
-        context.close();
+        try {
+            Map<String, HelloService> helloServiceMap = context.getBeansOfType(HelloService.class);
+            Assertions.assertEquals(2, helloServiceMap.size());
+            Assertions.assertNotNull(helloServiceMap.get("helloService"));
+            Assertions.assertNotNull(helloServiceMap.get("helloServiceImpl"));
+
+            Map<String, ReferenceBean> referenceBeanMap = context.getBeansOfType(ReferenceBean.class);
+            Assertions.assertEquals(1, referenceBeanMap.size());
+            ReferenceBean referenceBean = referenceBeanMap.get("&helloService");
+            Assertions.assertEquals("demo", referenceBean.getGroup());
+            Assertions.assertEquals(HelloService.class, referenceBean.getInterfaceClass());
+            Assertions.assertEquals(HelloService.class.getName(), referenceBean.getServiceInterface());
+        } finally {
+            context.close();
+        }
     }
 
     @Test
@@ -122,19 +152,21 @@ public class JavaConfigReferenceBeanTest {
         AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(CommonConfig.class,
                 AnnotationBeanConfiguration.class);
 
-        Map<String, HelloService> helloServiceMap = context.getBeansOfType(HelloService.class);
-        Assertions.assertEquals(2, helloServiceMap.size());
-        Assertions.assertNotNull(helloServiceMap.get("helloService"));
-        Assertions.assertNotNull(helloServiceMap.get("helloServiceImpl"));
-
-        Map<String, ReferenceBean> referenceBeanMap = context.getBeansOfType(ReferenceBean.class);
-        Assertions.assertEquals(1, referenceBeanMap.size());
-        ReferenceBean referenceBean = referenceBeanMap.get("&helloService");
-        Assertions.assertEquals("demo", referenceBean.getGroup());
-        Assertions.assertEquals(HelloService.class, referenceBean.getInterfaceClass());
-        Assertions.assertEquals(HelloService.class.getName(), referenceBean.getServiceInterface());
-
-        context.close();
+        try {
+            Map<String, HelloService> helloServiceMap = context.getBeansOfType(HelloService.class);
+            Assertions.assertEquals(2, helloServiceMap.size());
+            Assertions.assertNotNull(helloServiceMap.get("helloService"));
+            Assertions.assertNotNull(helloServiceMap.get("helloServiceImpl"));
+
+            Map<String, ReferenceBean> referenceBeanMap = context.getBeansOfType(ReferenceBean.class);
+            Assertions.assertEquals(1, referenceBeanMap.size());
+            ReferenceBean referenceBean = referenceBeanMap.get("&helloService");
+            Assertions.assertEquals("demo", referenceBean.getGroup());
+            Assertions.assertEquals(HelloService.class, referenceBean.getInterfaceClass());
+            Assertions.assertEquals(HelloService.class.getName(), referenceBean.getServiceInterface());
+        } finally {
+            context.close();
+        }
     }
 
     @Test
@@ -142,33 +174,36 @@ public class JavaConfigReferenceBeanTest {
         AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(CommonConfig.class,
             GenericServiceAnnotationBeanConfiguration.class);
 
-        Map<String, HelloService> helloServiceMap = context.getBeansOfType(HelloService.class);
-        Assertions.assertEquals(1, helloServiceMap.size());
-        Assertions.assertNotNull(helloServiceMap.get("helloServiceImpl"));
-
-        Map<String, GenericService> genericServiceMap = context.getBeansOfType(GenericService.class);
-        Assertions.assertEquals(3, genericServiceMap.size());
-        Assertions.assertNotNull(genericServiceMap.get("genericHelloService"));
-
-        Map<String, ReferenceBean> referenceBeanMap = context.getBeansOfType(ReferenceBean.class);
-        Assertions.assertEquals(2, referenceBeanMap.size());
-
-        ReferenceBean genericHelloServiceReferenceBean = referenceBeanMap.get("&genericHelloService");
-        Assertions.assertEquals("demo", genericHelloServiceReferenceBean.getGroup());
-        Assertions.assertEquals(GenericService.class, genericHelloServiceReferenceBean.getInterfaceClass());
-        Assertions.assertEquals(HelloService.class.getName(), genericHelloServiceReferenceBean.getServiceInterface());
-
-        ReferenceBean genericServiceWithoutInterfaceBean = referenceBeanMap.get("&genericServiceWithoutInterface");
-        Assertions.assertEquals("demo", genericServiceWithoutInterfaceBean.getGroup());
-        Assertions.assertEquals(GenericService.class, genericServiceWithoutInterfaceBean.getInterfaceClass());
-        Assertions.assertEquals("org.apache.dubbo.config.spring.api.LocalMissClass", genericServiceWithoutInterfaceBean.getServiceInterface());
-
-        GenericService genericServiceWithoutInterface = context.getBean("genericServiceWithoutInterface", GenericService.class);
-        Assertions.assertNotNull(genericServiceWithoutInterface);
-        Object sayHelloResult = genericServiceWithoutInterface.$invoke("sayHello", new String[]{"java.lang.String"}, new Object[]{"Dubbo"});
-        Assertions.assertEquals("Hello Dubbo", sayHelloResult);
+        try {
+            Map<String, HelloService> helloServiceMap = context.getBeansOfType(HelloService.class);
+            Assertions.assertEquals(1, helloServiceMap.size());
+            Assertions.assertNotNull(helloServiceMap.get("helloServiceImpl"));
+
+            Map<String, GenericService> genericServiceMap = context.getBeansOfType(GenericService.class);
+            Assertions.assertEquals(3, genericServiceMap.size());
+            Assertions.assertNotNull(genericServiceMap.get("genericHelloService"));
+
+            Map<String, ReferenceBean> referenceBeanMap = context.getBeansOfType(ReferenceBean.class);
+            Assertions.assertEquals(2, referenceBeanMap.size());
+
+            ReferenceBean genericHelloServiceReferenceBean = referenceBeanMap.get("&genericHelloService");
+            Assertions.assertEquals("demo", genericHelloServiceReferenceBean.getGroup());
+            Assertions.assertEquals(GenericService.class, genericHelloServiceReferenceBean.getInterfaceClass());
+            Assertions.assertEquals(HelloService.class.getName(), genericHelloServiceReferenceBean.getServiceInterface());
+
+            ReferenceBean genericServiceWithoutInterfaceBean = referenceBeanMap.get("&genericServiceWithoutInterface");
+            Assertions.assertEquals("demo", genericServiceWithoutInterfaceBean.getGroup());
+            Assertions.assertEquals(GenericService.class, genericServiceWithoutInterfaceBean.getInterfaceClass());
+            Assertions.assertEquals("org.apache.dubbo.config.spring.api.LocalMissClass", genericServiceWithoutInterfaceBean.getServiceInterface());
+
+            GenericService genericServiceWithoutInterface = context.getBean("genericServiceWithoutInterface", GenericService.class);
+            Assertions.assertNotNull(genericServiceWithoutInterface);
+            Object sayHelloResult = genericServiceWithoutInterface.$invoke("sayHello", new String[]{"java.lang.String"}, new Object[]{"Dubbo"});
+            Assertions.assertEquals("Hello Dubbo", sayHelloResult);
+        } finally {
+            context.close();
+        }
 
-        context.close();
     }
 
     @Test
@@ -176,22 +211,24 @@ public class JavaConfigReferenceBeanTest {
         AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(CommonConfig.class,
                 ReferenceBeanConfiguration.class);
 
-        Map<String, HelloService> helloServiceMap = context.getBeansOfType(HelloService.class);
-        Assertions.assertEquals(2, helloServiceMap.size());
-        Assertions.assertNotNull(helloServiceMap.get("helloService"));
-        Assertions.assertNotNull(helloServiceMap.get("helloServiceImpl"));
-
-        Map<String, ReferenceBean> referenceBeanMap = context.getBeansOfType(ReferenceBean.class);
-        Assertions.assertEquals(2, referenceBeanMap.size());
-        ReferenceBean referenceBean = referenceBeanMap.get("&helloService");
-        Assertions.assertEquals(HelloService.class, referenceBean.getInterfaceClass());
-        Assertions.assertEquals(HelloService.class.getName(), referenceBean.getServiceInterface());
-
-        ReferenceBean demoServiceReferenceBean = referenceBeanMap.get("&demoService");
-        Assertions.assertEquals(DemoService.class, demoServiceReferenceBean.getInterfaceClass());
-        Assertions.assertEquals(DemoService.class.getName(), demoServiceReferenceBean.getServiceInterface());
-
-        context.close();
+        try {
+            Map<String, HelloService> helloServiceMap = context.getBeansOfType(HelloService.class);
+            Assertions.assertEquals(2, helloServiceMap.size());
+            Assertions.assertNotNull(helloServiceMap.get("helloService"));
+            Assertions.assertNotNull(helloServiceMap.get("helloServiceImpl"));
+
+            Map<String, ReferenceBean> referenceBeanMap = context.getBeansOfType(ReferenceBean.class);
+            Assertions.assertEquals(2, referenceBeanMap.size());
+            ReferenceBean referenceBean = referenceBeanMap.get("&helloService");
+            Assertions.assertEquals(HelloService.class, referenceBean.getInterfaceClass());
+            Assertions.assertEquals(HelloService.class.getName(), referenceBean.getServiceInterface());
+
+            ReferenceBean demoServiceReferenceBean = referenceBeanMap.get("&demoService");
+            Assertions.assertEquals(DemoService.class, demoServiceReferenceBean.getInterfaceClass());
+            Assertions.assertEquals(DemoService.class.getName(), demoServiceReferenceBean.getServiceInterface());
+        } finally {
+            context.close();
+        }
     }
 
     @Test
@@ -199,24 +236,26 @@ public class JavaConfigReferenceBeanTest {
         AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(CommonConfig.class,
             GenericServiceReferenceBeanConfiguration.class);
 
-        Map<String, HelloService> helloServiceMap = context.getBeansOfType(HelloService.class);
-        Assertions.assertEquals(1, helloServiceMap.size());
-        Assertions.assertNotNull(helloServiceMap.get("helloServiceImpl"));
-
-        Map<String, GenericService> genericServiceMap = context.getBeansOfType(GenericService.class);
-        Assertions.assertEquals(2, genericServiceMap.size());
-        Assertions.assertNotNull(genericServiceMap.get("localMissClassGenericServiceImpl"));
-        Assertions.assertNotNull(genericServiceMap.get("genericHelloService"));
-
-        Map<String, ReferenceBean> referenceBeanMap = context.getBeansOfType(ReferenceBean.class);
-        Assertions.assertEquals(1, referenceBeanMap.size());
-
-        ReferenceBean genericHelloServiceReferenceBean = referenceBeanMap.get("&genericHelloService");
-        Assertions.assertEquals("demo", genericHelloServiceReferenceBean.getGroup());
-        Assertions.assertEquals(GenericService.class, genericHelloServiceReferenceBean.getInterfaceClass());
-        Assertions.assertEquals(HelloService.class.getName(), genericHelloServiceReferenceBean.getServiceInterface());
-
-        context.close();
+        try {
+            Map<String, HelloService> helloServiceMap = context.getBeansOfType(HelloService.class);
+            Assertions.assertEquals(1, helloServiceMap.size());
+            Assertions.assertNotNull(helloServiceMap.get("helloServiceImpl"));
+
+            Map<String, GenericService> genericServiceMap = context.getBeansOfType(GenericService.class);
+            Assertions.assertEquals(2, genericServiceMap.size());
+            Assertions.assertNotNull(genericServiceMap.get("localMissClassGenericServiceImpl"));
+            Assertions.assertNotNull(genericServiceMap.get("genericHelloService"));
+
+            Map<String, ReferenceBean> referenceBeanMap = context.getBeansOfType(ReferenceBean.class);
+            Assertions.assertEquals(1, referenceBeanMap.size());
+
+            ReferenceBean genericHelloServiceReferenceBean = referenceBeanMap.get("&genericHelloService");
+            Assertions.assertEquals("demo", genericHelloServiceReferenceBean.getGroup());
+            Assertions.assertEquals(GenericService.class, genericHelloServiceReferenceBean.getInterfaceClass());
+            Assertions.assertEquals(HelloService.class.getName(), genericHelloServiceReferenceBean.getServiceInterface());
+        } finally {
+            context.close();
+        }
     }
 
     @Test
diff --git a/dubbo-config/pom.xml b/dubbo-config/pom.xml
index ea7e398..168b5ba 100644
--- a/dubbo-config/pom.xml
+++ b/dubbo-config/pom.xml
@@ -33,4 +33,13 @@
         <module>dubbo-config-api</module>
         <module>dubbo-config-spring</module>
     </modules>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-test-check</artifactId>
+            <version>${project.parent.version}</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
 </project>
diff --git a/dubbo-configcenter/dubbo-configcenter-apollo/src/main/java/org/apache/dubbo/configcenter/support/apollo/ApolloDynamicConfiguration.java b/dubbo-configcenter/dubbo-configcenter-apollo/src/main/java/org/apache/dubbo/configcenter/support/apollo/ApolloDynamicConfiguration.java
index dbf6822..b4a49aa 100644
--- a/dubbo-configcenter/dubbo-configcenter-apollo/src/main/java/org/apache/dubbo/configcenter/support/apollo/ApolloDynamicConfiguration.java
+++ b/dubbo-configcenter/dubbo-configcenter-apollo/src/main/java/org/apache/dubbo/configcenter/support/apollo/ApolloDynamicConfiguration.java
@@ -16,15 +16,6 @@
  */
 package org.apache.dubbo.configcenter.support.apollo;
 
-import org.apache.dubbo.common.URL;
-import org.apache.dubbo.common.config.configcenter.ConfigChangeType;
-import org.apache.dubbo.common.config.configcenter.ConfigChangedEvent;
-import org.apache.dubbo.common.config.configcenter.ConfigurationListener;
-import org.apache.dubbo.common.config.configcenter.DynamicConfiguration;
-import org.apache.dubbo.common.logger.Logger;
-import org.apache.dubbo.common.logger.LoggerFactory;
-import org.apache.dubbo.common.utils.StringUtils;
-
 import com.ctrip.framework.apollo.Config;
 import com.ctrip.framework.apollo.ConfigChangeListener;
 import com.ctrip.framework.apollo.ConfigFile;
@@ -33,6 +24,14 @@ import com.ctrip.framework.apollo.core.enums.ConfigFileFormat;
 import com.ctrip.framework.apollo.enums.ConfigSourceType;
 import com.ctrip.framework.apollo.enums.PropertyChangeType;
 import com.ctrip.framework.apollo.model.ConfigChange;
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.config.configcenter.ConfigChangeType;
+import org.apache.dubbo.common.config.configcenter.ConfigChangedEvent;
+import org.apache.dubbo.common.config.configcenter.ConfigurationListener;
+import org.apache.dubbo.common.config.configcenter.DynamicConfiguration;
+import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.common.utils.StringUtils;
 
 import java.util.Arrays;
 import java.util.Collections;
@@ -113,6 +112,12 @@ public class ApolloDynamicConfiguration implements DynamicConfiguration {
         }
     }
 
+    // TODO release apollo
+//    @Override
+//    public void close() throws Exception {
+//        DynamicConfiguration.super.close();
+//    }
+
     private String getAddressWithProtocolPrefix(URL url) {
         String address = url.getBackupAddress();
         if (StringUtils.isNotEmpty(address)) {
diff --git a/dubbo-configcenter/dubbo-configcenter-nacos/src/main/java/org/apache/dubbo/configcenter/support/nacos/NacosConfigServiceWrapper.java b/dubbo-configcenter/dubbo-configcenter-nacos/src/main/java/org/apache/dubbo/configcenter/support/nacos/NacosConfigServiceWrapper.java
index 4ac0a78..c6a0a44 100644
--- a/dubbo-configcenter/dubbo-configcenter-nacos/src/main/java/org/apache/dubbo/configcenter/support/nacos/NacosConfigServiceWrapper.java
+++ b/dubbo-configcenter/dubbo-configcenter-nacos/src/main/java/org/apache/dubbo/configcenter/support/nacos/NacosConfigServiceWrapper.java
@@ -65,6 +65,10 @@ public class NacosConfigServiceWrapper {
         return configService.removeConfig(handleInnerSymbol(dataId), handleInnerSymbol(group));
     }
 
+    public void shutdown() throws NacosException {
+        configService.shutDown();
+    }
+
     /**
      * see {@link com.alibaba.nacos.client.config.utils.ParamUtils#isValid(java.lang.String)}
      */
diff --git a/dubbo-configcenter/dubbo-configcenter-nacos/src/main/java/org/apache/dubbo/configcenter/support/nacos/NacosDynamicConfiguration.java b/dubbo-configcenter/dubbo-configcenter-nacos/src/main/java/org/apache/dubbo/configcenter/support/nacos/NacosDynamicConfiguration.java
index 4c2e4ea..83a0fac 100644
--- a/dubbo-configcenter/dubbo-configcenter-nacos/src/main/java/org/apache/dubbo/configcenter/support/nacos/NacosDynamicConfiguration.java
+++ b/dubbo-configcenter/dubbo-configcenter-nacos/src/main/java/org/apache/dubbo/configcenter/support/nacos/NacosDynamicConfiguration.java
@@ -17,17 +17,6 @@
 
 package org.apache.dubbo.configcenter.support.nacos;
 
-import org.apache.dubbo.common.URL;
-import org.apache.dubbo.common.config.configcenter.ConfigChangeType;
-import org.apache.dubbo.common.config.configcenter.ConfigChangedEvent;
-import org.apache.dubbo.common.config.configcenter.ConfigItem;
-import org.apache.dubbo.common.config.configcenter.ConfigurationListener;
-import org.apache.dubbo.common.config.configcenter.DynamicConfiguration;
-import org.apache.dubbo.common.logger.Logger;
-import org.apache.dubbo.common.logger.LoggerFactory;
-import org.apache.dubbo.common.utils.MD5Utils;
-import org.apache.dubbo.common.utils.StringUtils;
-
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
@@ -38,6 +27,16 @@ import com.alibaba.nacos.api.config.listener.AbstractSharedListener;
 import com.alibaba.nacos.api.exception.NacosException;
 import com.alibaba.nacos.client.config.http.HttpAgent;
 import com.alibaba.nacos.common.http.HttpRestResult;
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.config.configcenter.ConfigChangeType;
+import org.apache.dubbo.common.config.configcenter.ConfigChangedEvent;
+import org.apache.dubbo.common.config.configcenter.ConfigItem;
+import org.apache.dubbo.common.config.configcenter.ConfigurationListener;
+import org.apache.dubbo.common.config.configcenter.DynamicConfiguration;
+import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.common.utils.MD5Utils;
+import org.apache.dubbo.common.utils.StringUtils;
 
 import java.lang.reflect.Field;
 import java.util.HashMap;
@@ -177,6 +176,11 @@ public class NacosDynamicConfiguration implements DynamicConfiguration {
     }
 
     @Override
+    public void close() throws Exception {
+        configService.shutdown();
+    }
+
+    @Override
     public void addListener(String key, String group, ConfigurationListener listener) {
         String listenerKey = buildListenerKey(key, group);
         NacosConfigListener nacosConfigListener =
diff --git a/dubbo-configcenter/dubbo-configcenter-zookeeper/src/main/java/org/apache/dubbo/configcenter/support/zookeeper/ZookeeperDynamicConfiguration.java b/dubbo-configcenter/dubbo-configcenter-zookeeper/src/main/java/org/apache/dubbo/configcenter/support/zookeeper/ZookeeperDynamicConfiguration.java
index 0ab06d2..d755fd8 100644
--- a/dubbo-configcenter/dubbo-configcenter-zookeeper/src/main/java/org/apache/dubbo/configcenter/support/zookeeper/ZookeeperDynamicConfiguration.java
+++ b/dubbo-configcenter/dubbo-configcenter-zookeeper/src/main/java/org/apache/dubbo/configcenter/support/zookeeper/ZookeeperDynamicConfiguration.java
@@ -25,7 +25,6 @@ import org.apache.dubbo.common.utils.CollectionUtils;
 import org.apache.dubbo.common.utils.NamedThreadFactory;
 import org.apache.dubbo.remoting.zookeeper.ZookeeperClient;
 import org.apache.dubbo.remoting.zookeeper.ZookeeperTransporter;
-
 import org.apache.zookeeper.data.Stat;
 
 import java.util.Collection;
@@ -43,7 +42,7 @@ public class ZookeeperDynamicConfiguration extends TreePathDynamicConfiguration
     private Executor executor;
     // The final root path would be: /configRootPath/"config"
     private String rootPath;
-    private final ZookeeperClient zkClient;
+    private ZookeeperClient zkClient;
 
     private CacheListener cacheListener;
     private URL url;
@@ -83,7 +82,11 @@ public class ZookeeperDynamicConfiguration extends TreePathDynamicConfiguration
 
     @Override
     protected void doClose() throws Exception {
-        zkClient.close();
+        // zkClient is shared in framework, should not close it here
+        // zkClient.close();
+        // See: org.apache.dubbo.remoting.zookeeper.AbstractZookeeperTransporter#destroy()
+        // All zk clients is created and destroyed in ZookeeperTransporter.
+        zkClient = null;
     }
 
     @Override
diff --git a/dubbo-configcenter/dubbo-configcenter-zookeeper/src/main/java/org/apache/dubbo/configcenter/support/zookeeper/ZookeeperDynamicConfigurationFactory.java b/dubbo-configcenter/dubbo-configcenter-zookeeper/src/main/java/org/apache/dubbo/configcenter/support/zookeeper/ZookeeperDynamicConfigurationFactory.java
index 6dcedd9..309cf43 100644
--- a/dubbo-configcenter/dubbo-configcenter-zookeeper/src/main/java/org/apache/dubbo/configcenter/support/zookeeper/ZookeeperDynamicConfigurationFactory.java
+++ b/dubbo-configcenter/dubbo-configcenter-zookeeper/src/main/java/org/apache/dubbo/configcenter/support/zookeeper/ZookeeperDynamicConfigurationFactory.java
@@ -21,6 +21,7 @@ import org.apache.dubbo.common.config.configcenter.AbstractDynamicConfigurationF
 import org.apache.dubbo.common.config.configcenter.DynamicConfiguration;
 import org.apache.dubbo.common.extension.DisableInject;
 import org.apache.dubbo.remoting.zookeeper.ZookeeperTransporter;
+import org.apache.dubbo.rpc.model.ApplicationModel;
 
 /**
  *
@@ -29,8 +30,11 @@ public class ZookeeperDynamicConfigurationFactory extends AbstractDynamicConfigu
 
     private ZookeeperTransporter zookeeperTransporter;
 
-    public ZookeeperDynamicConfigurationFactory() {
-        this.zookeeperTransporter = ZookeeperTransporter.getExtension();
+    private ApplicationModel applicationModel;
+
+    public ZookeeperDynamicConfigurationFactory(ApplicationModel applicationModel) {
+        this.applicationModel = applicationModel;
+        this.zookeeperTransporter = ZookeeperTransporter.getExtension(applicationModel);
     }
 
     @DisableInject
diff --git a/dubbo-configcenter/pom.xml b/dubbo-configcenter/pom.xml
index f69f389..b27bae4 100644
--- a/dubbo-configcenter/pom.xml
+++ b/dubbo-configcenter/pom.xml
@@ -35,4 +35,13 @@
         <module>dubbo-configcenter-apollo</module>
         <module>dubbo-configcenter-nacos</module>
     </modules>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-test-check</artifactId>
+            <version>${project.parent.version}</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
 </project>
diff --git a/dubbo-filter/pom.xml b/dubbo-filter/pom.xml
index 9b588c5..4da7c40 100644
--- a/dubbo-filter/pom.xml
+++ b/dubbo-filter/pom.xml
@@ -33,4 +33,13 @@
         <module>dubbo-filter-cache</module>
         <module>dubbo-filter-validation</module>
     </modules>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-test-check</artifactId>
+            <version>${project.parent.version}</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
 </project>
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/MetadataInfo.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/MetadataInfo.java
index e9f53ed..b445d15 100644
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/MetadataInfo.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/MetadataInfo.java
@@ -18,6 +18,8 @@ package org.apache.dubbo.metadata;
 
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.extension.ExtensionLoader;
+import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.url.component.URLParam;
 import org.apache.dubbo.common.utils.ArrayUtils;
 import org.apache.dubbo.common.utils.CollectionUtils;
@@ -51,6 +53,7 @@ import static org.apache.dubbo.rpc.Constants.INTERFACES;
 
 public class MetadataInfo implements Serializable {
     public static final MetadataInfo EMPTY = new MetadataInfo();
+    private static final Logger logger = LoggerFactory.getLogger(MetadataInfo.class);
 
     private String app;
     private String revision;
@@ -115,7 +118,13 @@ public class MetadataInfo implements Serializable {
             for (Map.Entry<String, ServiceInfo> entry : new TreeMap<>(services).entrySet()) {
                 sb.append(entry.getValue().toDescString());
             }
-            this.revision = RevisionResolver.calRevision(sb.toString());
+            String tempRevision = RevisionResolver.calRevision(sb.toString());
+            if (!StringUtils.isEquals(this.revision, tempRevision)) {
+                if (logger.isInfoEnabled()) {
+                    logger.info(String.format("metadata revision changed: %s -> %s, app: %s, services: %d", this.revision, tempRevision, this.app, this.services.size()));
+                }
+                this.revision = tempRevision;
+            }
         }
         return revision;
     }
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/MetadataReportFactory.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/MetadataReportFactory.java
index 1414c8b..78f98f6 100644
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/MetadataReportFactory.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/MetadataReportFactory.java
@@ -27,4 +27,7 @@ public interface MetadataReportFactory {
 
     @Adaptive({"protocol"})
     MetadataReport getMetadataReport(URL url);
+
+    default void destroy() {
+    }
 }
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/support/AbstractMetadataReport.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/support/AbstractMetadataReport.java
index 986b8a6..4f18491 100644
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/support/AbstractMetadataReport.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/support/AbstractMetadataReport.java
@@ -16,6 +16,8 @@
  */
 package org.apache.dubbo.metadata.report.support;
 
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.logger.Logger;
 import org.apache.dubbo.common.logger.LoggerFactory;
@@ -30,9 +32,6 @@ import org.apache.dubbo.metadata.report.identifier.MetadataIdentifier;
 import org.apache.dubbo.metadata.report.identifier.ServiceMetadataIdentifier;
 import org.apache.dubbo.metadata.report.identifier.SubscriberMetadataIdentifier;
 
-import com.google.gson.Gson;
-import com.google.gson.reflect.TypeToken;
-
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
@@ -101,6 +100,7 @@ public abstract class AbstractMetadataReport implements MetadataReport {
     File file;
     private AtomicBoolean initialized = new AtomicBoolean(false);
     public MetadataReportRetry metadataReportRetry;
+    private ScheduledExecutorService reportTimerScheduler;
 
     public AbstractMetadataReport(URL reportServerURL) {
         setUrl(reportServerURL);
@@ -129,8 +129,8 @@ public abstract class AbstractMetadataReport implements MetadataReport {
             reportServerURL.getParameter(RETRY_PERIOD_KEY, DEFAULT_METADATA_REPORT_RETRY_PERIOD));
         // cycle report the data switch
         if (reportServerURL.getParameter(CYCLE_REPORT_KEY, DEFAULT_METADATA_REPORT_CYCLE_REPORT)) {
-            ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(new NamedThreadFactory("DubboMetadataReportTimer", true));
-            scheduler.scheduleAtFixedRate(this::publishAll, calculateStartTime(), ONE_DAY_IN_MILLISECONDS, TimeUnit.MILLISECONDS);
+            reportTimerScheduler = Executors.newSingleThreadScheduledExecutor(new NamedThreadFactory("DubboMetadataReportTimer", true));
+            reportTimerScheduler.scheduleAtFixedRate(this::publishAll, calculateStartTime(), ONE_DAY_IN_MILLISECONDS, TimeUnit.MILLISECONDS);
         }
     }
 
@@ -302,8 +302,12 @@ public abstract class AbstractMetadataReport implements MetadataReport {
         if (reportCacheExecutor != null) {
             reportCacheExecutor.shutdown();
         }
+        if (reportTimerScheduler != null) {
+            reportTimerScheduler.shutdown();
+        }
         if (metadataReportRetry != null) {
             metadataReportRetry.destroy();
+            metadataReportRetry = null;
         }
     }
 
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/support/AbstractMetadataReportFactory.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/support/AbstractMetadataReportFactory.java
index 29f21d8..b2a5142 100644
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/support/AbstractMetadataReportFactory.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/support/AbstractMetadataReportFactory.java
@@ -37,14 +37,12 @@ public abstract class AbstractMetadataReportFactory implements MetadataReportFac
     /**
      * The lock for the acquisition process of the registry
      */
-    private static final ReentrantLock LOCK = new ReentrantLock();
+    private final ReentrantLock lock = new ReentrantLock();
 
     /**
      * Registry Collection Map<metadataAddress, MetadataReport>
      */
-    private static final Map<String, MetadataReport> SERVICE_STORE_MAP = new ConcurrentHashMap<>();
-
-    private static final Logger LOGGER = LoggerFactory.getLogger(AbstractMetadataReportFactory.class);
+    private final Map<String, MetadataReport> serviceStoreMap = new ConcurrentHashMap<>();
 
     @Override
     public MetadataReport getMetadataReport(URL url) {
@@ -52,15 +50,15 @@ public abstract class AbstractMetadataReportFactory implements MetadataReportFac
             .removeParameters(EXPORT_KEY, REFER_KEY);
         String key = url.toServiceString();
 
-        MetadataReport metadataReport = SERVICE_STORE_MAP.get(key);
+        MetadataReport metadataReport = serviceStoreMap.get(key);
         if (metadataReport != null) {
             return metadataReport;
         }
 
         // Lock the metadata access process to ensure a single instance of the metadata instance
-        LOCK.lock();
+        lock.lock();
         try {
-            metadataReport = SERVICE_STORE_MAP.get(key);
+            metadataReport = serviceStoreMap.get(key);
             if (metadataReport != null) {
                 return metadataReport;
             }
@@ -69,7 +67,7 @@ public abstract class AbstractMetadataReportFactory implements MetadataReportFac
                 metadataReport = createMetadataReport(url);
             } catch (Exception e) {
                 if (!check) {
-                    LOGGER.warn("The metadata reporter failed to initialize", e);
+                    logger.warn("The metadata reporter failed to initialize", e);
                 } else {
                     throw e;
                 }
@@ -79,19 +77,20 @@ public abstract class AbstractMetadataReportFactory implements MetadataReportFac
                 throw new IllegalStateException("Can not create metadata Report " + url);
             }
             if (metadataReport != null) {
-                SERVICE_STORE_MAP.put(key, metadataReport);
+                serviceStoreMap.put(key, metadataReport);
             }
             return metadataReport;
         } finally {
             // Release the lock
-            LOCK.unlock();
+            lock.unlock();
         }
     }
 
-    public static void destroy() {
-        LOCK.lock();
+    @Override
+    public void destroy() {
+        lock.lock();
         try {
-            for (MetadataReport metadataReport : SERVICE_STORE_MAP.values()) {
+            for (MetadataReport metadataReport : serviceStoreMap.values()) {
                 try{
                     metadataReport.destroy();
                 }catch (Throwable ignored){
@@ -99,11 +98,10 @@ public abstract class AbstractMetadataReportFactory implements MetadataReportFac
                     logger.warn(ignored.getMessage(),ignored);
                 }
             }
-            SERVICE_STORE_MAP.clear();
+            serviceStoreMap.clear();
         } finally {
-            LOCK.unlock();
+            lock.unlock();
         }
-
     }
 
     protected abstract MetadataReport createMetadataReport(URL url);
diff --git a/dubbo-metadata/dubbo-metadata-report-zookeeper/src/main/java/org/apache/dubbo/metadata/store/zookeeper/ZookeeperMetadataReport.java b/dubbo-metadata/dubbo-metadata-report-zookeeper/src/main/java/org/apache/dubbo/metadata/store/zookeeper/ZookeeperMetadataReport.java
index 1e36894..78a1909 100644
--- a/dubbo-metadata/dubbo-metadata-report-zookeeper/src/main/java/org/apache/dubbo/metadata/store/zookeeper/ZookeeperMetadataReport.java
+++ b/dubbo-metadata/dubbo-metadata-report-zookeeper/src/main/java/org/apache/dubbo/metadata/store/zookeeper/ZookeeperMetadataReport.java
@@ -16,6 +16,7 @@
  */
 package org.apache.dubbo.metadata.store.zookeeper;
 
+import com.google.gson.Gson;
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.config.configcenter.ConfigItem;
 import org.apache.dubbo.common.logger.Logger;
@@ -34,8 +35,6 @@ import org.apache.dubbo.remoting.zookeeper.DataListener;
 import org.apache.dubbo.remoting.zookeeper.EventType;
 import org.apache.dubbo.remoting.zookeeper.ZookeeperClient;
 import org.apache.dubbo.remoting.zookeeper.ZookeeperTransporter;
-
-import com.google.gson.Gson;
 import org.apache.zookeeper.data.Stat;
 
 import java.util.ArrayList;
@@ -59,7 +58,7 @@ public class ZookeeperMetadataReport extends AbstractMetadataReport {
 
     private final String root;
 
-    final ZookeeperClient zkClient;
+    ZookeeperClient zkClient;
 
     private Gson gson = new Gson();
 
@@ -188,6 +187,13 @@ public class ZookeeperMetadataReport extends AbstractMetadataReport {
         }
     }
 
+    @Override
+    public void destroy() {
+        super.destroy();
+        // release zk client reference, but should not close it
+        zkClient = null;
+    }
+
     private String buildPathKey(String group, String serviceKey) {
         return toRootDir() + group + PATH_SEPARATOR + serviceKey;
     }
diff --git a/dubbo-metadata/dubbo-metadata-report-zookeeper/src/main/java/org/apache/dubbo/metadata/store/zookeeper/ZookeeperMetadataReportFactory.java b/dubbo-metadata/dubbo-metadata-report-zookeeper/src/main/java/org/apache/dubbo/metadata/store/zookeeper/ZookeeperMetadataReportFactory.java
index ee3e2d9..c628fc8 100644
--- a/dubbo-metadata/dubbo-metadata-report-zookeeper/src/main/java/org/apache/dubbo/metadata/store/zookeeper/ZookeeperMetadataReportFactory.java
+++ b/dubbo-metadata/dubbo-metadata-report-zookeeper/src/main/java/org/apache/dubbo/metadata/store/zookeeper/ZookeeperMetadataReportFactory.java
@@ -21,6 +21,7 @@ import org.apache.dubbo.common.extension.DisableInject;
 import org.apache.dubbo.metadata.report.MetadataReport;
 import org.apache.dubbo.metadata.report.support.AbstractMetadataReportFactory;
 import org.apache.dubbo.remoting.zookeeper.ZookeeperTransporter;
+import org.apache.dubbo.rpc.model.ApplicationModel;
 
 /**
  * ZookeeperRegistryFactory.
@@ -29,8 +30,11 @@ public class ZookeeperMetadataReportFactory extends AbstractMetadataReportFactor
 
     private ZookeeperTransporter zookeeperTransporter;
 
-    public ZookeeperMetadataReportFactory() {
-        this.zookeeperTransporter = ZookeeperTransporter.getExtension();
+    private ApplicationModel applicationModel;
+
+    public ZookeeperMetadataReportFactory(ApplicationModel applicationModel) {
+        this.applicationModel = applicationModel;
+        this.zookeeperTransporter = ZookeeperTransporter.getExtension(applicationModel);
     }
 
     @DisableInject
diff --git a/dubbo-metadata/dubbo-metadata-report-zookeeper/src/test/java/org/apache/dubbo/metadata/store/zookeeper/ZookeeperMetadataReportTest.java b/dubbo-metadata/dubbo-metadata-report-zookeeper/src/test/java/org/apache/dubbo/metadata/store/zookeeper/ZookeeperMetadataReportTest.java
index b77897c..57cc6ee 100644
--- a/dubbo-metadata/dubbo-metadata-report-zookeeper/src/test/java/org/apache/dubbo/metadata/store/zookeeper/ZookeeperMetadataReportTest.java
+++ b/dubbo-metadata/dubbo-metadata-report-zookeeper/src/test/java/org/apache/dubbo/metadata/store/zookeeper/ZookeeperMetadataReportTest.java
@@ -16,6 +16,8 @@
  */
 package org.apache.dubbo.metadata.store.zookeeper;
 
+import com.google.gson.Gson;
+import org.apache.curator.test.TestingServer;
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.config.configcenter.ConfigItem;
 import org.apache.dubbo.common.utils.NetUtils;
@@ -27,15 +29,18 @@ import org.apache.dubbo.metadata.report.identifier.KeyTypeEnum;
 import org.apache.dubbo.metadata.report.identifier.MetadataIdentifier;
 import org.apache.dubbo.metadata.report.identifier.ServiceMetadataIdentifier;
 import org.apache.dubbo.metadata.report.identifier.SubscriberMetadataIdentifier;
-
-import com.google.gson.Gson;
-import org.apache.curator.test.TestingServer;
+import org.apache.dubbo.rpc.model.ApplicationModel;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
-import java.util.*;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutionException;
 
@@ -58,7 +63,7 @@ public class ZookeeperMetadataReportTest {
         this.zkServer = new TestingServer(zkServerPort, true);
         this.registryUrl = URL.valueOf("zookeeper://127.0.0.1:" + zkServerPort);
 
-        zookeeperMetadataReportFactory = new ZookeeperMetadataReportFactory();
+        zookeeperMetadataReportFactory = new ZookeeperMetadataReportFactory(ApplicationModel.defaultModel());
         this.zookeeperMetadataReport = (ZookeeperMetadataReport) zookeeperMetadataReportFactory.getMetadataReport(registryUrl);
     }
 
diff --git a/dubbo-metadata/pom.xml b/dubbo-metadata/pom.xml
index 9fdffe9..2eec45e 100644
--- a/dubbo-metadata/pom.xml
+++ b/dubbo-metadata/pom.xml
@@ -36,4 +36,12 @@
         <module>dubbo-metadata-report-nacos</module>
     </modules>
 
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-test-check</artifactId>
+            <version>${project.parent.version}</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
 </project>
diff --git a/dubbo-metrics/pom.xml b/dubbo-metrics/pom.xml
index 1f0d32a..8c4291a 100644
--- a/dubbo-metrics/pom.xml
+++ b/dubbo-metrics/pom.xml
@@ -33,4 +33,13 @@
     <properties>
         <skip_maven_deploy>false</skip_maven_deploy>
     </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-test-check</artifactId>
+            <version>${project.parent.version}</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
 </project>
diff --git a/dubbo-monitor/dubbo-monitor-default/src/main/java/org/apache/dubbo/monitor/dubbo/DubboMonitor.java b/dubbo-monitor/dubbo-monitor-default/src/main/java/org/apache/dubbo/monitor/dubbo/DubboMonitor.java
index ea32fc5..c46d02b 100644
--- a/dubbo-monitor/dubbo-monitor-default/src/main/java/org/apache/dubbo/monitor/dubbo/DubboMonitor.java
+++ b/dubbo-monitor/dubbo-monitor-default/src/main/java/org/apache/dubbo/monitor/dubbo/DubboMonitor.java
@@ -20,7 +20,6 @@ import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.logger.Logger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.utils.ExecutorUtil;
-import org.apache.dubbo.common.utils.NamedThreadFactory;
 import org.apache.dubbo.monitor.Monitor;
 import org.apache.dubbo.monitor.MonitorService;
 import org.apache.dubbo.rpc.Invoker;
@@ -29,7 +28,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.TimeUnit;
@@ -52,7 +50,7 @@ public class DubboMonitor implements Monitor {
     /**
      * The timer for sending statistics
      */
-    private final ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3, new NamedThreadFactory("DubboMonitorSendTimer", true));
+    private final ScheduledExecutorService scheduledExecutorService;
 
     /**
      * The future that can cancel the <b>scheduledExecutorService</b>
@@ -68,6 +66,7 @@ public class DubboMonitor implements Monitor {
     public DubboMonitor(Invoker<MonitorService> monitorInvoker, MonitorService monitorService) {
         this.monitorInvoker = monitorInvoker;
         this.monitorService = monitorService;
+        scheduledExecutorService = monitorInvoker.getUrl().getOrDefaultApplicationModel().getApplicationExecutorRepository().getSharedScheduledExecutor();
         // The time interval for timer <b>scheduledExecutorService</b> to send data
         final long monitorInterval = monitorInvoker.getUrl().getPositiveParameter("interval", 60000);
         // collect timer for collecting statistics data
diff --git a/dubbo-monitor/dubbo-monitor-default/src/test/resources/log4j.properties b/dubbo-monitor/dubbo-monitor-default/src/test/resources/log4j.properties
new file mode 100644
index 0000000..8de4c4f
--- /dev/null
+++ b/dubbo-monitor/dubbo-monitor-default/src/test/resources/log4j.properties
@@ -0,0 +1,7 @@
+###set log levels###
+log4j.rootLogger=info, stdout
+###output to the console###
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.Target=System.out
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=[%d{dd/MM/yy HH:mm:ss:SSS z}] %t %5p %c{2}: %m%n
diff --git a/dubbo-monitor/pom.xml b/dubbo-monitor/pom.xml
index 4e3b53c..735bf37 100644
--- a/dubbo-monitor/pom.xml
+++ b/dubbo-monitor/pom.xml
@@ -33,4 +33,14 @@
         <module>dubbo-monitor-api</module>
         <module>dubbo-monitor-default</module>
     </modules>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-test-check</artifactId>
+            <version>${project.parent.version}</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
 </project>
diff --git a/dubbo-native/pom.xml b/dubbo-native/pom.xml
index fc70304..727e58c 100644
--- a/dubbo-native/pom.xml
+++ b/dubbo-native/pom.xml
@@ -64,7 +64,12 @@
             <version>${project.parent.version}</version>
         </dependency>
 
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-test-check</artifactId>
+            <version>${project.parent.version}</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
-
 </project>
diff --git a/dubbo-native/src/main/java/org/apache/dubbo/common/serialize/Serialization$Adaptive.java b/dubbo-native/src/main/java/org/apache/dubbo/common/serialize/Serialization$Adaptive.java
index 088c1ba..9bf1988 100644
--- a/dubbo-native/src/main/java/org/apache/dubbo/common/serialize/Serialization$Adaptive.java
+++ b/dubbo-native/src/main/java/org/apache/dubbo/common/serialize/Serialization$Adaptive.java
@@ -21,25 +21,25 @@ public class Serialization$Adaptive implements org.apache.dubbo.common.serialize
 public byte getContentTypeId()  {
 throw new UnsupportedOperationException("The method public abstract byte org.apache.dubbo.common.serialize.Serialization.getContentTypeId() of interface org.apache.dubbo.common.serialize.Serialization is not adaptive method!");
 }
-public org.apache.dubbo.common.serialize.ObjectOutput serialize(org.apache.dubbo.common.URL arg0, java.io.OutputStream arg1) throws java.io.IOException {
+public java.lang.String getContentType()  {
+throw new UnsupportedOperationException("The method public abstract java.lang.String org.apache.dubbo.common.serialize.Serialization.getContentType() of interface org.apache.dubbo.common.serialize.Serialization is not adaptive method!");
+}
+public org.apache.dubbo.common.serialize.ObjectInput deserialize(org.apache.dubbo.common.URL arg0, java.io.InputStream arg1) throws java.io.IOException {
 if (arg0 == null) throw new IllegalArgumentException("url == null");
 org.apache.dubbo.common.URL url = arg0;
 String extName = url.getParameter("serialization", "hessian2");
 if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.common.serialize.Serialization) name from url (" + url.toString() + ") use keys([serialization])");
 ScopeModel scopeModel = ScopeModelUtil.getOrDefault(url.getScopeModel(), org.apache.dubbo.common.serialize.Serialization.class);
 org.apache.dubbo.common.serialize.Serialization extension = (org.apache.dubbo.common.serialize.Serialization)scopeModel.getExtensionLoader(org.apache.dubbo.common.serialize.Serialization.class).getExtension(extName);
-return extension.serialize(arg0, arg1);
+return extension.deserialize(arg0, arg1);
 }
-public org.apache.dubbo.common.serialize.ObjectInput deserialize(org.apache.dubbo.common.URL arg0, java.io.InputStream arg1) throws java.io.IOException {
+public org.apache.dubbo.common.serialize.ObjectOutput serialize(org.apache.dubbo.common.URL arg0, java.io.OutputStream arg1) throws java.io.IOException {
 if (arg0 == null) throw new IllegalArgumentException("url == null");
 org.apache.dubbo.common.URL url = arg0;
 String extName = url.getParameter("serialization", "hessian2");
 if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.common.serialize.Serialization) name from url (" + url.toString() + ") use keys([serialization])");
 ScopeModel scopeModel = ScopeModelUtil.getOrDefault(url.getScopeModel(), org.apache.dubbo.common.serialize.Serialization.class);
 org.apache.dubbo.common.serialize.Serialization extension = (org.apache.dubbo.common.serialize.Serialization)scopeModel.getExtensionLoader(org.apache.dubbo.common.serialize.Serialization.class).getExtension(extName);
-return extension.deserialize(arg0, arg1);
-}
-public java.lang.String getContentType()  {
-throw new UnsupportedOperationException("The method public abstract java.lang.String org.apache.dubbo.common.serialize.Serialization.getContentType() of interface org.apache.dubbo.common.serialize.Serialization is not adaptive method!");
+return extension.serialize(arg0, arg1);
 }
 }
diff --git a/dubbo-native/src/main/java/org/apache/dubbo/metadata/report/MetadataReportFactory$Adaptive.java b/dubbo-native/src/main/java/org/apache/dubbo/metadata/report/MetadataReportFactory$Adaptive.java
index 3e5ad9b..069e3b9 100644
--- a/dubbo-native/src/main/java/org/apache/dubbo/metadata/report/MetadataReportFactory$Adaptive.java
+++ b/dubbo-native/src/main/java/org/apache/dubbo/metadata/report/MetadataReportFactory$Adaptive.java
@@ -27,4 +27,7 @@ ScopeModel scopeModel = ScopeModelUtil.getOrDefault(url.getScopeModel(), org.apa
 org.apache.dubbo.metadata.report.MetadataReportFactory extension = (org.apache.dubbo.metadata.report.MetadataReportFactory)scopeModel.getExtensionLoader(org.apache.dubbo.metadata.report.MetadataReportFactory.class).getExtension(extName);
 return extension.getMetadataReport(arg0);
 }
+public void destroy()  {
+throw new UnsupportedOperationException("The method public abstract void org.apache.dubbo.metadata.report.MetadataReportFactory.destroy() of interface org.apache.dubbo.metadata.report.MetadataReportFactory is not adaptive method!");
+}
 }
diff --git a/dubbo-native/src/main/java/org/apache/dubbo/remoting/zookeeper/ZookeeperTransporter$Adaptive.java b/dubbo-native/src/main/java/org/apache/dubbo/remoting/zookeeper/ZookeeperTransporter$Adaptive.java
deleted file mode 100644
index aeed407..0000000
--- a/dubbo-native/src/main/java/org/apache/dubbo/remoting/zookeeper/ZookeeperTransporter$Adaptive.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.dubbo.remoting.zookeeper;
-import org.apache.dubbo.common.extension.ExtensionLoader;
-public class ZookeeperTransporter$Adaptive implements org.apache.dubbo.remoting.zookeeper.ZookeeperTransporter {
-public org.apache.dubbo.remoting.zookeeper.ZookeeperClient connect(org.apache.dubbo.common.URL arg0)  {
-if (arg0 == null) throw new IllegalArgumentException("url == null");
-org.apache.dubbo.common.URL url = arg0;
-String extName = url.getParameter("client", url.getParameter("transporter", "curator"));
-if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.remoting.zookeeper.ZookeeperTransporter) name from url (" + url.toString() + ") use keys([client, transporter])");
-org.apache.dubbo.remoting.zookeeper.ZookeeperTransporter extension = (org.apache.dubbo.remoting.zookeeper.ZookeeperTransporter)ExtensionLoader.getExtensionLoader(org.apache.dubbo.remoting.zookeeper.ZookeeperTransporter.class).getExtension(extName);
-return extension.connect(arg0);
-}
-}
diff --git a/dubbo-native/src/main/java/org/apache/dubbo/rpc/Protocol$Adaptive.java b/dubbo-native/src/main/java/org/apache/dubbo/rpc/Protocol$Adaptive.java
index fe16e16..16367fa 100644
--- a/dubbo-native/src/main/java/org/apache/dubbo/rpc/Protocol$Adaptive.java
+++ b/dubbo-native/src/main/java/org/apache/dubbo/rpc/Protocol$Adaptive.java
@@ -18,6 +18,9 @@ package org.apache.dubbo.rpc;
 import org.apache.dubbo.rpc.model.ScopeModel;
 import org.apache.dubbo.rpc.model.ScopeModelUtil;
 public class Protocol$Adaptive implements org.apache.dubbo.rpc.Protocol {
+public int getDefaultPort()  {
+throw new UnsupportedOperationException("The method public abstract int org.apache.dubbo.rpc.Protocol.getDefaultPort() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
+}
 public org.apache.dubbo.rpc.Exporter export(org.apache.dubbo.rpc.Invoker arg0) throws org.apache.dubbo.rpc.RpcException {
 if (arg0 == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
 if (arg0.getUrl() == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");
@@ -37,13 +40,10 @@ ScopeModel scopeModel = ScopeModelUtil.getOrDefault(url.getScopeModel(), org.apa
 org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol)scopeModel.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
 return extension.refer(arg0, arg1);
 }
-public java.util.List getServers()  {
-throw new UnsupportedOperationException("The method public default java.util.List org.apache.dubbo.rpc.Protocol.getServers() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
-}
 public void destroy()  {
 throw new UnsupportedOperationException("The method public abstract void org.apache.dubbo.rpc.Protocol.destroy() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
 }
-public int getDefaultPort()  {
-throw new UnsupportedOperationException("The method public abstract int org.apache.dubbo.rpc.Protocol.getDefaultPort() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
+public java.util.List getServers()  {
+throw new UnsupportedOperationException("The method public default java.util.List org.apache.dubbo.rpc.Protocol.getServers() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
 }
 }
diff --git a/dubbo-native/src/main/java/org/apache/dubbo/rpc/ProxyFactory$Adaptive.java b/dubbo-native/src/main/java/org/apache/dubbo/rpc/ProxyFactory$Adaptive.java
index 297287f..101925d 100644
--- a/dubbo-native/src/main/java/org/apache/dubbo/rpc/ProxyFactory$Adaptive.java
+++ b/dubbo-native/src/main/java/org/apache/dubbo/rpc/ProxyFactory$Adaptive.java
@@ -18,14 +18,15 @@ package org.apache.dubbo.rpc;
 import org.apache.dubbo.rpc.model.ScopeModel;
 import org.apache.dubbo.rpc.model.ScopeModelUtil;
 public class ProxyFactory$Adaptive implements org.apache.dubbo.rpc.ProxyFactory {
-public org.apache.dubbo.rpc.Invoker getInvoker(java.lang.Object arg0, java.lang.Class arg1, org.apache.dubbo.common.URL arg2) throws org.apache.dubbo.rpc.RpcException {
-if (arg2 == null) throw new IllegalArgumentException("url == null");
-org.apache.dubbo.common.URL url = arg2;
+public java.lang.Object getProxy(org.apache.dubbo.rpc.Invoker arg0) throws org.apache.dubbo.rpc.RpcException {
+if (arg0 == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
+if (arg0.getUrl() == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");
+org.apache.dubbo.common.URL url = arg0.getUrl();
 String extName = url.getParameter("proxy", "javassist");
 if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.ProxyFactory) name from url (" + url.toString() + ") use keys([proxy])");
 ScopeModel scopeModel = ScopeModelUtil.getOrDefault(url.getScopeModel(), org.apache.dubbo.rpc.ProxyFactory.class);
 org.apache.dubbo.rpc.ProxyFactory extension = (org.apache.dubbo.rpc.ProxyFactory)scopeModel.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension(extName);
-return extension.getInvoker(arg0, arg1, arg2);
+return extension.getProxy(arg0);
 }
 public java.lang.Object getProxy(org.apache.dubbo.rpc.Invoker arg0, boolean arg1) throws org.apache.dubbo.rpc.RpcException {
 if (arg0 == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
@@ -37,14 +38,13 @@ ScopeModel scopeModel = ScopeModelUtil.getOrDefault(url.getScopeModel(), org.apa
 org.apache.dubbo.rpc.ProxyFactory extension = (org.apache.dubbo.rpc.ProxyFactory)scopeModel.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension(extName);
 return extension.getProxy(arg0, arg1);
 }
-public java.lang.Object getProxy(org.apache.dubbo.rpc.Invoker arg0) throws org.apache.dubbo.rpc.RpcException {
-if (arg0 == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
-if (arg0.getUrl() == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");
-org.apache.dubbo.common.URL url = arg0.getUrl();
+public org.apache.dubbo.rpc.Invoker getInvoker(java.lang.Object arg0, java.lang.Class arg1, org.apache.dubbo.common.URL arg2) throws org.apache.dubbo.rpc.RpcException {
+if (arg2 == null) throw new IllegalArgumentException("url == null");
+org.apache.dubbo.common.URL url = arg2;
 String extName = url.getParameter("proxy", "javassist");
 if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.ProxyFactory) name from url (" + url.toString() + ") use keys([proxy])");
 ScopeModel scopeModel = ScopeModelUtil.getOrDefault(url.getScopeModel(), org.apache.dubbo.rpc.ProxyFactory.class);
 org.apache.dubbo.rpc.ProxyFactory extension = (org.apache.dubbo.rpc.ProxyFactory)scopeModel.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension(extName);
-return extension.getProxy(arg0);
+return extension.getInvoker(arg0, arg1, arg2);
 }
 }
diff --git a/dubbo-native/src/main/java/org/apache/dubbo/rpc/cluster/Cluster$Adaptive.java b/dubbo-native/src/main/java/org/apache/dubbo/rpc/cluster/Cluster$Adaptive.java
index 5f80166..e8b087d 100644
--- a/dubbo-native/src/main/java/org/apache/dubbo/rpc/cluster/Cluster$Adaptive.java
+++ b/dubbo-native/src/main/java/org/apache/dubbo/rpc/cluster/Cluster$Adaptive.java
@@ -18,12 +18,6 @@ package org.apache.dubbo.rpc.cluster;
 import org.apache.dubbo.rpc.model.ScopeModel;
 import org.apache.dubbo.rpc.model.ScopeModelUtil;
 public class Cluster$Adaptive implements org.apache.dubbo.rpc.cluster.Cluster {
-public org.apache.dubbo.rpc.cluster.Cluster getCluster(org.apache.dubbo.rpc.model.ScopeModel arg0, java.lang.String arg1)  {
-throw new UnsupportedOperationException("The method public static org.apache.dubbo.rpc.cluster.Cluster org.apache.dubbo.rpc.cluster.Cluster.getCluster(org.apache.dubbo.rpc.model.ScopeModel,java.lang.String) of interface org.apache.dubbo.rpc.cluster.Cluster is not adaptive method!");
-}
-public org.apache.dubbo.rpc.cluster.Cluster getCluster(org.apache.dubbo.rpc.model.ScopeModel arg0, java.lang.String arg1, boolean arg2)  {
-throw new UnsupportedOperationException("The method public static org.apache.dubbo.rpc.cluster.Cluster org.apache.dubbo.rpc.cluster.Cluster.getCluster(org.apache.dubbo.rpc.model.ScopeModel,java.lang.String,boolean) of interface org.apache.dubbo.rpc.cluster.Cluster is not adaptive method!");
-}
 public org.apache.dubbo.rpc.Invoker join(org.apache.dubbo.rpc.cluster.Directory arg0, boolean arg1) throws org.apache.dubbo.rpc.RpcException {
 if (arg0 == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.cluster.Directory argument == null");
 if (arg0.getUrl() == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.cluster.Directory argument getUrl() == null");
@@ -34,4 +28,10 @@ ScopeModel scopeModel = ScopeModelUtil.getOrDefault(url.getScopeModel(), org.apa
 org.apache.dubbo.rpc.cluster.Cluster extension = (org.apache.dubbo.rpc.cluster.Cluster)scopeModel.getExtensionLoader(org.apache.dubbo.rpc.cluster.Cluster.class).getExtension(extName);
 return extension.join(arg0, arg1);
 }
+public org.apache.dubbo.rpc.cluster.Cluster getCluster(org.apache.dubbo.rpc.model.ScopeModel arg0, java.lang.String arg1)  {
+throw new UnsupportedOperationException("The method public static org.apache.dubbo.rpc.cluster.Cluster org.apache.dubbo.rpc.cluster.Cluster.getCluster(org.apache.dubbo.rpc.model.ScopeModel,java.lang.String) of interface org.apache.dubbo.rpc.cluster.Cluster is not adaptive method!");
+}
+public org.apache.dubbo.rpc.cluster.Cluster getCluster(org.apache.dubbo.rpc.model.ScopeModel arg0, java.lang.String arg1, boolean arg2)  {
+throw new UnsupportedOperationException("The method public static org.apache.dubbo.rpc.cluster.Cluster org.apache.dubbo.rpc.cluster.Cluster.getCluster(org.apache.dubbo.rpc.model.ScopeModel,java.lang.String,boolean) of interface org.apache.dubbo.rpc.cluster.Cluster is not adaptive method!");
+}
 }
diff --git a/dubbo-native/src/main/java/org/apache/dubbo/utils/CodeGenerator.java b/dubbo-native/src/main/java/org/apache/dubbo/utils/CodeGenerator.java
index a632d4a..f4ca7d1 100644
--- a/dubbo-native/src/main/java/org/apache/dubbo/utils/CodeGenerator.java
+++ b/dubbo-native/src/main/java/org/apache/dubbo/utils/CodeGenerator.java
@@ -17,7 +17,6 @@
 package org.apache.dubbo.utils;
 
 import org.apache.commons.io.FileUtils;
-
 import org.apache.dubbo.common.extension.Adaptive;
 import org.apache.dubbo.common.extension.AdaptiveClassCodeGenerator;
 import org.apache.dubbo.common.extension.SPI;
@@ -69,7 +68,7 @@ public class CodeGenerator {
                 value = "adaptive";
             }
             AdaptiveClassCodeGenerator codeGenerator = new AdaptiveClassCodeGenerator(it, value);
-            String code = codeGenerator.generate();
+            String code = codeGenerator.generate(true);
             System.out.println(code);
             System.out.println("-----:" + it.getPackage());
             try {
diff --git a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/ShutdownTelnet.java b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/ShutdownTelnet.java
index 736e329..aef3983 100644
--- a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/ShutdownTelnet.java
+++ b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/ShutdownTelnet.java
@@ -17,7 +17,6 @@
 package org.apache.dubbo.qos.command.impl;
 
 import org.apache.dubbo.common.utils.StringUtils;
-import org.apache.dubbo.config.deploy.DefaultApplicationDeployer;
 import org.apache.dubbo.qos.command.BaseCommand;
 import org.apache.dubbo.qos.command.CommandContext;
 import org.apache.dubbo.qos.command.annotation.Cmd;
@@ -60,8 +59,7 @@ public class ShutdownTelnet implements BaseCommand {
         StringBuilder buf = new StringBuilder();
         List<ApplicationModel> applicationModels = frameworkModel.getApplicationModels();
         for (ApplicationModel applicationModel : new ArrayList<>(applicationModels)) {
-            DefaultApplicationDeployer deployer = applicationModel.getBeanFactory().getBean(DefaultApplicationDeployer.class);
-            deployer.destroy();
+            applicationModel.destroy();
         }
         // TODO change to ApplicationDeployer.destroy() or ApplicationModel.destroy()
 //        DubboShutdownHook.getDubboShutdownHook().unregister();
diff --git a/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/command/impl/ChangeTelnetTest.java b/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/command/impl/ChangeTelnetTest.java
index 0b08b87..5ecc778 100644
--- a/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/command/impl/ChangeTelnetTest.java
+++ b/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/command/impl/ChangeTelnetTest.java
@@ -22,7 +22,6 @@ import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.extension.ExtensionLoader;
 import org.apache.dubbo.qos.command.BaseCommand;
 import org.apache.dubbo.qos.command.CommandContext;
-import org.apache.dubbo.qos.legacy.ProtocolUtils;
 import org.apache.dubbo.qos.legacy.service.DemoService;
 import org.apache.dubbo.rpc.Invoker;
 import org.apache.dubbo.rpc.Protocol;
@@ -30,6 +29,7 @@ import org.apache.dubbo.rpc.model.FrameworkModel;
 import org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol;
 import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
@@ -40,7 +40,7 @@ import static org.mockito.Mockito.reset;
 
 public class ChangeTelnetTest {
     private final DefaultAttributeMap defaultAttributeMap = new DefaultAttributeMap();
-    private static final BaseCommand change = new ChangeTelnet(FrameworkModel.defaultModel());
+    private BaseCommand change;
 
     private Channel mockChannel;
     private CommandContext mockCommandContext;
@@ -48,12 +48,19 @@ public class ChangeTelnetTest {
 
     @AfterAll
     public static void tearDown() {
+        FrameworkModel.destroyAll();
+    }
 
+    @BeforeAll
+    public static void setUp() {
+        FrameworkModel.destroyAll();
     }
 
     @SuppressWarnings("unchecked")
     @BeforeEach
-    public void setUp() {
+    public void beforeEach() {
+        change = new ChangeTelnet(FrameworkModel.defaultModel());
+
         mockCommandContext = mock(CommandContext.class);
         mockChannel = mock(Channel.class);
         mockInvoker = mock(Invoker.class);
@@ -66,8 +73,8 @@ public class ChangeTelnetTest {
     }
 
     @AfterEach
-    public void after() {
-        ProtocolUtils.closeAll();
+    public void afterEach() {
+        FrameworkModel.destroyAll();
         reset(mockCommandContext, mockChannel, mockInvoker);
     }
 
diff --git a/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/command/impl/CountTelnetTest.java b/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/command/impl/CountTelnetTest.java
index 1c0bfc6..c28c9f5 100644
--- a/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/command/impl/CountTelnetTest.java
+++ b/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/command/impl/CountTelnetTest.java
@@ -21,7 +21,6 @@ import org.apache.dubbo.common.extension.ExtensionLoader;
 import org.apache.dubbo.qos.command.BaseCommand;
 import org.apache.dubbo.qos.command.CommandContext;
 import org.apache.dubbo.qos.command.impl.channel.MockNettyChannel;
-import org.apache.dubbo.qos.legacy.ProtocolUtils;
 import org.apache.dubbo.qos.legacy.service.DemoService;
 import org.apache.dubbo.remoting.telnet.support.TelnetUtils;
 import org.apache.dubbo.rpc.Invoker;
@@ -44,7 +43,7 @@ import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.reset;
 
 public class CountTelnetTest {
-    private static final BaseCommand count = new CountTelnet(FrameworkModel.defaultModel());
+    private BaseCommand count;
 
     private MockNettyChannel mockChannel;
     private Invoker<DemoService> mockInvoker;
@@ -56,6 +55,7 @@ public class CountTelnetTest {
 
     @BeforeEach
     public void setUp() {
+        count = new CountTelnet(FrameworkModel.defaultModel());
         latch = new CountDownLatch(2);
         mockInvoker = mock(Invoker.class);
         mockCommandContext = mock(CommandContext.class);
@@ -68,7 +68,7 @@ public class CountTelnetTest {
 
     @AfterEach
     public void tearDown() {
-        ProtocolUtils.closeAll();
+        FrameworkModel.destroyAll();
         mockChannel.close();
         reset(mockInvoker, mockCommandContext);
     }
diff --git a/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/command/impl/InvokeTelnetTest.java b/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/command/impl/InvokeTelnetTest.java
index 8fcda4c..e8a9a99 100644
--- a/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/command/impl/InvokeTelnetTest.java
+++ b/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/command/impl/InvokeTelnetTest.java
@@ -21,7 +21,6 @@ import io.netty.util.DefaultAttributeMap;
 import org.apache.dubbo.config.bootstrap.DubboBootstrap;
 import org.apache.dubbo.qos.command.BaseCommand;
 import org.apache.dubbo.qos.command.CommandContext;
-import org.apache.dubbo.qos.legacy.ProtocolUtils;
 import org.apache.dubbo.qos.legacy.service.DemoService;
 import org.apache.dubbo.qos.legacy.service.DemoServiceImpl;
 import org.apache.dubbo.remoting.RemotingException;
@@ -64,7 +63,6 @@ public class InvokeTelnetTest {
 
     @AfterEach
     public void after() {
-        ProtocolUtils.closeAll();
         frameworkModel.destroy();
         reset(mockChannel, mockCommandContext);
     }
diff --git a/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/command/impl/PortTelnetTest.java b/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/command/impl/PortTelnetTest.java
index 2752935..b3b4ecf 100644
--- a/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/command/impl/PortTelnetTest.java
+++ b/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/command/impl/PortTelnetTest.java
@@ -17,11 +17,9 @@
 package org.apache.dubbo.qos.command.impl;
 
 import org.apache.dubbo.common.URL;
-import org.apache.dubbo.common.extension.ExtensionLoader;
 import org.apache.dubbo.common.utils.NetUtils;
 import org.apache.dubbo.qos.command.BaseCommand;
 import org.apache.dubbo.qos.command.CommandContext;
-import org.apache.dubbo.qos.legacy.ProtocolUtils;
 import org.apache.dubbo.qos.legacy.service.DemoService;
 import org.apache.dubbo.remoting.RemotingException;
 import org.apache.dubbo.remoting.exchange.ExchangeClient;
@@ -41,7 +39,7 @@ import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.reset;
 
 public class PortTelnetTest {
-    private static final BaseCommand port = new PortTelnet(FrameworkModel.defaultModel());
+    private BaseCommand port;
 
     private Invoker<DemoService> mockInvoker;
     private CommandContext mockCommandContext;
@@ -51,17 +49,19 @@ public class PortTelnetTest {
     @SuppressWarnings("unchecked")
     @BeforeEach
     public void before() {
+        FrameworkModel frameworkModel = FrameworkModel.defaultModel();
+        port = new PortTelnet(frameworkModel);
         mockCommandContext = mock(CommandContext.class);
         mockInvoker = mock(Invoker.class);
         given(mockInvoker.getInterface()).willReturn(DemoService.class);
         given(mockInvoker.getUrl()).willReturn(URL.valueOf("dubbo://127.0.0.1:" + availablePort + "/demo"));
 
-        ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(DubboProtocol.NAME).export(mockInvoker);
+        frameworkModel.getExtensionLoader(Protocol.class).getExtension(DubboProtocol.NAME).export(mockInvoker);
     }
 
     @AfterEach
-    public void after() {
-        ProtocolUtils.closeAll();
+    public void afterEach() {
+        FrameworkModel.destroyAll();
         reset(mockInvoker, mockCommandContext);
     }
 
diff --git a/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/command/impl/PwdTelnetTest.java b/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/command/impl/PwdTelnetTest.java
index eb5046a..e66fb54 100644
--- a/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/command/impl/PwdTelnetTest.java
+++ b/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/command/impl/PwdTelnetTest.java
@@ -16,13 +16,12 @@
  */
 package org.apache.dubbo.qos.command.impl;
 
+import io.netty.channel.Channel;
+import io.netty.util.DefaultAttributeMap;
 import org.apache.dubbo.qos.command.BaseCommand;
 import org.apache.dubbo.qos.command.CommandContext;
-import org.apache.dubbo.qos.legacy.ProtocolUtils;
 import org.apache.dubbo.remoting.RemotingException;
-
-import io.netty.channel.Channel;
-import io.netty.util.DefaultAttributeMap;
+import org.apache.dubbo.rpc.model.FrameworkModel;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -49,7 +48,7 @@ public class PwdTelnetTest {
 
     @AfterEach
     public void tearDown() {
-        ProtocolUtils.closeAll();
+        FrameworkModel.destroyAll();
         mockChannel.close();
         reset(mockChannel, mockCommandContext);
     }
diff --git a/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/command/impl/SelectTelnetTest.java b/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/command/impl/SelectTelnetTest.java
index b66ba48..9f2192b 100644
--- a/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/command/impl/SelectTelnetTest.java
+++ b/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/command/impl/SelectTelnetTest.java
@@ -21,7 +21,6 @@ import io.netty.util.DefaultAttributeMap;
 import org.apache.dubbo.config.bootstrap.DubboBootstrap;
 import org.apache.dubbo.qos.command.BaseCommand;
 import org.apache.dubbo.qos.command.CommandContext;
-import org.apache.dubbo.qos.legacy.ProtocolUtils;
 import org.apache.dubbo.qos.legacy.service.DemoService;
 import org.apache.dubbo.qos.legacy.service.DemoServiceImpl;
 import org.apache.dubbo.remoting.RemotingException;
@@ -44,17 +43,19 @@ import static org.mockito.Mockito.reset;
 
 public class SelectTelnetTest {
 
-    private static BaseCommand select = new SelectTelnet(FrameworkModel.defaultModel());
+    private BaseCommand select;
 
     private Channel mockChannel;
     private CommandContext mockCommandContext;
 
-    private final ModuleServiceRepository repository = ApplicationModel.defaultModel().getDefaultModule().getServiceRepository();
+    private ModuleServiceRepository repository;
     private final DefaultAttributeMap defaultAttributeMap = new DefaultAttributeMap();
     private List<Method> methods;
 
     @BeforeEach
     public void setup() {
+        repository = ApplicationModel.defaultModel().getDefaultModule().getServiceRepository();
+        select = new SelectTelnet(FrameworkModel.defaultModel());
         String methodName = "getPerson";
         methods = new ArrayList<>();
         for (Method method : DemoService.class.getMethods()) {
@@ -71,7 +72,7 @@ public class SelectTelnetTest {
 
     @AfterEach
     public void after() {
-        ProtocolUtils.closeAll();
+        FrameworkModel.destroyAll();
         reset(mockChannel, mockCommandContext);
     }
 
diff --git a/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/command/impl/ShutdownTelnetTest.java b/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/command/impl/ShutdownTelnetTest.java
index 4e574a8..4fa797c 100644
--- a/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/command/impl/ShutdownTelnetTest.java
+++ b/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/command/impl/ShutdownTelnetTest.java
@@ -19,7 +19,6 @@ package org.apache.dubbo.qos.command.impl;
 import io.netty.channel.Channel;
 import org.apache.dubbo.qos.command.BaseCommand;
 import org.apache.dubbo.qos.command.CommandContext;
-import org.apache.dubbo.qos.legacy.ProtocolUtils;
 import org.apache.dubbo.remoting.RemotingException;
 import org.apache.dubbo.rpc.model.FrameworkModel;
 import org.junit.jupiter.api.AfterEach;
@@ -33,12 +32,13 @@ import static org.mockito.Mockito.reset;
 
 public class ShutdownTelnetTest {
 
-    private static final BaseCommand shutdown = new ShutdownTelnet(FrameworkModel.defaultModel());
+    private BaseCommand shutdown;
     private Channel mockChannel;
     private CommandContext mockCommandContext;
 
     @BeforeEach
     public void setUp() {
+        shutdown = new ShutdownTelnet(FrameworkModel.defaultModel());
         mockCommandContext = mock(CommandContext.class);
         mockChannel = mock(Channel.class);
         given(mockCommandContext.getRemote()).willReturn(mockChannel);
@@ -46,7 +46,7 @@ public class ShutdownTelnetTest {
 
     @AfterEach
     public void after() {
-        ProtocolUtils.closeAll();
+        FrameworkModel.destroyAll();
         reset(mockChannel, mockCommandContext);
     }
 
diff --git a/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/command/util/CommandHelperTest.java b/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/command/util/CommandHelperTest.java
index 377d4c3..3eb73a2 100644
--- a/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/command/util/CommandHelperTest.java
+++ b/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/command/util/CommandHelperTest.java
@@ -53,6 +53,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 
 public class CommandHelperTest {
     private CommandHelper commandHelper = new CommandHelper(FrameworkModel.defaultModel());
+
     @Test
     public void testHasCommand() throws Exception {
         assertTrue(commandHelper.hasCommand("greeting"));
diff --git a/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/legacy/ChangeTelnetHandlerTest.java b/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/legacy/ChangeTelnetHandlerTest.java
index 3a93d44..98a5c59 100644
--- a/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/legacy/ChangeTelnetHandlerTest.java
+++ b/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/legacy/ChangeTelnetHandlerTest.java
@@ -24,8 +24,8 @@ import org.apache.dubbo.remoting.RemotingException;
 import org.apache.dubbo.remoting.telnet.TelnetHandler;
 import org.apache.dubbo.rpc.Invoker;
 import org.apache.dubbo.rpc.Protocol;
+import org.apache.dubbo.rpc.model.FrameworkModel;
 import org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol;
-
 import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
@@ -74,7 +74,7 @@ public class ChangeTelnetHandlerTest {
 
     @AfterEach
     public void after() {
-        ProtocolUtils.closeAll();
+        FrameworkModel.destroyAll();
         reset(mockChannel, mockInvoker);
     }
 
diff --git a/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/legacy/ProtocolUtils.java b/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/legacy/ProtocolUtils.java
index eb6b59f..c2a088f 100644
--- a/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/legacy/ProtocolUtils.java
+++ b/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/legacy/ProtocolUtils.java
@@ -20,25 +20,22 @@ import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.extension.ExtensionLoader;
 import org.apache.dubbo.rpc.Exporter;
 import org.apache.dubbo.rpc.Protocol;
-import org.apache.dubbo.rpc.ProtocolServer;
 import org.apache.dubbo.rpc.ProxyFactory;
+import org.apache.dubbo.rpc.model.FrameworkModel;
 import org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol;
 
-import java.util.Collection;
-
 /**
  * TODO Comment of ProtocolUtils
  */
 public class ProtocolUtils {
 
-    private static Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
-    private static ProxyFactory proxy = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
-
     public static <T> T refer(Class<T> type, String url) {
         return refer(type, URL.valueOf(url));
     }
 
     public static <T> T refer(Class<T> type, URL url) {
+        Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
+        ProxyFactory proxy = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
         return proxy.getProxy(protocol.refer(type, url));
     }
 
@@ -47,14 +44,14 @@ public class ProtocolUtils {
     }
 
     public static <T> Exporter<T> export(T instance, Class<T> type, URL url) {
+        Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
+        ProxyFactory proxy = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
         return protocol.export(proxy.getInvoker(instance, type, url));
     }
 
     public static void closeAll() {
         DubboProtocol.getDubboProtocol().destroy();
-        Collection<ProtocolServer> servers = DubboProtocol.getDubboProtocol().getServers();
-        for (ProtocolServer server : servers) {
-            server.close();
-        }
+        ExtensionLoader.getExtensionLoader(Protocol.class).destroy();
+        FrameworkModel.destroyAll();
     }
 }
diff --git a/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/legacy/TraceTelnetHandlerTest.java b/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/legacy/TraceTelnetHandlerTest.java
index dd74db3..8a83021 100644
--- a/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/legacy/TraceTelnetHandlerTest.java
+++ b/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/legacy/TraceTelnetHandlerTest.java
@@ -23,9 +23,9 @@ import org.apache.dubbo.remoting.Channel;
 import org.apache.dubbo.remoting.telnet.TelnetHandler;
 import org.apache.dubbo.rpc.Invoker;
 import org.apache.dubbo.rpc.Protocol;
+import org.apache.dubbo.rpc.model.FrameworkModel;
 import org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol;
 import org.apache.dubbo.rpc.protocol.dubbo.filter.TraceFilter;
-
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeEach;
@@ -58,7 +58,7 @@ public class TraceTelnetHandlerTest {
     @AfterEach
     public void tearDown() {
         reset(mockChannel, mockInvoker);
-        ProtocolUtils.closeAll();
+        FrameworkModel.destroyAll();
     }
 
     @Test
diff --git a/dubbo-plugin/pom.xml b/dubbo-plugin/pom.xml
index 194525c..ed77204 100644
--- a/dubbo-plugin/pom.xml
+++ b/dubbo-plugin/pom.xml
@@ -36,4 +36,12 @@
         <skip_maven_deploy>false</skip_maven_deploy>
     </properties>
 
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-test-check</artifactId>
+            <version>${project.parent.version}</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
 </project>
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/store/InMemoryWritableMetadataService.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/store/InMemoryWritableMetadataService.java
index 30ec1ed..d563972 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/store/InMemoryWritableMetadataService.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/store/InMemoryWritableMetadataService.java
@@ -37,6 +37,7 @@ import org.apache.dubbo.rpc.model.ApplicationModel;
 import org.apache.dubbo.rpc.model.ScopeModelAware;
 import org.apache.dubbo.rpc.support.ProtocolUtils;
 
+import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -134,7 +135,7 @@ public class InMemoryWritableMetadataService implements WritableMetadataService,
     @Override
     public void setApplicationModel(ApplicationModel applicationModel) {
         this.applicationModel = applicationModel;
-        this.metadataPublishDelayTime = ConfigurationUtils.get(applicationModel, METADATA_PUBLISH_DELAY_KEY, DEFAULT_METADATA_PUBLISH_DELAY) * 100L;
+        this.metadataPublishDelayTime = ConfigurationUtils.get(applicationModel, METADATA_PUBLISH_DELAY_KEY, DEFAULT_METADATA_PUBLISH_DELAY);
     }
 
     @Override
@@ -196,6 +197,15 @@ public class InMemoryWritableMetadataService implements WritableMetadataService,
         }
     }
 
+    public void addMetadataInfo(String key, MetadataInfo metadataInfo) {
+        updateLock.readLock().lock();
+        try {
+            metadataInfos.put(key, metadataInfo);
+        } finally {
+            updateLock.readLock().unlock();
+        }
+    }
+
     @Override
     public boolean unexportURL(URL url) {
         if (MetadataService.class.getName().equals(url.getServiceInterface())) {
@@ -286,6 +296,9 @@ public class InMemoryWritableMetadataService implements WritableMetadataService,
                 return metadataInfo;
             }
         }
+        if (logger.isInfoEnabled()) {
+            logger.info("metadata not found for revision: " + revision);
+        }
         return null;
     }
 
@@ -324,7 +337,7 @@ public class InMemoryWritableMetadataService implements WritableMetadataService,
             metadataSemaphore.drainPermits();
             updateLock.writeLock().lock();
         } catch (InterruptedException e) {
-            if (!applicationModel.isStopping()) {
+            if (!applicationModel.isDestroyed()) {
                 logger.warn("metadata refresh thread has been interrupted unexpectedly while waiting for update.", e);
             }
         }
@@ -335,7 +348,7 @@ public class InMemoryWritableMetadataService implements WritableMetadataService,
     }
 
     public Map<String, MetadataInfo> getMetadataInfos() {
-        return metadataInfos;
+        return Collections.unmodifiableMap(metadataInfos);
     }
 
     void addMetaServiceURL(URL url) {
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/migration/MigrationRuleListener.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/migration/MigrationRuleListener.java
index 1651e8c..ca1f3dd 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/migration/MigrationRuleListener.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/migration/MigrationRuleListener.java
@@ -110,7 +110,7 @@ public class MigrationRuleListener implements RegistryProtocolListener, Configur
 
         String localRawRule = moduleModel.getModelEnvironment().getLocalMigrationRule();
         if (!StringUtils.isEmpty(localRawRule)) {
-            localRuleMigrationFuture = Executors.newSingleThreadScheduledExecutor(new NamedThreadFactory("DubboMigrationRuleDelayWorker", true))
+            localRuleMigrationFuture = moduleModel.getApplicationModel().getApplicationExecutorRepository().getSharedScheduledExecutor()
                 .schedule(() -> {
                     if (this.rawRule.equals(INIT)) {
                         this.process(new ConfigChangedEvent(null, null, localRawRule));
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/RegistryProtocol.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/RegistryProtocol.java
index c2bedc3..48cea93 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/RegistryProtocol.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/RegistryProtocol.java
@@ -21,6 +21,7 @@ import org.apache.dubbo.common.config.ConfigurationUtils;
 import org.apache.dubbo.common.config.configcenter.DynamicConfiguration;
 import org.apache.dubbo.common.logger.Logger;
 import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.common.threadpool.manager.ExecutorRepository;
 import org.apache.dubbo.common.timer.HashedWheelTimer;
 import org.apache.dubbo.common.url.component.ServiceConfigURL;
 import org.apache.dubbo.common.utils.CollectionUtils;
@@ -64,10 +65,9 @@ import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.ExecutorService;
+import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
 
-import static java.util.concurrent.Executors.newSingleThreadExecutor;
 import static org.apache.dubbo.common.constants.CommonConstants.APPLICATION_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.CLUSTER_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.COMMA_SPLIT_PATTERN;
@@ -592,6 +592,7 @@ public class RegistryProtocol implements Protocol, ScopeModelAware {
 
     @Override
     public void destroy() {
+        // FIXME all application models in framework are removed at this moment
         for (ApplicationModel applicationModel : frameworkModel.getApplicationModels()) {
             for (ModuleModel moduleModel : applicationModel.getModuleModels()) {
                 List<RegistryProtocolListener> listeners = moduleModel.getExtensionLoader(RegistryProtocolListener.class)
@@ -849,7 +850,7 @@ public class RegistryProtocol implements Protocol, ScopeModelAware {
      */
     private class ExporterChangeableWrapper<T> implements Exporter<T> {
 
-        private final ExecutorService executor = newSingleThreadExecutor(new NamedThreadFactory("Exporter-Unexport", true));
+        private final ScheduledExecutorService executor;
 
         private final Invoker<T> originInvoker;
         private Exporter<T> exporter;
@@ -859,6 +860,9 @@ public class RegistryProtocol implements Protocol, ScopeModelAware {
         public ExporterChangeableWrapper(Exporter<T> exporter, Invoker<T> originInvoker) {
             this.exporter = exporter;
             this.originInvoker = originInvoker;
+            ExecutorRepository executorRepository = originInvoker.getUrl().getOrDefaultApplicationModel()
+                .getDefaultExtension(ExecutorRepository.class);
+            this.executor = executorRepository.getSharedScheduledExecutor();
         }
 
         public Invoker<T> getOriginInvoker() {
@@ -907,19 +911,15 @@ public class RegistryProtocol implements Protocol, ScopeModelAware {
                 logger.warn(t.getMessage(), t);
             }
 
-            executor.submit(() -> {
+            //TODO wait for shutdown timeout is a bit strange
+            int timeout = ConfigurationUtils.getServerShutdownTimeout(subscribeUrl.getScopeModel());
+            executor.schedule(() -> {
                 try {
-                    int timeout = ConfigurationUtils.getServerShutdownTimeout(subscribeUrl.getScopeModel());
-                    if (timeout > 0) {
-                        logger.info("Waiting " + timeout + "ms for registry to notify all consumers before unexport. " +
-                            "Usually, this is called when you use dubbo API");
-                        Thread.sleep(timeout);
-                    }
                     exporter.unexport();
                 } catch (Throwable t) {
                     logger.warn(t.getMessage(), t);
                 }
-            });
+            }, timeout, TimeUnit.MILLISECONDS);
         }
 
         public void setSubscribeUrl(URL subscribeUrl) {
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/support/AbstractRegistry.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/support/AbstractRegistry.java
index 21b5bd9..7965cbd 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/support/AbstractRegistry.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/support/AbstractRegistry.java
@@ -19,10 +19,10 @@ package org.apache.dubbo.registry.support;
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.logger.Logger;
 import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.common.threadpool.manager.ExecutorRepository;
 import org.apache.dubbo.common.utils.CollectionUtils;
 import org.apache.dubbo.common.utils.ConcurrentHashSet;
 import org.apache.dubbo.common.utils.ConfigUtils;
-import org.apache.dubbo.common.utils.NamedThreadFactory;
 import org.apache.dubbo.common.utils.StringUtils;
 import org.apache.dubbo.common.utils.UrlUtils;
 import org.apache.dubbo.registry.NotifyListener;
@@ -48,7 +48,6 @@ import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.concurrent.atomic.AtomicReference;
@@ -82,7 +81,7 @@ public abstract class AbstractRegistry implements Registry {
     // Local disk cache, where the special key value.registries records the list of registry centers, and the others are the list of notified service providers
     private final Properties properties = new Properties();
     // File cache timing writing
-    private final ExecutorService registryCacheExecutor = Executors.newFixedThreadPool(1, new NamedThreadFactory("DubboSaveRegistryCache", true));
+    private final ExecutorService registryCacheExecutor;
     private final AtomicLong lastCacheChanged = new AtomicLong();
     private final AtomicInteger savePropertiesRetryTimes = new AtomicInteger();
     private final Set<URL> registered = new ConcurrentHashSet<>();
@@ -100,6 +99,7 @@ public abstract class AbstractRegistry implements Registry {
         setUrl(url);
         registryManager = url.getOrDefaultApplicationModel().getBeanFactory().getBean(RegistryManager.class);
         localCacheEnabled = url.getParameter(REGISTRY_LOCAL_FILE_CACHE_ENABLED, true);
+        registryCacheExecutor = url.getOrDefaultApplicationModel().getDefaultExtension(ExecutorRepository.class).getSharedExecutor();
         if (localCacheEnabled) {
             // Start file save timer
             syncSaveFile = url.getParameter(REGISTRY_FILESAVE_SYNC_KEY, false);
diff --git a/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/metadata/ServiceInstanceMetadataUtilsTest.java b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/metadata/ServiceInstanceMetadataUtilsTest.java
index 3f08c8d..7a83c1d 100644
--- a/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/metadata/ServiceInstanceMetadataUtilsTest.java
+++ b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/metadata/ServiceInstanceMetadataUtilsTest.java
@@ -26,6 +26,7 @@ import org.apache.dubbo.registry.client.DefaultServiceInstance;
 import org.apache.dubbo.registry.client.InMemoryServiceDiscovery;
 import org.apache.dubbo.registry.client.ServiceDiscovery;
 import org.apache.dubbo.registry.client.ServiceDiscoveryRegistry;
+import org.apache.dubbo.registry.client.metadata.store.InMemoryWritableMetadataService;
 import org.apache.dubbo.registry.support.RegistryManager;
 import org.apache.dubbo.rpc.model.ApplicationModel;
 
@@ -172,17 +173,16 @@ public class ServiceInstanceMetadataUtilsTest {
 
         ServiceDiscovery serviceDiscovery = Mockito.mock(ServiceDiscovery.class);
 
-        WritableMetadataService writableMetadataService = WritableMetadataService.getDefaultExtension(serviceInstance.getApplicationModel());
-        Map<String, MetadataInfo> metadataInfoMap = writableMetadataService.getMetadataInfos();
+        InMemoryWritableMetadataService writableMetadataService = (InMemoryWritableMetadataService) WritableMetadataService.getDefaultExtension(serviceInstance.getApplicationModel());
         MetadataInfo metadataInfo = new MetadataInfo("demo");
         metadataInfo.addService(new MetadataInfo.ServiceInfo(url1));
-        metadataInfoMap.put(DEFAULT_KEY, metadataInfo);
+        writableMetadataService.addMetadataInfo(DEFAULT_KEY, metadataInfo);
 
         ServiceInstanceMetadataUtils.calInstanceRevision(serviceDiscovery, serviceInstance);
         Assertions.assertEquals(metadataInfo.calAndGetRevision(), serviceInstance.getMetadata().get(EXPORTED_SERVICES_REVISION_PROPERTY_NAME));
         Assertions.assertNull(serviceInstance.getExtendParams().get(INSTANCE_REVISION_UPDATED_KEY));
 
-        metadataInfoMap.get(DEFAULT_KEY).addService(new MetadataInfo.ServiceInfo(url2));
+        writableMetadataService.getMetadataInfos().get(DEFAULT_KEY).addService(new MetadataInfo.ServiceInfo(url2));
         ServiceInstanceMetadataUtils.calInstanceRevision(serviceDiscovery, serviceInstance);
         Assertions.assertEquals(metadataInfo.calAndGetRevision(), serviceInstance.getMetadata().get(EXPORTED_SERVICES_REVISION_PROPERTY_NAME));
         Assertions.assertEquals(serviceInstance.getExtendParams().get(INSTANCE_REVISION_UPDATED_KEY), "true");
diff --git a/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/migration/MigrationInvokerTest.java b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/migration/MigrationInvokerTest.java
index 3a4cc35..c21a2e9 100644
--- a/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/migration/MigrationInvokerTest.java
+++ b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/migration/MigrationInvokerTest.java
@@ -20,12 +20,13 @@ import org.apache.dubbo.common.URL;
 import org.apache.dubbo.config.ApplicationConfig;
 import org.apache.dubbo.registry.client.migration.model.MigrationRule;
 import org.apache.dubbo.registry.client.migration.model.MigrationStep;
+import org.apache.dubbo.registry.integration.DemoService;
 import org.apache.dubbo.registry.integration.DynamicDirectory;
 import org.apache.dubbo.registry.integration.RegistryProtocol;
 import org.apache.dubbo.rpc.Invoker;
 import org.apache.dubbo.rpc.cluster.ClusterInvoker;
 import org.apache.dubbo.rpc.model.ApplicationModel;
-
+import org.apache.dubbo.rpc.model.FrameworkModel;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeEach;
@@ -39,7 +40,7 @@ import java.util.List;
 public class MigrationInvokerTest {
     @BeforeEach
     public void before() {
-        ApplicationModel.reset();
+        FrameworkModel.destroyAll();
         ApplicationConfig applicationConfig = new ApplicationConfig();
         applicationConfig.setName("Test");
         ApplicationModel.defaultModel().getApplicationConfigManager().setApplication(applicationConfig);
@@ -47,7 +48,7 @@ public class MigrationInvokerTest {
 
     @AfterEach
     public void after() {
-        ApplicationModel.reset();
+        FrameworkModel.destroyAll();
     }
 
     @Test
@@ -88,7 +89,7 @@ public class MigrationInvokerTest {
         Mockito.when(invoker.getUrl()).thenReturn(consumerURL);
         Mockito.when(serviceDiscoveryInvoker.getUrl()).thenReturn(consumerURL);
 
-        MigrationInvoker migrationInvoker = new MigrationInvoker(registryProtocol, null, null, null, null, consumerURL);
+        MigrationInvoker migrationInvoker = new MigrationInvoker(registryProtocol, null, null, DemoService.class, null, consumerURL);
 
         MigrationRule migrationRule = Mockito.mock(MigrationRule.class);
         Mockito.when(migrationRule.getForce(Mockito.any())).thenReturn(true);
diff --git a/dubbo-registry/dubbo-registry-api/src/test/resources/log4j.xml b/dubbo-registry/dubbo-registry-api/src/test/resources/log4j.xml
index de4c580..3a4fc86 100644
--- a/dubbo-registry/dubbo-registry-api/src/test/resources/log4j.xml
+++ b/dubbo-registry/dubbo-registry-api/src/test/resources/log4j.xml
@@ -23,7 +23,7 @@
         </layout>
     </appender>
     <root>
-        <level value="WARN"/>
+        <level value="INFO"/>
         <appender-ref ref="CONSOLE"/>
     </root>
-</log4j:configuration>
\ No newline at end of file
+</log4j:configuration>
diff --git a/dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/ZookeeperRegistry.java b/dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/ZookeeperRegistry.java
index e4940b0..f3b8f72 100644
--- a/dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/ZookeeperRegistry.java
+++ b/dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/ZookeeperRegistry.java
@@ -67,7 +67,7 @@ public class ZookeeperRegistry extends CacheableFailbackRegistry {
 
     private final ConcurrentMap<URL, ConcurrentMap<NotifyListener, ChildListener>> zkListeners = new ConcurrentHashMap<>();
 
-    private final ZookeeperClient zkClient;
+    private ZookeeperClient zkClient;
 
     public ZookeeperRegistry(URL url, ZookeeperTransporter zookeeperTransporter) {
         super(url);
@@ -106,22 +106,27 @@ public class ZookeeperRegistry extends CacheableFailbackRegistry {
 
     @Override
     public boolean isAvailable() {
-        return zkClient.isConnected();
+        return zkClient != null && zkClient.isConnected();
     }
 
     @Override
     public void destroy() {
         super.destroy();
-        try {
-            zkClient.close();
-        } catch (Exception e) {
-            logger.warn("Failed to close zookeeper client " + getUrl() + ", cause: " + e.getMessage(), e);
+        // Just release zkClient reference, but can not close zk client here for zk client is shared somewhere else.
+        // See org.apache.dubbo.remoting.zookeeper.AbstractZookeeperTransporter#destroy()
+        zkClient = null;
+    }
+
+    private void checkDestroyed() {
+        if (zkClient == null) {
+            throw new IllegalStateException("registry is destroyed");
         }
     }
 
     @Override
     public void doRegister(URL url) {
         try {
+            checkDestroyed();
             zkClient.create(toUrlPath(url), url.getParameter(DYNAMIC_KEY, true));
         } catch (Throwable e) {
             throw new RpcException("Failed to register " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
@@ -131,6 +136,7 @@ public class ZookeeperRegistry extends CacheableFailbackRegistry {
     @Override
     public void doUnregister(URL url) {
         try {
+            checkDestroyed();
             zkClient.delete(toUrlPath(url));
         } catch (Throwable e) {
             throw new RpcException("Failed to unregister " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
@@ -140,6 +146,7 @@ public class ZookeeperRegistry extends CacheableFailbackRegistry {
     @Override
     public void doSubscribe(final URL url, final NotifyListener listener) {
         try {
+            checkDestroyed();
             if (ANY_VALUE.equals(url.getServiceInterface())) {
                 String root = toRootPath();
                 boolean check = url.getParameter(CHECK_KEY, false);
@@ -193,6 +200,7 @@ public class ZookeeperRegistry extends CacheableFailbackRegistry {
 
     @Override
     public void doUnsubscribe(URL url, NotifyListener listener) {
+        checkDestroyed();
         ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.get(url);
         if (listeners != null) {
             ChildListener zkListener = listeners.remove(listener);
@@ -219,6 +227,7 @@ public class ZookeeperRegistry extends CacheableFailbackRegistry {
             throw new IllegalArgumentException("lookup url == null");
         }
         try {
+            checkDestroyed();
             List<String> providers = new ArrayList<>();
             for (String path : toCategoriesPath(url)) {
                 List<String> children = zkClient.getChildren(path);
diff --git a/dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/ZookeeperRegistryFactory.java b/dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/ZookeeperRegistryFactory.java
index 011683f..07858b6 100644
--- a/dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/ZookeeperRegistryFactory.java
+++ b/dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/ZookeeperRegistryFactory.java
@@ -21,6 +21,7 @@ import org.apache.dubbo.common.extension.DisableInject;
 import org.apache.dubbo.registry.Registry;
 import org.apache.dubbo.registry.support.AbstractRegistryFactory;
 import org.apache.dubbo.remoting.zookeeper.ZookeeperTransporter;
+import org.apache.dubbo.rpc.model.ApplicationModel;
 
 /**
  * ZookeeperRegistryFactory.
@@ -29,8 +30,16 @@ public class ZookeeperRegistryFactory extends AbstractRegistryFactory {
 
     private ZookeeperTransporter zookeeperTransporter;
 
+    private ApplicationModel applicationModel;
+
+    // for compatible usage
     public ZookeeperRegistryFactory() {
-        this.zookeeperTransporter = ZookeeperTransporter.getExtension();
+        this(ApplicationModel.defaultModel());
+    }
+
+    public ZookeeperRegistryFactory(ApplicationModel applicationModel) {
+        this.applicationModel = applicationModel;
+        this.zookeeperTransporter = ZookeeperTransporter.getExtension(applicationModel);
     }
 
     @DisableInject
diff --git a/dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/ZookeeperServiceDiscovery.java b/dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/ZookeeperServiceDiscovery.java
index 0cc65da..fc8e596 100644
--- a/dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/ZookeeperServiceDiscovery.java
+++ b/dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/ZookeeperServiceDiscovery.java
@@ -87,6 +87,7 @@ public class ZookeeperServiceDiscovery extends AbstractServiceDiscovery {
     @Override
     public void doDestroy() throws Exception {
         serviceDiscovery.close();
+        curatorFramework.close();
     }
 
     @Override
diff --git a/dubbo-registry/dubbo-registry-zookeeper/src/test/java/org/apache/dubbo/registry/zookeeper/ZookeeperRegistryTest.java b/dubbo-registry/dubbo-registry-zookeeper/src/test/java/org/apache/dubbo/registry/zookeeper/ZookeeperRegistryTest.java
index 2052c3b..3cf5d80 100644
--- a/dubbo-registry/dubbo-registry-zookeeper/src/test/java/org/apache/dubbo/registry/zookeeper/ZookeeperRegistryTest.java
+++ b/dubbo-registry/dubbo-registry-zookeeper/src/test/java/org/apache/dubbo/registry/zookeeper/ZookeeperRegistryTest.java
@@ -16,6 +16,7 @@
  */
 package org.apache.dubbo.registry.zookeeper;
 
+import org.apache.curator.test.TestingServer;
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.status.Status;
 import org.apache.dubbo.common.utils.NetUtils;
@@ -24,8 +25,6 @@ import org.apache.dubbo.registry.Registry;
 import org.apache.dubbo.registry.status.RegistryStatusChecker;
 import org.apache.dubbo.rpc.RpcException;
 import org.apache.dubbo.rpc.model.ApplicationModel;
-
-import org.apache.curator.test.TestingServer;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeEach;
@@ -62,7 +61,7 @@ public class ZookeeperRegistryTest {
         this.zkServer.start();
 
         this.registryUrl = URL.valueOf("zookeeper://localhost:" + zkServerPort);
-        zookeeperRegistryFactory = new ZookeeperRegistryFactory();
+        zookeeperRegistryFactory = new ZookeeperRegistryFactory(ApplicationModel.defaultModel());
         this.zookeeperRegistry = (ZookeeperRegistry) zookeeperRegistryFactory.createRegistry(registryUrl);
     }
 
@@ -75,7 +74,7 @@ public class ZookeeperRegistryTest {
     public void testAnyHost() {
         Assertions.assertThrows(IllegalStateException.class, () -> {
             URL errorUrl = URL.valueOf("multicast://0.0.0.0/");
-            new ZookeeperRegistryFactory().createRegistry(errorUrl);
+            new ZookeeperRegistryFactory(ApplicationModel.defaultModel()).createRegistry(errorUrl);
         });
     }
 
diff --git a/dubbo-registry/pom.xml b/dubbo-registry/pom.xml
index a652eb8..0f9a787 100644
--- a/dubbo-registry/pom.xml
+++ b/dubbo-registry/pom.xml
@@ -39,4 +39,13 @@
         <module>dubbo-registry-dns</module>
         <module>dubbo-registry-xds</module>
     </modules>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-test-check</artifactId>
+            <version>${project.parent.version}</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
 </project>
diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ConfigScopeModelInitializer.java b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/RemotingScopeModelInitializer.java
similarity index 55%
copy from dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ConfigScopeModelInitializer.java
copy to dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/RemotingScopeModelInitializer.java
index 8639cc2..706d7cc 100644
--- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ConfigScopeModelInitializer.java
+++ b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/RemotingScopeModelInitializer.java
@@ -14,20 +14,24 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.config;
-
-import org.apache.dubbo.common.beans.factory.ScopeBeanFactory;
-import org.apache.dubbo.common.deploy.ApplicationDeployer;
-import org.apache.dubbo.common.deploy.ModuleDeployer;
-import org.apache.dubbo.config.deploy.DefaultApplicationDeployer;
-import org.apache.dubbo.config.deploy.DefaultModuleDeployer;
-import org.apache.dubbo.config.utils.DefaultConfigValidator;
+package org.apache.dubbo.remoting;
+
+import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.remoting.zookeeper.ZookeeperTransporter;
 import org.apache.dubbo.rpc.model.ApplicationModel;
 import org.apache.dubbo.rpc.model.FrameworkModel;
 import org.apache.dubbo.rpc.model.ModuleModel;
 import org.apache.dubbo.rpc.model.ScopeModelInitializer;
 
-public class ConfigScopeModelInitializer implements ScopeModelInitializer {
+import java.util.List;
+
+/**
+ * Scope model initializer for remoting-api
+ */
+public class RemotingScopeModelInitializer implements ScopeModelInitializer {
+
+    private static final Logger logger = LoggerFactory.getLogger(RemotingScopeModelInitializer.class);
 
     @Override
     public void initializeFrameworkModel(FrameworkModel frameworkModel) {
@@ -36,19 +40,21 @@ public class ConfigScopeModelInitializer implements ScopeModelInitializer {
 
     @Override
     public void initializeApplicationModel(ApplicationModel applicationModel) {
-        ScopeBeanFactory beanFactory = applicationModel.getBeanFactory();
-        beanFactory.registerBean(DefaultConfigValidator.class);
-        // applicationDeployer
-        ApplicationDeployer applicationDeployer = beanFactory.registerBean(DefaultApplicationDeployer.class);
-        applicationModel.setDeployer(applicationDeployer);
-
+        applicationModel.addDestroyListener(m -> {
+            // destroy zookeeper clients if any
+            try {
+                List<ZookeeperTransporter> transporters = applicationModel.getExtensionLoader(ZookeeperTransporter.class).getLoadedExtensionInstances();
+                for (ZookeeperTransporter zkTransporter : transporters) {
+                    zkTransporter.destroy();
+                }
+            } catch (Exception e) {
+                logger.error("Error encountered while destroying ZookeeperTransporter: " + e.getMessage(), e);
+            }
+        });
     }
 
     @Override
     public void initializeModuleModel(ModuleModel moduleModel) {
-        ScopeBeanFactory beanFactory = moduleModel.getBeanFactory();
-        // moduleDeployer
-        ModuleDeployer moduleDeployer = beanFactory.registerBean(DefaultModuleDeployer.class);
-        moduleModel.setDeployer(moduleDeployer);
+
     }
 }
diff --git a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/Connection.java b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/Connection.java
index a675ef4..9fc9115 100644
--- a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/Connection.java
+++ b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/Connection.java
@@ -90,7 +90,7 @@ public class Connection extends AbstractReferenceCounted {
 
     private Bootstrap create() {
         final Bootstrap bootstrap = new Bootstrap();
-        bootstrap.group(NettyEventLoopFactory.NIO_EVENT_LOOP_GROUP)
+        bootstrap.group(NettyEventLoopFactory.NIO_EVENT_LOOP_GROUP.get())
                 .option(ChannelOption.SO_KEEPALIVE, true)
                 .option(ChannelOption.TCP_NODELAY, true)
                 .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
diff --git a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/NettyEventLoopFactory.java b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/NettyEventLoopFactory.java
index c766c07..09d4baf 100644
--- a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/NettyEventLoopFactory.java
+++ b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/NettyEventLoopFactory.java
@@ -16,8 +16,6 @@
  */
 package org.apache.dubbo.remoting.api;
 
-import org.apache.dubbo.remoting.Constants;
-
 import io.netty.channel.EventLoopGroup;
 import io.netty.channel.epoll.Epoll;
 import io.netty.channel.epoll.EpollEventLoopGroup;
@@ -29,6 +27,8 @@ import io.netty.channel.socket.SocketChannel;
 import io.netty.channel.socket.nio.NioServerSocketChannel;
 import io.netty.channel.socket.nio.NioSocketChannel;
 import io.netty.util.concurrent.DefaultThreadFactory;
+import org.apache.dubbo.common.resource.GlobalResourceInitializer;
+import org.apache.dubbo.remoting.Constants;
 
 import java.util.concurrent.ThreadFactory;
 
@@ -40,7 +40,10 @@ public class NettyEventLoopFactory {
     /**
      * netty client bootstrap
      */
-    public static final EventLoopGroup NIO_EVENT_LOOP_GROUP = eventLoopGroup(Constants.DEFAULT_IO_THREADS, "NettyClientWorker");
+    public static final GlobalResourceInitializer<EventLoopGroup> NIO_EVENT_LOOP_GROUP = new GlobalResourceInitializer<>(() ->
+        eventLoopGroup(Constants.DEFAULT_IO_THREADS, "NettyClientWorker"),
+        eventLoopGroup -> eventLoopGroup.shutdownGracefully()
+    );
 
     public static EventLoopGroup eventLoopGroup(int threads, String threadFactoryName) {
         ThreadFactory threadFactory = new DefaultThreadFactory(threadFactoryName, true);
diff --git a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/DefaultFuture.java b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/DefaultFuture.java
index f97a0f7..f1f0ed1 100644
--- a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/DefaultFuture.java
+++ b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/DefaultFuture.java
@@ -18,6 +18,7 @@ package org.apache.dubbo.remoting.exchange.support;
 
 import org.apache.dubbo.common.logger.Logger;
 import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.common.resource.GlobalResourceInitializer;
 import org.apache.dubbo.common.threadpool.ThreadlessExecutor;
 import org.apache.dubbo.common.timer.HashedWheelTimer;
 import org.apache.dubbo.common.timer.Timeout;
@@ -52,10 +53,9 @@ public class DefaultFuture extends CompletableFuture<Object> {
 
     private static final Map<Long, DefaultFuture> FUTURES = new ConcurrentHashMap<>();
 
-    public static final Timer TIME_OUT_TIMER = new HashedWheelTimer(
-            new NamedThreadFactory("dubbo-future-timeout", true),
-            30,
-            TimeUnit.MILLISECONDS);
+    private static GlobalResourceInitializer<Timer> TIME_OUT_TIMER = new GlobalResourceInitializer<>(() -> new HashedWheelTimer(
+        new NamedThreadFactory("dubbo-future-timeout", true), 30, TimeUnit.MILLISECONDS),
+        () -> destroy());
 
     // invoke id.
     private final Long id;
@@ -91,7 +91,13 @@ public class DefaultFuture extends CompletableFuture<Object> {
      */
     private static void timeoutCheck(DefaultFuture future) {
         TimeoutCheckTask task = new TimeoutCheckTask(future.getId());
-        future.timeoutCheckTask = TIME_OUT_TIMER.newTimeout(task, future.getTimeout(), TimeUnit.MILLISECONDS);
+        future.timeoutCheckTask = TIME_OUT_TIMER.get().newTimeout(task, future.getTimeout(), TimeUnit.MILLISECONDS);
+    }
+
+    public static void destroy() {
+        TIME_OUT_TIMER.remove(timer-> timer.stop());
+        FUTURES.clear();
+        CHANNELS.clear();
     }
 
     /**
diff --git a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/DefaultFuture2.java b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/DefaultFuture2.java
index 2e995ee..de8baf1 100644
--- a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/DefaultFuture2.java
+++ b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/DefaultFuture2.java
@@ -18,6 +18,7 @@ package org.apache.dubbo.remoting.exchange.support;
 
 import org.apache.dubbo.common.logger.Logger;
 import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.common.resource.GlobalResourceInitializer;
 import org.apache.dubbo.common.threadpool.ThreadlessExecutor;
 import org.apache.dubbo.common.timer.HashedWheelTimer;
 import org.apache.dubbo.common.timer.Timeout;
@@ -46,12 +47,14 @@ import java.util.concurrent.TimeUnit;
  */
 public class DefaultFuture2 extends CompletableFuture<Object> {
 
-    public static final Timer TIME_OUT_TIMER = new HashedWheelTimer(
-            new NamedThreadFactory("dubbo-future-timeout", true),
-            30,
-            TimeUnit.MILLISECONDS);
+    private static GlobalResourceInitializer<Timer> TIME_OUT_TIMER = new GlobalResourceInitializer<>(
+        () -> new HashedWheelTimer(new NamedThreadFactory("dubbo-future-timeout", true),
+            30, TimeUnit.MILLISECONDS),
+        () -> destroy());
+
     private static final Logger logger = LoggerFactory.getLogger(DefaultFuture2.class);
     private static final Map<Long, DefaultFuture2> FUTURES = new ConcurrentHashMap<>();
+
     // invoke id.
     private final Request request;
     private final Connection connection;
@@ -90,7 +93,12 @@ public class DefaultFuture2 extends CompletableFuture<Object> {
      */
     private static void timeoutCheck(DefaultFuture2 future) {
         TimeoutCheckTask task = new TimeoutCheckTask(future.getId());
-        future.timeoutCheckTask = TIME_OUT_TIMER.newTimeout(task, future.getTimeout(), TimeUnit.MILLISECONDS);
+        future.timeoutCheckTask = TIME_OUT_TIMER.get().newTimeout(task, future.getTimeout(), TimeUnit.MILLISECONDS);
+    }
+
+    public static void destroy() {
+        TIME_OUT_TIMER.remove(timer-> timer.stop());
+        FUTURES.clear();
     }
 
     /**
diff --git a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/header/HeaderExchangeClient.java b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/header/HeaderExchangeClient.java
index ced7dba..81df865 100644
--- a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/header/HeaderExchangeClient.java
+++ b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/header/HeaderExchangeClient.java
@@ -17,7 +17,9 @@
 package org.apache.dubbo.remoting.exchange.support.header;
 
 import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.resource.GlobalResourceInitializer;
 import org.apache.dubbo.common.timer.HashedWheelTimer;
+import org.apache.dubbo.common.timer.Timeout;
 import org.apache.dubbo.common.utils.Assert;
 import org.apache.dubbo.common.utils.NamedThreadFactory;
 import org.apache.dubbo.remoting.ChannelHandler;
@@ -48,10 +50,13 @@ public class HeaderExchangeClient implements ExchangeClient {
     private final Client client;
     private final ExchangeChannel channel;
 
-    private static final HashedWheelTimer IDLE_CHECK_TIMER = new HashedWheelTimer(
-            new NamedThreadFactory("dubbo-client-idleCheck", true), 1, TimeUnit.SECONDS, TICKS_PER_WHEEL);
-    private HeartbeatTimerTask heartBeatTimerTask;
-    private ReconnectTimerTask reconnectTimerTask;
+    public static GlobalResourceInitializer<HashedWheelTimer> IDLE_CHECK_TIMER = new GlobalResourceInitializer<>(() ->
+        new HashedWheelTimer(new NamedThreadFactory("dubbo-client-idleCheck", true), 1,
+            TimeUnit.SECONDS, TICKS_PER_WHEEL),
+        timer -> timer.stop());
+
+    private Timeout reconnectTimer;
+    private Timeout heartBeatTimer;
 
     public HeaderExchangeClient(Client client, boolean startTimer) {
         Assert.notNull(client, "Client can't be null");
@@ -191,8 +196,8 @@ public class HeaderExchangeClient implements ExchangeClient {
             AbstractTimerTask.ChannelProvider cp = () -> Collections.singletonList(HeaderExchangeClient.this);
             int heartbeat = getHeartbeat(url);
             long heartbeatTick = calculateLeastDuration(heartbeat);
-            this.heartBeatTimerTask = new HeartbeatTimerTask(cp, heartbeatTick, heartbeat);
-            IDLE_CHECK_TIMER.newTimeout(heartBeatTimerTask, heartbeatTick, TimeUnit.MILLISECONDS);
+            HeartbeatTimerTask heartBeatTimerTask = new HeartbeatTimerTask(cp, heartbeatTick, heartbeat);
+            heartBeatTimer = IDLE_CHECK_TIMER.get().newTimeout(heartBeatTimerTask, heartbeatTick, TimeUnit.MILLISECONDS);
         }
     }
 
@@ -201,18 +206,19 @@ public class HeaderExchangeClient implements ExchangeClient {
             AbstractTimerTask.ChannelProvider cp = () -> Collections.singletonList(HeaderExchangeClient.this);
             int idleTimeout = getIdleTimeout(url);
             long heartbeatTimeoutTick = calculateLeastDuration(idleTimeout);
-            this.reconnectTimerTask = new ReconnectTimerTask(cp, heartbeatTimeoutTick, idleTimeout);
-            IDLE_CHECK_TIMER.newTimeout(reconnectTimerTask, heartbeatTimeoutTick, TimeUnit.MILLISECONDS);
+            ReconnectTimerTask reconnectTimerTask = new ReconnectTimerTask(cp, heartbeatTimeoutTick, idleTimeout);
+            reconnectTimer = IDLE_CHECK_TIMER.get().newTimeout(reconnectTimerTask, heartbeatTimeoutTick, TimeUnit.MILLISECONDS);
         }
     }
 
     private void doClose() {
-        if (heartBeatTimerTask != null) {
-            heartBeatTimerTask.cancel();
+        if (heartBeatTimer != null) {
+            heartBeatTimer.cancel();
+            heartBeatTimer = null;
         }
-
-        if (reconnectTimerTask != null) {
-            reconnectTimerTask.cancel();
+        if (reconnectTimer != null) {
+            reconnectTimer.cancel();
+            reconnectTimer = null;
         }
     }
 
diff --git a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/header/HeaderExchangeServer.java b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/header/HeaderExchangeServer.java
index 2367465..d0d940d 100644
--- a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/header/HeaderExchangeServer.java
+++ b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/header/HeaderExchangeServer.java
@@ -20,7 +20,9 @@ import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.Version;
 import org.apache.dubbo.common.logger.Logger;
 import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.common.resource.GlobalResourceInitializer;
 import org.apache.dubbo.common.timer.HashedWheelTimer;
+import org.apache.dubbo.common.timer.Timeout;
 import org.apache.dubbo.common.utils.Assert;
 import org.apache.dubbo.common.utils.CollectionUtils;
 import org.apache.dubbo.common.utils.NamedThreadFactory;
@@ -58,10 +60,12 @@ public class HeaderExchangeServer implements ExchangeServer {
     private final RemotingServer server;
     private AtomicBoolean closed = new AtomicBoolean(false);
 
-    private static final HashedWheelTimer IDLE_CHECK_TIMER = new HashedWheelTimer(new NamedThreadFactory("dubbo-server-idleCheck", true), 1,
-            TimeUnit.SECONDS, TICKS_PER_WHEEL);
+    public static GlobalResourceInitializer<HashedWheelTimer> IDLE_CHECK_TIMER = new GlobalResourceInitializer<>(() ->
+        new HashedWheelTimer(new NamedThreadFactory("dubbo-server-idleCheck", true), 1,
+            TimeUnit.SECONDS, TICKS_PER_WHEEL),
+        timer -> timer.stop());
 
-    private CloseTimerTask closeTimerTask;
+    private Timeout closeTimer;
 
     public HeaderExchangeServer(RemotingServer server) {
         Assert.notNull(server, "server == null");
@@ -160,8 +164,8 @@ public class HeaderExchangeServer implements ExchangeServer {
     }
 
     private void cancelCloseTask() {
-        if (closeTimerTask != null) {
-            closeTimerTask.cancel();
+        if (closeTimer != null) {
+            closeTimer.cancel();
         }
     }
 
@@ -272,10 +276,9 @@ public class HeaderExchangeServer implements ExchangeServer {
             int idleTimeout = getIdleTimeout(url);
             long idleTimeoutTick = calculateLeastDuration(idleTimeout);
             CloseTimerTask closeTimerTask = new CloseTimerTask(cp, idleTimeoutTick, idleTimeout);
-            this.closeTimerTask = closeTimerTask;
 
             // init task and start timer.
-            IDLE_CHECK_TIMER.newTimeout(closeTimerTask, idleTimeoutTick, TimeUnit.MILLISECONDS);
+            this.closeTimer = IDLE_CHECK_TIMER.get().newTimeout(closeTimerTask, idleTimeoutTick, TimeUnit.MILLISECONDS);
         }
     }
 }
diff --git a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/transport/dispatcher/WrappedChannelHandler.java b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/transport/dispatcher/WrappedChannelHandler.java
index dadada1..7742d04 100644
--- a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/transport/dispatcher/WrappedChannelHandler.java
+++ b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/transport/dispatcher/WrappedChannelHandler.java
@@ -19,6 +19,7 @@ package org.apache.dubbo.remoting.transport.dispatcher;
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.logger.Logger;
 import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.common.resource.GlobalResourcesRepository;
 import org.apache.dubbo.common.threadpool.manager.ExecutorRepository;
 import org.apache.dubbo.remoting.Channel;
 import org.apache.dubbo.remoting.ChannelHandler;
@@ -27,6 +28,7 @@ import org.apache.dubbo.remoting.exchange.Request;
 import org.apache.dubbo.remoting.exchange.Response;
 import org.apache.dubbo.remoting.exchange.support.DefaultFuture;
 import org.apache.dubbo.remoting.transport.ChannelHandlerDelegate;
+import org.apache.dubbo.rpc.model.ApplicationModel;
 
 import java.util.concurrent.ExecutorService;
 
@@ -130,11 +132,17 @@ public class WrappedChannelHandler implements ChannelHandlerDelegate {
      * @return
      */
     public ExecutorService getSharedExecutorService() {
+        ApplicationModel applicationModel = url.getOrDefaultApplicationModel();
         ExecutorRepository executorRepository =
-                url.getOrDefaultApplicationModel().getExtensionLoader(ExecutorRepository.class).getDefaultExtension();
+                applicationModel.getExtensionLoader(ExecutorRepository.class).getDefaultExtension();
         ExecutorService executor = executorRepository.getExecutor(url);
         if (executor == null) {
-            executor = executorRepository.createExecutorIfAbsent(url);
+            // if application is destroyed, use global executor service
+            if (applicationModel.isDestroyed()) {
+                executor = GlobalResourcesRepository.getGlobalExecutorService();
+            }else {
+                executor = executorRepository.createExecutorIfAbsent(url);
+            }
         }
         return executor;
     }
diff --git a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/zookeeper/AbstractZookeeperClient.java b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/zookeeper/AbstractZookeeperClient.java
index ca620cb..309a85b 100644
--- a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/zookeeper/AbstractZookeeperClient.java
+++ b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/zookeeper/AbstractZookeeperClient.java
@@ -206,7 +206,10 @@ public abstract class AbstractZookeeperClient<TargetDataListener, TargetChildLis
         return doGetConfigItem(path);
     }
 
-    protected abstract void doClose();
+    protected void doClose() {
+        // Break circular reference of zk client
+        stateListeners.clear();
+    }
 
     protected abstract void createPersistent(String path);
 
diff --git a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/zookeeper/AbstractZookeeperTransporter.java b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/zookeeper/AbstractZookeeperTransporter.java
index bd322d6..9dd266b 100644
--- a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/zookeeper/AbstractZookeeperTransporter.java
+++ b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/zookeeper/AbstractZookeeperTransporter.java
@@ -97,6 +97,7 @@ public abstract class AbstractZookeeperTransporter implements ZookeeperTransport
                 break;
             }
         }
+        // mapping new backup address
         if (zookeeperClient != null && zookeeperClient.isConnected()) {
             writeToClientMap(addressList, zookeeperClient);
         }
@@ -178,4 +179,13 @@ public abstract class AbstractZookeeperTransporter implements ZookeeperTransport
     public Map<String, ZookeeperClient> getZookeeperClientMap() {
         return zookeeperClientMap;
     }
+
+    @Override
+    public void destroy() {
+        // only destroy zk clients here
+        for (ZookeeperClient client : zookeeperClientMap.values()) {
+            client.close();
+        }
+        zookeeperClientMap.clear();
+    }
 }
diff --git a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/zookeeper/ZookeeperTransporter.java b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/zookeeper/ZookeeperTransporter.java
index 136d8d3..f6bc79c 100644
--- a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/zookeeper/ZookeeperTransporter.java
+++ b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/zookeeper/ZookeeperTransporter.java
@@ -20,10 +20,9 @@ import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.extension.ExtensionLoader;
 import org.apache.dubbo.common.extension.ExtensionScope;
 import org.apache.dubbo.common.extension.SPI;
+import org.apache.dubbo.rpc.model.ApplicationModel;
 
-import static org.apache.dubbo.common.extension.ExtensionLoader.getExtensionLoader;
-
-@SPI(scope = ExtensionScope.FRAMEWORK)
+@SPI(scope = ExtensionScope.APPLICATION)
 public interface ZookeeperTransporter {
 
     String CURATOR_5 = "curator5";
@@ -32,8 +31,10 @@ public interface ZookeeperTransporter {
 
     ZookeeperClient connect(URL url);
 
-    static ZookeeperTransporter getExtension() {
-        ExtensionLoader<ZookeeperTransporter> extensionLoader = getExtensionLoader(ZookeeperTransporter.class);
+    void destroy();
+
+    static ZookeeperTransporter getExtension(ApplicationModel applicationModel) {
+        ExtensionLoader<ZookeeperTransporter> extensionLoader = applicationModel.getExtensionLoader(ZookeeperTransporter.class);
         boolean isHighVersion = isHighVersionCurator();
         if (isHighVersion) {
             return extensionLoader.getExtension(CURATOR_5);
diff --git a/dubbo-remoting/dubbo-remoting-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.model.ScopeModelInitializer b/dubbo-remoting/dubbo-remoting-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.model.ScopeModelInitializer
new file mode 100644
index 0000000..1568955
--- /dev/null
+++ b/dubbo-remoting/dubbo-remoting-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.model.ScopeModelInitializer
@@ -0,0 +1 @@
+dubbo-remoting-api=org.apache.dubbo.remoting.RemotingScopeModelInitializer
diff --git a/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyClient.java b/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyClient.java
index f18893b..998f5e3 100644
--- a/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyClient.java
+++ b/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyClient.java
@@ -31,6 +31,7 @@ import org.apache.dubbo.common.Version;
 import org.apache.dubbo.common.config.ConfigurationUtils;
 import org.apache.dubbo.common.logger.Logger;
 import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.common.resource.GlobalResourceInitializer;
 import org.apache.dubbo.common.utils.NetUtils;
 import org.apache.dubbo.common.utils.StringUtils;
 import org.apache.dubbo.remoting.ChannelHandler;
@@ -53,18 +54,21 @@ import static org.apache.dubbo.remoting.api.NettyEventLoopFactory.socketChannelC
  */
 public class NettyClient extends AbstractClient {
 
-    private static final Logger logger = LoggerFactory.getLogger(NettyClient.class);
-    /**
-     * netty client bootstrap
-     */
-    private static final EventLoopGroup EVENT_LOOP_GROUP = eventLoopGroup(Constants.DEFAULT_IO_THREADS, "NettyClientWorker");
-
     private static final String SOCKS_PROXY_HOST = "socksProxyHost";
 
     private static final String SOCKS_PROXY_PORT = "socksProxyPort";
 
     private static final String DEFAULT_SOCKS_PROXY_PORT = "1080";
 
+    private static final Logger logger = LoggerFactory.getLogger(NettyClient.class);
+
+    /**
+     * netty client bootstrap
+     */
+    private static final GlobalResourceInitializer<EventLoopGroup> EVENT_LOOP_GROUP = new GlobalResourceInitializer<>(() ->
+        eventLoopGroup(Constants.DEFAULT_IO_THREADS, "NettyClientWorker"),
+        eventLoopGroup -> eventLoopGroup.shutdownGracefully());
+
     private Bootstrap bootstrap;
 
     /**
@@ -93,7 +97,7 @@ public class NettyClient extends AbstractClient {
     protected void doOpen() throws Throwable {
         final NettyClientHandler nettyClientHandler = new NettyClientHandler(getUrl(), this);
         bootstrap = new Bootstrap();
-        bootstrap.group(EVENT_LOOP_GROUP)
+        bootstrap.group(EVENT_LOOP_GROUP.get())
                 .option(ChannelOption.SO_KEEPALIVE, true)
                 .option(ChannelOption.TCP_NODELAY, true)
                 .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
diff --git a/dubbo-remoting/dubbo-remoting-zookeeper-curator5/src/main/java/org/apache/dubbo/remoting/zookeeper/curator5/Curator5ZookeeperClient.java b/dubbo-remoting/dubbo-remoting-zookeeper-curator5/src/main/java/org/apache/dubbo/remoting/zookeeper/curator5/Curator5ZookeeperClient.java
index 9fc626a..168ce14 100644
--- a/dubbo-remoting/dubbo-remoting-zookeeper-curator5/src/main/java/org/apache/dubbo/remoting/zookeeper/curator5/Curator5ZookeeperClient.java
+++ b/dubbo-remoting/dubbo-remoting-zookeeper-curator5/src/main/java/org/apache/dubbo/remoting/zookeeper/curator5/Curator5ZookeeperClient.java
@@ -16,16 +16,6 @@
  */
 package org.apache.dubbo.remoting.zookeeper.curator5;
 
-import org.apache.dubbo.common.URL;
-import org.apache.dubbo.common.config.configcenter.ConfigItem;
-import org.apache.dubbo.common.logger.Logger;
-import org.apache.dubbo.common.logger.LoggerFactory;
-import org.apache.dubbo.remoting.zookeeper.ChildListener;
-import org.apache.dubbo.remoting.zookeeper.DataListener;
-import org.apache.dubbo.remoting.zookeeper.EventType;
-import org.apache.dubbo.remoting.zookeeper.StateListener;
-import org.apache.dubbo.remoting.zookeeper.AbstractZookeeperClient;
-
 import org.apache.curator.framework.CuratorFramework;
 import org.apache.curator.framework.CuratorFrameworkFactory;
 import org.apache.curator.framework.api.CuratorWatcher;
@@ -35,6 +25,15 @@ import org.apache.curator.framework.recipes.cache.NodeCacheListener;
 import org.apache.curator.framework.state.ConnectionState;
 import org.apache.curator.framework.state.ConnectionStateListener;
 import org.apache.curator.retry.RetryNTimes;
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.config.configcenter.ConfigItem;
+import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.remoting.zookeeper.AbstractZookeeperClient;
+import org.apache.dubbo.remoting.zookeeper.ChildListener;
+import org.apache.dubbo.remoting.zookeeper.DataListener;
+import org.apache.dubbo.remoting.zookeeper.EventType;
+import org.apache.dubbo.remoting.zookeeper.StateListener;
 import org.apache.zookeeper.CreateMode;
 import org.apache.zookeeper.KeeperException.NoNodeException;
 import org.apache.zookeeper.KeeperException.NodeExistsException;
@@ -252,6 +251,7 @@ public class Curator5ZookeeperClient extends AbstractZookeeperClient<Curator5Zoo
 
     @Override
     public void doClose() {
+        super.doClose();
         client.close();
     }
 
diff --git a/dubbo-remoting/dubbo-remoting-zookeeper/src/main/java/org/apache/dubbo/remoting/zookeeper/curator/CuratorZookeeperClient.java b/dubbo-remoting/dubbo-remoting-zookeeper/src/main/java/org/apache/dubbo/remoting/zookeeper/curator/CuratorZookeeperClient.java
index 91dc2d7..433def2 100644
--- a/dubbo-remoting/dubbo-remoting-zookeeper/src/main/java/org/apache/dubbo/remoting/zookeeper/curator/CuratorZookeeperClient.java
+++ b/dubbo-remoting/dubbo-remoting-zookeeper/src/main/java/org/apache/dubbo/remoting/zookeeper/curator/CuratorZookeeperClient.java
@@ -16,16 +16,6 @@
  */
 package org.apache.dubbo.remoting.zookeeper.curator;
 
-import org.apache.dubbo.common.URL;
-import org.apache.dubbo.common.config.configcenter.ConfigItem;
-import org.apache.dubbo.common.logger.Logger;
-import org.apache.dubbo.common.logger.LoggerFactory;
-import org.apache.dubbo.remoting.zookeeper.ChildListener;
-import org.apache.dubbo.remoting.zookeeper.DataListener;
-import org.apache.dubbo.remoting.zookeeper.EventType;
-import org.apache.dubbo.remoting.zookeeper.StateListener;
-import org.apache.dubbo.remoting.zookeeper.AbstractZookeeperClient;
-
 import org.apache.curator.framework.CuratorFramework;
 import org.apache.curator.framework.CuratorFrameworkFactory;
 import org.apache.curator.framework.api.CuratorWatcher;
@@ -35,6 +25,15 @@ import org.apache.curator.framework.recipes.cache.NodeCacheListener;
 import org.apache.curator.framework.state.ConnectionState;
 import org.apache.curator.framework.state.ConnectionStateListener;
 import org.apache.curator.retry.RetryNTimes;
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.config.configcenter.ConfigItem;
+import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.remoting.zookeeper.AbstractZookeeperClient;
+import org.apache.dubbo.remoting.zookeeper.ChildListener;
+import org.apache.dubbo.remoting.zookeeper.DataListener;
+import org.apache.dubbo.remoting.zookeeper.EventType;
+import org.apache.dubbo.remoting.zookeeper.StateListener;
 import org.apache.zookeeper.CreateMode;
 import org.apache.zookeeper.KeeperException.NoNodeException;
 import org.apache.zookeeper.KeeperException.NodeExistsException;
@@ -84,6 +83,7 @@ public class CuratorZookeeperClient extends AbstractZookeeperClient<CuratorZooke
                 throw new IllegalStateException("zookeeper not connected");
             }
         } catch (Exception e) {
+            close();
             throw new IllegalStateException(e.getMessage(), e);
         }
     }
@@ -252,6 +252,7 @@ public class CuratorZookeeperClient extends AbstractZookeeperClient<CuratorZooke
 
     @Override
     public void doClose() {
+        super.close();
         client.close();
     }
 
diff --git a/dubbo-remoting/pom.xml b/dubbo-remoting/pom.xml
index 4dcf7ed..f1c1547 100644
--- a/dubbo-remoting/pom.xml
+++ b/dubbo-remoting/pom.xml
@@ -38,4 +38,13 @@
         <module>dubbo-remoting-zookeeper-curator5</module>
         <module>dubbo-remoting-netty4</module>
     </modules>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-test-check</artifactId>
+            <version>${project.parent.version}</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
 </project>
diff --git a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/filter/AccessLogFilter.java b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/filter/AccessLogFilter.java
index 653a4fc..6c9aa26 100644
--- a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/filter/AccessLogFilter.java
+++ b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/filter/AccessLogFilter.java
@@ -21,7 +21,6 @@ import org.apache.dubbo.common.logger.Logger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.utils.ConcurrentHashSet;
 import org.apache.dubbo.common.utils.ConfigUtils;
-import org.apache.dubbo.common.utils.NamedThreadFactory;
 import org.apache.dubbo.rpc.Filter;
 import org.apache.dubbo.rpc.Invocation;
 import org.apache.dubbo.rpc.Invoker;
@@ -39,9 +38,8 @@ import java.util.Iterator;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 import static org.apache.dubbo.common.constants.CommonConstants.PROVIDER;
 import static org.apache.dubbo.rpc.Constants.ACCESS_LOG_KEY;
@@ -74,18 +72,17 @@ public class AccessLogFilter implements Filter {
     private static final String FILE_DATE_FORMAT = "yyyyMMdd";
 
     // It's safe to declare it as singleton since it runs on single thread only
-    private static final DateFormat FILE_NAME_FORMATTER = new SimpleDateFormat(FILE_DATE_FORMAT);
+    private final DateFormat fileNameFormatter = new SimpleDateFormat(FILE_DATE_FORMAT);
 
-    private static final Map<String, Set<AccessLogData>> LOG_ENTRIES = new ConcurrentHashMap<>();
+    private final Map<String, Set<AccessLogData>> logEntries = new ConcurrentHashMap<>();
 
-    private static final ScheduledExecutorService LOG_SCHEDULED = Executors.newSingleThreadScheduledExecutor(new NamedThreadFactory("Dubbo-Access-Log", true));
+    private AtomicBoolean scheduled = new AtomicBoolean();
 
     /**
      * Default constructor initialize demon thread for writing into access log file with names with access log key
      * defined in url <b>accesslog</b>
      */
     public AccessLogFilter() {
-        LOG_SCHEDULED.scheduleWithFixedDelay(this::writeLogToFile, LOG_OUTPUT_INTERVAL, LOG_OUTPUT_INTERVAL, TimeUnit.MILLISECONDS);
     }
 
     /**
@@ -98,6 +95,10 @@ public class AccessLogFilter implements Filter {
      */
     @Override
     public Result invoke(Invoker<?> invoker, Invocation inv) throws RpcException {
+        if (scheduled.compareAndSet(false, true)) {
+            inv.getModuleModel().getApplicationModel().getApplicationExecutorRepository().getSharedScheduledExecutor()
+                .scheduleWithFixedDelay(this::writeLogToFile, LOG_OUTPUT_INTERVAL, LOG_OUTPUT_INTERVAL, TimeUnit.MILLISECONDS);
+        }
         try {
             String accessLogKey = invoker.getUrl().getParameter(ACCESS_LOG_KEY);
             if (ConfigUtils.isNotEmpty(accessLogKey)) {
@@ -112,7 +113,7 @@ public class AccessLogFilter implements Filter {
     }
 
     private void log(String accessLog, AccessLogData accessLogData) {
-        Set<AccessLogData> logSet = LOG_ENTRIES.computeIfAbsent(accessLog, k -> new ConcurrentHashSet<>());
+        Set<AccessLogData> logSet = logEntries.computeIfAbsent(accessLog, k -> new ConcurrentHashSet<>());
 
         if (logSet.size() < LOG_MAX_BUFFER) {
             logSet.add(accessLogData);
@@ -144,8 +145,8 @@ public class AccessLogFilter implements Filter {
     }
 
     private void writeLogToFile() {
-        if (!LOG_ENTRIES.isEmpty()) {
-            for (Map.Entry<String, Set<AccessLogData>> entry : LOG_ENTRIES.entrySet()) {
+        if (!logEntries.isEmpty()) {
+            for (Map.Entry<String, Set<AccessLogData>> entry : logEntries.entrySet()) {
                 String accessLog = entry.getKey();
                 Set<AccessLogData> logSet = entry.getValue();
                 writeLogSetToFile(accessLog, logSet);
@@ -195,8 +196,8 @@ public class AccessLogFilter implements Filter {
 
     private void renameFile(File file) {
         if (file.exists()) {
-            String now = FILE_NAME_FORMATTER.format(new Date());
-            String last = FILE_NAME_FORMATTER.format(new Date(file.lastModified()));
+            String now = fileNameFormatter.format(new Date());
+            String last = fileNameFormatter.format(new Date(file.lastModified()));
             if (!now.equals(last)) {
                 File archive = new File(file.getAbsolutePath() + "." + last);
                 file.renameTo(archive);
diff --git a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/protocol/AbstractProtocol.java b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/protocol/AbstractProtocol.java
index 4192ba7..388aa60 100644
--- a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/protocol/AbstractProtocol.java
+++ b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/protocol/AbstractProtocol.java
@@ -94,7 +94,6 @@ public abstract class AbstractProtocol implements Protocol, ScopeModelAware {
     public void destroy() {
         for (Invoker<?> invoker : invokers) {
             if (invoker != null) {
-                invokers.remove(invoker);
                 try {
                     if (logger.isInfoEnabled()) {
                         logger.info("Destroy reference: " + invoker.getUrl());
@@ -105,8 +104,9 @@ public abstract class AbstractProtocol implements Protocol, ScopeModelAware {
                 }
             }
         }
-        for (String key : new ArrayList<>(exporterMap.keySet())) {
-            Exporter<?> exporter = exporterMap.remove(key);
+        invokers.clear();
+
+        exporterMap.forEach((key, exporter)-> {
             if (exporter != null) {
                 try {
                     if (logger.isInfoEnabled()) {
@@ -117,7 +117,8 @@ public abstract class AbstractProtocol implements Protocol, ScopeModelAware {
                     logger.warn(t.getMessage(), t);
                 }
             }
-        }
+        });
+        exporterMap.clear();
     }
 
     @Override
diff --git a/dubbo-rpc/dubbo-rpc-api/src/test/java/org/apache/dubbo/rpc/filter/AccessLogFilterTest.java b/dubbo-rpc/dubbo-rpc-api/src/test/java/org/apache/dubbo/rpc/filter/AccessLogFilterTest.java
index 53e1537..5b87b88 100644
--- a/dubbo-rpc/dubbo-rpc-api/src/test/java/org/apache/dubbo/rpc/filter/AccessLogFilterTest.java
+++ b/dubbo-rpc/dubbo-rpc-api/src/test/java/org/apache/dubbo/rpc/filter/AccessLogFilterTest.java
@@ -25,7 +25,6 @@ import org.apache.dubbo.rpc.Invoker;
 import org.apache.dubbo.rpc.support.AccessLogData;
 import org.apache.dubbo.rpc.support.MockInvocation;
 import org.apache.dubbo.rpc.support.MyInvoker;
-
 import org.junit.jupiter.api.Test;
 
 import java.lang.reflect.Field;
@@ -63,13 +62,13 @@ public class AccessLogFilterTest {
         Invoker<AccessLogFilterTest> invoker = new MyInvoker<AccessLogFilterTest>(url);
         Invocation invocation = new MockInvocation();
 
-        Field field = AccessLogFilter.class.getDeclaredField("LOG_ENTRIES");
+        Field field = AccessLogFilter.class.getDeclaredField("logEntries");
         field.setAccessible(true);
-        assertTrue(((Map) field.get(AccessLogFilter.class)).isEmpty());
+        assertTrue(((Map) field.get(accessLogFilter)).isEmpty());
 
         accessLogFilter.invoke(invoker, invocation);
 
-        Map<String, Set<AccessLogData>> logs = (Map<String, Set<AccessLogData>>) field.get(AccessLogFilter.class);
+        Map<String, Set<AccessLogData>> logs = (Map<String, Set<AccessLogData>>) field.get(accessLogFilter);
         assertFalse(logs.isEmpty());
         assertFalse(logs.get("true").isEmpty());
         AccessLogData log = logs.get("true").iterator().next();
diff --git a/dubbo-rpc/dubbo-rpc-dubbo/src/main/java/org/apache/dubbo/rpc/protocol/dubbo/DubboProtocol.java b/dubbo-rpc/dubbo-rpc-dubbo/src/main/java/org/apache/dubbo/rpc/protocol/dubbo/DubboProtocol.java
index 2555aab..7605959 100644
--- a/dubbo-rpc/dubbo-rpc-dubbo/src/main/java/org/apache/dubbo/rpc/protocol/dubbo/DubboProtocol.java
+++ b/dubbo-rpc/dubbo-rpc-dubbo/src/main/java/org/apache/dubbo/rpc/protocol/dubbo/DubboProtocol.java
@@ -57,6 +57,7 @@ import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Function;
 
 import static org.apache.dubbo.common.constants.CommonConstants.GROUP_KEY;
@@ -104,6 +105,8 @@ public class DubboProtocol extends AbstractProtocol {
     private static final Object PENDING_OBJECT = new Object();
     private final Set<String> optimizers = new ConcurrentHashSet<>();
 
+    private AtomicBoolean destroyed = new AtomicBoolean();
+
     private ExchangeHandler requestHandler = new ExchangeHandlerAdapter() {
 
         @Override
@@ -286,6 +289,7 @@ public class DubboProtocol extends AbstractProtocol {
 
     @Override
     public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
+        checkDestroyed();
         URL url = invoker.getUrl();
 
         // export service.
@@ -314,6 +318,7 @@ public class DubboProtocol extends AbstractProtocol {
     }
 
     private void openServer(URL url) {
+        checkDestroyed();
         // find server.
         String key = url.getAddress();
         //client can export a service which's only for server to invoke
@@ -336,6 +341,12 @@ public class DubboProtocol extends AbstractProtocol {
         }
     }
 
+    private void checkDestroyed() {
+        if (destroyed.get()) {
+            throw new IllegalStateException( getClass().getSimpleName() + " is destroyed");
+        }
+    }
+
     private ProtocolServer createServer(URL url) {
         url = URLBuilder.from(url)
                 // send readonly event when server closes, it's enabled by default
@@ -407,11 +418,13 @@ public class DubboProtocol extends AbstractProtocol {
 
     @Override
     public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
+        checkDestroyed();
         return protocolBindingRefer(type, url);
     }
 
     @Override
     public <T> Invoker<T> protocolBindingRefer(Class<T> serviceType, URL url) throws RpcException {
+        checkDestroyed();
         optimizeSerialization(url);
 
         // create rpc invoker.
@@ -644,6 +657,12 @@ public class DubboProtocol extends AbstractProtocol {
     @Override
     @SuppressWarnings("unchecked")
     public void destroy() {
+        if (!destroyed.compareAndSet(false, true)) {
+            return;
+        }
+        if (logger.isInfoEnabled()) {
+            logger.info("Destroying protocol [" + this.getClass().getSimpleName() + "] ...");
+        }
         for (String key : new ArrayList<>(serverMap.keySet())) {
             ProtocolServer protocolServer = serverMap.remove(key);
 
@@ -664,6 +683,7 @@ public class DubboProtocol extends AbstractProtocol {
                 logger.warn("Close dubbo server [" + server.getLocalAddress()+ "] failed: " + t.getMessage(), t);
             }
         }
+        serverMap.clear();
 
         for (String key : new ArrayList<>(referenceClientMap.keySet())) {
             Object clients = referenceClientMap.remove(key);
@@ -679,6 +699,7 @@ public class DubboProtocol extends AbstractProtocol {
                 }
             }
         }
+        referenceClientMap.clear();
 
         super.destroy();
     }
diff --git a/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/org/apache/dubbo/rpc/protocol/dubbo/decode/DubboTelnetDecodeTest.java b/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/org/apache/dubbo/rpc/protocol/dubbo/decode/DubboTelnetDecodeTest.java
index 1ef1702..b11cab0 100644
--- a/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/org/apache/dubbo/rpc/protocol/dubbo/decode/DubboTelnetDecodeTest.java
+++ b/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/org/apache/dubbo/rpc/protocol/dubbo/decode/DubboTelnetDecodeTest.java
@@ -52,6 +52,8 @@ import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 
+import static org.apache.dubbo.rpc.Constants.SERIALIZATION_SECURITY_CHECK_KEY;
+
 /**
  * These junit tests aim to test unpack and stick pack of dubbo and telnet
  */
@@ -72,11 +74,17 @@ public class DubboTelnetDecodeTest {
     public static void setup() {
         ModuleServiceRepository serviceRepository = ApplicationModel.defaultModel().getDefaultModule().getServiceRepository();
         serviceRepository.registerService(DemoService.class);
+
+        // disable org.apache.dubbo.remoting.transport.CodecSupport.checkSerialization to avoid error:
+        // java.io.IOException: Service org.apache.dubbo.rpc.protocol.dubbo.support.DemoService with version 0.0.0 not found, invocation rejected.
+        System.setProperty(SERIALIZATION_SECURITY_CHECK_KEY, "false");
     }
 
     @AfterAll
     public static void teardown() {
         FrameworkModel.defaultModel().destroy();
+        System.clearProperty(SERIALIZATION_SECURITY_CHECK_KEY);
+
     }
 
     /**
@@ -470,6 +478,9 @@ public class DubboTelnetDecodeTest {
         DubboCodec dubboCodec = new DubboCodec(FrameworkModel.defaultModel());
         dubboCodec.encode(new MockChannel(), buffer, request);
 
+        // register
+        // frameworkModel.getServiceRepository().registerProviderUrl();
+
         return dubboByteBuf;
     }
 
diff --git a/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/org/apache/dubbo/rpc/protocol/dubbo/support/ProtocolUtils.java b/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/org/apache/dubbo/rpc/protocol/dubbo/support/ProtocolUtils.java
index 533e408..e614b26 100644
--- a/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/org/apache/dubbo/rpc/protocol/dubbo/support/ProtocolUtils.java
+++ b/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/org/apache/dubbo/rpc/protocol/dubbo/support/ProtocolUtils.java
@@ -17,33 +17,34 @@
 package org.apache.dubbo.rpc.protocol.dubbo.support;
 
 import org.apache.dubbo.common.URL;
-import org.apache.dubbo.common.extension.ExtensionLoader;
 import org.apache.dubbo.rpc.Exporter;
 import org.apache.dubbo.rpc.Invoker;
 import org.apache.dubbo.rpc.Protocol;
-import org.apache.dubbo.rpc.ProtocolServer;
 import org.apache.dubbo.rpc.ProxyFactory;
+import org.apache.dubbo.rpc.model.FrameworkModel;
 import org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol;
 
-import java.util.List;
-
 /**
  * TODO Comment of ProtocolUtils
  */
 public class ProtocolUtils {
 
-    public static ProxyFactory proxy = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
-    private static Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
 
     public static <T> T refer(Class<T> type, String url) {
         return refer(type, URL.valueOf(url));
     }
 
     public static <T> T refer(Class<T> type, URL url) {
+        FrameworkModel frameworkModel = url.getOrDefaultFrameworkModel();
+        ProxyFactory proxy = frameworkModel.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
+        Protocol protocol = frameworkModel.getExtensionLoader(Protocol.class).getAdaptiveExtension();
         return proxy.getProxy(protocol.refer(type, url));
     }
 
     public static Invoker<?> referInvoker(Class<?> type, URL url) {
+        FrameworkModel frameworkModel = url.getOrDefaultFrameworkModel();
+        ProxyFactory proxy = frameworkModel.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
+        Protocol protocol = frameworkModel.getExtensionLoader(Protocol.class).getAdaptiveExtension();
         return (Invoker<?>) protocol.refer(type, url);
     }
 
@@ -52,14 +53,14 @@ public class ProtocolUtils {
     }
 
     public static <T> Exporter<T> export(T instance, Class<T> type, URL url) {
+        FrameworkModel frameworkModel = url.getOrDefaultFrameworkModel();
+        ProxyFactory proxy = frameworkModel.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
+        Protocol protocol = frameworkModel.getExtensionLoader(Protocol.class).getAdaptiveExtension();
         return protocol.export(proxy.getInvoker(instance, type, url));
     }
 
     public static void closeAll() {
         DubboProtocol.getDubboProtocol().destroy();
-        List<ProtocolServer> servers = DubboProtocol.getDubboProtocol().getServers();
-        for (ProtocolServer server : servers) {
-            server.close();
-        }
+        FrameworkModel.destroyAll();
     }
 }
diff --git a/dubbo-rpc/dubbo-rpc-dubbo/src/test/resources/log4j.xml b/dubbo-rpc/dubbo-rpc-dubbo/src/test/resources/log4j.xml
index 09dba05..4d4f4b6 100644
--- a/dubbo-rpc/dubbo-rpc-dubbo/src/test/resources/log4j.xml
+++ b/dubbo-rpc/dubbo-rpc-dubbo/src/test/resources/log4j.xml
@@ -31,8 +31,16 @@
             <param name="LevelMax" value="DEBUG" />
         </filter> -->
     </appender>
+
+    <appender name="console" class="org.apache.log4j.ConsoleAppender">
+        <layout class="org.apache.log4j.PatternLayout">
+            <param name="ConversionPattern" value="[%d{dd/MM/yy HH:mm:ss:SSS z}] %t %5p %c{2}: %m%n"/>
+        </layout>
+    </appender>
+
     <root>
         <level value="INFO"/>
         <appender-ref ref="dubbo"/>
+        <appender-ref ref="console"/>
     </root>
 </log4j:configuration>
diff --git a/dubbo-rpc/dubbo-rpc-grpc/src/main/java/org/apache/dubbo/rpc/protocol/grpc/GrpcProtocol.java b/dubbo-rpc/dubbo-rpc-grpc/src/main/java/org/apache/dubbo/rpc/protocol/grpc/GrpcProtocol.java
index 0e80932..c3128ae 100644
--- a/dubbo-rpc/dubbo-rpc-grpc/src/main/java/org/apache/dubbo/rpc/protocol/grpc/GrpcProtocol.java
+++ b/dubbo-rpc/dubbo-rpc-grpc/src/main/java/org/apache/dubbo/rpc/protocol/grpc/GrpcProtocol.java
@@ -16,6 +16,12 @@
  */
 package org.apache.dubbo.rpc.protocol.grpc;
 
+import io.grpc.BindableService;
+import io.grpc.CallOptions;
+import io.grpc.Channel;
+import io.grpc.ManagedChannel;
+import io.grpc.Server;
+import io.grpc.netty.NettyServerBuilder;
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.logger.Logger;
 import org.apache.dubbo.common.logger.LoggerFactory;
@@ -27,13 +33,6 @@ import org.apache.dubbo.rpc.model.FrameworkServiceRepository;
 import org.apache.dubbo.rpc.model.ProviderModel;
 import org.apache.dubbo.rpc.protocol.AbstractProxyProtocol;
 
-import io.grpc.BindableService;
-import io.grpc.CallOptions;
-import io.grpc.Channel;
-import io.grpc.ManagedChannel;
-import io.grpc.Server;
-import io.grpc.netty.NettyServerBuilder;
-
 import java.io.IOException;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
@@ -190,6 +189,9 @@ public class GrpcProtocol extends AbstractProxyProtocol {
 
     @Override
     public void destroy() {
+        if (logger.isInfoEnabled()) {
+            logger.info("Destroying protocol [" + this.getClass().getSimpleName() + "] ...");
+        }
         serverMap.values().forEach(ProtocolServer::close);
         channelMap.values().forEach(ReferenceCountManagedChannel::shutdown);
         serverMap.clear();
diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/RestProtocol.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/RestProtocol.java
index 1e658b6..0c8720c 100644
--- a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/RestProtocol.java
+++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/RestProtocol.java
@@ -24,7 +24,6 @@ import org.apache.dubbo.remoting.http.servlet.ServletManager;
 import org.apache.dubbo.rpc.ProtocolServer;
 import org.apache.dubbo.rpc.RpcException;
 import org.apache.dubbo.rpc.protocol.AbstractProxyProtocol;
-
 import org.apache.http.HeaderElement;
 import org.apache.http.HeaderElementIterator;
 import org.apache.http.client.config.RequestConfig;
@@ -205,6 +204,9 @@ public class RestProtocol extends AbstractProxyProtocol {
 
     @Override
     public void destroy() {
+        if (logger.isInfoEnabled()) {
+            logger.info("Destroying protocol [" + this.getClass().getSimpleName() + "] ...");
+        }
         super.destroy();
 
         if (connectionMonitor != null) {
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleProtocol.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleProtocol.java
index 8a5a6c8..940a824 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleProtocol.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleProtocol.java
@@ -16,6 +16,7 @@
  */
 package org.apache.dubbo.rpc.protocol.tri;
 
+import grpc.health.v1.HealthCheckResponse;
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.logger.Logger;
 import org.apache.dubbo.common.logger.LoggerFactory;
@@ -32,10 +33,6 @@ import org.apache.dubbo.rpc.protocol.AbstractExporter;
 import org.apache.dubbo.rpc.protocol.AbstractProtocol;
 import org.apache.dubbo.rpc.protocol.tri.service.TriBuiltinService;
 
-import grpc.health.v1.HealthCheckResponse;
-
-import java.util.ArrayList;
-
 import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_CLIENT_THREADPOOL;
 import static org.apache.dubbo.common.constants.CommonConstants.THREADPOOL_KEY;
 
@@ -110,21 +107,11 @@ public class TripleProtocol extends AbstractProtocol implements Protocol {
 
     @Override
     public void destroy() {
+        if (logger.isInfoEnabled()) {
+            logger.info("Destroying protocol [" + this.getClass().getSimpleName() + "] ...");
+        }
         PortUnificationExchanger.close();
         pathResolver.destroy();
-        for (String key : new ArrayList<>(exporterMap.keySet())) {
-            Exporter<?> exporter = exporterMap.remove(key);
-            if (exporter != null) {
-                try {
-                    if (logger.isInfoEnabled()) {
-                        logger.info("Unexport service: " + exporter.getInvoker().getUrl());
-                    }
-                    exporter.unexport();
-                } catch (Throwable t) {
-                    logger.warn(t.getMessage(), t);
-                }
-            }
-        }
-
+        super.destroy();
     }
 }
diff --git a/dubbo-rpc/pom.xml b/dubbo-rpc/pom.xml
index ef384d5..091f8e9 100644
--- a/dubbo-rpc/pom.xml
+++ b/dubbo-rpc/pom.xml
@@ -37,4 +37,13 @@
         <module>dubbo-rpc-grpc</module>
         <module>dubbo-rpc-triple</module>
     </modules>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-test-check</artifactId>
+            <version>${project.parent.version}</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
 </project>
diff --git a/dubbo-serialization/pom.xml b/dubbo-serialization/pom.xml
index 2d23812..6f75995 100644
--- a/dubbo-serialization/pom.xml
+++ b/dubbo-serialization/pom.xml
@@ -34,4 +34,13 @@
         <module>dubbo-serialization-hessian2</module>
         <module>dubbo-serialization-jdk</module>
     </modules>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-test-check</artifactId>
+            <version>${project.parent.version}</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
 </project>
diff --git a/dubbo-spring-boot/dubbo-spring-boot-compatible/autoconfigure/src/main/java/org/apache/dubbo/spring/boot/context/event/AwaitingNonWebApplicationListener.java b/dubbo-spring-boot/dubbo-spring-boot-compatible/autoconfigure/src/main/java/org/apache/dubbo/spring/boot/context/event/AwaitingNonWebApplicationListener.java
index 8dd4266..deab7fb 100644
--- a/dubbo-spring-boot/dubbo-spring-boot-compatible/autoconfigure/src/main/java/org/apache/dubbo/spring/boot/context/event/AwaitingNonWebApplicationListener.java
+++ b/dubbo-spring-boot/dubbo-spring-boot-compatible/autoconfigure/src/main/java/org/apache/dubbo/spring/boot/context/event/AwaitingNonWebApplicationListener.java
@@ -57,26 +57,26 @@ public class AwaitingNonWebApplicationListener implements SmartApplicationListen
     private static final Class<? extends ApplicationEvent>[] SUPPORTED_APPLICATION_EVENTS =
             of(ApplicationReadyEvent.class, ContextClosedEvent.class);
 
-    private static final AtomicBoolean awaited = new AtomicBoolean(false);
+    private final AtomicBoolean awaited = new AtomicBoolean(false);
 
     private static final Integer UNDEFINED_ID = Integer.valueOf(-1);
 
     /**
      * Target the application id
      */
-    private static final AtomicInteger applicationContextId = new AtomicInteger(UNDEFINED_ID);
+    private final AtomicInteger applicationContextId = new AtomicInteger(UNDEFINED_ID);
 
-    private static final Lock lock = new ReentrantLock();
+    private final Lock lock = new ReentrantLock();
 
-    private static final Condition condition = lock.newCondition();
+    private final Condition condition = lock.newCondition();
 
-    private static final ExecutorService executorService = newSingleThreadExecutor();
+    private final ExecutorService executorService = newSingleThreadExecutor();
 
     private static <T> T[] of(T... values) {
         return values;
     }
 
-    static AtomicBoolean getAwaited() {
+    AtomicBoolean getAwaited() {
         return awaited;
     }
 
diff --git a/dubbo-spring-boot/pom.xml b/dubbo-spring-boot/pom.xml
index f3ea332..2f52e8a 100644
--- a/dubbo-spring-boot/pom.xml
+++ b/dubbo-spring-boot/pom.xml
@@ -78,6 +78,12 @@
             <scope>test</scope>
         </dependency>
 
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-test-check</artifactId>
+            <version>${project.parent.version}</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <profiles>
diff --git a/dubbo-test/pom.xml b/dubbo-test/dubbo-test-check/pom.xml
similarity index 62%
copy from dubbo-test/pom.xml
copy to dubbo-test/dubbo-test-check/pom.xml
index bc6fee2..0af14d7 100644
--- a/dubbo-test/pom.xml
+++ b/dubbo-test/dubbo-test-check/pom.xml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
 <!--
   Licensed to the Apache Software Foundation (ASF) under one or more
   contributor license agreements.  See the NOTICE file distributed with
@@ -18,39 +19,33 @@
          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">
     <parent>
+        <artifactId>dubbo-test</artifactId>
         <groupId>org.apache.dubbo</groupId>
-        <artifactId>dubbo-parent</artifactId>
         <version>${revision}</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
-    <artifactId>dubbo-test</artifactId>
-    <packaging>pom</packaging>
 
-    <modules>
-        <module>dubbo-test-common</module>
-        <module>dubbo-test-spring</module>
-        <module>dubbo-test-spring3.2</module>
-        <module>dubbo-test-spring4.1</module>
-        <module>dubbo-test-spring4.2</module>
-    </modules>
+    <artifactId>dubbo-test-check</artifactId>
 
     <properties>
         <maven.compiler.source>8</maven.compiler.source>
         <maven.compiler.target>8</maven.compiler.target>
-        <skip_maven_deploy>true</skip_maven_deploy>
     </properties>
 
-    <dependencyManagement>
-        <dependencies>
-            <dependency>
-                <groupId>org.apache.dubbo</groupId>
-                <artifactId>dubbo-bom</artifactId>
-                <version>${project.parent.version}</version>
-                <type>pom</type>
-                <scope>import</scope>
-            </dependency>
-        </dependencies>
-    </dependencyManagement>
-
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-common</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.junit.platform</groupId>
+            <artifactId>junit-platform-launcher</artifactId>
+            <version>1.6.2</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+        </dependency>
+    </dependencies>
 </project>
diff --git a/dubbo-test/dubbo-test-check/src/main/java/org/apache/dubbo/test/check/DubboTestChecker.java b/dubbo-test/dubbo-test-check/src/main/java/org/apache/dubbo/test/check/DubboTestChecker.java
new file mode 100644
index 0000000..e823360
--- /dev/null
+++ b/dubbo-test/dubbo-test-check/src/main/java/org/apache/dubbo/test/check/DubboTestChecker.java
@@ -0,0 +1,309 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.test.check;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.rpc.model.FrameworkModel;
+import org.junit.platform.engine.TestExecutionResult;
+import org.junit.platform.engine.TestSource;
+import org.junit.platform.engine.support.descriptor.ClassSource;
+import org.junit.platform.engine.support.descriptor.MethodSource;
+import org.junit.platform.launcher.TestExecutionListener;
+import org.junit.platform.launcher.TestIdentifier;
+import org.junit.platform.launcher.TestPlan;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Comparator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+
+/**
+ * A test listener to check unclosed threads of test.
+ *
+ * <pre>
+ * Usages:
+ *  # enable thread checking
+ *  mvn test -DcheckThreads=true
+ *
+ *  # change thread dump wait time (ms)
+ *  mvn test -DcheckThreads=true -DthreadDumpWaitTime=5000
+ *
+ *  # print test reports of all sub modules to single file
+ *  mvn test -DcheckThreads=true -DthreadDumpWaitTime=5000 -DreportFile=/path/test-check-report.txt
+ * </pre>
+ */
+public class DubboTestChecker implements TestExecutionListener {
+
+    private static final String CONFIG_CHECK_MODE = "checkMode";
+    private static final String CONFIG_CHECK_THREADS = "checkThreads";
+    private static final String CONFIG_THREAD_DUMP_WAIT_TIME = "threadDumpWaitTime";
+    private static final String CONFIG_FORCE_DESTROY = "forceDestroy";
+    private static final String CONFIG_REPORT_FILE = "reportFile";
+    private static final String MODE_CLASS = "class";
+    private static final String MODE_METHOD = "method";
+
+    private static final Logger logger = LoggerFactory.getLogger(DubboTestChecker.class);
+
+    /**
+     * check mode:
+     * class - check after class execution finished
+     * method - check after method execution finished
+     */
+    private String checkMode;
+    /**
+     * whether check unclosed threads
+     */
+    private boolean checkThreads;
+    /**
+     * sleep time before dump threads
+     */
+    private long threadDumpWaitTimeMs;
+    /**
+     * whether force destroy dubbo engine, default value is true.
+     */
+    private boolean forceDestroyDubboAfterClass;
+
+    /**
+     * Check report file
+     */
+    private File reportFile;
+
+    /**
+     * thread -> stacktrace
+     */
+    private Map<Thread, StackTraceElement[]> unclosedThreadMap = new ConcurrentHashMap<>();
+    // test class name -> thread list
+    private Map<String, List<Thread>> unclosedThreadsOfTestMap = new ConcurrentHashMap<>();
+    private String identifier;
+    private PrintWriter reportWriter;
+    private String projectDir;
+    private FileOutputStream reportFileOut;
+
+    @Override
+    public void testPlanExecutionStarted(TestPlan testPlan) {
+        try {
+            init(System.getProperties());
+        } catch (IOException e) {
+            throw new IllegalStateException("Test checker init failed", e);
+        }
+    }
+
+    public void init(Properties properties) throws IOException {
+        if (properties == null) {
+            properties = new Properties();
+        }
+        // log prefix
+        identifier = "[" + this.getClass().getSimpleName() + "] ";
+
+        // checkMode: class/method
+        checkMode = StringUtils.lowerCase(properties.getProperty(CONFIG_CHECK_MODE, MODE_CLASS));
+        // checkThreads: true/false
+        checkThreads = Boolean.parseBoolean(properties.getProperty(CONFIG_CHECK_THREADS, "false"));
+        // threadDumpWaitTime
+        threadDumpWaitTimeMs = Long.parseLong(properties.getProperty(CONFIG_THREAD_DUMP_WAIT_TIME, "5000"));
+        // force destroy dubbo
+        forceDestroyDubboAfterClass = Boolean.parseBoolean(properties.getProperty(CONFIG_FORCE_DESTROY, "true"));
+
+        // project dir
+        projectDir = new File(".").getCanonicalPath();
+
+        // report file
+        String reportFileCanonicalPath = "";
+        String defaultReportDir = "target/";
+        String defaultReportFileName = "test-check-report.txt";
+        if (checkThreads) {
+            String reportFilePath = properties.getProperty(CONFIG_REPORT_FILE, defaultReportDir + defaultReportFileName);
+            this.reportFile = new File(reportFilePath);
+            if (reportFile.isDirectory()) {
+                reportFile.mkdirs();
+                reportFile = new File(reportFile, defaultReportFileName);
+            }
+            reportFileOut = new FileOutputStream(this.reportFile);
+            reportWriter = new PrintWriter(reportFileOut);
+            reportFileCanonicalPath = reportFile.getCanonicalPath();
+        }
+
+        log("Project dir: " + projectDir);
+        log(String.format("Dubbo test checker configs: checkMode=%s, checkThreads=%s, threadDumpWaitTimeMs=%s, forceDestroy=%s, reportFile=%s",
+            checkMode, checkThreads, threadDumpWaitTimeMs, forceDestroyDubboAfterClass, reportFileCanonicalPath));
+        flushReportFile();
+    }
+
+    @Override
+    public void testPlanExecutionFinished(TestPlan testPlan) {
+
+        // print all unclosed threads
+        if (checkThreads) {
+            printThreadCheckingSummaryReport();
+        } else {
+            log("Thread checking is disabled, use -DcheckThreads=true to check unclosed threads.");
+        }
+        if (reportWriter != null) {
+            reportWriter.close();
+        }
+    }
+
+    private void printThreadCheckingSummaryReport() {
+        log("===== Thread Checking Summary Report ======");
+        log("Project dir: " + projectDir);
+        log("Total found " + unclosedThreadMap.size() + " unclosed threads in " + unclosedThreadsOfTestMap.size() + " tests.");
+        log("");
+        unclosedThreadsOfTestMap.forEach((testClassName, threads) -> {
+            printUnclosedThreads(threads, testClassName);
+        });
+        flushReportFile();
+    }
+
+    private void flushReportFile() {
+        try {
+            reportWriter.flush();
+            if (reportFileOut != null) {
+                reportFileOut.getFD().sync();
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public Map<Thread, StackTraceElement[]> getUnclosedThreadMap() {
+        return unclosedThreadMap;
+    }
+
+    @Override
+    public void executionStarted(TestIdentifier testIdentifier) {
+//        TestSource testSource = testIdentifier.getSource().orElse(null);
+//        if (testSource instanceof ClassSource) {
+//            ClassSource source = (ClassSource) testSource;
+//            log("Run test class: " + source.getClassName());
+//        } else if (testSource instanceof MethodSource) {
+//            MethodSource source = (MethodSource) testSource;
+//            log("Run test method: " + source.getClassName() + "#" + source.getMethodName());
+//        }
+    }
+
+    @Override
+    public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) {
+
+        TestSource testSource = testIdentifier.getSource().orElse(null);
+        String testClassName;
+        if (testSource instanceof MethodSource) {
+            if (!StringUtils.contains(checkMode, MODE_METHOD)) {
+                return;
+            }
+            MethodSource methodSource = (MethodSource) testSource;
+            testClassName = methodSource.getClassName();
+            //log("Finish test method: " + methodSource.getClassName() + "#" + methodSource.getMethodName());
+        } else if (testSource instanceof ClassSource) {
+            if (forceDestroyDubboAfterClass) {
+                // make sure destroy dubbo engine
+                FrameworkModel.destroyAll();
+            }
+
+            if (!StringUtils.contains(checkMode, MODE_CLASS)) {
+                return;
+            }
+
+            ClassSource source = (ClassSource) testSource;
+            testClassName = source.getClassName();
+            //log("Finish test class: " + source.getClassName());
+        } else {
+            return;
+        }
+
+        if (checkThreads) {
+            checkUnclosedThreads(testClassName, threadDumpWaitTimeMs);
+        }
+    }
+
+    public Map<Thread, StackTraceElement[]> checkUnclosedThreads(String testClassName, long waitMs) {
+        // wait for shutdown
+        log("Wait " + waitMs + "ms to check threads of " + testClassName + " ...");
+        try {
+            Thread.sleep(waitMs);
+        } catch (InterruptedException e) {
+        }
+
+        Map<Thread, StackTraceElement[]> threadStacks = Thread.getAllStackTraces();
+        List<Thread> unclosedThreads = threadStacks.keySet().stream()
+            .filter(thread -> !StringUtils.startsWithAny(thread.getName(),
+                "Reference Handler", "Finalizer", "Signal Dispatcher", "Attach Listener", "process reaper", "main" // jvm
+                , "surefire-forkedjvm-" // surefire plugin
+            ))
+            .filter(thread -> !unclosedThreadMap.containsKey(thread))
+            .collect(Collectors.toList());
+        unclosedThreads.sort(Comparator.comparing(Thread::getName));
+        if (unclosedThreads.size() > 0) {
+            for (Thread thread : unclosedThreads) {
+                unclosedThreadMap.put(thread, threadStacks.get(thread));
+            }
+            unclosedThreadsOfTestMap.put(testClassName, unclosedThreads);
+            printUnclosedThreads(unclosedThreads, testClassName);
+        }
+
+        // return new unclosed thread map
+        Map<Thread, StackTraceElement[]> unclosedThreadMap = new LinkedHashMap<>();
+        for (Thread thread : unclosedThreads) {
+            unclosedThreadMap.put(thread, threadStacks.get(thread));
+        }
+        return unclosedThreadMap;
+    }
+
+    private void printUnclosedThreads(List<Thread> threads, String testClassName) {
+        if (threads.size() > 0) {
+            log("Found " + threads.size() + " unclosed threads in test: " + testClassName);
+            for (Thread thread : threads) {
+                StackTraceElement[] stackTrace = unclosedThreadMap.get(thread);
+                log(getFullStacktrace(thread, stackTrace));
+            }
+            flushReportFile();
+        }
+    }
+
+    private void log(String msg) {
+        // logger.info(identifier + msg);
+        String s = identifier + msg;
+        System.out.println(s);
+        if (reportWriter != null) {
+            reportWriter.println(s);
+        }
+    }
+
+    public static String getFullStacktrace(Thread thread, StackTraceElement[] stackTrace) {
+        StringBuilder sb = new StringBuilder("Thread: \"" + thread.getName() + "\"" + " Id="
+            + thread.getId());
+        sb.append(" ").append(thread.getState());
+        sb.append('\n');
+        if (stackTrace == null) {
+            stackTrace = thread.getStackTrace();
+        }
+        for (StackTraceElement ste : stackTrace) {
+            sb.append("    at ").append(ste.toString());
+            sb.append('\n');
+        }
+        return sb.toString();
+    }
+
+}
diff --git a/dubbo-test/dubbo-test-check/src/main/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener b/dubbo-test/dubbo-test-check/src/main/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener
new file mode 100644
index 0000000..0b6e457
--- /dev/null
+++ b/dubbo-test/dubbo-test-check/src/main/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener
@@ -0,0 +1 @@
+org.apache.dubbo.test.check.DubboTestChecker
diff --git a/dubbo-test/dubbo-test-common/pom.xml b/dubbo-test/dubbo-test-common/pom.xml
index 0cbfa34..69b53ff 100644
--- a/dubbo-test/dubbo-test-common/pom.xml
+++ b/dubbo-test/dubbo-test-common/pom.xml
@@ -68,5 +68,11 @@
                 </exclusion>
             </exclusions>
         </dependency>
+
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-test-check</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
     </dependencies>
 </project>
diff --git a/dubbo-test/pom.xml b/dubbo-test/pom.xml
index bc6fee2..ee20080 100644
--- a/dubbo-test/pom.xml
+++ b/dubbo-test/pom.xml
@@ -28,6 +28,7 @@
     <packaging>pom</packaging>
 
     <modules>
+        <module>dubbo-test-check</module>
         <module>dubbo-test-common</module>
         <module>dubbo-test-spring</module>
         <module>dubbo-test-spring3.2</module>