精华内容
下载资源
问答
  • 如果这个类存在直接父类,并且这个类还没有初始化(**在一个类加载器中,类只能被初始化一次**),那就先初始化直接父类(不适用于接口)。 3. 加入类中存在的初始化语句(如static变量和static块),那就先执行...
  • 主要介绍了Java类加载机制实现流程及原理详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
  • 类加载流程006

    万次阅读 2019-05-21 20:12:55
    类加载流程004 预加载基础类.此处的代码如下: void SystemDictionary::initialize_preloaded_classes(TRAPS) { assert(WK_KLASS(Object_klass) == NULL, "preloaded classes should only be initialized ...

    本文来介绍SystemDictionary::initialize(Thread* the_thread).在此之前,先介绍一下SystemDictionary涉及的类图:

    在这里插入图片描述

    图中没有画出的是,SymbolPropertyTable,LoaderConstraintTable等,其内部存放的是SymbolPropertyEntry,LoaderConstraintEntry.

    解析

    SystemDictionary内部定义了两个枚举,分别如下:

    enum WKID {
        NO_WKID = 0,
    
        #define WK_KLASS_ENUM(name, ignore_s, ignore_o) WK_KLASS_ENUM_NAME(name),
        WK_KLASSES_DO(WK_KLASS_ENUM)
        #undef WK_KLASS_ENUM
    
        WKID_LIMIT,
    
        FIRST_WKID = NO_WKID + 1
      };
    
      enum InitOption {
        Pre,                        // preloaded; error if not present 预加载,如果不存在,则退出
    
        // Order is significant.  Options before this point require resolve_or_fail.
        // Options after this point will use resolve_or_null instead.
        // 秩序是重要的。注释之前的的选项是resolve_or_fail方法所使用的。之后的的选项是resolve_or_null使用的。
        Opt,                        // preload tried; NULL if not present 尝试预加载,如果不存在,则返回NULL
        Opt_Only_JDK14NewRef,       // preload tried; use only with NewReflection 尝试预加载,只和NewReflection 使用
        Opt_Only_JDK15,             // preload tried; use only with JDK1.5+ 尝试预加载,只在JDK1.5+使用
        Opt_Kernel,                 // preload tried only #ifdef KERNEL 尝试预加载,只在#ifdef KERNEL中使用
        OPTION_LIMIT,
        CEIL_LG_OPTION_LIMIT = 4    // OPTION_LIMIT <= (1<<CEIL_LG_OPTION_LIMIT)
      };
    
    

    其中,WKID 中的宏展开如下:

      Object_klass_knum, \
      String_klass_knum, \
      Class_klass_knum, \
      Cloneable_klass_knum, \
      ClassLoader_klass_knum, \
      Serializable_klass_knum, \
      System_klass_knum, \
      Throwable_klass_knum, \
      ....
    

    SystemDictionary::initialize的代码如下:

    void SystemDictionary::initialize(TRAPS) {
      // Allocate arrays
      assert(dictionary() == NULL,
             "SystemDictionary should only be initialized once");
      _dictionary          = new Dictionary(_nof_buckets);
      _placeholders        = new PlaceholderTable(_nof_buckets);
      _number_of_modifications = 0;
      _loader_constraints  = new LoaderConstraintTable(_loader_constraint_size);
      _resolution_errors   = new ResolutionErrorTable(_resolution_error_size);
      _invoke_method_table = new SymbolPropertyTable(_invoke_method_size);
    
      // Allocate private object used as system class loader lock
      // 使用系统类加载器锁来分配私有的对象
      _system_loader_lock_obj = oopFactory::new_system_objArray(0, CHECK);
      // Initialize basic classes 初始化基础类
      initialize_preloaded_classes(CHECK);
    }
    

    做了3件事:

    1. 实例化内部变量.这里需要介绍的是,不同xxxxTable的大小是不同的

         enum Constants {
           _loader_constraint_size = 107,                     // number of entries in constraint table
           _resolution_error_size  = 107,                     // number of entries in resolution error table
           _invoke_method_size     = 139,                     // number of entries in invoke method table
           _nof_buckets            = 1009                     // number of buckets in hash table
         };
      
    2. 使用系统类加载器锁来分配私有的对象,代码如下:

      objArrayOop oopFactory::new_system_objArray(int length, TRAPS) {
        int size = objArrayOopDesc::object_size(length);
        KlassHandle klass (THREAD, Universe::systemObjArrayKlassObj());
        objArrayOop o = (objArrayOop)
          Universe::heap()->permanent_array_allocate(klass, size, length, CHECK_NULL);
        // initialization not needed, allocated cleared
        return o;
      }
      

      关于permanent_array_allocate,之前的文章有介绍.类加载流程004

    3. 预加载基础类.此处的代码如下:

          void SystemDictionary::initialize_preloaded_classes(TRAPS) {
         assert(WK_KLASS(Object_klass) == NULL, "preloaded classes should only be initialized once");
         // 预加载使用的类
         WKID scan = FIRST_WKID;
         // 1.  初始化object,string,class 所对应的class
         initialize_wk_klasses_through(WK_KLASS_ENUM_NAME(Class_klass), scan, CHECK);
       
       
        	 // 2.在java.lang.class之前加载的类的修复mirrors。这些调用循环访问perm gen中当前的对象,因此此时调用它们很重要(在对象较少的情况下不早于此,在perm gen中对象较多的情况下不迟于此)。
         Universe::initialize_basic_type_mirrors(CHECK);
         Universe::fixup_mirrors(CHECK);
       
         // 3. 继续预加载类,从Cloneable_klass_knum 至Reference_klass_knum
         initialize_wk_klasses_through(WK_KLASS_ENUM_NAME(Reference_klass), scan, CHECK);
       
         // 4 预加载Reference_klass,SoftReference_klass_knum,WeakReference_klass_knum,FinalReference_klass_knum,PhantomReference_klass_knum,同时重写设置reference type
         instanceKlass::cast(WK_KLASS(Reference_klass))->set_reference_type(REF_OTHER);
         instanceRefKlass::update_nonstatic_oop_maps(WK_KLASS(Reference_klass));
       
         initialize_wk_klasses_through(WK_KLASS_ENUM_NAME(PhantomReference_klass), scan, CHECK);
         instanceKlass::cast(WK_KLASS(SoftReference_klass))->set_reference_type(REF_SOFT);
         instanceKlass::cast(WK_KLASS(WeakReference_klass))->set_reference_type(REF_WEAK);
         instanceKlass::cast(WK_KLASS(FinalReference_klass))->set_reference_type(REF_FINAL);
         instanceKlass::cast(WK_KLASS(PhantomReference_klass))->set_reference_type(REF_PHANTOM);
       
         WKID meth_group_start = WK_KLASS_ENUM_NAME(MethodHandle_klass);
         WKID meth_group_end   = WK_KLASS_ENUM_NAME(WrongMethodTypeException_klass);
         // 5. 预加载从Finalizer_klass_knum到MethodHandle_klass_knum的类
         initialize_wk_klasses_until(meth_group_start, scan, CHECK);
         if (EnableMethodHandles) {// 默认为false
       	  // 预加载从MemberName_klass_knum到WrongMethodTypeException_klass_knum的类
           initialize_wk_klasses_through(meth_group_end, scan, CHECK);
         }
         if (_well_known_klasses[meth_group_start] == NULL) {
           // Skip the rest of the method handle classes, if MethodHandle is not loaded.
       	 // 如果加载失败的话,则跳过剩余的method handle 相关的类
           scan = WKID(meth_group_end+1);
         }
         // 6. 预加载Linkage_klass_knum类
         WKID indy_group_start = WK_KLASS_ENUM_NAME(Linkage_klass);
         WKID indy_group_end   = WK_KLASS_ENUM_NAME(CallSite_klass);
         initialize_wk_klasses_until(indy_group_start, scan, CHECK);
         if (EnableInvokeDynamic) { // 默认为false
       	  // 预加载CallSite_klass_knumm类
           initialize_wk_klasses_through(indy_group_end, scan, CHECK);
         }
         if (_well_known_klasses[indy_group_start] == NULL) {
           // Skip the rest of the dynamic typing classes, if Linkage is not loaded.
       	// 如果Linkage_klass_knum 没有加载,则跳过剩余的类
           scan = WKID(indy_group_end+1);
         }
       
         // 7. 加载剩下的类
         initialize_wk_klasses_until(WKID_LIMIT, scan, CHECK);
       
         // 保存基本类型所对对应的包装类
         _box_klasses[T_BOOLEAN] = WK_KLASS(Boolean_klass);
         _box_klasses[T_CHAR]    = WK_KLASS(Character_klass);
         _box_klasses[T_FLOAT]   = WK_KLASS(Float_klass);
         _box_klasses[T_DOUBLE]  = WK_KLASS(Double_klass);
         _box_klasses[T_BYTE]    = WK_KLASS(Byte_klass);
         _box_klasses[T_SHORT]   = WK_KLASS(Short_klass);
         _box_klasses[T_INT]     = WK_KLASS(Integer_klass);
         _box_klasses[T_LONG]    = WK_KLASS(Long_klass);
        
       
       
       
         { 
       	// 计算当加载的类的时候,是使用loadClass,或者是loadClassInternal
           methodOop method = instanceKlass::cast(ClassLoader_klass())->find_method(vmSymbols::loadClassInternal_name(), vmSymbols::string_class_signature());
           _has_loadClassInternal = (method != NULL);
         }
         { 	    // 计算是否需要检查包的访问权限
           methodOop method = instanceKlass::cast(ClassLoader_klass())->find_method(vmSymbols::checkPackageAccess_name(), vmSymbols::class_protectiondomain_signature());
           _has_checkPackageAccess = (method != NULL);
         }
       }
      

    预加载流程

    SystemDictionary::initialize_preloaded_classes 是分批次的预加载类的.其首先是调用initialize_wk_klasses_through方法,如下:

      static void initialize_wk_klasses_through(WKID end_id, WKID &start_id, TRAPS) {
        int limit = (int)end_id + 1;
        initialize_wk_klasses_until((WKID) limit, start_id, THREAD);
      }
     
    

    调用initialize_wk_klasses_until,代码如下:

    void SystemDictionary::initialize_wk_klasses_until(WKID limit_id, WKID &start_id, TRAPS) {
      assert((int)start_id <= (int)limit_id, "IDs are out of order!");
      for (int id = (int)start_id; id < (int)limit_id; id++) {
        assert(id >= (int)FIRST_WKID && id < (int)WKID_LIMIT, "oob");
        int info = wk_init_info[id - FIRST_WKID];
        int sid  = (info >> CEIL_LG_OPTION_LIMIT); // 获得sid
        int opt  = (info & right_n_bits(CEIL_LG_OPTION_LIMIT)); // 获得opt = info & (OneBit << (CEIL_LG_OPTION_LIMIT)) - 1)
    
        // 1.初始化对应的类
        initialize_wk_klass((WKID)id, opt, CHECK);
    
        // 2.更新wk_klass_name_limits
        symbolOop s = vmSymbols::symbol_at((vmSymbols::SID)sid);
        if (wk_klass_name_limits[1] == NULL) {
          wk_klass_name_limits[0] = wk_klass_name_limits[1] = s;
        } else if (wk_klass_name_limits[1] < s) {
          wk_klass_name_limits[1] = s;
        } else if (wk_klass_name_limits[0] > s) {
          wk_klass_name_limits[0] = s;
        }
      }
    
      // 3. 更新start_id的值
      start_id = limit_id;
    }
    
    

    这里有一个wk_init_info数组,代码如下:

    // 将初始化的klass 压缩到一个表中
    static const short wk_init_info[] = {
      #define WK_KLASS_INIT_INFO(name, symbol, option) \
        ( ((int)vmSymbols::VM_SYMBOL_ENUM_NAME(symbol) \
              << SystemDictionary::CEIL_LG_OPTION_LIMIT) \
          | (int)SystemDictionary::option ),
      WK_KLASSES_DO(WK_KLASS_INIT_INFO)
      #undef WK_KLASS_INIT_INFO
      0
    };
    

    宏展开如下:

    
    ( ((int)vmSymbols::java_lang_Object_enum << SystemDictionary::CEIL_LG_OPTION_LIMIT) | (int)SystemDictionary::Pre ), \ 
      ( ((int)vmSymbols::java_lang_String_enum \
              << SystemDictionary::CEIL_LG_OPTION_LIMIT) \
          | (int)SystemDictionary::Pre ), \
      ( ((int)vmSymbols::java_lang_Class_enum \
              << SystemDictionary::CEIL_LG_OPTION_LIMIT) \
          | (int)SystemDictionary::Pre ), \
      ( ((int)vmSymbols::java_lang_Cloneable_enum \
              << SystemDictionary::CEIL_LG_OPTION_LIMIT) \
          | (int)SystemDictionary::Pre ), \
      ( ((int)vmSymbols::java_lang_ClassLoader_enum \
              << SystemDictionary::CEIL_LG_OPTION_LIMIT) \
          | (int)SystemDictionary::Pre ), \
      ( ((int)vmSymbols::java_io_Serializable_enum \
              << SystemDictionary::CEIL_LG_OPTION_LIMIT) \
          | (int)SystemDictionary::Pre ), \
      ( ((int)vmSymbols::java_lang_System_enum \
              << SystemDictionary::CEIL_LG_OPTION_LIMIT) \
          | (int)SystemDictionary::Pre ), \
      ( ((int)vmSymbols::java_lang_Throwable_enum \
              << SystemDictionary::CEIL_LG_OPTION_LIMIT) \
          | (int)SystemDictionary::Pre ), \
          
          .....
    

    这里最重要的是调用initialize_wk_klass方法,其代码如下:

    bool SystemDictionary::initialize_wk_klass(WKID id, int init_opt, TRAPS) {
      assert(id >= (int)FIRST_WKID && id < (int)WKID_LIMIT, "oob");
      int  info = wk_init_info[id - FIRST_WKID];
      int  sid  = (info >> CEIL_LG_OPTION_LIMIT); // = vmSymbols::VM_SYMBOL_ENUM_NAME(symbol)
      symbolHandle symbol = vmSymbolHandles::symbol_handle_at((vmSymbols::SID)sid); // 获得对应的symbolHandle
      klassOop*    klassp = &_well_known_klasses[id];
      bool must_load = (init_opt < SystemDictionary::Opt);
      bool try_load  = true;
      if (init_opt == SystemDictionary::Opt_Kernel) {
    #ifndef KERNEL
        try_load = false;
    #endif //KERNEL
      }
      if ((*klassp) == NULL && try_load) {
        if (must_load) {
          (*klassp) = resolve_or_fail(symbol, true, CHECK_0); // load required class 加载类
        } else {
          (*klassp) = resolve_or_null(symbol,       CHECK_0); // load optional klass
        }
      }
      return ((*klassp) != NULL);
    }
    

    其方法的逻辑比较简单:

    1. 如果该类没有加载

      1. 如果该类所对应的init_opt(初始化选项) 小于SystemDictionary::Opt,则使用resolve_or_fail 方法加载,如果加载失败,则抛出异常
      2. 如果该类所对应的init_opt(初始化选项) 大于SystemDictionary::Opt,则使用resolve_or_null方法加载,如果加载失败则返回null
      3. 如果该类所对应的init_opt(初始化选项) 等于SystemDictionary::Opt_Kernel,则不进行加载
    2. 如果该类已加载,则直接返回所对应的klassOop就可以了

    其中,不管resolve_or_fail,还是resolve_or_null方法,都最终会调用SystemDictionary::resolve_or_null(symbolHandle class_name, Handle class_loader, Handle protection_domain, Thread* the_thread) 方法.

    其代码如下:

    klassOop SystemDictionary::resolve_or_null(symbolHandle class_name, Handle class_loader, Handle protection_domain, TRAPS) {
      assert(!THREAD->is_Compiler_thread(), "Can not load classes with the Compiler thread");
      if (FieldType::is_array(class_name())) {
    	  // 1. 如果是数组的话
        return resolve_array_class_or_null(class_name, class_loader, protection_domain, CHECK_NULL);
      } else {
    	  // 2. 如果是普通类的话
        return resolve_instance_class_or_null(class_name, class_loader, protection_domain, CHECK_NULL);
      }
    }
    

    其中,解析数组最终会调用resolve_instance_class_or_null方法.

    klassOop SystemDictionary::resolve_array_class_or_null(symbolHandle class_name,
                                                           Handle class_loader,
                                                           Handle protection_domain,
                                                           TRAPS) {
      assert(FieldType::is_array(class_name()), "must be array");
      jint dimension;
      symbolOop object_key;
      klassOop k = NULL;
      // dimension and object_key are assigned as a side-effect of this call
      BasicType t = FieldType::get_array_info(class_name(),
                                              &dimension,
                                              &object_key,
                                              CHECK_NULL);
    
      if (t == T_OBJECT) {
        symbolHandle h_key(THREAD, object_key);
        // naked oop "k" is OK here -- we assign back into it
        k = SystemDictionary::resolve_instance_class_or_null(h_key,
                                                             class_loader,
                                                             protection_domain,
                                                             CHECK_NULL);
        if (k != NULL) {
          k = Klass::cast(k)->array_klass(dimension, CHECK_NULL);
        }
      } else {
        k = Universe::typeArrayKlassObj(t);
        k = typeArrayKlass::cast(k)->array_klass(dimension, CHECK_NULL);
      }
      return k;
    }
    

    这里思考一下:

    java中的数组可以按照如下的标准来划分:

    一种是基本类型所对应的数组,另一种就是对象所对应的数组

    其中: 基本类型所对应的数组,在SystemDictionary::initialize方法调用之前就已创建,区别的地方在于其维度不同罢了.这点在之前的文章有介绍,类加载流程004

    对于另一种对象所对应的数组,只需加载其数组类型所对应的类,然后将其转成数组类即可.

    因此,我们只需要看resolve_instance_class_or_null 即可.其代码如下:

    klassOop SystemDictionary::resolve_instance_class_or_null(symbolHandle class_name, Handle class_loader, Handle protection_domain, TRAPS) {
      assert(class_name.not_null() && !FieldType::is_array(class_name()), "invalid class name");
      // First check to see if we should remove wrapping L and ;
      // 1. 如果类名是object的话,则跳过处理L和;
      symbolHandle name;
      if (FieldType::is_obj(class_name())) {
        ResourceMark rm(THREAD);
        // Ignore wrapping L and ;.
        name = oopFactory::new_symbol_handle(class_name()->as_C_string() + 1, class_name()->utf8_length() - 2, CHECK_NULL);
      } else {
        name = class_name;
      }
    
      // UseNewReflection
      // Fix for 4474172; see evaluation for more details
      class_loader = Handle(THREAD, java_lang_ClassLoader::non_reflection_class_loader(class_loader()));
    
      // Do lookup to see if class already exist and the protection domain
      // has the right access
      // 2. 计算获得hash
      unsigned int d_hash = dictionary()->compute_hash(name, class_loader);
      int d_index = dictionary()->hash_to_index(d_hash);
      // 3. 根据hash和index 查到对应的klassOop
      klassOop probe = dictionary()->find(d_index, d_hash, name, class_loader,
                                          protection_domain, THREAD);
      // 4. 如果直接找到的话,则返回
      if (probe != NULL) return probe;
    
    
      // Non-bootstrap class loaders will call out to class loader and
      // define via jvm/jni_DefineClass which will acquire the
      // class loader object lock to protect against multiple threads
      // defining the class in parallel by accident.
      // This lock must be acquired here so the waiter will find
      // any successful result in the SystemDictionary and not attempt
      // the define
      // ParallelCapable Classloaders and the bootstrap classloader,
      // or all classloaders with UnsyncloadClass do not acquire lock here
      // 非引导类加载器将调用类加载器,并通过jvm/jni定义类进行定义,该类将获取类加载器对象锁,以防止多个线程意外地并行定义类。
      // 必须在此处获取此锁,这样等待的线程才能在SystemDictionary中找到任何成功的结果,并且不会尝试定义.
      // 可并行的类加载器和引导类加载器,或者所有具有非同步加载类的类加载器都不会在此处获取锁。
      bool DoObjectLock = true;
      if (is_parallelCapable(class_loader)) {
        DoObjectLock = false;
      }
    
      // 5. 计算 placeholders 中的hash和index
      unsigned int p_hash = placeholders()->compute_hash(name, class_loader);
      int p_index = placeholders()->hash_to_index(p_hash);
    
      // Class is not in SystemDictionary so we have to do loading.
      // Make sure we are synchronized on the class loader before we proceed
      // 在SystemDictionary中类不存在,因此我们需要进行加载
      // 在我们处理之前,我们必须进行同步
      Handle lockObject = compute_loader_lock_object(class_loader, THREAD); // 如果是null的话,则获得的是_system_loader_lock_obj
      check_loader_lock_contention(lockObject, THREAD);
      ObjectLocker ol(lockObject, THREAD, DoObjectLock);
    
      // Check again (after locking) if class already exist in SystemDictionary
      // 6.在获得锁后再次进行检查该类是否存在
      bool class_has_been_loaded   = false;
      bool super_load_in_progress  = false;
      bool havesupername = false;
      instanceKlassHandle k;
      PlaceholderEntry* placeholder;
      symbolHandle superclassname;
    
      {
        MutexLocker mu(SystemDictionary_lock, THREAD);
        // 6.1  查找该类是否已经在Dictionary中
        klassOop check = find_class(d_index, d_hash, name, class_loader);
        if (check != NULL) {
          // Klass is already loaded, so just return it 如果已经存在的话,则直接返回
          class_has_been_loaded = true;
          k = instanceKlassHandle(THREAD, check);
        } else {
          // 如果在Dictionary中不存在,但是正在加载父类的过程中
          placeholder = placeholders()->get_entry(p_index, p_hash, name, class_loader);
          if (placeholder && placeholder->super_load_in_progress()) {
             super_load_in_progress = true;
             if (placeholder->havesupername() == true) {
               superclassname = symbolHandle(THREAD, placeholder->supername());
               havesupername = true;
             }
          }
        }
      }
    
      // If the class in is in the placeholder table, class loading is in progress
      // 7. 如果在Dictionary中不存在,但是正在加载父类的过程中
      if (super_load_in_progress && havesupername==true) {
    	  // 7.1  处理并行加载
        k = SystemDictionary::handle_parallel_super_load(name, superclassname,
            class_loader, protection_domain, lockObject, THREAD);
        // 7.2  如果有错误发生,则返回null,否则则代表类已加载成功
        if (HAS_PENDING_EXCEPTION) {
          return NULL;
        }
        // 7.3 如果k 非空,则设置class_has_been_loaded为true,意味着类已加载成功
        if (!k.is_null()) {
          class_has_been_loaded = true;
        }
      }
    
      if (!class_has_been_loaded) {
    
        // add placeholder entry to record loading instance class
    	 // 添加placeholder 来记录加载中的instance class
        // Five cases:
        // All cases need to prevent modifying bootclasssearchpath
        // in parallel with a classload of same classname
        // Redefineclasses uses existence of the placeholder for the duration
        // of the class load to prevent concurrent redefinition of not completely
        // defined classes.
    	// 所有情况都需要防止与同名的类加载同时修改bootclasssearchpath redefineclasses在类加载期间使用占位符的存在来防止未完全定义的类的并发重定义。
        // case 1. traditional classloaders that rely on the classloader object lock
        //   - no other need for LOAD_INSTANCE 依赖类加载器对象锁的传统类加载器-不需要加载实例
        // case 2. traditional classloaders that break the classloader object lock
        //    as a deadlock workaround. Detection of this case requires that
        //    this check is done while holding the classloader object lock,
        //    and that lock is still held when calling classloader's loadClass.
        //    For these classloaders, we ensure that the first requestor
        //    completes the load and other requestors wait for completion.
    	//    传统的类加载器将类加载器对象锁作为死锁解决方案来断开。检测这种情况需要在保持类加载器对象锁的同时执行此检查,
    	 // 并且在调用类加载器的LoadClass时仍然保持该锁。对于这些类加载器,我们确保第一个请求者完成加载,其他请求者等待完成。
        // case 3. UnsyncloadClass - don't use objectLocker
        //    With this flag, we allow parallel classloading of a
        //    class/classloader pair unsyncloadclass-不要将objectlocker与此标志一起使用,我们允许类/类加载器对的并行类加载。
        // case4. Bootstrap classloader - don't own objectLocker
        //    This classloader supports parallelism at the classloader level,
        //    but only allows a single load of a class/classloader pair.
        //    No performance benefit and no deadlock issues.
    	// 引导类加载器-不拥有ObjectLocker这个类加载器支持类加载器级别的并行性,但只允许类/类加载器对的单个加载。没有性能优势,也没有死锁问题。
        // case 5. parallelCapable user level classloaders - without objectLocker
        //    Allow parallel classloading of a class/classloader pair 可并行的用户级类加载器-不带ObjectLocker,允许类/类加载器对的并行类加载
        symbolHandle nullsymbolHandle;
        bool throw_circularity_error = false;
        {
          MutexLocker mu(SystemDictionary_lock, THREAD);
          if (class_loader.is_null() || !is_parallelCapable(class_loader)) {
        	  // 从placeholders 中 查找对应的PlaceholderEntry
            PlaceholderEntry* oldprobe = placeholders()->get_entry(p_index, p_hash, name, class_loader);
            if (oldprobe) {
              // only need check_seen_thread once, not on each loop 只需要检查check_seen_thread一次,不需要在循环中每次都检查
              // 6341374 java/lang/Instrument with -Xcomp
              if (oldprobe->check_seen_thread(THREAD, PlaceholderTable::LOAD_INSTANCE)) {
                throw_circularity_error = true;
              } else {
                // case 1: traditional: should never see load_in_progress. 案例1: 经典情况:永远不可能看见load_in_progress
                while (!class_has_been_loaded && oldprobe && oldprobe->instance_load_in_progress()) {
    
                  // case 4: bootstrap classloader: prevent futile classloading,
                  // wait on first requestor
                  // 防止无效的类加载,等待bootstrap classloader 加载完毕
                  if (class_loader.is_null()) {
                    SystemDictionary_lock->wait();
                  } else {
                  // case 2: traditional with broken classloader lock. wait on first
                  // requestor.
                	//  传统的类加载器将类加载器对象锁作为死锁解决方案来断开。检测这种情况需要在保持类加载器对象锁的同时执行此检查,
                   // 并且在调用类加载器的LoadClass时仍然保持该锁。对于这些类加载器,我们确保第一个请求者完成加载,其他请求者等待完成。
                    double_lock_wait(lockObject, THREAD);
                  }
                  // Check if classloading completed while we were waiting
                  // 检查类是否加载完毕
                  klassOop check = find_class(d_index, d_hash, name, class_loader);
                  if (check != NULL) {
                    // Klass is already loaded, so just return it
                	 // 类已经加载完毕,所以直接返回
                    k = instanceKlassHandle(THREAD, check);
                    class_has_been_loaded = true;
                  }
                  // check if other thread failed to load and cleaned up
                  // 检查是否有其他线程加载失败并且进行清除
                  oldprobe = placeholders()->get_entry(p_index, p_hash, name, class_loader);
                }
              }
            }
          }
          // All cases: add LOAD_INSTANCE
          // case 3: UnsyncloadClass || case 5: parallelCapable: allow competing threads to try
          // LOAD_INSTANCE in parallel
          // add placeholder entry even if error - callers will remove on error
          if (!throw_circularity_error && !class_has_been_loaded) {
        	 // 添加到placeholders中
            PlaceholderEntry* newprobe = placeholders()->find_and_add(p_index, p_hash, name, class_loader, PlaceholderTable::LOAD_INSTANCE, nullsymbolHandle, THREAD);
            // For class loaders that do not acquire the classloader object lock,
            // if they did not catch another thread holding LOAD_INSTANCE,
            // need a check analogous to the acquire ObjectLocker/find_class
            // i.e. now that we hold the LOAD_INSTANCE token on loading this class/CL
            // one final check if the load has already completed
            // class loaders holding the ObjectLock shouldn't find the class here
            // 对于不获取类加载器对象锁的类加载器,
            // 如果他们没有捕捉到另一个持有LOAD_INSTANCE的线程,就需要一个类似于acquire ObjectLocker/find_class的检查,
            // 也就是说,既然我们在装载这个类/cl时持有LOAD_INSTANCE令牌,那么最后检查一下,如果装载已经完成,那么持有objectlock的类装入器不应该在这里找到该类。
            klassOop check = find_class(d_index, d_hash, name, class_loader);
            if (check != NULL) {
            // Klass is already loaded, so just return it 类加载结束,直接返回
              k = instanceKlassHandle(THREAD, check);
              class_has_been_loaded = true;
              newprobe->remove_seen_thread(THREAD, PlaceholderTable::LOAD_INSTANCE);
              placeholders()->find_and_remove(p_index, p_hash, name, class_loader, THREAD);
              SystemDictionary_lock->notify_all();
            }
          }
        }
        // must throw error outside of owning lock
        // 必须在释放锁的地方抛出异常
        if (throw_circularity_error) {
          ResourceMark rm(THREAD);
          THROW_MSG_0(vmSymbols::java_lang_ClassCircularityError(), name->as_C_string());
        }
    
        if (!class_has_been_loaded) {
    
          // Do actual loading  进行真正的加载
          k = load_instance_class(name, class_loader, THREAD);
    
          // For UnsyncloadClass only
          // If they got a linkageError, check if a parallel class load succeeded.
          // If it did, then for bytecode resolution the specification requires
          // that we return the same result we did for the other thread, i.e. the
          // successfully loaded instanceKlass
          // Should not get here for classloaders that support parallelism
          // with the new cleaner mechanism, even with AllowParallelDefineClass
          // Bootstrap goes through here to allow for an extra guarantee check
          // 对于非异步加载类的情况
          // 如果获得的异常时linkageError,则检查是否存在并行加载成功的情况
          // 如果是这样,那么对于字节码解析,规范要求我们返回与其他线程相同的结果
          if (UnsyncloadClass || (class_loader.is_null())) {
            if (k.is_null() && HAS_PENDING_EXCEPTION
              && PENDING_EXCEPTION->is_a(SystemDictionary::LinkageError_klass())) {
              MutexLocker mu(SystemDictionary_lock, THREAD);
              klassOop check = find_class(d_index, d_hash, name, class_loader);
              if (check != NULL) {
                // Klass is already loaded, so just use it
                k = instanceKlassHandle(THREAD, check);
                CLEAR_PENDING_EXCEPTION;
                guarantee((!class_loader.is_null()), "dup definition for bootstrap loader?");
              }
            }
          }
    
          // clean up placeholder entries for success or error
          // This cleans up LOAD_INSTANCE entries
          // It also cleans up LOAD_SUPER entries on errors from
          // calling load_instance_class
          // 不管是成功还是失败,都清理placeholder 中的entry.与此同时,还清除调用load_instance_class 加载父类失败时的LOAD_SUPERentries
          {
            MutexLocker mu(SystemDictionary_lock, THREAD);
            PlaceholderEntry* probe = placeholders()->get_entry(p_index, p_hash, name, class_loader);
            if (probe != NULL) {
              probe->remove_seen_thread(THREAD, PlaceholderTable::LOAD_INSTANCE);
              placeholders()->find_and_remove(p_index, p_hash, name, class_loader, THREAD);
              SystemDictionary_lock->notify_all();
            }
          }
    
          // If everything was OK (no exceptions, no null return value), and
          // class_loader is NOT the defining loader, do a little more bookkeeping.
          // 如果所有都是正常的(没有异常,没有返回null),同时class_loader 不是该类所对应的class_loader,则需要进行其他操作
          if (!HAS_PENDING_EXCEPTION && !k.is_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);
              }
              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());
              }
            }
          }
          // 有异常发生
          if (HAS_PENDING_EXCEPTION || k.is_null()) {
            // On error, clean up placeholders
            {
              MutexLocker mu(SystemDictionary_lock, THREAD);
              placeholders()->find_and_remove(p_index, p_hash, name, class_loader, THREAD);
              SystemDictionary_lock->notify_all();
            }
            return NULL;
          }
        }
      }
    
    #ifdef ASSERT
      {
        Handle loader (THREAD, k->class_loader());
        MutexLocker mu(SystemDictionary_lock, THREAD);
        oop kk = find_class_or_placeholder(name, loader);
        assert(kk == k(), "should be present in dictionary");
      }
    #endif
    
      // return if the protection domain in NULL
      //  如果protection domain 为null则直接返回
      if (protection_domain() == NULL) return k();
    
      // Check the protection domain has the right access
      // 检查protection domain 是否有正确的权限
      {
        MutexLocker mu(SystemDictionary_lock, THREAD);
        // Note that we have an entry, and entries can be deleted only during GC,
        // so we cannot allow GC to occur while we're holding this entry.
        // We're using a No_Safepoint_Verifier to catch any place where we
        // might potentially do a GC at all.
        // SystemDictionary::do_unloading() asserts that classes are only
        // unloaded at a safepoint.
        // 请注意,我们有一个条目,并且只能在GC期间删除条目,因此我们不能允许在保存此条目时发生GC。
        // 我们正在使用无安全点验证器来捕获任何可能进行GC的地方。SystemDictionary::do_Unloading()断言类仅在安全点上被卸载。
        No_Safepoint_Verifier nosafepoint;
        if (dictionary()->is_valid_protection_domain(d_index, d_hash, name,
                                                     class_loader,
                                                     protection_domain)) {
          return k();
        }
      }
    
      // Verify protection domain. If it fails an exception is thrown
      // 验证protection domain.如果抛出一个异常
      validate_protection_domain(k, class_loader, protection_domain, CHECK_(klassOop(NULL)));
    
      return k();
    }
    

    其中最重要的方法就是:load_instance_class,在该方法中,区分类加载器是否为空的情况,但是在当前情况下,进行预加载的情况下,class_lodar 是为空的.这是在SystemDictionary::resolve_or_fail(symbolHandle class_name,bool throw_error, TRAPS) 方法中一路传过来.代码如下:

    klassOop SystemDictionary::resolve_or_fail(symbolHandle class_name,
                                               bool throw_error, TRAPS)
    {
      return resolve_or_fail(class_name, Handle(), Handle(), throw_error, THREAD);
    }
    

    因此在load_instance_class方法中最终执行的代码如下:

    if (class_loader.is_null()) {
    
        // Search the shared system dictionary for classes preloaded into the
        // shared spaces.
    	// 1. 在共享系统字典中搜索预加载到共享空间中的类。
        instanceKlassHandle k;
        {
          PerfTraceTime vmtimer(ClassLoader::perf_shared_classload_time());
          k = load_shared_class(class_name, class_loader, THREAD);
        }
    
        if (k.is_null()) {
          // Use VM class loader 2. 使用vm 类加载器
          PerfTraceTime vmtimer(ClassLoader::perf_sys_classload_time());
          k = ClassLoader::load_classfile(class_name, CHECK_(nh));
        }
        
    }   
    

    又因为默认情况下,是不使用共享空间,因此最终会使用ClassLoader::load_classfile(class_name, CHECK_(nh));进行加载。代码如下:

    instanceKlassHandle ClassLoader::load_classfile(symbolHandle h_name, TRAPS) {
      ResourceMark rm(THREAD);
      EventMark m("loading class " INTPTR_FORMAT, (address)h_name());
      ThreadProfilerMark tpm(ThreadProfilerMark::classLoaderRegion);
    
      stringStream st;
      // st.print() uses too much stack space while handling a StackOverflowError
      // st.print("%s.class", h_name->as_utf8());
      // 1.获得对应的文件名
      st.print_raw(h_name->as_utf8());
      st.print_raw(".class");
      char* name = st.as_string();
    
      // Lookup stream for parsing .class file
      // 2. 加载对应的文件流
      ClassFileStream* stream = NULL;
      int classpath_index = 0;
      {
        PerfClassTraceTime vmtimer(perf_sys_class_lookup_time(),
                                   ((JavaThread*) THREAD)->get_thread_stat()->perf_timers_addr(),
                                   PerfClassTraceTime::CLASS_LOAD);
        ClassPathEntry* e = _first_entry;
        while (e != NULL) {
          stream = e->open_stream(name);
          if (stream != NULL) {
            break;
          }
          e = e->next();
          ++classpath_index;
        }
      }
    
      instanceKlassHandle h(THREAD, klassOop(NULL));
      if (stream != NULL) {
    
        // class file found, parse it
    	// 3. 解析类文件
        ClassFileParser parser(stream);
        Handle class_loader;
        Handle protection_domain;
        symbolHandle parsed_name;
        instanceKlassHandle result = parser.parseClassFile(h_name,
                                                           class_loader,
                                                           protection_domain,
                                                           parsed_name,
                                                           false,
                                                           CHECK_(h));
    
        // add to package table
        // 4. 添加到package table
        if (add_package(name, classpath_index, THREAD)) {
          h = result;
        }
      }
    
      return h;
    }
    

    其中第三步我们会在后续的文章中进行讲解,此处我们先来看下第4步.

    bool ClassLoader::add_package(const char *pkgname, int classpath_index, TRAPS) {
      assert(pkgname != NULL, "just checking");
      // Bootstrap loader no longer holds system loader lock obj serializing
      // load_instance_class and thereby add_package
      {
        MutexLocker ml(PackageTable_lock, THREAD);
        // First check for previously loaded entry 1.1 根据包名找到所对应的PackageInfo
        PackageInfo* pp = lookup_package(pkgname);
        if (pp != NULL) {
          // Existing entry found, check source of package
          // 1.2 如果找到的话,则设置classpath_index
          pp->set_index(classpath_index);
          return true;
        }
    
        // 2. 如果没有找的的话
        // strrchr 函数功能:查找一个字符c在另一个字符串str中末次出现的位置(也就是从str的右侧开始查找字符c首次出现的位置),并返回这个位置的地址。如果未能找到指定字符,那么函数将返回NULL。使用这个
        // 地址返回从最后一个字符c到str末尾的字符
        const char *cp = strrchr(pkgname, '/'); // 2.1 获得包名,如此时为java/lang/Class.class ,则cp 为java/lang
        if (cp != NULL) {
          // Package prefix found 2.2 计算包名的长度
          int n = cp - pkgname + 1;
    
          // 2.3 在c堆中分配数组,如果失败,则返回false
          char* new_pkgname = NEW_C_HEAP_ARRAY(char, n + 1);
          if (new_pkgname == NULL) {
            return false;
          }
    
          // 2.4 添加到_package_hash_table 中
          memcpy(new_pkgname, pkgname, n);
          new_pkgname[n] = '\0';
          pp = _package_hash_table->new_entry(new_pkgname, n);
          pp->set_index(classpath_index);
    
          // Insert into hash table
          _package_hash_table->add_entry(pp);
        }
        return true;
      }
    }
    
    展开全文
  • java类加载流程

    千次阅读 2020-02-23 05:01:49
    类加载流程 演示代码: public class Demo01 { public static void main(String[] args) { Demo demo = new Demo(); //3、创建对象 demo.test01(); //18、调用test01方法 } } class Demo{ ...

    类加载流程

    演示代码:

    public class Demo01 {
        public static void main(String[] args) {
            Demo demo = new Demo();
            //3、创建对象
            demo.test01();
            //18、调用test01方法
        }
    }
    
    class Demo{
        static int a;
        //1、a=0
        int b;
        //4、demo.b=0
        int num2 = test01();
        //5、调用test01方法
        //9、demo.num2=1
        static{
            System.out.println("1"+a);
            //2、打印10
        }
        {
            System.out.println("2"+a);
            //10、打印21
            System.out.println("2"+b);
            //11、打印21
        }
        public Demo(){
            System.out.println("3"+a);
            //12、打印31
            System.out.println("3"+b);
            //13、打印31
            test01();
            //14、调用test01方法
        }
    
        public int test01(){
            System.out.println("4"+(a++));
            //6、打印40,a=1
            //15、打印41,a=2
            //19、打印42,a=3
            System.out.println("4"+(b++));
            //7、打印40,b=1
            //16、打印41,b=2
            //20、打印42,b=3
            return a;
            //8、返回1
            //17、返回2
            //21、返回3
        }
    }
    

    结果:在这里插入图片描述
    1、将所有static加载的属性,代码块分配内存

    2、依次对属性赋值,然后执行静态代码块

    3、创建对象也是同理

    4、先给成员变量划分空间,然后依次赋值

    5、由于静态部分已经随着类的加载而全部加载进内存,所以不再执行。执行构造代码块

    6、再执行构造函数

    据说是华为n年前的题目

    public class Demo02 {
        //1、将所有static修饰的属性,代码块和方法加载进内存
        //2、对static修饰的属性进行初始化
        public static int k = 0;
        //3、k=0
        public static Demo02 t1 = new Demo02("t1");
        //4、创建一个Demo02对象为t1赋值,static修饰的属性和代码块已经加载进内存,不再执行
        public static Demo02 t2 = new Demo02("t2");
        //17、与t1同理,结果为:4:j i=3 n=3;5:构造块 i=4 n=4;6:t2 i=5 n=5
        //t2.a=0 t2.j=3
        public static int i = print("i");
        //18、调用print方法,为i赋值,此时i=6
        //22、i=7
        public static int n = 99;
        //23、n=99
        private int a = 0;
        //5、k=0 t1.a=0
        public int j = print("j");
        //6、调用print(String str)为j赋值
        //t1.j=1
        {
            print("构造快");
            //10、调用print()函数
        }
        static{
            print("静态块");
            //24、调用print方法
        }
        public Demo02(String str){
            System.out.println((++k)+":"+str+"  i="+i+"  n="+n);
            //14、k=3,i=2,n=2; 3:t1 i=2 n=2
            ++i;
            //15、i=3
            ++n;
            //16、n=3
        }
        public static int print(String str){
            System.out.println((++k)+":"+str+" i="+i+" n="+n);
            //7、k=1;i,n还没赋值,默认值为0; 1:j i=0 n=0
            //11、k=2;i=1,n=1; 2:构造块 i=1 n=1
            //19、k=7;i=6,n=6;7:j i=6 n=6
            //25、k=8;i=7,n=99;8:静态块 i=7 n=99
            ++n;
            //8、n=1
            //12、n=2
            //20、n=7
            //26、n=100
            return ++i;
            //9、i=1,返回1
            //13、i=2,返回2
            //21、i=7,返回7
            //27、i=8,返回8
        }
        public static void main(String[] args) {
            //28、结束
            //i=8 n=100
        }
    }
    

    结果:在这里插入图片描述

    代码执行按顺序看代码注释

    1、这个面试题的难点在于static成员变量的初始化创建了一个对象,需要理解类的加载机制。


    类的加载机制

    类的加载机制主要分为三个部分:加载,链接,初始化。链接又可以细分为验证,准备和解析。

    加载

    指把class字节码文件从各个来源通过类加载器装载进内存。

    验证

    为了保证加载进来的字节流符合虚拟机规范,不会造成安全错误。(这个我也不懂原理,只知道有这么一回事)

    准备

    (面试题考点)主要为静态变量(注意不是实例变量)分配内存和赋予初值。初值,不是代码中具体写的初始化的值,而是Java虚拟机根据不同变量类型的默认初始值。如基本类型的初值就是0,引用类型初值为null,常量初值为代码中设置的值.

    解释

    将常量池内的符号引用替换为直接引用。(这个我也不懂原理,只知道有这么一回事)

    初始化

    (面试题考点)这个阶段对static修饰的变量、代码块进行初始化。如果初始化一个类的时候,其父类还没有初始化,就先对父类初始化。如果含有多个静态变量和静态代码块,就按顺序从上到下执行。


    拓展:

    内存主要分为四个区:

    栈:栈中保存基本数据类型的变量和自定义的对象的引用(不是对象)**,**对象本身都存放在堆区中,被执行的方法的也是pull到栈中,当方法执行完后再push出栈。

    堆:所有使用new创建的对象本身都存放在堆中

    数据区:存放类中的方法

    代码区:static变量和String常量

    如果错漏,欢迎指正。

    展开全文
  • 类加载流程003

    万次阅读 2019-05-16 14:51:44
    上篇文章讲解了klassKlass的创建过程,本文将Universe::genesis(TRAPS)方法中创建的klass已图的方式进行展示.注意,其创建的方式是一致的,不同的地方... arrayKlassKlass是所有数组类类的抽象基类 objArrayKlassKlass...

    上篇文章讲解了klassKlass的创建过程,本文将Universe::genesis(TRAPS)方法中创建的klass已图的方式进行展示.注意,其创建的方式是一致的,不同的地方是大小不同等.以下先列举一下在该方法中创建的klass.

    klass名作用
    klassKlassklass链路的末端
    arrayKlassKlass是所有数组类类的抽象基类
    objArrayKlassKlass是所有objArrayKlass所对应的klass
    instanceKlassKlass是instanceKlass所对应的klass
    typeArrayKlassKlass是typeArrayKlass所对应的klass
    symbolKlass是symbolOop所对应的klass
    typeArrayKlass创建了BOOLEAN,CHAR,Float,DOUBLE,BYTE,SHORT,INT,LONG所对应的数组klass
    methodKlass描述java类的方法
    constMethodKlass描述java类方法所对应的字节码指令信息的固有属性
    methodDataKlass是methodDataOop所对应的klass
    constantPoolKlass描述java字节码文件中的常量池的数据结构
    constantPoolCacheKlass constantPool 缓存所对应的klass
    compiledICHolderKlasscompiledICHolderOop 所对应的klass

    解析

    arrayKlassKlass

    在这里插入图片描述

    objArrayKlassKlass

    objArrayKlassKlass

    instanceKlassKlass

    instanceKlassKlass

    typeArrayKlassKlass

    typeArrayKlassKlass

    symbolKlass

    symbolKlass

    typeArrayKlass

    typeArrayKlass

    methodKlass

    methodKlass

    constMethodKlass

    constMethodKlass

    methodDataKlass

    methodDataKlass

    constantPoolKlass

    constantPoolKlass

    constantPoolCacheKlass

    constantPoolCacheKlass

    compiledICHolderKlass

    compiledICHolderKlass

    总结

    可以发现,***Klass在创建的时候总是会申请oopDesc::header_size() + sizeof(***Klass)/HeapWordSize 大小的内存空间.并将_metadata 指向klassKlass.

    展开全文
  • 二、类加载流程 【红色虚线表示】:应用需要到某个类时,则会按照下面的顺序进行类加载。 当tomcat启动时,会创建几种类加载器: 1 Bootstrap 引导类加载器 加载JVM启动所需的类,以及标准扩展类(位于jre/lib/...

    一、Tomcat结构

    模块组成结构:Tomcat 的核心组件就 Connector 和 Container,一个Connector+一个Container(Engine)构成一个Service,Service就是对外提供服务的组件,有了Service组件Tomcat就能对外提供服务了,但是光有服务还不行,还需要有环境让你提供服务才行,所以最外层的Server就是为Service提供了生存的土壤。

    在这里插入图片描述
    Connector是一个连接器,主要负责接受请求并把请求交给Container,Container就是一个容器,主要装的是具有处理请求的组件。Service主要是为了关联 Container与 Connect,只有两个结合起来才能够处理一个请求。Server负责管理 Service集合,从图中我们可以看到Tomcat可以提供多种服务,那么这些Service就是由Server来管理的。具体工作包括:对外提供一个接口访问Service,对内维护 Service集合,维护 Service集合包括管理 Service声明周期等等。

    二、类加载器流程

    https://images0.cnblogs.com/blog2015/449064/201506/141304597074685.jpg
    【红色虚线表示】:应用需要到某个类时,则会按照下面的顺序进行类加载。

    当tomcat启动时,会创建几种类加载器:

    1 Bootstrap 引导类加载器

    加载JVM启动所需的类,以及标准扩展类(位于jre/lib/ext下)

    2 System 系统类加载器

    加载tomcat启动的类,比如bootstrap.jar,通常在catalina.bat或者catalina.sh中指定。位于CATALINA_HOME/bin下。
      在这里插入图片描述
      3 Common 通用类加载器

    加载tomcat使用以及应用通用的一些类,位于CATALINA_HOME/lib下,比如servlet-api.jar
      在这里插入图片描述
    4 webapp 应用类加载器

    每个应用在部署后,都会创建一个唯一的类加载器。该类加载器会加载位于 WEB-INF/lib下的jar文件中的class 和 WEB-INF/classes下的class文件。

    当应用需要到某个类时,则会按照下面的顺序进行类加载:

    1 使用bootstrap引导类加载器加载

    2 使用system系统类加载器加载

    3 使用应用类加载器在WEB-INF/classes中加载

    4 使用应用类加载器在WEB-INF/lib中加载

    5 使用common类加载器在CATALINA_HOME/lib中加载

    问题扩展

    通过对上面tomcat类加载机制的理解,就不难明白 为什么java文件放在Eclipse中的src文件夹下会优先jar包中的class?

    这是因为Eclipse中的src文件夹中的文件java以及webContent中的JSP都会在tomcat启动时,被编译成class文件放在 WEB-INF/class 中。

    而Eclipse外部引用的jar包,则相当于放在 WEB-INF/lib 中。

    因此肯定是 java文件或者JSP文件编译出的class优先加载。

    通过这样,我们就可以简单的把java文件放置在src文件夹中,通过对该java文件的修改以及调试,便于学习拥有源码java文件、却没有打包成xxx-source的jar包。

    另外呢,开发者也会因为粗心而犯下面的错误。

    在 CATALINA_HOME/lib 以及 WEB-INF/lib 中放置了 不同版本的jar包,此时就会导致某些情况下报加载不到类的错误。

    还有如果多个应用使用同一jar包文件,当放置了多份,就可能导致 多个应用间 出现类加载不到的错误。

    线程模型:支持以下四种线程模型。

    在这里插入图片描述
    【主要介绍,NIO线程模型】:Poller组件在非阻塞的模式下轮询多个客户端连接,不断检测,处理各种事件。例如检测各个连接是否可读,对于可读的客户端连接尝试进行读取和解析消息报文。

    在这里插入图片描述

    NioEndpoint 组件包含很多子组件:LimitLatch(连接数控制器)Acceptor(套接字接收器)Poller(轮询器)Poller池SocketProcessor(任务处理器)以及Executor(任务执行器)。
    ✘ LimitLatch(连接数控制器):负责对连接数的控制。初始化同步器的最大限制值,然后每接受一个套接字就将计数器变量+1,每关闭一个套接字,将计数器变量-1,如此一来,一旦技术变量值>最大限制值,则AQS机制将接受线程阻塞,而停止对套接字的接受。直到某些套接字处理完,关闭后重新唤起接受线程往下接受套接字。
    Tomcat默认同时接受客户端连接数为200,但可以通过server.xml中的节点的maxConnections属性进行调节,Tomcat BIO模式下的LimitLatch的限制数与线程池的最大线程数密切相关。他们之间的比例1:1。
    ✘ Acceptor(套接字接收器):负责接收套接字连接并注册到通道连接里面。(接收请求)
    ✘ Poller(轮询器):负责轮询检查事件列表。
    ✘ Poller池:包含了若干个轮询组件。
    ✘ SocketProcessor(任务处理器):任务定义器,将socket扔进线程池前需要定义好任务。主要任务有3个任务:处理套接字并响应客户端,连接数计数器减1,关闭套接字。套接字的处理包括对底层套接字字节流的处理,HTTP协议请求报文的报文的解析(请求头,请求体,请求行等信息的解析),根据请求行解析得到的路径去寻找相应虚拟主机上的Web项目资源,根据处理好的结果组装好HTTP协议响应报文输出到客户端。
    ✘ Executor(任务执行器):负责处理套接字的线程池。

    整体的流程图如下:

    在这里插入图片描述

    二、Tomcat 如何调优,涉及哪些参数

    【1】Tomcat调优主要从四个方面考虑:1)、吞吐量。2)、Responsetime。 3)、Cpuload。 4)、MemoryUsage。

    【2】参数调优:1)、Tomcat启动参数的优化:Tomcat 的启动参数位于tomcat的安装目录\bin目录下,如果你是Linux操作系统就是catalina.sh文件,如果你是Windows操作系统那么你需要改动的就是catalina.bat文件。

    ✔ Linux系统中catalina.sh文件中添加如下参数(重要参数随后说明):

    export JAVA_OPTS="-server -Xms1400M -Xmx1400M -Xss512k -XX:+AggressiveOpts -XX:+UseBiasedLocking
    -XX:PermSize=128M -XX:MaxPermSize=256M -XX:+DisableExplicitGC -XX:MaxTenuringThreshold=31
    -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:+CMSParallelRemarkEnabled
    -XX:+UseCMSCompactAtFullCollection -XX:LargePageSizeInBytes=128m -XX:+UseFastAccessorMethods
    -XX:+UseCMSInitiatingOccupancyOnly -Djava.awt.headless=true "

    ✔ Windowns系统中catalina.bat文件中添加如下参数(重要参数随后说明):

    set JAVA_OPTS=-server -Xms1400M -Xmx1400M -Xss512k -XX:+AggressiveOpts -XX:+UseBiasedLocking
    -XX:PermSize=128M -XX:MaxPermSize=256M -XX:+DisableExplicitGC -XX:MaxTenuringThreshold=31
    -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:+CMSParallelRemarkEnabled
    -XX:+UseCMSCompactAtFullCollection -XX:LargePageSizeInBytes=128m -XX:+UseFastAccessorMethods
    -XX:+UseCMSInitiatingOccupancyOnly -Djava.awt.headless=true

    ◀ -Server(重要):只要Tomcat是运行在生产环境中,这个参数必须添加。因为Tomcat默认是java-client模式运行,添加server后表示以真实的production的模式运行,将拥有更大、更高的并发处理能力,更快、更强的JVM垃圾回收机制,可以获得更多的负载和吞吐量等等。
    ◀ -Xms -Xmx:既JVM内存设置了,把Xms与Xmx两个值设成一样是最优的做法。(否则当内存=Xmx向Xms变化时,CPU高速运转触发垃圾回收机制,严重时会导致系统‘卡壳’,因此一开始我们就把这两个设成一样,使得Tomcat在启动时就为最大化参数充分利用系统的效率。)
    ※在设这个最大内存即Xmx值时请先打开一个命令行:能够正常显示JDK的版本信息,说明这个值能够用。

    ◀ -Xmn:设置年轻代大小为512m。整个堆大小=年轻代 + 老年代 + 持久代。持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。
    ◀ -Xss:是指设定每个线程的堆栈大小。这个就要依据程序,看一个线程大约需要占用多少内存,可能会有多少线程同时运行等。一般不易设置超过1M,要不然容易出现out ofmemory。

    **【3】Tomcat容器内优化:**打开 tomcat安装目录 \conf\server.xml文件。其中如下参数的默认值远远不够我们使用,我们对其进行了更改,更改后的配置如下:

    <Connector port="8080" protocol="HTTP/1.1"           
    URIEncoding="UTF-8"  minSpareThreads="25" maxSpareThreads="75"          
    enableLookups="false" disableUploadTimeout="true" connectionTimeout="20000" 
    acceptCount="300"  maxThreads="300" maxProcessors="1000" minProcessors="5"
    useURIValidationHack="false"    
    compression="on" compressionMinSize="2048"
    compressableMimeType="text/html,text/xml,text/javascript,text/css,text/plain" 
    redirectPort="8443" />
    

    ▶ URIEncoding=”UTF-8”:使得tomcat可以解析含有中文名的文件的url。
    ▶ minSpareThreads:最小备用线程数,tomcat启动时的初始化的线程数。
    ▶ maxSpareThreads:如果空闲状态的线程数多于设置的数目,则将这些线程中止,减少这个池中的线程总数。
    ▶ connectionTimeout:网络连接超时时间毫秒数。
    ▶ maxThreads:Tomcat使用线程来处理接收的每个请求。这个值表示Tomcat可创建的最大的线程数,即最大并发数。
    ▶ acceptCount:当线程数达到maxThreads后,后续请求会被放入一个等待队列,这个acceptCount是这个队列的大小,如果这个队列也满了,就直接refuse connection。
    ▶ maxProcessors与minProcessors:在 Java中线程是程序运行时的路径,是在一个程序中与其它控制线程无关的、能够独立运行的代码段。它们共享相同的地址空间。多线程帮助程序员写出CPU最大利用率的高效程序,使空闲时间保持最低,从而接受更多的请求。 通常Windows是1000个左右,Linux是2000个左右。
    ▶ useURIValidationHack:设成"false",可以减少它对一些url的不必要的检查从而减省开销。
    ▶ enableLookups:设置为"false",主要为了消除DNS查询对性能的影响我们可以关闭DNS查询,方式是修改server.xml文件中的enableLookups参数值
    ▶ disableUploadTimeout:允许Servlet容器,正在执行使用一个较长的连接超时值,以使Servlet有较长的时间来完成它的执行,默认值为false
    ▶ 给Tomcat配置gzip压缩(HTTP压缩)功能:HTTP 压缩可以大大提高浏览网站的速度,它的原理是,在客户端请求网页后,从服务器端将网页文件压缩,再下载到客户端,由客户端的浏览器负责解压缩并浏览。相对于普通的浏览过程HTML、CSS、Javascript、Text ,它可以节省40%左右的流量。更为重要的是,它可以对动态生成的,包括CGI、PHP、JSP、ASP、 Servlet、SHTML等输出的网页也能进行压缩,压缩效率惊人。

        1)、compression="on" 打开压缩功能
        2)、compressionMinSize="2048" 启用压缩的输出内容大小,这里面默认为2KB
        3)、noCompressionUserAgents="gozilla, traviata" 对于以下的浏览器,不启用压缩
        4)、compressableMimeType="text/html,text/xml" 压缩类型
      ▶ redirectPort: 如果我们走https协议的话,我们将会用到8443端口这个段的配置。
    
    展开全文
  • JVM类加载流程及双亲委派机制

    万次阅读 2021-10-12 16:57:41
    一、类加载流程 二、类加载器 三、源码分析 查看java.lang.ClassLoader的loadClass(java.lang.String, boolean)方法的源码: protected Class<?> loadClass(String name, boolean resolve) throws ...
  • 深入理解Java中类加载流程和机制

    千次阅读 2017-06-13 16:05:47
    首先让我们连接类加载器的种类和作用: 引导类加载器:(Bootstrap ClassLoader),使用c++编写的,将一些必须系统类(位于{JAVA_HOME/lib下的jar包})加入到内存的方法区中,由于该加载器是由c++实现的,java代码不能...
  • 类加载流程-01

    万次阅读 2019-03-01 16:05:11
    从本文开始讲述类加载过程,在这里先解释一下类加载的时机是什么? 此时可以分为2种情况: 主动引用 被动引用 类的主动引用指的是: new一个类的对象 调用类的静态成员(除了final常量)和静态方法 使用java.lang....
  • Android中的类加载机制

    千次阅读 2019-08-26 19:44:32
    在类加载进内存以后,Android程序是通过ClassLoader类去加载内存中的类,然后进行解析运行的,在插件化技术中,因为需要我们自己去加载插件,所以要...类加载流程 一个类被加载到虚拟机内存中需要经历几个过程:加载...
  • JVM类加载过程

    万次阅读 多人点赞 2019-06-20 15:10:25
    1. JVM类加载过程 1.概述 从类的生命周期而言,一个类包括如下阶段: 加载、验证、准备、初始化和卸载这5个阶段的顺序是确定的,类的加载过程必须按照这种顺序进行,而解析阶段则不一定,它在某些情况下...
  • java类加载过程

    万次阅读 2019-03-10 11:39:38
    类加载机制 JVM将类描述数据从.class文件中加载到内存,并对数据进行,解析和初始化,最终形成被JVM直接使用的Java类型。 类从被加载到JVM中开始,到卸载为止,整个生命周期包括:加载、验证、准备、解析、初始化、...
  • 类加载过程

    万次阅读 多人点赞 2018-08-06 12:30:04
    类的加载就是将class文件中的二进制数据读取到... 类加载的最终产物是堆内存中的Class对象,对于同一个ClassLoader来说,无论类被加载多少次,对应带堆内存中的对象始终是同一个,这里所说的加载是类加载过程中的第...
  • 简述类加载过程

    2020-02-29 14:01:53
    3. 类加载的总体流程3.1 加载过程3.2 验证过程3.3 准备过程3.4 解析过程3.5 初始化过程 < hr/> 1. 什么是类加载 1.1 类加载的五大步骤 2. 什么时候触发类加载? 3. 类加载的总体流程 3.1 加载过程 3.2 验证...
  • 【OSGi】OSGi类加载流程

    万次阅读 2013-10-19 21:29:37
    OSGi每个模块都有自己独立的classpath。如何实现这一点呢?是因为OSGi采取了不同的类加载机制: ... 为了让bundle能互相协作,可以基于依赖关系,从一个bundle类加载器委托到另一个bundle类加载器。
  • 一、什么是类加载机制?虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。二、类加载的时机类从被加载到...
  • 详细说明了tomcat启动过程中 加载资源的顺序
  • 【总结】类加载过程

    千次阅读 2020-07-27 18:48:47
    该文章为知识总结的文章,如果是初学者,建议先从专栏学习:JVM专栏 文章目录一、类的加载过程二、加载...类加载过程的第一步,主要完成下面3件事情: 通过全类名获取定义此类的二进制字节流 将字节流所代表的静态存.
  • 本章将详细讲解Spring中Bean的加载过程,相比解析而言,加载稍微复杂一点.。 Spring入口 public class Application { public static void main(String[] args) { ApplicationContext context = new ...
  • Java类加载的过程

    万次阅读 多人点赞 2018-09-14 19:09:22
    JVM和 当我们调用 Java 命令运行某个 Java 程序时,该命令将会启动一条 Java 虚拟机进程,不管该 Java 程序有多么复杂,该程序启动了多少个线程,它们都处于该 Java 虚拟机进程里。同一个 JVM 的所有线程、所有...
  • 加载过程

    千次阅读 2018-09-08 16:34:10
    类的主动引用和被动引用、类加载过程
  • 图解Tomcat类加载机制  说到本篇的tomcat类加载机制,不得不说翻译学习tomcat的初衷。  之前实习的时候学习javaMelody的源码,但是它是一个Maven的项目,与我们自己的web项目整合后无法直接断点调试。后来...
  • Java类加载过程图解

    万次阅读 2018-08-22 16:52:36
    为什么用.class的方式加载类和以Class.forName()的方式加载的结果不同呢,原因很简单,就是类加载过程的不同。 这就扯到基础理解上了,就是Java是如何加载一个类的呢? 上图是我绘制的整个Java类加载过程。 ...
  • springboot启动加载流程

    2021-03-06 09:05:27
    1、新建module,在主程序加入断点,启动springboot 2、首先进入SpringAplicationrun方法 3、run方法新建SpringApplication对象 4、SpringApplication对象的run方法,首先创建并启动计时监控 5、接着通过...
  • JVM 类加载机制深度剖析(JDK 1.8)

    千次阅读 2020-08-29 10:22:22
    整体的类加载流程如下图所示: 例如我想要加载一个Demo 类: 首先会先启动 JVM 虚拟机(JVM 虚拟机底层是由 C++ 来实现的,在 windows 下启动即为 java.exe 方法调用 jvm.dll 创建 JVM) 由 JVM 虚拟机创建 引导类...
  • Java中类加载过程和对象创建过程

    千次阅读 2015-11-21 21:47:26
    类加载过程: 1, JVM会先去方法区中找有没有相应类的.class存在。如果有,就直接使用;如果没有,则把相关类的.class加载到方法区 2, 在.class加载到方法区时,会分为两部分加载:先加载非静态内容,再加载静态...
  • 从Java到android:加载机制

    千次阅读 2017-01-05 15:15:36
    (3)应用程序类加载器(Application ClassLoader):该类加载器也成为系统类加载器,它负责加载用户类路径(Classpath)上所指定的类库,开发者可以直接使用该类加载器,如果应用程序中没有自定义过自己的类加载器...
  • 02 类加载子系统.pptx

    2020-06-15 07:19:45
    02 类加载子系统 PPT 介绍 JVM 的类加载子系统, 包括类加载阶段和流程、加载器分类和类加载机制等。
  • BootstrapClassLoader BootstrapClassLoader是顶级加载器,默认加载的是%JAVA_HOME%中lib下的jar包和class类文件,他也...ExtClassLoader扩展类加载器,负责加载%JAVA_HOME%中lib/ext文件下的jar包和class类文件,ExtClas

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 321,364
精华内容 128,545
关键字:

类加载流程