专栏首页健程之道Class.forName 造成的线程阻塞

Class.forName 造成的线程阻塞

今天在查看服务器时,发现机器上稳定的会有 3 ~ 4 个线程处于阻塞状态,感觉应该是有问题的,仔细排查了一下,最终发现和 Class.forName 有关。

现象

某一天突然收到了公司的系统提醒,说是我们的服务中,长时间都有好几个处于BLOCKED状态的线程。

因为我们的访问量还是不小的,因此写了一段代码模拟了一下,大致类似于:

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class Test {

  public static void main(String[] args) throws InterruptedException {
    ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 10, 0, TimeUnit.SECONDS,
        new LinkedBlockingQueue<>(), new ThreadFactory() {
      private final AtomicInteger count = new AtomicInteger(0);

      @Override
      public Thread newThread(Runnable r) {
        return new Thread(r, "testThread-" + count.incrementAndGet());
      }
    });

    int totalCount = 1000;
    CountDownLatch latch = new CountDownLatch(totalCount);
    for (int i = 0; i < totalCount; i++) {
      int current = i;
      executor.execute(() -> {
        try {
          for (int j = 0; j < totalCount; j++) {
            BeanInfo destBean = Introspector.getBeanInfo(Test.class, java.lang.Object.class);
          }
        } catch (Exception e) {
          System.out.println(current + "\tfail");
        } finally {
          latch.countDown();
        }
      });
    }

    latch.await();
    System.out.println("finish");
    executor.shutdown();
  }

  private String name;

  private Integer age;

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public Integer getAge() {
    return age;
  }

  public void setAge(Integer age) {
    this.age = age;
  }
}

当时登上了服务器,首先查询出我们的 java 进程号:

ps -ef | grep java

假设结果是26385,这时再借助jstack命令打印出各个线程的状态:

jstack 26385 > 26385.txt

然后分析了26385.txt,发现了原因:

"testThread-7" #14 prio=5 os_prio=0 tid=0x00007f72a810e800 nid=0x6706 waiting for monitor entry [0x00007f729837c000]
   java.lang.Thread.State: BLOCKED (on object monitor)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:404)
    - waiting to lock <0x00000000f7c773b8> (a java.lang.Object)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    at java.lang.Class.forName0(Native Method)
    at java.lang.Class.forName(Class.java:348)
    at com.sun.beans.finder.ClassFinder.findClass(ClassFinder.java:103)
    at java.beans.Introspector.findCustomizerClass(Introspector.java:1301)
    at java.beans.Introspector.getTargetBeanDescriptor(Introspector.java:1295)
    at java.beans.Introspector.getBeanInfo(Introspector.java:425)
    at java.beans.Introspector.getBeanInfo(Introspector.java:262)
    at java.beans.Introspector.getBeanInfo(Introspector.java:224)
    at Test.lambda$main$0(Test.java:35)
    at Test$$Lambda$1/303563356.run(Unknown Source)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at java.lang.Thread.run(Thread.java:748)

上面很清晰地显示了,我们写的Introspector.getBeanInfo代码,最终会调用Class类中的forName0方法:

    /** Called after security check for system loader access checks have been made. */
    private static native Class<?> forName0(String name, boolean initialize,
                                            ClassLoader loader,
                                            Class<?> caller)
        throws ClassNotFoundException;

从上面的stack中分析可以得知,这个方法内部应该是有锁的,因此会阻塞其他线程。

解决方法

既然它是有锁的,为了不让它在运行时每次都执行,最简单的方法就是在初始化时,就将需要处理的类全部处理好,这样在应用运行期间,完全不会再去反射。

源码

有些人可能会好奇,forName0中究竟是如何使用到了锁,这里就把源码展示给大家。

下文的调用链路是:

forName0 -> JVM_FindClassFromCaller -> find_class_from_class_loader -> resolve_or_fail -> resolve_or_null -> resolve_instance_class_or_null -> load_instance_class

forName0源码实现位于src/java.base/share/native/libjava/Class.c

// 动态装载类型入口
JNIEXPORT jclass JNICALL
Java_java_lang_Class_forName0(JNIEnv *env, jclass this, jstring classname,
                              jboolean initialize, jobject loader, jclass caller)
{
    char *clname;
    jclass cls = 0;
    char buf[128];
    jsize len;
    jsize unicode_len;

    if (classname == NULL) {
        JNU_ThrowNullPointerException(env, 0);
        return 0;
    }

    // 把类全限定名里的'.'翻译成'/'
    if (VerifyFixClassname(clname) == JNI_TRUE) {
        /* slashes present in clname, use name b4 translation for exception */
        (*env)->GetStringUTFRegion(env, classname, 0, unicode_len, clname);
        JNU_ThrowClassNotFoundException(env, clname);
        goto done;
    }

    // 验证类全限定名名合法性(是否以'/'分隔)
    if (!VerifyClassname(clname, JNI_TRUE)) {  /* expects slashed name */
        JNU_ThrowClassNotFoundException(env, clname);
        goto done;
    }

    // 从指定的加载器查找该类
    cls = JVM_FindClassFromCaller(env, clname, initialize, loader, caller);

 done:
    if (clname != buf) {
        free(clname);
    }
    return cls;
}

