diff --git a/src/php/bin/run_tests.sh b/src/php/bin/run_tests.sh
index b913166ca28..295bcb2430c 100755
--- a/src/php/bin/run_tests.sh
+++ b/src/php/bin/run_tests.sh
@@ -23,4 +23,8 @@ source ./determine_extension_dir.sh
 # in some jenkins macos machine, somehow the PHP build script can't find libgrpc.dylib
 export DYLD_LIBRARY_PATH=$root/libs/$CONFIG
 php $extension_dir -d max_execution_time=300 $(which phpunit) -v --debug \
-  ../tests/unit_tests
+  --exclude-group persistent_list_bound_tests ../tests/unit_tests
+
+php $extension_dir -d max_execution_time=300 $(which phpunit) -v --debug \
+  ../tests/unit_tests/PersistentChannelTests
+
diff --git a/src/php/ext/grpc/call.c b/src/php/ext/grpc/call.c
index b802f04f531..4a647ec7c27 100644
--- a/src/php/ext/grpc/call.c
+++ b/src/php/ext/grpc/call.c
@@ -230,7 +230,7 @@ PHP_METHOD(Call, __construct) {
   }
   wrapped_grpc_channel *channel = Z_WRAPPED_GRPC_CHANNEL_P(channel_obj);
   gpr_mu_lock(&channel->wrapper->mu);