FindClassFromCaller位于src/hotspot/share/prims/jvm.cpp

// 从指定的加载器查找该类
// Find a class with this name in this loader, using the caller's protection domain.
JVM_ENTRY(jclass, JVM_FindClassFromCaller(JNIEnv* env, const char* name,
                                          jboolean init, jobject loader,
                                          jclass caller))

  // 把当前类加入符号表(一个哈希表实现)
  TempNewSymbol h_name = SymbolTable::new_symbol(name, CHECK_NULL);

  // 获取加载器和调用类
  oop loader_oop = JNIHandles::resolve(loader);
  oop from_class = JNIHandles::resolve(caller);
  oop protection_domain = NULL;

  if (from_class != NULL && loader_oop != NULL) {
    protection_domain = java_lang_Class::as_Klass(from_class)->protection_domain();
  }

  // 查找该类
  jclass result = find_class_from_class_loader(env, h_name, init, h_loader,
                                               h_prot, false, THREAD);

  // 返回结果
  return result;
JVM_END

find_class_from_class_loader位于/src/hotspot/share/prims/jvm.cpp

// Shared JNI/JVM entry points //////////////////////////////////////////////////////////////
// 从指定的classloader中查找类
jclass find_class_from_class_loader(JNIEnv* env, Symbol* name, jboolean init,
                                    Handle loader, Handle protection_domain,
                                    jboolean throwError, TRAPS) {

  //==========================================
  //
  // 根据指定的类名和加载器返回一个Klass对象,必要情况下需要加载该类。
  // 如果未找到该类则抛出NoClassDefFoundError或ClassNotFoundException
  //
  //=========================================
  Klass* klass = SystemDictionary::resolve_or_fail(name, loader, protection_domain, throwError != 0, CHECK_NULL);

  // Check if we should initialize the class
  if (init && klass->is_instance_klass()) {
    klass->initialize(CHECK_NULL);
  }
  return (jclass) JNIHandles::make_local(env, klass->java_mirror());
}

resolve_or_fail位于src/hotspot/share/classfile/systemDictionary.cpp

// Forwards to resolve_or_null

Klass* SystemDictionary::resolve_or_fail(Symbol* class_name, Handle class_loader, Handle protection_domain, bool throw_error, TRAPS) {
  Klass* klass = resolve_or_null(class_name, class_loader, protection_domain, THREAD);
  if (HAS_PENDING_EXCEPTION || klass == NULL) {
    // can return a null klass
    klass = handle_resolution_exception(class_name, throw_error, klass, THREAD);
  }
  return klass;
}

resolve_or_null也位于src/hotspot/share/classfile/systemDictionary.cpp

// Forwards to resolve_instance_class_or_null

Klass* SystemDictionary::resolve_or_null(Symbol* class_name, Handle class_loader, Handle protection_domain, TRAPS) {

  if (FieldType::is_array(class_name)) {
    return resolve_array_class_or_null(class_name, class_loader, protection_domain, THREAD);
  } else if (FieldType::is_obj(class_name)) {
    ResourceMark rm(THREAD);
    // Ignore wrapping L and ;.
    TempNewSymbol name = SymbolTable::new_symbol(class_name->as_C_string() + 1,
                                   class_name->utf8_length() - 2, CHECK_NULL);
    return resolve_instance_class_or_null(name, class_loader, protection_domain, THREAD);
  } else {
    // 解析实例类
    return resolve_instance_class_or_null(class_name, class_loader, protection_domain, THREAD);
  }
}

resolve_instance_class_or_null也位于src/hotspot/share/classfile/systemDictionary.cpp