-  if (channel->wrapper->wrapped == NULL) {
+  if (channel->wrapper == NULL || channel->wrapper->wrapped == NULL) {
     zend_throw_exception(spl_ce_InvalidArgumentException,
                          "Call cannot be constructed from a closed Channel",
                          1 TSRMLS_CC);
@@ -251,6 +251,7 @@ PHP_METHOD(Call, __construct) {
   grpc_slice_unref(method_slice);
   grpc_slice_unref(host_slice);
   call->owned = true;
+  call->channel = channel;
   gpr_mu_unlock(&channel->wrapper->mu);
 }
 
@@ -270,6 +271,15 @@ PHP_METHOD(Call, startBatch) {
   zval *message_value;
   zval *message_flags;
   wrapped_grpc_call *call = Z_WRAPPED_GRPC_CALL_P(getThis());
+  if (call->channel) {
+    // startBatch in gRPC PHP server doesn't have channel in it.
+    if (call->channel->wrapper == NULL ||
+        call->channel->wrapper->wrapped == NULL) {
+      zend_throw_exception(spl_ce_RuntimeException,
+                           "startBatch Error. Channel is closed",
+                           1 TSRMLS_CC);
+    }
+  }
   
   grpc_op ops[8];
   size_t op_num = 0;
diff --git a/src/php/ext/grpc/call.h b/src/php/ext/grpc/call.h
index 104ac301c13..bb73354c428 100644
--- a/src/php/ext/grpc/call.h
+++ b/src/php/ext/grpc/call.h
@@ -27,6 +27,7 @@
 #include <php_ini.h>
 #include <ext/standard/info.h>
 #include "php_grpc.h"
+#include "channel.h"
 
 #include <grpc/grpc.h>
 
@@ -37,6 +38,7 @@ extern zend_class_entry *grpc_ce_call;
 PHP_GRPC_WRAP_OBJECT_START(wrapped_grpc_call)
   bool owned;
   grpc_call *wrapped;
+  wrapped_grpc_channel* channel;
 PHP_GRPC_WRAP_OBJECT_END(wrapped_grpc_call)
 
 #if PHP_MAJOR_VERSION < 7
diff --git a/src/php/ext/grpc/channel.c b/src/php/ext/grpc/channel.c
index 35adf6b3425..6f3acc7afb4 100644
--- a/src/php/ext/grpc/channel.c
+++ b/src/php/ext/grpc/channel.c
@@ -54,47 +54,51 @@ static zend_object_handlers channel_ce_handlers;
 #endif
 static gpr_mu global_persistent_list_mu;
 int le_plink;
+int le_bound;
 extern HashTable grpc_persistent_list;
+extern HashTable grpc_target_upper_bound_map;
+
+void free_grpc_channel_wrapper(grpc_channel_wrapper* channel, bool free_channel) {
+  if (free_channel) {
+    grpc_channel_destroy(channel->wrapped);
+    channel->wrapped = NULL;
+  }
+  free(channel->target);
+  free(channel->args_hashstr);
+  free(channel->creds_hashstr);
+  free(channel->key);
+  channel->target = NULL;
+  channel->args_hashstr = NULL;
+  channel->creds_hashstr = NULL;
+  channel->key = NULL;
+}
+
+void php_grpc_channel_ref(grpc_channel_wrapper* wrapper) {
+  gpr_mu_lock(&wrapper->mu);
+  wrapper->ref_count += 1;
+  gpr_mu_unlock(&wrapper->mu);
+}
+
+void php_grpc_channel_unref(grpc_channel_wrapper* wrapper) {
+  gpr_mu_lock(&wrapper->mu);
+  wrapper->ref_count -= 1;
+  if (wrapper->ref_count == 0) {
+    free_grpc_channel_wrapper(wrapper, true);
+    gpr_mu_unlock(&wrapper->mu);
+    free(wrapper);
+    wrapper = NULL;
+    return;
+  }
+  gpr_mu_unlock(&wrapper->mu);
+}
 
 /* Frees and destroys an instance of wrapped_grpc_channel */
 PHP_GRPC_FREE_WRAPPED_FUNC_START(wrapped_grpc_channel)
   // In_persistent_list is used when the user don't close the channel,
   // In this case, channels not in the list should be freed.
-  bool in_persistent_list = true;
   if (p->wrapper != NULL) {
-    gpr_mu_lock(&p->wrapper->mu);
-    if (p->wrapper->wrapped != NULL) {
-      if (p->wrapper->is_valid) {
-        php_grpc_zend_resource *rsrc;
-        php_grpc_int key_len = strlen(p->wrapper->key);
-        // only destroy the channel here if not found in the persistent list
-        gpr_mu_lock(&global_persistent_list_mu);
-        if (!(PHP_GRPC_PERSISTENT_LIST_FIND(&grpc_persistent_list, p->wrapper->key,
-                                            key_len, rsrc))) {
-          in_persistent_list = false;
-          grpc_channel_destroy(p->wrapper->wrapped);
-          free(p->wrapper->target);
-          free(p->wrapper->args_hashstr);
-          if (p->wrapper->creds_hashstr != NULL) {
-            free(p->wrapper->creds_hashstr);
-            p->wrapper->creds_hashstr = NULL;
-          }
-          free(p->wrapper->key);
-          p->wrapper->wrapped = NULL;
-          p->wrapper->target = NULL;
-          p->wrapper->args_hashstr = NULL;
-          p->wrapper->key = NULL;
-        }
-        gpr_mu_unlock(&global_persistent_list_mu);
-      }
-    }
-    p->wrapper->ref_count -= 1;
-    gpr_mu_unlock(&p->wrapper->mu);
-    if (!in_persistent_list) {
-      gpr_mu_destroy(&p->wrapper->mu);
-      free(p->wrapper);
-      p->wrapper = NULL;
-    }
+    php_grpc_channel_unref(p->wrapper);
+    p->wrapper = NULL;
   }
 PHP_GRPC_FREE_WRAPPED_FUNC_END()
 
@@ -162,6 +166,67 @@ void generate_sha1_str(char *sha1str, char *str, php_grpc_int len) {
   make_sha1_digest(sha1str, digest);
 }
 
+bool php_grpc_persistent_list_delete_unused_channel(
+    char* target,
+    target_bound_le_t* target_bound_status TSRMLS_DC) {
+  zval *data;
+  PHP_GRPC_HASH_FOREACH_VAL_START(&grpc_persistent_list, data)
+    php_grpc_zend_resource *rsrc  = (php_grpc_zend_resource*) PHP_GRPC_HASH_VALPTR_TO_VAL(data)
+    if (rsrc == NULL) {
+      break;
+    }
+    channel_persistent_le_t* le = rsrc->ptr;
+    // Find the channel sharing the same target.
+    if (strcmp(le->channel->target, target) == 0) {
+      // ref_count=1 means that only the map holds the reference to the channel.
+      if (le->channel->ref_count == 1) {
+        php_grpc_delete_persistent_list_entry(le->channel->key,
+                                              strlen(le->channel->key)
+                                              TSRMLS_CC);
+        target_bound_status->current_count -= 1;
+        if (target_bound_status->current_count < target_bound_status->upper_bound) {
+          return true;
+        }
+      }
+    }
+  PHP_GRPC_HASH_FOREACH_END()
+  return false;
+}
+
+target_bound_le_t* update_and_get_target_upper_bound(char* target, int bound) {
+  php_grpc_zend_resource *rsrc;
+  target_bound_le_t* target_bound_status;
+  php_grpc_int key_len = strlen(target);
+  if (!(PHP_GRPC_PERSISTENT_LIST_FIND(&grpc_target_upper_bound_map, target,
+      key_len, rsrc))) {
+    // Target is not not persisted.
+    php_grpc_zend_resource new_rsrc;
+    target_bound_status = malloc(sizeof(target_bound_le_t));
+    if (bound == -1) {
+      // If the bound is not set, use 1 as default.s
+      bound = 1;
+    }
+    target_bound_status->upper_bound = bound;
+    // Init current_count with 1. It should be add 1 when the channel is successfully
+    // created and minus 1 when it is removed from the persistent list.
+    target_bound_status->current_count = 0;
+    new_rsrc.type = le_bound;
+    new_rsrc.ptr = target_bound_status;
+    gpr_mu_lock(&global_persistent_list_mu);
+    PHP_GRPC_PERSISTENT_LIST_UPDATE(&grpc_target_upper_bound_map,
+                                    target, key_len, (void *)&new_rsrc);
+    gpr_mu_unlock(&global_persistent_list_mu);
+  } else {
+    // The target already in the map recording the upper bound.
+    // If no newer bound set, use the original now.
+    target_bound_status = (target_bound_le_t *)rsrc->ptr;
+    if (bound != -1) {
+      target_bound_status->upper_bound = bound;
+    }
+  }
+  return target_bound_status;
+}
+
 void create_channel(
     wrapped_grpc_channel *channel,
     char *target,
@@ -174,6 +239,8 @@ void create_channel(
     channel->wrapper->wrapped =
         grpc_secure_channel_create(creds->wrapped, target, &args, NULL);
   }
+  // There is an Grpc\Channel object refer to it.
+  php_grpc_channel_ref(channel->wrapper);
   efree(args.args);
 }
 
@@ -183,7 +250,28 @@ void create_and_add_channel_to_persistent_list(
     grpc_channel_args args,
     wrapped_grpc_channel_credentials *creds,
     char *key,
-    php_grpc_int key_len TSRMLS_DC) {
+    php_grpc_int key_len,
+    int target_upper_bound TSRMLS_DC) {
+  target_bound_le_t* target_bound_status =
+    update_and_get_target_upper_bound(target, target_upper_bound);
+  // Check the upper bound status before inserting to the persistent map.
+  if (target_bound_status->current_count >=
+      target_bound_status->upper_bound) {
+    if (!php_grpc_persistent_list_delete_unused_channel(
+          target, target_bound_status TSRMLS_CC)) {
+      // If no channel can be deleted from the persistent map,
+      // do not persist this one.
+      create_channel(channel, target, args, creds);
+      php_printf("[Warning] The number of channel for the"
+                 " target %s is maxed out bounded.\n", target);
+      php_printf("[Warning] Target upper bound: %d. Current size: %d.\n",
+                 target_bound_status->upper_bound,
+                 target_bound_status->current_count);
+      php_printf("[Warning] Target %s will not be persisted.\n", target);
+      return;
+    }
+  }
+  // There is space in the persistent map.
   php_grpc_zend_resource new_rsrc;
   channel_persistent_le_t *le;
   // this links each persistent list entry to a destructor
@@ -191,12 +279,15 @@ void create_and_add_channel_to_persistent_list(
   le = malloc(sizeof(channel_persistent_le_t));
 
   create_channel(channel, target, args, creds);
+  target_bound_status->current_count += 1;
 
   le->channel = channel->wrapper;
   new_rsrc.ptr = le;
   gpr_mu_lock(&global_persistent_list_mu);
   PHP_GRPC_PERSISTENT_LIST_UPDATE(&grpc_persistent_list, key, key_len,
                                   (void *)&new_rsrc);
+  // Persistent map refer to it.
+  php_grpc_channel_ref(channel->wrapper);
   gpr_mu_unlock(&global_persistent_list_mu);
 }
 
@@ -230,6 +321,7 @@ PHP_METHOD(Channel, __construct) {
   php_grpc_zend_resource *rsrc;
   bool force_new = false;
   zval *force_new_obj = NULL;
+  int target_upper_bound = -1;
 
   /* "sa" == 1 string, 1 array */
   if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sa", &target,
@@ -263,6 +355,19 @@ PHP_METHOD(Channel, __construct) {
     php_grpc_zend_hash_del(array_hash, "force_new", sizeof("force_new"));
   }
 
+  if (php_grpc_zend_hash_find(array_hash, "grpc_target_persist_bound",
+                              sizeof("grpc_target_persist_bound"),
+                              (void **)&force_new_obj) == SUCCESS) {
+    if (Z_TYPE_P(force_new_obj) != IS_LONG) {
+      zend_throw_exception(spl_ce_InvalidArgumentException,
+                           "plist_bound must be a number",
+                           1 TSRMLS_CC);
+    }
+    target_upper_bound = (int)Z_LVAL_P(force_new_obj);
+    php_grpc_zend_hash_del(array_hash, "grpc_target_persist_bound",
+                           sizeof("grpc_target_persist_bound"));
+  }
+
   // parse the rest of the channel args array
   if (php_grpc_read_args_array(args_array, &args TSRMLS_CC) == FAILURE) {
     efree(args.args);
@@ -296,12 +401,11 @@ PHP_METHOD(Channel, __construct) {
     strcat(key, creds->hashstr);
   }
   channel->wrapper = malloc(sizeof(grpc_channel_wrapper));
+  channel->wrapper->ref_count = 0;
   channel->wrapper->key = key;
   channel->wrapper->target = strdup(target);
   channel->wrapper->args_hashstr = strdup(sha1str);
   channel->wrapper->creds_hashstr = NULL;
-  channel->wrapper->ref_count = 1;
-  channel->wrapper->is_valid = true;
   if (creds != NULL && creds->hashstr != NULL) {
     php_grpc_int creds_hashstr_len = strlen(creds->hashstr);
     char *channel_creds_hashstr = malloc(creds_hashstr_len + 1);
@@ -319,7 +423,7 @@ PHP_METHOD(Channel, __construct) {
   } else if (!(PHP_GRPC_PERSISTENT_LIST_FIND(&grpc_persistent_list, key,
                                              key_len, rsrc))) {
     create_and_add_channel_to_persistent_list(
-        channel, target, args, creds, key, key_len TSRMLS_CC);
+        channel, target, args, creds, key, key_len, target_upper_bound TSRMLS_CC);
   } else {
     // Found a previously stored channel in the persistent list
     channel_persistent_le_t *le = (channel_persistent_le_t *)rsrc->ptr;
@@ -329,20 +433,17 @@ PHP_METHOD(Channel, __construct) {
          strcmp(creds->hashstr, le->channel->creds_hashstr) != 0)) {
       // somehow hash collision
       create_and_add_channel_to_persistent_list(
-          channel, target, args, creds, key, key_len TSRMLS_CC);
+          channel, target, args, creds, key, key_len, target_upper_bound TSRMLS_CC);
     } else {
       efree(args.args);
-      if (channel->wrapper->creds_hashstr != NULL) {
-        free(channel->wrapper->creds_hashstr);
-        channel->wrapper->creds_hashstr = NULL;
-      }
-      free(channel->wrapper->creds_hashstr);
-      free(channel->wrapper->key);
-      free(channel->wrapper->target);
-      free(channel->wrapper->args_hashstr);
+      free_grpc_channel_wrapper(channel->wrapper, false);
+      gpr_mu_destroy(&channel->wrapper->mu);
       free(channel->wrapper);
+      channel->wrapper = NULL;
       channel->wrapper = le->channel;
-      channel->wrapper->ref_count += 1;
+      // One more Grpc\Channel object refer to it.
+      php_grpc_channel_ref(channel->wrapper);
+      update_and_get_target_upper_bound(target, target_upper_bound);
     }
   }
 }
@@ -353,13 +454,13 @@ PHP_METHOD(Channel, __construct) {
  */
 PHP_METHOD(Channel, getTarget) {
   wrapped_grpc_channel *channel = Z_WRAPPED_GRPC_CHANNEL_P(getThis());
-  gpr_mu_lock(&channel->wrapper->mu);
-  if (channel->wrapper->wrapped == NULL) {
+  if (channel->wrapper == NULL) {
     zend_throw_exception(spl_ce_RuntimeException,
-                         "Channel already closed", 1 TSRMLS_CC);
-    gpr_mu_unlock(&channel->wrapper->mu);
+                         "getTarget error."
+                         "Channel is already closed.", 1 TSRMLS_CC);
     return;
   }
+  gpr_mu_lock(&channel->wrapper->mu);
   char *target = grpc_channel_get_target(channel->wrapper->wrapped);
   gpr_mu_unlock(&channel->wrapper->mu);
   PHP_GRPC_RETVAL_STRING(target, 1);
@@ -373,16 +474,14 @@ PHP_METHOD(Channel, getTarget) {
  */
 PHP_METHOD(Channel, getConnectivityState) {
   wrapped_grpc_channel *channel = Z_WRAPPED_GRPC_CHANNEL_P(getThis());
-  gpr_mu_lock(&channel->wrapper->mu);
-  if (channel->wrapper->wrapped == NULL) {
+  if (channel->wrapper == NULL) {
     zend_throw_exception(spl_ce_RuntimeException,
-                         "Channel already closed", 1 TSRMLS_CC);
-    gpr_mu_unlock(&channel->wrapper->mu);
+                         "getConnectivityState error."
+                         "Channel is already closed.", 1 TSRMLS_CC);
     return;
   }
-
+  gpr_mu_lock(&channel->wrapper->mu);
   bool try_to_connect = false;
-
   /* "|b" == 1 optional bool */
   if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|b", &try_to_connect)
       == FAILURE) {
@@ -393,11 +492,6 @@ PHP_METHOD(Channel, getConnectivityState) {
   }
   int state = grpc_channel_check_connectivity_state(channel->wrapper->wrapped,
                                                     (int)try_to_connect);
-  // this can happen if another shared Channel object close the underlying
-  // channel
-  if (state == GRPC_CHANNEL_SHUTDOWN) {
-    channel->wrapper->wrapped = NULL;
-  }
   gpr_mu_unlock(&channel->wrapper->mu);
   RETURN_LONG(state);
 }
@@ -411,14 +505,13 @@ PHP_METHOD(Channel, getConnectivityState) {
  */
 PHP_METHOD(Channel, watchConnectivityState) {
   wrapped_grpc_channel *channel = Z_WRAPPED_GRPC_CHANNEL_P(getThis());
-  gpr_mu_lock(&channel->wrapper->mu);
-  if (channel->wrapper->wrapped == NULL) {
+  if (channel->wrapper == NULL) {
     zend_throw_exception(spl_ce_RuntimeException,
-                         "Channel already closed", 1 TSRMLS_CC);
-    gpr_mu_unlock(&channel->wrapper->mu);
+                         "watchConnectivityState error"
+                         "Channel is already closed.", 1 TSRMLS_CC);
     return;
   }
-
+  gpr_mu_lock(&channel->wrapper->mu);
   php_grpc_long last_state;
   zval *deadline_obj;
 
@@ -451,46 +544,10 @@ PHP_METHOD(Channel, watchConnectivityState) {
  */
 PHP_METHOD(Channel, close) {
   wrapped_grpc_channel *channel = Z_WRAPPED_GRPC_CHANNEL_P(getThis());
-  bool is_last_wrapper = false;
   if (channel->wrapper != NULL) {
-    // Channel_wrapper hasn't call close before.
-    gpr_mu_lock(&channel->wrapper->mu);
-    if (channel->wrapper->wrapped != NULL) {
-      if (channel->wrapper->is_valid) {
-        // Wrapped channel hasn't been destoryed by other wrapper.
-        grpc_channel_destroy(channel->wrapper->wrapped);
-        free(channel->wrapper->target);
-        free(channel->wrapper->args_hashstr);
-        free(channel->wrapper->creds_hashstr);
-        channel->wrapper->creds_hashstr = NULL;
-        channel->wrapper->target = NULL;
-        channel->wrapper->args_hashstr = NULL;
-        channel->wrapper->wrapped = NULL;
-        channel->wrapper->is_valid = false;
-
-        php_grpc_delete_persistent_list_entry(channel->wrapper->key,
-                                              strlen(channel->wrapper->key)
-                                              TSRMLS_CC);
-      }
-    }
-    channel->wrapper->ref_count -= 1;
-    if (channel->wrapper->ref_count == 0) {
-      // Mark that the wrapper can be freed because mu should be
-      // destroyed outside the lock.
-      is_last_wrapper = true;
-    }
-    gpr_mu_unlock(&channel->wrapper->mu);
+    php_grpc_channel_unref(channel->wrapper);
+    channel->wrapper = NULL;
   }
-  gpr_mu_lock(&global_persistent_list_mu);
-  if (is_last_wrapper) {
-    gpr_mu_destroy(&channel->wrapper->mu);
-    free(channel->wrapper->key);
-    free(channel->wrapper);
-  }
-  // Set channel->wrapper to NULL to avoid call close twice for the same
-  // channel.
-  channel->wrapper = NULL;
-  gpr_mu_unlock(&global_persistent_list_mu);
 }
 
 // Delete an entry from the persistent list
@@ -501,11 +558,7 @@ void php_grpc_delete_persistent_list_entry(char *key, php_grpc_int key_len
   gpr_mu_lock(&global_persistent_list_mu);
   if (PHP_GRPC_PERSISTENT_LIST_FIND(&grpc_persistent_list, key,
                                     key_len, rsrc)) {
-    channel_persistent_le_t *le;
-    le = (channel_persistent_le_t *)rsrc->ptr;
-    le->channel = NULL;
     php_grpc_zend_hash_del(&grpc_persistent_list, key, key_len+1);
-    free(le);
   }
   gpr_mu_unlock(&global_persistent_list_mu);
 }
@@ -518,20 +571,137 @@ static void php_grpc_channel_plink_dtor(php_grpc_zend_resource *rsrc
     return;
   }
   if (le->channel != NULL) {
-    gpr_mu_lock(&le->channel->mu);
-    if (le->channel->wrapped != NULL) {
-      grpc_channel_destroy(le->channel->wrapped);
-      free(le->channel->args_hashstr);
-      le->channel->wrapped = NULL;
-      le->channel->target = NULL;
-      le->channel->args_hashstr = NULL;
-      free(le->channel->key);
-      le->channel->key = NULL;
-    }
-    gpr_mu_unlock(&le->channel->mu);
+    php_grpc_channel_unref(le->channel);
+    le->channel = NULL;
   }
+  free(le);
+  le = NULL;
 }
 
+// A destructor associated with each list entry from the target_bound map
+static void php_grpc_target_bound_dtor(php_grpc_zend_resource *rsrc
+                                        TSRMLS_DC) {
+  target_bound_le_t *le = (target_bound_le_t *) rsrc->ptr;
+  if (le == NULL) {
+    return;
+  }
+  free(le);
+  le = NULL;
+}
+
+#ifdef GRPC_PHP_DEBUG
+
+/**
+* Clean all channels in the persistent. Test only.
+* @return void
+*/
+PHP_METHOD(Channel, cleanPersistentList) {
+  zend_hash_clean(&grpc_persistent_list);
+  zend_hash_clean(&grpc_target_upper_bound_map);
+}
+
+char *grpc_connectivity_state_name(grpc_connectivity_state state) {
+ switch (state) {
+   case GRPC_CHANNEL_IDLE:
+     return "IDLE";
+   case GRPC_CHANNEL_CONNECTING:
+     return "CONNECTING";
+   case GRPC_CHANNEL_READY:
+     return "READY";
+   case GRPC_CHANNEL_TRANSIENT_FAILURE:
+     return "TRANSIENT_FAILURE";
+   case GRPC_CHANNEL_SHUTDOWN:
+     return "SHUTDOWN";
+ }
+ return "UNKNOWN";
+}
+
+/**
+* Return the info about the current channel. Test only.
+* @return array
+*/
+PHP_METHOD(Channel, getChannelInfo) {
+  wrapped_grpc_channel *channel = Z_WRAPPED_GRPC_CHANNEL_P(getThis());
+  array_init(return_value);
+   // Info about the target
+  PHP_GRPC_ADD_STRING_TO_ARRAY(return_value, "target",
+              sizeof("target"), channel->wrapper->target, true);
+  // Info about the upper bound for the target
+  target_bound_le_t* target_bound_status =
+    update_and_get_target_upper_bound(channel->wrapper->target, -1);
+  PHP_GRPC_ADD_LONG_TO_ARRAY(return_value, "target_upper_bound",
+    sizeof("target_upper_bound"), target_bound_status->upper_bound);
+  PHP_GRPC_ADD_LONG_TO_ARRAY(return_value, "target_current_size",
+    sizeof("target_current_size"), target_bound_status->current_count);
+  // Info about key
+  PHP_GRPC_ADD_STRING_TO_ARRAY(return_value, "key",
+              sizeof("key"), channel->wrapper->key, true);
+  // Info about persistent channel ref_count
+  PHP_GRPC_ADD_LONG_TO_ARRAY(return_value, "ref_count",
+              sizeof("ref_count"), channel->wrapper->ref_count);
+  // Info about connectivity status
+  int state =
+      grpc_channel_check_connectivity_state(channel->wrapper->wrapped, (int)0);
+  // It should be set to 'true' in PHP 5.6.33
+  PHP_GRPC_ADD_LONG_TO_ARRAY(return_value, "connectivity_status",
+              sizeof("connectivity_status"), state);
+  PHP_GRPC_ADD_STRING_TO_ARRAY(return_value, "ob",
+              sizeof("ob"),
+              grpc_connectivity_state_name(state), true);
+  // Info about the channel is closed or not
+  PHP_GRPC_ADD_BOOL_TO_ARRAY(return_value, "is_valid",
+              sizeof("is_valid"), (channel->wrapper == NULL));
+}
+
+/**
+* Return an array of all channels in the persistent list. Test only.
+* @return array
+*/
+PHP_METHOD(Channel, getPersistentList) {
+  array_init(return_value);
+  zval *data;
+  PHP_GRPC_HASH_FOREACH_VAL_START(&grpc_persistent_list, data)
+    php_grpc_zend_resource *rsrc  =
+                (php_grpc_zend_resource*) PHP_GRPC_HASH_VALPTR_TO_VAL(data)
+    if (rsrc == NULL) {
+      break;
+    }
+    channel_persistent_le_t* le = rsrc->ptr;
+    zval* ret_arr;
+    PHP_GRPC_MAKE_STD_ZVAL(ret_arr);
+    array_init(ret_arr);
+    // Info about the target
+    PHP_GRPC_ADD_STRING_TO_ARRAY(ret_arr, "target",
+                sizeof("target"), le->channel->target, true);
+    // Info about the upper bound for the target
+    target_bound_le_t* target_bound_status =
+      update_and_get_target_upper_bound(le->channel->target, -1);
+    PHP_GRPC_ADD_LONG_TO_ARRAY(ret_arr, "target_upper_bound",
+      sizeof("target_upper_bound"), target_bound_status->upper_bound);
+    PHP_GRPC_ADD_LONG_TO_ARRAY(ret_arr, "target_current_size",
+      sizeof("target_current_size"), target_bound_status->current_count);
+    // Info about key
+    PHP_GRPC_ADD_STRING_TO_ARRAY(ret_arr, "key",
+                sizeof("key"), le->channel->key, true);
+    // Info about persistent channel ref_count
+    PHP_GRPC_ADD_LONG_TO_ARRAY(ret_arr, "ref_count",
+                sizeof("ref_count"), le->channel->ref_count);
+    // Info about connectivity status
+    int state =
+        grpc_channel_check_connectivity_state(le->channel->wrapped, (int)0);
+    // It should be set to 'true' in PHP 5.6.33
+    PHP_GRPC_ADD_LONG_TO_ARRAY(ret_arr, "connectivity_status",
+                sizeof("connectivity_status"), state);
+    PHP_GRPC_ADD_STRING_TO_ARRAY(ret_arr, "ob",
+                sizeof("ob"),
+                grpc_connectivity_state_name(state), true);
+    add_assoc_zval(return_value, le->channel->key, ret_arr);
+    PHP_GRPC_FREE_STD_ZVAL(ret_arr);
+  PHP_GRPC_HASH_FOREACH_END()
+}
+#endif
+
+
 ZEND_BEGIN_ARG_INFO_EX(arginfo_construct, 0, 0, 2)
   ZEND_ARG_INFO(0, target)
   ZEND_ARG_INFO(0, args)
@@ -552,6 +722,18 @@ ZEND_END_ARG_INFO()
 ZEND_BEGIN_ARG_INFO_EX(arginfo_close, 0, 0, 0)
 ZEND_END_ARG_INFO()
 
+#ifdef GRPC_PHP_DEBUG
+ZEND_BEGIN_ARG_INFO_EX(arginfo_getChannelInfo, 0, 0, 0)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO_EX(arginfo_cleanPersistentList, 0, 0, 0)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO_EX(arginfo_getPersistentList, 0, 0, 0)
+ZEND_END_ARG_INFO()
+#endif
+
+
 static zend_function_entry channel_methods[] = {
   PHP_ME(Channel, __construct, arginfo_construct,
          ZEND_ACC_PUBLIC | ZEND_ACC_CTOR)
@@ -563,6 +745,14 @@ static zend_function_entry channel_methods[] = {
          ZEND_ACC_PUBLIC)
   PHP_ME(Channel, close, arginfo_close,
          ZEND_ACC_PUBLIC)
+  #ifdef GRPC_PHP_DEBUG
+  PHP_ME(Channel, getChannelInfo, arginfo_getChannelInfo,
+         ZEND_ACC_PUBLIC)
+  PHP_ME(Channel, cleanPersistentList, arginfo_cleanPersistentList,
+         ZEND_ACC_PUBLIC)
+  PHP_ME(Channel, getPersistentList, arginfo_getPersistentList,
+         ZEND_ACC_PUBLIC)
+  #endif
   PHP_FE_END
 };
 
@@ -576,6 +766,12 @@ GRPC_STARTUP_FUNCTION(channel) {
       NULL, php_grpc_channel_plink_dtor, "Persistent Channel", module_number);
   zend_hash_init_ex(&grpc_persistent_list, 20, NULL,
                     EG(persistent_list).pDestructor, 1, 0);
+  // Register the target->upper_bound map.
+  le_bound = zend_register_list_destructors_ex(
+      NULL, php_grpc_target_bound_dtor, "Target Bound", module_number);
+  zend_hash_init_ex(&grpc_target_upper_bound_map, 20, NULL,
+                    EG(persistent_list).pDestructor, 1, 0);
+
   PHP_GRPC_INIT_HANDLER(wrapped_grpc_channel, channel_ce_handlers);
   return SUCCESS;
 }
diff --git a/src/php/ext/grpc/channel.h b/src/php/ext/grpc/channel.h
index 86bfdea51a2..7aad59abe52 100644
--- a/src/php/ext/grpc/channel.h
+++ b/src/php/ext/grpc/channel.h
@@ -39,12 +39,8 @@ typedef struct _grpc_channel_wrapper {
   char *target;
   char *args_hashstr;
   char *creds_hashstr;
-  gpr_mu mu;
-  // is_valid is used to check the wrapped channel has been freed
-  // before to avoid double free.
-  bool is_valid;
-  // ref_count is used to let the last wrapper free related channel and key.
   size_t ref_count;
+  gpr_mu mu;
 } grpc_channel_wrapper;
 
 /* Wrapper struct for grpc_channel that can be associated with a PHP object */
@@ -86,5 +82,9 @@ typedef struct _channel_persistent_le {
   grpc_channel_wrapper *channel;
 } channel_persistent_le_t;
 
+typedef struct _target_bound_le {
+  int upper_bound;
+  int current_count;
+} target_bound_le_t;
 
 #endif /* NET_GRPC_PHP_GRPC_CHANNEL_H_ */
diff --git a/src/php/ext/grpc/config.m4 b/src/php/ext/grpc/config.m4
index 0fb843d51fd..fa54ebd9200 100755
--- a/src/php/ext/grpc/config.m4
+++ b/src/php/ext/grpc/config.m4
@@ -4,6 +4,14 @@ PHP_ARG_ENABLE(grpc, whether to enable grpc support,
 PHP_ARG_ENABLE(coverage, whether to include code coverage symbols,
 [  --enable-coverage       Enable coverage support], no, no)
 
+PHP_ARG_ENABLE(tests, whether to compile helper methods for tests,
+[  --enable-tests          Enable tests methods], no, no)
+
+dnl Check whether to enable tests
+if test "$PHP_TESTS" != "no"; then
+  CPPFLAGS="$CPPFLAGS -DGRPC_PHP_DEBUG"
+fi
+
 if test "$PHP_GRPC" != "no"; then
   dnl Write more examples of tests here...
 
diff --git a/src/php/ext/grpc/php7_wrapper.h b/src/php/ext/grpc/php7_wrapper.h
index 0239e04f761..3b95fbe4513 100644
--- a/src/php/ext/grpc/php7_wrapper.h
+++ b/src/php/ext/grpc/php7_wrapper.h
@@ -46,6 +46,8 @@
    add_assoc_long_ex(val, key, key_len, str);
 #define PHP_GRPC_ADD_BOOL_TO_ARRAY(val, key, key_len, str) \
    add_assoc_bool_ex(val, key, key_len, str);
+#define PHP_GRPC_ADD_LONG_TO_RETVAL(val, key, key_len, str) \
+   add_assoc_long_ex(val, key, key_len+1, str);
 
 #define RETURN_DESTROY_ZVAL(val) \
   RETURN_ZVAL(val, false /* Don't execute copy constructor */, \
@@ -140,7 +142,7 @@ static inline int php_grpc_zend_hash_find(HashTable *ht, char *key, int len,
   zend_hash_update(plist, key, len+1, rsrc, sizeof(php_grpc_zend_resource), \
                    NULL)
 #define PHP_GRPC_PERSISTENT_LIST_SIZE(plist) \
-  plist.nTableSize
+  *plist.nNumOfElements
 
 #define PHP_GRPC_GET_CLASS_ENTRY(object) zend_get_class_entry(object TSRMLS_CC)
 
@@ -176,6 +178,8 @@ static inline int php_grpc_zend_hash_find(HashTable *ht, char *key, int len,
    add_assoc_long_ex(val, key, key_len - 1, str);
 #define PHP_GRPC_ADD_BOOL_TO_ARRAY(val, key, key_len, str) \
    add_assoc_bool_ex(val, key, key_len - 1, str);
+#define PHP_GRPC_ADD_LONG_TO_RETVAL(val, key, key_len, str) \
+   add_assoc_long_ex(val, key, key_len, str);
 
 #define RETURN_DESTROY_ZVAL(val) \
   RETVAL_ZVAL(val, false /* Don't execute copy constructor */, \
diff --git a/src/php/ext/grpc/php_grpc.c b/src/php/ext/grpc/php_grpc.c
index 883ee6f3e50..1dd1f4fe505 100644
--- a/src/php/ext/grpc/php_grpc.c
+++ b/src/php/ext/grpc/php_grpc.c
@@ -37,6 +37,7 @@
 ZEND_DECLARE_MODULE_GLOBALS(grpc)
 static PHP_GINIT_FUNCTION(grpc);
 HashTable grpc_persistent_list;
+HashTable grpc_target_upper_bound_map;
 /* {{{ grpc_functions[]
  *
  * Every user visible function must have an entry in grpc_functions[].
@@ -242,6 +243,8 @@ PHP_MSHUTDOWN_FUNCTION(grpc) {
   if (GRPC_G(initialized)) {
     zend_hash_clean(&grpc_persistent_list);
     zend_hash_destroy(&grpc_persistent_list);
+    zend_hash_clean(&grpc_target_upper_bound_map);
+    zend_hash_destroy(&grpc_target_upper_bound_map);
     grpc_shutdown_timeval(TSRMLS_C);
     grpc_php_shutdown_completion_queue(TSRMLS_C);
     grpc_shutdown();
diff --git a/src/php/tests/unit_tests/ChannelTest.php b/src/php/tests/unit_tests/ChannelTest.php
index 5baff1fbd93..49b0e350f72 100644
--- a/src/php/tests/unit_tests/ChannelTest.php
+++ b/src/php/tests/unit_tests/ChannelTest.php
@@ -1,7 +1,7 @@
 <?php
 /*
  *
- * Copyright 2015 gRPC authors.
+ * Copyright 2018 gRPC authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -32,14 +32,14 @@ class ChannelTest extends PHPUnit_Framework_TestCase
 
     public function testInsecureCredentials()
     {
-        $this->channel = new Grpc\Channel('localhost:0',
+        $this->channel = new Grpc\Channel('localhost:50000',
             ['credentials' => Grpc\ChannelCredentials::createInsecure()]);
         $this->assertSame('Grpc\Channel', get_class($this->channel));
     }
 
     public function testGetConnectivityState()
     {
-        $this->channel = new Grpc\Channel('localhost:0',
+        $this->channel = new Grpc\Channel('localhost:50001',
              ['credentials' => Grpc\ChannelCredentials::createInsecure()]);
         $state = $this->channel->getConnectivityState();
         $this->assertEquals(0, $state);
@@ -47,7 +47,7 @@ class ChannelTest extends PHPUnit_Framework_TestCase
 
     public function testGetConnectivityStateWithInt()
     {
-        $this->channel = new Grpc\Channel('localhost:0',
+        $this->channel = new Grpc\Channel('localhost:50002',
              ['credentials' => Grpc\ChannelCredentials::createInsecure()]);
         $state = $this->channel->getConnectivityState(123);
         $this->assertEquals(0, $state);
@@ -55,7 +55,7 @@ class ChannelTest extends PHPUnit_Framework_TestCase
 
     public function testGetConnectivityStateWithString()
     {
-        $this->channel = new Grpc\Channel('localhost:0',
+        $this->channel = new Grpc\Channel('localhost:50003',
              ['credentials' => Grpc\ChannelCredentials::createInsecure()]);
         $state = $this->channel->getConnectivityState('hello');
         $this->assertEquals(0, $state);
@@ -63,7 +63,7 @@ class ChannelTest extends PHPUnit_Framework_TestCase
 
     public function testGetConnectivityStateWithBool()
     {
-        $this->channel = new Grpc\Channel('localhost:0',
+        $this->channel = new Grpc\Channel('localhost:50004',
              ['credentials' => Grpc\ChannelCredentials::createInsecure()]);
         $state = $this->channel->getConnectivityState(true);
         $this->assertEquals(0, $state);
@@ -71,7 +71,7 @@ class ChannelTest extends PHPUnit_Framework_TestCase
 
     public function testGetTarget()
     {
-        $this->channel = new Grpc\Channel('localhost:8888',
+        $this->channel = new Grpc\Channel('localhost:50005',
              ['credentials' => Grpc\ChannelCredentials::createInsecure()]);
         $target = $this->channel->getTarget();
         $this->assertTrue(is_string($target));
@@ -79,7 +79,7 @@ class ChannelTest extends PHPUnit_Framework_TestCase
 
     public function testWatchConnectivityState()
     {
-        $this->channel = new Grpc\Channel('localhost:0',
+        $this->channel = new Grpc\Channel('localhost:50006',
              ['credentials' => Grpc\ChannelCredentials::createInsecure()]);
         $now = Grpc\Timeval::now();
         $deadline = $now->add(new Grpc\Timeval(100*1000));  // 100ms
@@ -93,7 +93,7 @@ class ChannelTest extends PHPUnit_Framework_TestCase
 
     public function testClose()
     {
-        $this->channel = new Grpc\Channel('localhost:0',
+        $this->channel = new Grpc\Channel('localhost:50007',
              ['credentials' => Grpc\ChannelCredentials::createInsecure()]);
         $this->assertNotNull($this->channel);
         $this->channel->close();
@@ -113,7 +113,7 @@ class ChannelTest extends PHPUnit_Framework_TestCase
      */
     public function testInvalidConstructorWith()
     {
-        $this->channel = new Grpc\Channel('localhost:0', 'invalid');
+        $this->channel = new Grpc\Channel('localhost:50008', 'invalid');
         $this->assertNull($this->channel);
     }
 
@@ -122,7 +122,7 @@ class ChannelTest extends PHPUnit_Framework_TestCase
      */
     public function testInvalidCredentials()
     {
-        $this->channel = new Grpc\Channel('localhost:0',
+        $this->channel = new Grpc\Channel('localhost:50009',
             ['credentials' => new Grpc\Timeval(100)]);
     }
 
@@ -131,7 +131,7 @@ class ChannelTest extends PHPUnit_Framework_TestCase
      */
     public function testInvalidOptionsArray()
     {
-        $this->channel = new Grpc\Channel('localhost:0',
+        $this->channel = new Grpc\Channel('localhost:50010',
             ['abc' => []]);
     }
 
@@ -140,7 +140,7 @@ class ChannelTest extends PHPUnit_Framework_TestCase
      */
     public function testInvalidGetConnectivityStateWithArray()
     {
-        $this->channel = new Grpc\Channel('localhost:0',
+        $this->channel = new Grpc\Channel('localhost:50011',
             ['credentials' => Grpc\ChannelCredentials::createInsecure()]);
         $this->channel->getConnectivityState([]);
     }
@@ -150,7 +150,7 @@ class ChannelTest extends PHPUnit_Framework_TestCase
      */
     public function testInvalidWatchConnectivityState()
     {
-        $this->channel = new Grpc\Channel('localhost:0',
+        $this->channel = new Grpc\Channel('localhost:50012',
             ['credentials' => Grpc\ChannelCredentials::createInsecure()]);
         $this->channel->watchConnectivityState([]);
     }
@@ -160,7 +160,7 @@ class ChannelTest extends PHPUnit_Framework_TestCase
      */
     public function testInvalidWatchConnectivityState2()
     {
-        $this->channel = new Grpc\Channel('localhost:0',
+        $this->channel = new Grpc\Channel('localhost:50013',
             ['credentials' => Grpc\ChannelCredentials::createInsecure()]);
         $this->channel->watchConnectivityState(1, 'hi');
     }
@@ -185,10 +185,12 @@ class ChannelTest extends PHPUnit_Framework_TestCase
 
     public function testPersistentChannelSameHost()
     {
-        $this->channel1 = new Grpc\Channel('localhost:1', []);
+        $this->channel1 = new Grpc\Channel('localhost:50014', [
+            "grpc_target_persist_bound" => 3,
+        ]);
         // the underlying grpc channel is the same by default
         // when connecting to the same host
-        $this->channel2 = new Grpc\Channel('localhost:1', []);
+        $this->channel2 = new Grpc\Channel('localhost:50014', []);
 
         // both channels should be IDLE
         $state = $this->channel1->getConnectivityState();
@@ -213,8 +215,10 @@ class ChannelTest extends PHPUnit_Framework_TestCase
     public function testPersistentChannelDifferentHost()
     {
         // two different underlying channels because different hostname
-        $this->channel1 = new Grpc\Channel('localhost:1', []);
-        $this->channel2 = new Grpc\Channel('localhost:2', []);
+        $this->channel1 = new Grpc\Channel('localhost:50015', [
+            "grpc_target_persist_bound" => 3,
+        ]);
+        $this->channel2 = new Grpc\Channel('localhost:50016', []);
 
         // both channels should be IDLE
         $state = $this->channel1->getConnectivityState();
@@ -239,8 +243,11 @@ class ChannelTest extends PHPUnit_Framework_TestCase
 
     public function testPersistentChannelSameArgs()
     {
-        $this->channel1 = new Grpc\Channel('localhost:1', ["abc" => "def"]);
-        $this->channel2 = new Grpc\Channel('localhost:1', ["abc" => "def"]);
+        $this->channel1 = new Grpc\Channel('localhost:50017', [
+          "grpc_target_persist_bound" => 3,
+          "abc" => "def",
+          ]);
+        $this->channel2 = new Grpc\Channel('localhost:50017', ["abc" => "def"]);
 
         // try to connect on channel1
         $state = $this->channel1->getConnectivityState(true);
@@ -257,8 +264,10 @@ class ChannelTest extends PHPUnit_Framework_TestCase
 
     public function testPersistentChannelDifferentArgs()
     {
-        $this->channel1 = new Grpc\Channel('localhost:1', []);
-        $this->channel2 = new Grpc\Channel('localhost:1', ["abc" => "def"]);
+        $this->channel1 = new Grpc\Channel('localhost:50018', [
+            "grpc_target_persist_bound" => 3,
+          ]);
+        $this->channel2 = new Grpc\Channel('localhost:50018', ["abc" => "def"]);
 
         // try to connect on channel1
         $state = $this->channel1->getConnectivityState(true);
@@ -278,9 +287,11 @@ class ChannelTest extends PHPUnit_Framework_TestCase
         $creds1 = Grpc\ChannelCredentials::createSsl();
         $creds2 = Grpc\ChannelCredentials::createSsl();
 
-        $this->channel1 = new Grpc\Channel('localhost:1',
-                                           ["credentials" => $creds1]);
-        $this->channel2 = new Grpc\Channel('localhost:1',
+        $this->channel1 = new Grpc\Channel('localhost:50019',
+                                           ["credentials" => $creds1,
+                                             "grpc_target_persist_bound" => 3,
+                                             ]);
+        $this->channel2 = new Grpc\Channel('localhost:50019',
                                            ["credentials" => $creds2]);
 
         // try to connect on channel1
@@ -302,9 +313,11 @@ class ChannelTest extends PHPUnit_Framework_TestCase
         $creds2 = Grpc\ChannelCredentials::createSsl(
             file_get_contents(dirname(__FILE__).'/../data/ca.pem'));
 
-        $this->channel1 = new Grpc\Channel('localhost:1',
-                                           ["credentials" => $creds1]);
-        $this->channel2 = new Grpc\Channel('localhost:1',
+        $this->channel1 = new Grpc\Channel('localhost:50020',
+                                           ["credentials" => $creds1,
+                                             "grpc_target_persist_bound" => 3,
+                                             ]);
+        $this->channel2 = new Grpc\Channel('localhost:50020',
                                            ["credentials" => $creds2]);
 
         // try to connect on channel1
@@ -327,9 +340,11 @@ class ChannelTest extends PHPUnit_Framework_TestCase
         $creds2 = Grpc\ChannelCredentials::createSsl(
             file_get_contents(dirname(__FILE__).'/../data/ca.pem'));
 
-        $this->channel1 = new Grpc\Channel('localhost:1',
-                                           ["credentials" => $creds1]);
-        $this->channel2 = new Grpc\Channel('localhost:1',
+        $this->channel1 = new Grpc\Channel('localhost:50021',
+                                           ["credentials" => $creds1,
+                                             "grpc_target_persist_bound" => 3,
+                                             ]);
+        $this->channel2 = new Grpc\Channel('localhost:50021',
                                            ["credentials" => $creds2]);
 
         // try to connect on channel1
@@ -350,9 +365,11 @@ class ChannelTest extends PHPUnit_Framework_TestCase
         $creds1 = Grpc\ChannelCredentials::createSsl();
         $creds2 = Grpc\ChannelCredentials::createInsecure();
 
-        $this->channel1 = new Grpc\Channel('localhost:1',
-                                           ["credentials" => $creds1]);
-        $this->channel2 = new Grpc\Channel('localhost:1',
+        $this->channel1 = new Grpc\Channel('localhost:50022',
+                                           ["credentials" => $creds1,
+                                             "grpc_target_persist_bound" => 3,
+                                             ]);
+        $this->channel2 = new Grpc\Channel('localhost:50022',
                                            ["credentials" => $creds2]);
 
         // try to connect on channel1
@@ -368,29 +385,55 @@ class ChannelTest extends PHPUnit_Framework_TestCase
         $this->channel2->close();
     }
 
+    public function testPersistentChannelSharedChannelClose1()
+    {
+        // same underlying channel
+        $this->channel1 = new Grpc\Channel('localhost:50123', [
+            "grpc_target_persist_bound" => 3,
+        ]);
+        $this->channel2 = new Grpc\Channel('localhost:50123', []);
+
+        // close channel1
+        $this->channel1->close();
+
+        // channel2 can still be use. We need to exclude the possible that
+        // in testPersistentChannelSharedChannelClose2, the exception is thrown
+        // by channel1.
+        $state = $this->channel2->getConnectivityState();
+        $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
+    }
+
     /**
      * @expectedException RuntimeException
      */
-    public function testPersistentChannelSharedChannelClose()
+    public function testPersistentChannelSharedChannelClose2()
     {
         // same underlying channel
-        $this->channel1 = new Grpc\Channel('localhost:1', []);
-        $this->channel2 = new Grpc\Channel('localhost:1', []);
+        $this->channel1 = new Grpc\Channel('localhost:50223', [
+            "grpc_target_persist_bound" => 3,
+        ]);
+        $this->channel2 = new Grpc\Channel('localhost:50223', []);
 
         // close channel1
         $this->channel1->close();
 
-        // channel is already closed
+        // channel2 can still be use
         $state = $this->channel2->getConnectivityState();
+        $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
+
+        // channel 1 is closed
+        $state = $this->channel1->getConnectivityState();
     }
 
     public function testPersistentChannelCreateAfterClose()
     {
-        $this->channel1 = new Grpc\Channel('localhost:1', []);
+        $this->channel1 = new Grpc\Channel('localhost:50024', [
+            "grpc_target_persist_bound" => 3,
+        ]);
 
         $this->channel1->close();
 
-        $this->channel2 = new Grpc\Channel('localhost:1', []);
+        $this->channel2 = new Grpc\Channel('localhost:50024', []);
         $state = $this->channel2->getConnectivityState();
         $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
 
@@ -399,9 +442,11 @@ class ChannelTest extends PHPUnit_Framework_TestCase
 
     public function testPersistentChannelSharedMoreThanTwo()
     {
-        $this->channel1 = new Grpc\Channel('localhost:1', []);
-        $this->channel2 = new Grpc\Channel('localhost:1', []);
-        $this->channel3 = new Grpc\Channel('localhost:1', []);
+        $this->channel1 = new Grpc\Channel('localhost:50025', [
+            "grpc_target_persist_bound" => 3,
+        ]);
+        $this->channel2 = new Grpc\Channel('localhost:50025', []);
+        $this->channel3 = new Grpc\Channel('localhost:50025', []);
 
         // try to connect on channel1
         $state = $this->channel1->getConnectivityState(true);
@@ -439,10 +484,12 @@ class ChannelTest extends PHPUnit_Framework_TestCase
         // If a ChannelCredentials object is composed with a
         // CallCredentials object, the underlying grpc channel will
         // always be created new and NOT persisted.
-        $this->channel1 = new Grpc\Channel('localhost:1',
+        $this->channel1 = new Grpc\Channel('localhost:50026',
                                            ["credentials" =>
-                                            $credsWithCallCreds]);
-        $this->channel2 = new Grpc\Channel('localhost:1',
+                                            $credsWithCallCreds,
+                                            "grpc_target_persist_bound" => 3,
+                                            ]);
+        $this->channel2 = new Grpc\Channel('localhost:50026',
                                            ["credentials" =>
                                             $credsWithCallCreds]);
 
@@ -476,11 +523,13 @@ class ChannelTest extends PHPUnit_Framework_TestCase
         // object is composed with a CallCredentials object, the
         // underlying grpc channel will always be separate and not
         // persisted
-        $this->channel1 = new Grpc\Channel('localhost:1',
-                                           ["credentials" => $creds1]);
-        $this->channel2 = new Grpc\Channel('localhost:1',
+        $this->channel1 = new Grpc\Channel('localhost:50027',
+                                           ["credentials" => $creds1,
+                                            "grpc_target_persist_bound" => 3,
+                                            ]);
+        $this->channel2 = new Grpc\Channel('localhost:50027',
                                            ["credentials" => $creds2]);
-        $this->channel3 = new Grpc\Channel('localhost:1',
+        $this->channel3 = new Grpc\Channel('localhost:50027',
                                            ["credentials" => $creds3]);
 
         // try to connect on channel1
@@ -501,10 +550,12 @@ class ChannelTest extends PHPUnit_Framework_TestCase
 
     public function testPersistentChannelForceNew()
     {
-        $this->channel1 = new Grpc\Channel('localhost:1', []);
+        $this->channel1 = new Grpc\Channel('localhost:50028', [
+            "grpc_target_persist_bound" => 2,
+        ]);
         // even though all the channel params are the same, channel2
         // has a new and different underlying channel
-        $this->channel2 = new Grpc\Channel('localhost:1',
+        $this->channel2 = new Grpc\Channel('localhost:50028',
                                            ["force_new" => true]);
 
         // try to connect on channel1
@@ -520,19 +571,20 @@ class ChannelTest extends PHPUnit_Framework_TestCase
         $this->channel2->close();
     }
 
-    public function testPersistentChannelForceNewOldChannelIdle()
+    public function testPersistentChannelForceNewOldChannelIdle1()
     {
 
-        $this->channel1 = new Grpc\Channel('localhost:1', []);
-        $this->channel2 = new Grpc\Channel('localhost:1',
+        $this->channel1 = new Grpc\Channel('localhost:50029', [
+            "grpc_target_persist_bound" => 2,
+        ]);
+        $this->channel2 = new Grpc\Channel('localhost:50029',
                                            ["force_new" => true]);
         // channel3 shares with channel1
-        $this->channel3 = new Grpc\Channel('localhost:1', []);
+        $this->channel3 = new Grpc\Channel('localhost:50029', []);
 
         // try to connect on channel2
         $state = $this->channel2->getConnectivityState(true);
         $this->waitUntilNotIdle($this->channel2);
-
         $state = $this->channel1->getConnectivityState();
         $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
         $state = $this->channel2->getConnectivityState();
@@ -544,34 +596,85 @@ class ChannelTest extends PHPUnit_Framework_TestCase
         $this->channel2->close();
     }
 
+    public function testPersistentChannelForceNewOldChannelIdle2()
+    {
+
+        $this->channel1 = new Grpc\Channel('localhost:50029', [
+            "grpc_target_persist_bound" => 2,
+        ]);
+        $this->channel2 = new Grpc\Channel('localhost:50029', []);
+
+        // try to connect on channel2
+        $state = $this->channel1->getConnectivityState(true);
+        $this->waitUntilNotIdle($this->channel2);
+        $state = $this->channel1->getConnectivityState();
+        $this->assertConnecting($state);
+        $state = $this->channel2->getConnectivityState();
+        $this->assertConnecting($state);
+
+        $this->channel1->close();
+        $this->channel2->close();
+    }
+
+    public function testPersistentChannelForceNewOldChannelClose1()
+    {
+
+        $this->channel1 = new Grpc\Channel('localhost:50130', [
+            "grpc_target_persist_bound" => 2,
+        ]);
+        $this->channel2 = new Grpc\Channel('localhost:50130',
+                                           ["force_new" => true]);
+        // channel3 shares with channel1
+        $this->channel3 = new Grpc\Channel('localhost:50130', []);
+
+        $this->channel1->close();
+
+        $state = $this->channel2->getConnectivityState();
+        $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
+
+        // channel3 is still usable. We need to exclude the possibility that in
+        // testPersistentChannelForceNewOldChannelClose2, the exception is thrown
+        // by channel1 and channel2.
+        $state = $this->channel3->getConnectivityState();
+        $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
+    }
+
     /**
      * @expectedException RuntimeException
      */
-    public function testPersistentChannelForceNewOldChannelClose()
+    public function testPersistentChannelForceNewOldChannelClose2()
     {
 
-        $this->channel1 = new Grpc\Channel('localhost:1', []);
-        $this->channel2 = new Grpc\Channel('localhost:1',
-                                           ["force_new" => true]);
+        $this->channel1 = new Grpc\Channel('localhost:50230', [
+            "grpc_target_persist_bound" => 2,
+        ]);
+        $this->channel2 = new Grpc\Channel('localhost:50230',
+          ["force_new" => true]);
         // channel3 shares with channel1
-        $this->channel3 = new Grpc\Channel('localhost:1', []);
+        $this->channel3 = new Grpc\Channel('localhost:50230', []);
 
         $this->channel1->close();
 
         $state = $this->channel2->getConnectivityState();
         $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
 
-        // channel3 already closed
+        // channel3 is still usable
         $state = $this->channel3->getConnectivityState();
+        $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
+
+        // channel 1 is closed
+        $this->channel1->getConnectivityState();
     }
 
     public function testPersistentChannelForceNewNewChannelClose()
     {
 
-        $this->channel1 = new Grpc\Channel('localhost:1', []);
-        $this->channel2 = new Grpc\Channel('localhost:1',
+        $this->channel1 = new Grpc\Channel('localhost:50031', [
+            "grpc_target_persist_bound" => 2,
+        ]);
+        $this->channel2 = new Grpc\Channel('localhost:50031',
                                            ["force_new" => true]);
-        $this->channel3 = new Grpc\Channel('localhost:1', []);
+        $this->channel3 = new Grpc\Channel('localhost:50031', []);
 
         $this->channel2->close();
 
diff --git a/src/php/tests/unit_tests/PersistentChannelTests/PersistentChannelTest.php b/src/php/tests/unit_tests/PersistentChannelTests/PersistentChannelTest.php
new file mode 100644
index 00000000000..2bb5c4bb852
--- /dev/null
+++ b/src/php/tests/unit_tests/PersistentChannelTests/PersistentChannelTest.php
@@ -0,0 +1,489 @@
+<?php
+/*
+ *
+ * Copyright 2015 gRPC authors.
+ *
+ * Licensed 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.
+ *
+ */
+
+/**
+ * @group persistent_list_bound_tests
+ */
+class PersistentListTest extends PHPUnit_Framework_TestCase
+{
+  public function setUp()
+  {
+  }
+
+  public function tearDown()
+  {
+    $channel_clean_persistent =
+        new Grpc\Channel('localhost:50010', []);
+    $plist = $channel_clean_persistent->getPersistentList();
+    $channel_clean_persistent->cleanPersistentList();
+  }
+
+  public function waitUntilNotIdle($channel) {
+      for ($i = 0; $i < 10; $i++) {
+          $now = Grpc\Timeval::now();
+          $deadline = $now->add(new Grpc\Timeval(1000));
+          if ($channel->watchConnectivityState(GRPC\CHANNEL_IDLE,
+              $deadline)) {
+              return true;
+          }
+      }
+      $this->assertTrue(false);
+  }
+
+  public function assertConnecting($state) {
+      $this->assertTrue($state == GRPC\CHANNEL_CONNECTING ||
+      $state == GRPC\CHANNEL_TRANSIENT_FAILURE);
+  }
+
+  public function testInitHelper()
+  {
+      // PersistentList is not empty at the beginning of the tests
+      // because phpunit will cache the channels created by other test
+      // files.
+  }
+
+
+  public function testChannelNotPersist()
+  {
+      $this->channel1 = new Grpc\Channel('localhost:1', ['force_new' => true]);
+      $channel1_info = $this->channel1->getChannelInfo();
+      $plist_info = $this->channel1->getPersistentList();
+      $this->assertEquals($channel1_info['target'], 'localhost:1');
+      $this->assertEquals($channel1_info['ref_count'], 1);
+      $this->assertEquals($channel1_info['connectivity_status'],
+          GRPC\CHANNEL_IDLE);
+      $this->assertEquals(count($plist_info), 0);
+      $this->channel1->close();
+  }
+
+  public function testPersistentChannelCreateOneChannel()
+  {
+      $this->channel1 = new Grpc\Channel('localhost:1', []);
+      $channel1_info = $this->channel1->getChannelInfo();
+      $plist_info = $this->channel1->getPersistentList();
+      $this->assertEquals($channel1_info['target'], 'localhost:1');
+      $this->assertEquals($channel1_info['ref_count'], 2);
+      $this->assertEquals($channel1_info['connectivity_status'],
+                          GRPC\CHANNEL_IDLE);
+      $this->assertArrayHasKey($channel1_info['key'], $plist_info);
+      $this->assertEquals(count($plist_info), 1);
+      $this->channel1->close();
+  }
+
+  public function testPersistentChannelCreateMultipleChannels()
+  {
+      $this->channel1 = new Grpc\Channel('localhost:1', []);
+      $plist_info = $this->channel1->getPersistentList();
+      $this->assertEquals(count($plist_info), 1);
+
+      $this->channel2 = new Grpc\Channel('localhost:2', []);
+      $plist_info = $this->channel1->getPersistentList();
+      $this->assertEquals(count($plist_info), 2);
+
+      $this->channel3 = new Grpc\Channel('localhost:3', []);
+      $plist_info = $this->channel1->getPersistentList();
+      $this->assertEquals(count($plist_info), 3);
+  }
+
+  public function testPersistentChannelStatusChange()
+  {
+      $this->channel1 = new Grpc\Channel('localhost:4', []);
+      $channel1_info = $this->channel1->getChannelInfo();
+      $this->assertEquals($channel1_info['connectivity_status'],
+                          GRPC\CHANNEL_IDLE);
+
+      $this->channel1->getConnectivityState(true);
+      $this->waitUntilNotIdle($this->channel1);
+      $channel1_info = $this->channel1->getChannelInfo();
+      $this->assertConnecting($channel1_info['connectivity_status']);
+      $this->channel1->close();
+  }
+
+  public function testPersistentChannelCloseChannel()
+  {
+      $this->channel1 = new Grpc\Channel('localhost:1', []);
+      $this->channel2 = new Grpc\Channel('localhost:1', []);
+
+      $channel1_info = $this->channel1->getChannelInfo();
+      $this->assertEquals($channel1_info['ref_count'], 3);
+      $plist_info = $this->channel1->getPersistentList();
+      $this->assertEquals($plist_info[$channel1_info['key']]['ref_count'], 3);
+
+      $this->channel1->close();
+      $plist_info = $this->channel1->getPersistentList();
+      $this->assertEquals($plist_info[$channel1_info['key']]['ref_count'], 2);
+
+      $this->channel2->close();
+      $plist_info = $this->channel1->getPersistentList();
+      $this->assertEquals($plist_info[$channel1_info['key']]['ref_count'], 1);
+  }
+
+  public function testPersistentChannelSameTarget()
+  {
+      $this->channel1 = new Grpc\Channel('localhost:1', []);
+      $this->channel2 = new Grpc\Channel('localhost:1', []);
+      $plist = $this->channel2->getPersistentList();
+      $channel1_info = $this->channel1->getChannelInfo();
+      $channel2_info = $this->channel2->getChannelInfo();
+      // $channel1 and $channel2 shares the same channel, thus only 1
+      // channel should be in the persistent list.
+      $this->assertEquals($channel1_info['key'], $channel2_info['key']);
+      $this->assertArrayHasKey($channel1_info['key'], $plist);
+      $this->assertEquals(count($plist), 1);
+      $this->channel1->close();
+      $this->channel2->close();
+  }
+
+  public function testPersistentChannelDifferentTarget()
+  {
+      $this->channel1 = new Grpc\Channel('localhost:1', []);
+      $channel1_info = $this->channel1->getChannelInfo();
+      $this->channel2 = new Grpc\Channel('localhost:2', []);
+      $channel2_info = $this->channel1->getChannelInfo();
+      $plist_info = $this->channel1->getPersistentList();
+      $this->assertArrayHasKey($channel1_info['key'], $plist_info);
+      $this->assertArrayHasKey($channel2_info['key'], $plist_info);
+      $this->assertEquals($plist_info[$channel1_info['key']]['ref_count'], 2);
+      $this->assertEquals($plist_info[$channel2_info['key']]['ref_count'], 2);
+      $plist_info = $this->channel1->getPersistentList();
+      $this->assertEquals(count($plist_info), 2);
+      $this->channel1->close();
+      $this->channel2->close();
+  }
+
+  /**
+   * @expectedException RuntimeException
+   * @expectedExceptionMessage startBatch Error. Channel is closed
+   */
+  public function testPersistentChannelSharedChannelClose()
+  {
+      // same underlying channel
+      $this->channel1 = new Grpc\Channel('localhost:10010', [
+          "grpc_target_persist_bound" => 2,
+      ]);
+      $this->channel2 = new Grpc\Channel('localhost:10010', []);
+      $this->server = new Grpc\Server([]);
+      $this->port = $this->server->addHttp2Port('localhost:10010');
+
+      // channel2 can still be use
+      $state = $this->channel2->getConnectivityState();
+      $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
+
+      $call1 = new Grpc\Call($this->channel1,
+          '/foo',
+          Grpc\Timeval::infFuture());
+      $call2 = new Grpc\Call($this->channel2,
+          '/foo',
+          Grpc\Timeval::infFuture());
+      $call3 = new Grpc\Call($this->channel1,
+          '/foo',
+          Grpc\Timeval::infFuture());
+      $call4 = new Grpc\Call($this->channel2,
+          '/foo',
+          Grpc\Timeval::infFuture());
+      $batch = [
+          Grpc\OP_SEND_INITIAL_METADATA => [],
+      ];
+
+      $result = $call1->startBatch($batch);
+      $this->assertTrue($result->send_metadata);
+      $result = $call2->startBatch($batch);
+      $this->assertTrue($result->send_metadata);
+
+      $this->channel1->close();
+      // After closing channel1, channel2 can still be use
+      $result = $call4->startBatch($batch);
+      $this->assertTrue($result->send_metadata);
+      // channel 1 is closed, it will throw an exception.
+      $result = $call3->startBatch($batch);
+  }
+
+  public function testPersistentChannelTargetDefaultUpperBound()
+  {
+      $this->channel1 = new Grpc\Channel('localhost:10011', []);
+      $channel1_info = $this->channel1->getChannelInfo();
+      $this->assertEquals($channel1_info['target_upper_bound'], 1);
+      $this->assertEquals($channel1_info['target_current_size'], 1);
+  }
+
+  public function testPersistentChannelTargetUpperBoundZero()
+  {
+      $this->channel1 = new Grpc\Channel('localhost:10011', [
+          "grpc_target_persist_bound" => 0,
+      ]);
+      // channel1 will not be persisted.
+      $channel1_info = $this->channel1->getChannelInfo();
+      $this->assertEquals($channel1_info['target_upper_bound'], 0);
+      $this->assertEquals($channel1_info['target_current_size'], 0);
+      $plist_info = $this->channel1->getPersistentList();
+      $this->assertEquals(0, count($plist_info));
+  }
+
+  public function testPersistentChannelTargetUpperBoundNotZero()
+  {
+      $this->channel1 = new Grpc\Channel('localhost:10011', [
+          "grpc_target_persist_bound" => 3,
+      ]);
+      $channel1_info = $this->channel1->getChannelInfo();
+      $this->assertEquals($channel1_info['target_upper_bound'], 3);
+      $this->assertEquals($channel1_info['target_current_size'], 1);
+
+      // The upper bound should not be changed
+      $this->channel2 = new Grpc\Channel('localhost:10011', []);
+      $channel2_info = $this->channel2->getChannelInfo();
+      $this->assertEquals($channel2_info['target_upper_bound'], 3);
+      $this->assertEquals($channel2_info['target_current_size'], 1);
+
+      // The upper bound should not be changed
+      $channel_credentials = Grpc\ChannelCredentials::createSsl(null, null,
+          null);
+      $this->channel3 = new Grpc\Channel('localhost:10011',
+          ['credentials' => $channel_credentials]);
+      $channel3_info = $this->channel3->getChannelInfo();
+      $this->assertEquals($channel3_info['target_upper_bound'], 3);
+      $this->assertEquals($channel3_info['target_current_size'], 2);
+
+      // The upper bound should not be changed
+      $this->channel4 = new Grpc\Channel('localhost:10011', [
+          "grpc_target_persist_bound" => 5,
+      ]);
+      $channel4_info = $this->channel4->getChannelInfo();
+      $this->assertEquals($channel4_info['target_upper_bound'], 5);
+      $this->assertEquals($channel4_info['target_current_size'], 2);
+  }
+
+  public function testPersistentChannelDefaultOutBound1()
+  {
+      $this->channel1 = new Grpc\Channel('localhost:10011', []);
+      // Make channel1 not IDLE.
+      $this->channel1->getConnectivityState(true);
+      $this->waitUntilNotIdle($this->channel1);
+      $channel1_info = $this->channel1->getChannelInfo();
+      $this->assertConnecting($channel1_info['connectivity_status']);
+
+      // Since channel1 is CONNECTING, channel 2 will not be persisted
+      $channel_credentials = Grpc\ChannelCredentials::createSsl(null, null,
+        null);
+      $this->channel2 = new Grpc\Channel('localhost:10011',
+          ['credentials' => $channel_credentials]);
+      $channel2_info = $this->channel2->getChannelInfo();
+      $this->assertEquals(GRPC\CHANNEL_IDLE, $channel2_info['connectivity_status']);
+
+      // By default, target 'localhost:10011' only persist one channel.
+      // Since channel1 is not Idle channel2 will not be persisted.
+      $plist_info = $this->channel1->getPersistentList();
+      $this->assertEquals(1, count($plist_info));
+      $this->assertArrayHasKey($channel1_info['key'], $plist_info);
+      $this->assertArrayNotHasKey($channel2_info['key'], $plist_info);
+  }
+
+  public function testPersistentChannelDefaultOutBound2()
+  {
+      $this->channel1 = new Grpc\Channel('localhost:10011', []);
+      $channel1_info = $this->channel1->getChannelInfo();
+      $this->assertEquals(GRPC\CHANNEL_IDLE, $channel1_info['connectivity_status']);
+
+      // Although channel1 is IDLE, channel1 still has reference to the underline
+      // gRPC channel. channel2 will not be persisted
+      $channel_credentials = Grpc\ChannelCredentials::createSsl(null, null,
+        null);
+      $this->channel2 = new Grpc\Channel('localhost:10011',
+          ['credentials' => $channel_credentials]);
+      $channel2_info = $this->channel2->getChannelInfo();
+      $this->assertEquals(GRPC\CHANNEL_IDLE, $channel2_info['connectivity_status']);
+
+      // By default, target 'localhost:10011' only persist one channel.
+      // Since channel1 Idle, channel2 will be persisted.
+      $plist_info = $this->channel1->getPersistentList();
+      $this->assertEquals(1, count($plist_info));
+      $this->assertArrayHasKey($channel1_info['key'], $plist_info);
+      $this->assertArrayNotHasKey($channel2_info['key'], $plist_info);
+  }
+
+  public function testPersistentChannelDefaultOutBound3()
+  {
+      $this->channel1 = new Grpc\Channel('localhost:10011', []);
+      $channel1_info = $this->channel1->getChannelInfo();
+      $this->assertEquals(GRPC\CHANNEL_IDLE, $channel1_info['connectivity_status']);
+
+      $this->channel1->close();
+      // channel1 is closed, no reference holds to the underline channel.
+      // channel2 can be persisted.
+      $channel_credentials = Grpc\ChannelCredentials::createSsl(null, null,
+        null);
+      $this->channel2 = new Grpc\Channel('localhost:10011',
+        ['credentials' => $channel_credentials]);
+      $channel2_info = $this->channel2->getChannelInfo();
+      $this->assertEquals(GRPC\CHANNEL_IDLE, $channel2_info['connectivity_status']);
+
+      // By default, target 'localhost:10011' only persist one channel.
+      // Since channel1 Idle, channel2 will be persisted.
+      $plist_info = $this->channel2->getPersistentList();
+      $this->assertEquals(1, count($plist_info));
+      $this->assertArrayHasKey($channel2_info['key'], $plist_info);
+      $this->assertArrayNotHasKey($channel1_info['key'], $plist_info);
+  }
+
+  public function testPersistentChannelTwoUpperBound()
+  {
+      $this->channel1 = new Grpc\Channel('localhost:10011', [
+          "grpc_target_persist_bound" => 2,
+      ]);
+      $channel1_info = $this->channel1->getChannelInfo();
+      $this->assertEquals(GRPC\CHANNEL_IDLE, $channel1_info['connectivity_status']);
+
+      // Since channel1 is IDLE, channel 1 will be deleted
+      $channel_credentials = Grpc\ChannelCredentials::createSsl(null, null,
+          null);
+      $this->channel2 = new Grpc\Channel('localhost:10011',
+          ['credentials' => $channel_credentials]);
+      $channel2_info = $this->channel2->getChannelInfo();
+      $this->assertEquals(GRPC\CHANNEL_IDLE, $channel2_info['connectivity_status']);
+
+      $plist_info = $this->channel1->getPersistentList();
+      $this->assertEquals(2, count($plist_info));
+      $this->assertArrayHasKey($channel1_info['key'], $plist_info);
+      $this->assertArrayHasKey($channel2_info['key'], $plist_info);
+  }
+
+  public function testPersistentChannelTwoUpperBoundOutBound1()
+  {
+      $this->channel1 = new Grpc\Channel('localhost:10011', [
+          "grpc_target_persist_bound" => 2,
+      ]);
+      $channel1_info = $this->channel1->getChannelInfo();
+
+      $channel_credentials = Grpc\ChannelCredentials::createSsl(null, null,
+        null);
+      $this->channel2 = new Grpc\Channel('localhost:10011',
+          ['credentials' => $channel_credentials]);
+      $channel2_info = $this->channel2->getChannelInfo();
+
+      // Close channel1, so that new channel can be persisted.
+      $this->channel1->close();
+
+      $channel_credentials = Grpc\ChannelCredentials::createSsl("a", null,
+        null);
+      $this->channel3 = new Grpc\Channel('localhost:10011',
+          ['credentials' => $channel_credentials]);
+      $channel3_info = $this->channel3->getChannelInfo();
+
+      $plist_info = $this->channel1->getPersistentList();
+      $this->assertEquals(2, count($plist_info));
+      $this->assertArrayNotHasKey($channel1_info['key'], $plist_info);
+      $this->assertArrayHasKey($channel2_info['key'], $plist_info);
+      $this->assertArrayHasKey($channel3_info['key'], $plist_info);
+  }
+
+  public function testPersistentChannelTwoUpperBoundOutBound2()
+  {
+      $this->channel1 = new Grpc\Channel('localhost:10011', [
+          "grpc_target_persist_bound" => 2,
+      ]);
+      $channel1_info = $this->channel1->getChannelInfo();
+
+      $channel_credentials = Grpc\ChannelCredentials::createSsl(null, null,
+        null);
+      $this->channel2 = new Grpc\Channel('localhost:10011',
+        ['credentials' => $channel_credentials]);
+      $channel2_info = $this->channel2->getChannelInfo();
+
+      // Close channel2, so that new channel can be persisted.
+      $this->channel2->close();
+
+      $channel_credentials = Grpc\ChannelCredentials::createSsl("a", null,
+        null);
+      $this->channel3 = new Grpc\Channel('localhost:10011',
+        ['credentials' => $channel_credentials]);
+      $channel3_info = $this->channel3->getChannelInfo();
+
+      $plist_info = $this->channel1->getPersistentList();
+      $this->assertEquals(2, count($plist_info));
+      $this->assertArrayHasKey($channel1_info['key'], $plist_info);
+      $this->assertArrayNotHasKey($channel2_info['key'], $plist_info);
+      $this->assertArrayHasKey($channel3_info['key'], $plist_info);
+  }
+
+  public function testPersistentChannelTwoUpperBoundOutBound3()
+  {
+      $this->channel1 = new Grpc\Channel('localhost:10011', [
+          "grpc_target_persist_bound" => 2,
+      ]);
+      $channel1_info = $this->channel1->getChannelInfo();
+
+      $channel_credentials = Grpc\ChannelCredentials::createSsl(null, null,
+          null);
+      $this->channel2 = new Grpc\Channel('localhost:10011',
+          ['credentials' => $channel_credentials]);
+      $this->channel2->getConnectivityState(true);
+      $this->waitUntilNotIdle($this->channel2);
+      $channel2_info = $this->channel2->getChannelInfo();
+      $this->assertConnecting($channel2_info['connectivity_status']);
+
+      // Only one channel will be deleted
+      $this->channel1->close();
+      $this->channel2->close();
+
+      $channel_credentials = Grpc\ChannelCredentials::createSsl("a", null,
+        null);
+      $this->channel3 = new Grpc\Channel('localhost:10011',
+          ['credentials' => $channel_credentials]);
+      $channel3_info = $this->channel3->getChannelInfo();
+
+      // Only the Idle Channel will be deleted
+      $plist_info = $this->channel1->getPersistentList();
+      $this->assertEquals(2, count($plist_info));
+      $this->assertArrayNotHasKey($channel1_info['key'], $plist_info);
+      $this->assertArrayHasKey($channel2_info['key'], $plist_info);
+      $this->assertArrayHasKey($channel3_info['key'], $plist_info);
+  }
+
+  public function testPersistentChannelTwoUpperBoundOutBound4()
+  {
+      $this->channel1 = new Grpc\Channel('localhost:10011', [
+          "grpc_target_persist_bound" => 2,
+      ]);
+      $this->channel1->getConnectivityState(true);
+      $this->waitUntilNotIdle($this->channel1);
+      $channel1_info = $this->channel1->getChannelInfo();
+      $this->assertConnecting($channel1_info['connectivity_status']);
+
+      $channel_credentials = Grpc\ChannelCredentials::createSsl(null, null,
+          null);
+      $this->channel2 = new Grpc\Channel('localhost:10011',
+          ['credentials' => $channel_credentials]);
+      $this->channel2->getConnectivityState(true);
+      $this->waitUntilNotIdle($this->channel2);
+      $channel2_info = $this->channel2->getChannelInfo();
+      $this->assertConnecting($channel2_info['connectivity_status']);
+
+      $channel_credentials = Grpc\ChannelCredentials::createSsl("a", null,
+          null);
+      $this->channel3 = new Grpc\Channel('localhost:10011',
+          ['credentials' => $channel_credentials]);
+      $channel3_info = $this->channel3->getChannelInfo();
+
+      // Channel3 will not be persisted
+      $plist_info = $this->channel1->getPersistentList();
+      $this->assertEquals(2, count($plist_info));
+      $this->assertArrayHasKey($channel1_info['key'], $plist_info);
+      $this->assertArrayHasKey($channel2_info['key'], $plist_info);
+      $this->assertArrayNotHasKey($channel3_info['key'], $plist_info);
+  }
+}
diff --git a/tools/run_tests/helper_scripts/build_php.sh b/tools/run_tests/helper_scripts/build_php.sh
index 443be34fa0c..4add036672b 100755
--- a/tools/run_tests/helper_scripts/build_php.sh
+++ b/tools/run_tests/helper_scripts/build_php.sh
@@ -30,8 +30,8 @@ cd src/php
 cd ext/grpc
 phpize
 if [ "$CONFIG" != "gcov" ] ; then
-  ./configure --enable-grpc="$root"
+  ./configure --enable-grpc="$root" --enable-tests
 else
-  ./configure --enable-grpc="$root" --enable-coverage
+  ./configure --enable-grpc="$root" --enable-coverage --enable-tests
 fi
 make