Klass* SystemDictionary::resolve_instance_class_or_null(Symbol* name,
                                                        Handle class_loader,
                                                        Handle protection_domain,
                                                        TRAPS) {
  Handle lockObject = compute_loader_lock_object(class_loader, THREAD);
  check_loader_lock_contention(lockObject, THREAD);
  // 获取对象锁
  ObjectLocker ol(lockObject, THREAD, DoObjectLock);

  {
    MutexLocker mu(SystemDictionary_lock, THREAD);
    // 查找类
    InstanceKlass* check = find_class(d_index, d_hash, name, dictionary);
    if (check != NULL) {
      // Klass is already loaded, so just return it
      class_has_been_loaded = true;
      k = check;
    } else {
      // 查找该类是否在placeholder table中
      placeholder = placeholders()->get_entry(p_index, p_hash, name, loader_data);
      if (placeholder && placeholder->super_load_in_progress()) {
         super_load_in_progress = true;
         if (placeholder->havesupername() == true) {
           superclassname = placeholder->supername();
           havesupername = true;
         }
      }
    }
  }

  // 如果该类在placeholder table中,则说明类加载进行中
  if (super_load_in_progress && havesupername==true) {
    k = handle_parallel_super_load(name,
                                   superclassname,
                                   class_loader,
                                   protection_domain,
                                   lockObject, THREAD);
    if (HAS_PENDING_EXCEPTION) {
      return NULL;
    }
    if (k != NULL) {
      class_has_been_loaded = true;
    }
  }

  bool throw_circularity_error = false;
  if (!class_has_been_loaded) {
    bool load_instance_added = false;

    if (!class_has_been_loaded) {

      // =====================================
      //
      //      执行实例加载动作
      //
      // =====================================
      k = load_instance_class(name, class_loader, THREAD);

      if (!HAS_PENDING_EXCEPTION && k != NULL &&
        k->class_loader() != class_loader()) {

        check_constraints(d_index, d_hash, k, class_loader, false, THREAD);

        // Need to check for a PENDING_EXCEPTION again; check_constraints
        // can throw and doesn't use the CHECK macro.
        if (!HAS_PENDING_EXCEPTION) {
          { // Grabbing the Compile_lock prevents systemDictionary updates
            // during compilations.
            MutexLocker mu(Compile_lock, THREAD);
            update_dictionary(d_index, d_hash, p_index, p_hash,
              k, class_loader, THREAD);
          }

          // 通知JVMTI类加载事件
          if (JvmtiExport::should_post_class_load()) {
            Thread *thread = THREAD;
            assert(thread->is_Java_thread(), "thread->is_Java_thread()");
            JvmtiExport::post_class_load((JavaThread *) thread, k);
          }
        }
      }
    } // load_instance_class
  }
  ...

  return k;
}

load_instance_class也位于src/hotspot/share/classfile/systemDictionary.cpp

// ===================================================================================
//
//              加载实例class,这里有两种方式:
// ===================================================================================
//
// 1、如果classloader为null则说明是加载系统类,使用bootstrap loader
//    调用方式:直接调用ClassLoader::load_class()加载该类
//
// 2、如果classloader不为null则说明是非系统类,使用ext/app/自定义 classloader
//    调用方式:通过JavaCalls::call_virtual()调用Java方法ClassLoader.loadClass()加载该类
//
// ===================================================================================
InstanceKlass* SystemDictionary::load_instance_class(Symbol* class_name, Handle class_loader, TRAPS) {

  // 使用bootstrap加载器加载
  if (class_loader.is_null()) {

    // 根据全限定名获取包名
    // Find the package in the boot loader's package entry table.
    TempNewSymbol pkg_name = InstanceKlass::package_from_name(class_name, CHECK_NULL);
    if (pkg_name != NULL) {
      pkg_entry = loader_data->packages()->lookup_only(pkg_name);
    }

    InstanceKlass* k = NULL;

    if (k == NULL) {
      // Use VM class loader
      PerfTraceTime vmtimer(ClassLoader::perf_sys_classload_time());
      // =================================================================
      //
      //        使用bootstrap loader加载该类
      //
      // =================================================================
      k = ClassLoader::load_class(class_name, search_only_bootloader_append, CHECK_NULL);
    }


    return k;
  } else {
    // =======================================================================================
    //
    // 使用用户指定的加载器加载该类,调用class_loader的loadClass操作方法,
    // 最终返回一个标准的InstanceKlass,流程如下
    //
    // +-----------+  loadClass()   +---------------+  get_jobject()   +-------------+
    // | className | -------------> |   JavaValue   | ---------------> |     oop     |
    // +-----------+                +---------------+                  +-------------+
    //                                                                       |
    //                                                                       | as_Klass()
    //                                                                       v
    //                               +---------------+  cast()          +-------------+
    //                               | InstanceKlass | <--------------- |    Klass    |
    //                               +---------------+                  +-------------+
    //
    // =======================================================================================  
    ResourceMark rm(THREAD);

    assert(THREAD->is_Java_thread(), "must be a JavaThread");
    JavaThread* jt = (JavaThread*) THREAD;

    PerfClassTraceTime vmtimer(ClassLoader::perf_app_classload_time(),
                               ClassLoader::perf_app_classload_selftime(),
                               ClassLoader::perf_app_classload_count(),
                               jt->get_thread_stat()->perf_recursion_counts_addr(),
                               jt->get_thread_stat()->perf_timers_addr(),
                               PerfClassTraceTime::CLASS_LOAD);

    Handle s = java_lang_String::create_from_symbol(class_name, CHECK_NULL);
    // Translate to external class name format, i.e., convert '/' chars to '.'
    Handle string = java_lang_String::externalize_classname(s, CHECK_NULL);

    JavaValue result(T_OBJECT);

    InstanceKlass* spec_klass = SystemDictionary::ClassLoader_klass();

    // Added MustCallLoadClassInternal in case we discover in the field
    // a customer that counts on this call
    if (MustCallLoadClassInternal && has_loadClassInternal()) {
      JavaCalls::call_special(&result,
                              class_loader,
                              spec_klass,
                              vmSymbols::loadClassInternal_name(),
                              vmSymbols::string_class_signature(),
                              string,
                              CHECK_NULL);
    } else {
      // ===============================================================
      //
      // 调用ClassLoader.loadClass()方法加载该类,而最终会调用ClassLoader的native方法defineClass1()
      // 其实现位于ClassLoader.c # Java_java_lang_ClassLoader_defineClass1()
      //
      // ===============================================================
      JavaCalls::call_virtual(&result,
                              class_loader,
                              spec_klass,
                              vmSymbols::loadClass_name(),
                              vmSymbols::string_class_signature(),
                              string,
                              CHECK_NULL);
    }

    assert(result.get_type() == T_OBJECT, "just checking");
    // 获取oop对象
    oop obj = (oop) result.get_jobject();

    // 如果不是基本类,则转换成对应的InstanceKlass
    if ((obj != NULL) && !(java_lang_Class::is_primitive(obj))) {
      InstanceKlass* k = InstanceKlass::cast(java_lang_Class::as_Klass(obj));

      if (class_name == k->name()) {
        // 返回最终InstanceKlass
        return k;
      }
    }
    // Class is not found or has the wrong name, return NULL
    return NULL;
  }
}

以上便是相关的所有底层源码。

总结

一个小小的Class.forName方法,也会引出不少问题,如果仔细研究,在排查的过程,相信你一定会有所收获。

有兴趣的话可以访问我的博客或者关注我的公众号、头条号,说不定会有意外的惊喜。https://death00.github.io/公众号:健程之道

本文分享自微信公众号 - 健程之道(JianJianCoder),作者:健健壮

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-12-24

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Java线程阻塞

    阻塞指的是暂停一个线程的执行以等待某个条件发生(如某资源就绪),学过操作系统的同学对它一 定已经很熟悉了。Java 提供了大量方法来支持阻塞,下面让我们逐一分...

    全栈程序员站长
  • css加载会造成阻塞吗

    可能大家都知道,js执行会阻塞DOM树的解析和渲染,那么css加载会阻塞DOM树的解析和渲染吗?接下来,我就来对css加载对DOM树的解析和渲染的影响做一个测试...

    嘿嘿嘿
  • css加载会造成阻塞吗?

    终于考试完了,今天突然想起来前阵子找实习的时候,今日头条面试官问我,js执行会阻塞DOM树的解析和渲染,那么css加载会阻塞DOM树的解析和渲染吗?所以,接下来...

    嘿嘿嘿
  • css加载会造成阻塞吗

    可能大家都知道,js执行会阻塞DOM树的解析和渲染,那么css加载会阻塞DOM树的解析和渲染吗?接下来,我就来对css加载对DOM树的解析和渲染的影响做一个测试...

    Nealyang
  • [Android] 为什么主线程不会因为Looper.loop()方法造成阻塞

    首先,关于Handler相关机制,可以参考我之前整理的[Android] Handler消息传递机制。

    wOw
  • 阻塞队列中的线程协作(阻塞、唤醒、锁)

    阻塞队列,主要操作有两个,一个是put放入元素,另一个是take取出元素。所谓的阻塞就是当多个线程同时存取数据时,如果遇到队列为空或者队列为满时,会发生阻塞。并...

    naget
  • java 线程阻塞的问题

    中断线程最好的,最受推荐的方式是,使用共享变量(shared variable)发出信号,告诉线程必须停止正在运行的任务。线程必须周期性的核查这一变量(尤其在冗...

    田春峰-JCJC错别字检测
  • 线程的阻塞和唤醒

    park方法有两个参数来控制休眠多长时间,第一个参数isAbsolute表示第二个参数是绝对时间还是相对时间,单位是毫秒。

    春哥大魔王
  • 服务器模型——从单线程阻塞到多线程非阻塞(上)

    前言的前言 服务器模型涉及到线程模式和IO模式,搞清楚这些就能针对各种场景有的放矢。该系列分成三部分: 单线程/多线程阻塞I/O模型 单线程非阻塞I/O模型 多...

    企鹅号小编

扫码关注云+社区

领取腾讯云代金券