类加载时JVM在搞什么?JVM源码分析+OOP-KLASS模型分析

Jdk19 动态编译 Java 源码为 Class 文件(一) JDK 动态编译 Java 源码,生成 Class 类; 并将 Class 实例化,调用对象方法 阅读详情


前言

想要彻底学好JVM需要一定C语言基础,要理解头文件、指针、结构体、结构体指针、函数指针、类型别名等概念,本文尽量通俗易懂的介绍JNI,语雀地址:https://www.yuque.com/yangxiaofei-vquku/wmp1zm/dmz2gd

OOP-Klass模型

在撸源码之前首先科普下JVM的OOP-Klass模型:
JVM内部基于oop-klass模型描述一个java类,将一个java类分为两个部分进行描述,其中第一个模型是oop,第二个模型是klass,其中oop用来表示堆中的java对象实例,储存着对象实例的非静态成员变量属性,不包含任何方法;Klass用来表示java类的元数据,包含了java类中声明的方法存在于方法区;oop有klass的引用,如此多个oop实例就不用都保存一份相同的方法信息了。
Klass分类
在这里插入图片描述

OOP分类
在这里插入图片描述

类加载在JVM源码分析

废话不多说先撸一发源码,java中的类加载到jvm中的过程依靠类加载器来完成,Classloader有三个核心方法loadClass()、findClass()、definclass(),简单介绍一下各种作用
loadClass()

  • 查看这个Class是否已经别加载
  • 如果没有被加载,继续往下走,查看父类加载器,递归调用loadClass()
  • 如果父类加载器是null,说明是启动类加载器,查找对应的Class
  • 如果都没有找到,就调用findClass(String)
    findClass()
  • 根据名称或位置加载.class字节码,然后使用defineClass
    definclass()
  • 把字节码转化为Class
    由此可知Classloader加载类是最终起作用的是definclass方法,而definclass最终调用了Native方法definclass1()实现类的加载。
    definclass1()的实现在jdk-jdk8-b120-源码/jdk/src/share/native/java/lang/ClassLoader.c中的Java_java_lang_ClassLoader_defineClass1()函数
/**
 * 类加载native方法入口将字节码转化为Class
 */
JNIEXPORT jclass JNICALL
Java_java_lang_ClassLoader_defineClass1(JNIEnv *env,
                                        jobject loader,
                                        jstring name,
                                        jbyteArray data,
                                        jint offset,
                                        jint length,
                                        jobject pd,
                                        jstring source)
{
    jbyte *body;
    char *utfName;
    jclass result = 0;
    char buf[128];
    char* utfSource;
    char sourceBuf[1024];

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

    /* Work around 4153825. malloc crashes on Solaris when passed a
     * negative size.
     */
    if (length < 0) {
        JNU_ThrowArrayIndexOutOfBoundsException(env, 0);
        return 0;
    }

    body = (jbyte *)malloc(length);

    if (body == 0) {
        JNU_ThrowOutOfMemoryError(env, 0);
        return 0;
    }

    (*env)->GetByteArrayRegion(env, data, offset, length, body);

    if ((*env)->ExceptionOccurred(env))
        goto free_body;

    if (name != NULL) {
        utfName = getUTF(env, name, buf, sizeof(buf));
        if (utfName == NULL) {
            JNU_ThrowOutOfMemoryError(env, NULL);
            goto free_body;
        }
        VerifyFixClassname(utfName);
    } else {
        utfName = NULL;
    }

    if (source != NULL) {
        utfSource = getUTF(env, source, sourceBuf, sizeof(sourceBuf));
        if (utfSource == NULL) {
            JNU_ThrowOutOfMemoryError(env, NULL);
            goto free_utfName;
        }
    } else {
        utfSource = NULL;
    }
    // 核心方法调用jvm.cpp中的静态函数JVM_DefineClassWithSource
    result = JVM_DefineClassWithSource(env, utfName, loader, body, length, pd, utfSource);

    if (utfSource && utfSource != sourceBuf)
        free(utfSource);

 free_utfName:
    if (utfName && utfName != buf)
        free(utfName);

 free_body:
    free(body);
    return result;
}

调用了jdk-jdk8-b120-源码/hotspot/src/share/vm/prims/jvm.cpp中的JVM_DefineClassWithSource()函数

// JVM_DefineClassWithSource函数的定义
JVM_ENTRY(jclass, JVM_DefineClassWithSource(JNIEnv *env, const char *name, jobject loader, const jbyte *buf, jsize len, jobject pd, const char *source))
  JVMWrapper2("JVM_DefineClassWithSource %s", name);
    // 核心方法
  return jvm_define_class_common(env, name, loader, buf, len, pd, source, true, THREAD);
JVM_END

调用了jdk-jdk8-b120-源码/hotspot/src/share/vm/prims/jvm.cpp中的jvm_define_class_common()函数

static jclass jvm_define_class_common(JNIEnv *env, const char *name,
                                      jobject loader, const jbyte *buf,
                                      jsize len, jobject pd, const char *source,
                                      jboolean verify, TRAPS) {
  if (source == NULL)  source = "__JVM_DefineClass__";

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

  PerfClassTraceTime vmtimer(ClassLoader::perf_define_appclass_time(),
                             ClassLoader::perf_define_appclass_selftime(),
                             ClassLoader::perf_define_appclasses(),
                             jt->get_thread_stat()->perf_recursion_counts_addr(),
                             jt->get_thread_stat()->perf_timers_addr(),
                             PerfClassTraceTime::DEFINE_CLASS);

  if (UsePerfData) {
    ClassLoader::perf_app_classfile_bytes_read()->inc(len);
  }

  // Since exceptions can be thrown, class initialization can take place
  // if name is NULL no check for class name in .class stream has to be made.
  TempNewSymbol class_name = NULL;
  if (name != NULL) {
    const int str_len = (int)strlen(name);
    if (str_len > Symbol::max_length()) {
      // It's impossible to create this class;  the name cannot fit
      // into the constant pool.
      THROW_MSG_0(vmSymbols::java_lang_NoClassDefFoundError(), name);
    }
    class_name = SymbolTable::new_symbol(name, str_len, CHECK_NULL);
  }

  ResourceMark rm(THREAD);
  ClassFileStream st((u1*) buf, len, (char *)source);
  Handle class_loader (THREAD, JNIHandles::resolve(loader));
  if (UsePerfData) {
    is_lock_held_by_thread(class_loader,
                           ClassLoader::sync_JVMDefineClassLockFreeCounter(),
                           THREAD);
  }
  Handle protection_domain (THREAD, JNIHandles::resolve(pd));
  // 核心方法调用 SystemDictionary::resolve_from_stream
  Klass* k = SystemDictionary::resolve_from_stream(class_name, class_loader,
                                                     protection_domain, &st,
                                                     verify != 0,
                                                     CHECK_NULL);

  if (TraceClassResolution && k != NULL) {
    trace_class_resolution(k);
  }
    // 为class实例创建本地引用,防止class对象无GCROOT引用被gc掉
  return (jclass) JNIHandles::make_local(env, k->java_mirror());
}

调用JVM/jdk-jdk8-b120-源码/hotspot/src/share/vm/classfile/systemDictionary.cpp的resolve_from_stream函数获取Klass的指针,最后从Klass中获取java_mirror也就是堆里的class实例,java_mirror是Class类在堆中的oop实例。


Klass* SystemDictionary::resolve_from_stream(Symbol* class_name,
                                             Handle class_loader,
                                             Handle protection_domain,
                                             ClassFileStream* st,
                                             bool verify,
                                             TRAPS) {
 //...省略不重要代码

  instanceKlassHandle k = ClassFileParser(st).parseClassFile(class_name,
                                                             loader_data,
                                                             protection_domain,
//...省略不重要代码

  return k();
}

调用jdk-jdk8-b120-源码/hotspot/src/share/vm/classfile/classFileParser.cpp中的parseClassFile
函数,重头戏来了,这个方法贼鸡儿长,总结下完成的事情是:加载父类Klass、加载当前Klass、为当前Klass设置属性以及初始化默认属性值。

instanceKlassHandle ClassFileParser::parseClassFile(Symbol* name,
                                                    ClassLoaderData* loader_data,
                                                    Handle protection_domain,
                                                    KlassHandle host_klass,
                                                    GrowableArray<Handle>* cp_patches,
                                                    TempNewSymbol& parsed_name,
                                                    bool verify,
                                                    TRAPS) {

 	//...省略父类Klass逻辑

    // We can now create the basic Klass* for this klass
    // 获取加载.class文件保存在方法区的元数据instanceKlass
    _klass = InstanceKlass::allocate_instance_klass(loader_data,
                                                    vtable_size,
                                                    itable_size,
                                                    info.static_field_size,
                                                    total_oop_map_size2,
                                                    rt,
                                                    access_flags,
                                                    name,
                                                    super_klass(),
                                                    !host_klass.is_null(),
                                                    CHECK_(nullHandle));
    instanceKlassHandle this_klass (THREAD, _klass);
    //...省略instanceKlass设置静态方法个数、静态变量个数、java镜像(class对象)、版本号...等属性代码
    

    // Allocate mirror and initialize static fields
    // 为klass和class实例互相设置引用,并为静态变量设置默认值以及完成对带final static的属性赋恒定值
    java_lang_Class::create_mirror(this_klass, protection_domain, CHECK_(nullHandle));

	// ...省略不关键代码
    

  return this_klass;
}

下面先看一下JVM/jdk-jdk8-b120-源码/hotspot/src/share/vm/classfile/javaClasses.cpp中的create_mirror函数,为klass和class实例互相设置引用并初始化class的静态字段(在每个Klass中都会有个java_mirror字段用来保存Klass对应的Class实例是个oop模型,Klass不直接暴露给java代码,给到java代码的是java_mirror即Class实例)

oop java_lang_Class::create_mirror(KlassHandle k, Handle protection_domain, TRAPS) {
  assert(k->java_mirror() == NULL, "should only assign mirror once");
  // Use this moment of initialization to cache modifier_flags also,
  // to support Class.getModifiers().  Instance classes recalculate
  // the cached flags after the class file is parsed, but before the
  // class is put into the system dictionary.
  int computed_modifiers = k->compute_modifier_flags(CHECK_0);
  k->set_modifier_flags(computed_modifiers);
  // Class_klass has to be loaded because it is used to allocate
  // the mirror.
  if (SystemDictionary::Class_klass_loaded()) {
    // Allocate mirror (java.lang.Class instance)
    // 获取Class实例oop.
    Handle mirror = InstanceMirrorKlass::cast(SystemDictionary::Class_klass())->allocate_instance(k, CHECK_0);
    // 获取Class类对应的Klass InstanceMirrorKlass
    InstanceMirrorKlass* mk = InstanceMirrorKlass::cast(mirror->klass());
    java_lang_Class::set_static_oop_field_count(mirror(), mk->compute_static_oop_field_count(mirror()));

    // It might also have a component mirror.  This mirror must already exist.
    if (k->oop_is_array()) {
      Handle comp_mirror;
      if (k->oop_is_typeArray()) {
        BasicType type = TypeArrayKlass::cast(k())->element_type();
        comp_mirror = Universe::java_mirror(type);
      } else {
        assert(k->oop_is_objArray(), "Must be");
        Klass* element_klass = ObjArrayKlass::cast(k())->element_klass();
        assert(element_klass != NULL, "Must have an element klass");
          comp_mirror = element_klass->java_mirror();
      }
      assert(comp_mirror.not_null(), "must have a mirror");

        // Two-way link between the array klass and its component mirror:
      ArrayKlass::cast(k())->set_component_mirror(comp_mirror());
      set_array_klass(comp_mirror(), k());
    } else {
      assert(k->oop_is_instance(), "Must be");

      // Allocate a simple java object for a lock.
      // This needs to be a java object because during class initialization
      // it can be held across a java call.
      typeArrayOop r = oopFactory::new_typeArray(T_INT, 0, CHECK_NULL);
      set_init_lock(mirror(), r);

      // Set protection domain also
      set_protection_domain(mirror(), protection_domain());

      // Initialize static fields
      // 为final static 属性赋恒定值
      InstanceKlass::cast(k())->do_local_static_fields(&initialize_static_field, CHECK_NULL);
    }
    return mirror();
  } else {
    if (fixup_mirror_list() == NULL) {
      GrowableArray<Klass*>* list =
       new (ResourceObj::C_HEAP, mtClass) GrowableArray<Klass*>(40, true);
      set_fixup_mirror_list(list);
    }
    fixup_mirror_list()->push(k());
    return NULL;
  }
}

接下来看下mirror的函数allocate_instance究竟干了什么,它在jdk-jdk8-b120-源码/hotspot/src/share/vm/oops/instanceMirrorKlass.cpp中(每个类都对应一个Klass,instanceMirrorKlass就是Class类对于的Klass,保存了Class类里的方法信息),由源码可见Class对象是存在于堆中的,是从堆中申请的内存地址,同时为静态属性赋默认值

instanceOop InstanceMirrorKlass::allocate_instance(KlassHandle k, TRAPS) {
  // Query before forming handle.
  int size = instance_size(k);
  KlassHandle h_k(THREAD, this);
  // 在堆中申请内存创建Class实例,这里也证明了class实例是在堆中的。同时为静态属性赋默认值
  instanceOop i = (instanceOop) CollectedHeap::Class_obj_allocate(h_k, size, k, CHECK_NULL);
  return i;
}

接下来看下jdk-jdk8-b120-源码/hotspot/src/share/vm/gc_interface/collectedHeap.cpp中的Class_obj_allocate函数,可以看到Klass和Class互相持有对方的引用,Klass持有Class的引用论证了Person.Class和person.getClass()方法原理;Class持有Klass的引用论证了反射获取方法的原理。

oop CollectedHeap::Class_obj_allocate(KlassHandle klass, int size, KlassHandle real_klass, TRAPS) {
  debug_only(check_for_valid_allocation_state());
  assert(!Universe::heap()->is_gc_active(), "Allocation during gc not allowed");
  assert(size >= 0, "int won't convert to size_t");
  HeapWord* obj;
    assert(ScavengeRootsInCode > 0, "must be");
    obj = common_mem_allocate_init(real_klass, size, CHECK_NULL);
  post_allocation_setup_common(klass, obj);
  assert(Universe::is_bootstrapping() ||
         !((oop)obj)->is_array(), "must not be an array");
  NOT_PRODUCT(Universe::heap()->check_for_bad_heap_word_value(obj, size));
  oop mirror = (oop)obj;

  java_lang_Class::set_oop_size(mirror, size);

  // Setup indirections
  if (!real_klass.is_null()) {
      //为Class实例设置Klass引用
    java_lang_Class::set_klass(mirror, real_klass());
    // 为Klass设置java_mirror属性指向堆里的Class实例
    real_klass->set_java_mirror(mirror);
  }

  InstanceMirrorKlass* mk = InstanceMirrorKlass::cast(mirror->klass());
  assert(size == mk->instance_size(real_klass), "should have been set");

  // notify jvmti and dtrace
  post_allocation_notify(klass, (oop)obj);

  return mirror;
}

Class在Klass中的引用就是java_mirror在HSDB中可以清楚看到,但是Klass在Class中的引用在HSDB中是看不到的,也就是在Class中没有Klass相关的属性,那又是怎么储存的呢?jdk-jdk8-b120-源码/hotspot/src/share/vm/classfile/javaClasses.cpp中的set_klass()函数

void java_lang_Class::set_klass(oop java_class, Klass* klass) {
  assert(java_lang_Class::is_instance(java_class), "must be a Class object");
  // 在数据区基于偏移量设置klass引用,_klass_offset为相对于数据区的固定偏移量
  java_class->metadata_field_put(_klass_offset, klass);
}

C++并不像Java一样,保存信息时非要在类中定义出相关属性,C++只是在分配内存时为要存储的信息分配好特定的内存,然后直接通过内存偏移来操作即可,所以C++是允许无属性存储引用的,只要记录相对偏移量就可以进行存取。

下面再回到JVM/jdk-jdk8-b120-源码/hotspot/src/share/vm/classfile/javaClasses.cpp中的create_mirror函数中的第二个关键方法do_local_static_fields函数,在创建Class实例后需要为final属性赋恒定值。这个函数的第一个参数是initialize_static_field的函数引用,会被回调执行,核心代码也在initialize_static_field函数中。

static void initialize_static_field(fieldDescriptor* fd, TRAPS) {
  Handle mirror (THREAD, fd->field_holder()->java_mirror());
  assert(mirror.not_null() && fd->is_static(), "just checking");
  	// 是否带final static
    if (fd->has_initial_value()) {
    BasicType t = fd->field_type();
    switch (t) {
      case T_BYTE:
        mirror()->byte_field_put(fd->offset(), fd->int_initial_value());
              break;
      case T_BOOLEAN:
        mirror()->bool_field_put(fd->offset(), fd->int_initial_value());
              break;
      case T_CHAR:
        mirror()->char_field_put(fd->offset(), fd->int_initial_value());
              break;
      case T_SHORT:
        mirror()->short_field_put(fd->offset(), fd->int_initial_value());
              break;
      case T_INT:
        mirror()->int_field_put(fd->offset(), fd->int_initial_value());
        break;
      case T_FLOAT:
        mirror()->float_field_put(fd->offset(), fd->float_initial_value());
        break;
      case T_DOUBLE:
        mirror()->double_field_put(fd->offset(), fd->double_initial_value());
        break;
      case T_LONG:
        mirror()->long_field_put(fd->offset(), fd->long_initial_value());
        break;
      case T_OBJECT:
        {
          #ifdef ASSERT
          TempNewSymbol sym = SymbolTable::new_symbol("Ljava/lang/String;", CHECK);
          assert(fd->signature() == sym, "just checking");
          #endif
          oop string = fd->string_initial_value(CHECK);
          mirror()->obj_field_put(fd->offset(), string);
        }
        break;
      default:
        THROW_MSG(vmSymbols::java_lang_ClassFormatError(),
                  "Illegal ConstantValue attribute in class file");
    }
  }
}

至此类加载的源码分析完毕,总结下在源码分析中我们的收获:

  1. .class文件被jvm加载后会在方法区生产一个instanceKlass用来储存Java类的方法信息、静态方法个数、静态变量个数、版本号、常量池等信息;
  2. 会在堆区产生一个instanceOOP代表Java类对应的java.lang.Class实例,class实例里保存了Java类的静态变量,在创建class实例时会为静态变量设置默认值,之后调用initialize_static_field函数为带final修饰的静态常量赋恒定值(可以在常量池找到恒定值符号引用的常量);
  3. 在Klass和Class互相持有引用,在instanceKlass中有个java_mirror的属性用于储存class的oop引用,在class里基于固定的指针偏移量_klass_offset来储存klass引用;
  4. instanceMirrorKlass用来表时java.lang.Class类的Klass,同时java.lang.Class类在堆中也会有对应oop类型的class实例(Class.class),那这个oop中即保存了Class对象的非静态字段值,又包含了Class类自身也定义的静态字段,故比较特殊。

用HSDB分析JVM的运行时数据

用HSDB分析Person类的运行时数据

public class Person {
    // 静态常量(常量池中不存在恒定值引用)
    private final static String finalStr1= UUID.randomUUID().toString();

    // 静态常量(常量池中存在恒定值引用)
    private final static String finalStr2="final String";

    // String类型静态变量
    private static String staticStr="static String";

    // int类型静态变量
    private static int staticInt=1;

    // String成员变量
    private String a="String";

    // int类型成员变量
    private int b=2;

    /******************get/set方法****************************/
    public static String getFinalStr1() {
        return finalStr1;
    }

    public static String getFinalStr2() {
        return finalStr2;
    }

    public static String getStaticStr() {
        return staticStr;
    }

    public static void setStaticStr(String staticStr) {
        Person.staticStr = staticStr;
    }

    public static int getStaticInt() {
        return staticInt;
    }

    public static void setStaticInt(int staticInt) {
        Person.staticInt = staticInt;
    }

    public String getA() {
        return a;
    }

    public void setA(String a) {
        this.a = a;
    }

    public int getB() {
        return b;
    }

    public void setB(int b) {
        this.b = b;
    }
}

/**
 * JVM内存与垃圾回收统篇之类加载子系统
 */
public class Demo1 {


    public static void main(String[] args) {
       Person person1=new Person();
       while (true);
    }

}

Person在堆里的oop实例person1

在这里插入图片描述

Person类在方法区的Klass

在这里插入图片描述

在堆中Person.class oop实例

在这里插入图片描述

由上图可以得到Person类中的静态变量都保存在Klass中的java_mirror属性中,也就是都保存在Person.class实例中,成员变量保存在person1对应的Person oop实例中。
最后总结一波Person在JVM中的堆、栈、方法区的Klass-OOP引用关系:
(上图~)
在这里插入图片描述

final static 初始化问题

结合上面的java代码再从字节码层面论证一下"类加载在JVM源码分析"中的第二个总结:
会在堆区产生一个instanceOOP代表Java类对应的java.lang.Class实例,class实例里保存了Java类的静态变量,在创建class实例时会为静态变量设置默认值,之后调用initialize_static_field函数为带final修饰的静态常量赋恒定值(可以在常量池找到恒定值符号引用的常量);
静态变量存放在堆中的class实例里,这个上面三张图可以证明这一点,这次证明的主要是后半句话,final的静态常量赋值问题。

Person的

在这里插入图片描述

Person的常量池

Constant pool:
   #1 = Methodref          #13.#47        // java/lang/Object."<init>":()V
   #2 = String             #48            // String
   #3 = Fieldref           #6.#49         // jvm/memory/loading/Person.a:Ljava/lang/String;
   #4 = Fieldref           #6.#50         // jvm/memory/loading/Person.b:I
   #5 = Fieldref           #6.#51         // jvm/memory/loading/Person.finalStr1:Ljava/lang/String;
   #6 = Class              #52            // jvm/memory/loading/Person
   #7 = String             #53            // final String
   #8 = Fieldref           #6.#54         // jvm/memory/loading/Person.staticStr:Ljava/lang/String;
   #9 = Fieldref           #6.#55         // jvm/memory/loading/Person.staticInt:I
  #10 = Methodref          #56.#57        // java/util/UUID.randomUUID:()Ljava/util/UUID;
  #11 = Methodref          #56.#58        // java/util/UUID.toString:()Ljava/lang/String;
  #12 = String             #59            // static String
  #13 = Class              #60            // java/lang/Object
  #14 = Utf8               finalStr1
  #15 = Utf8               Ljava/lang/String;
  #16 = Utf8               finalStr2
  #17 = Utf8               ConstantValue
  #18 = Utf8               staticStr
  #19 = Utf8               staticInt
  #20 = Utf8               I
  #21 = Utf8               a
  #22 = Utf8               b
  #23 = Utf8               <init>
  #24 = Utf8               ()V
  #25 = Utf8               Code
  #26 = Utf8               LineNumberTable
  #27 = Utf8               LocalVariableTable
  #28 = Utf8               this
  #29 = Utf8               Ljvm/memory/loading/Person;
  #30 = Utf8               getFinalStr1
  #31 = Utf8               ()Ljava/lang/String;
  #32 = Utf8               getFinalStr2
  #33 = Utf8               getStaticStr
  #34 = Utf8               setStaticStr
  #35 = Utf8               (Ljava/lang/String;)V
  #36 = Utf8               getStaticInt
  #37 = Utf8               ()I
  #38 = Utf8               setStaticInt
  #39 = Utf8               (I)V
  #40 = Utf8               getA
  #41 = Utf8               setA
  #42 = Utf8               getB
  #43 = Utf8               setB
  #44 = Utf8               <clinit>
  #45 = Utf8               SourceFile
  #46 = Utf8               Person.java
  #47 = NameAndType        #23:#24        // "<init>":()V
  #48 = Utf8               String
  #49 = NameAndType        #21:#15        // a:Ljava/lang/String;
  #50 = NameAndType        #22:#20        // b:I
  #51 = NameAndType        #14:#15        // finalStr1:Ljava/lang/String;
  #52 = Utf8               jvm/memory/loading/Person
  #53 = Utf8               final String
  #54 = NameAndType        #18:#15        // staticStr:Ljava/lang/String;
  #55 = NameAndType        #19:#20        // staticInt:I
  #56 = Class              #61            // java/util/UUID
  #57 = NameAndType        #62:#63        // randomUUID:()Ljava/util/UUID;
  #58 = NameAndType        #64:#31        // toString:()Ljava/lang/String;
  #59 = Utf8               static String
  #60 = Utf8               java/lang/Object
  #61 = Utf8               java/util/UUID
  #62 = Utf8               randomUUID
  #63 = Utf8               ()Ljava/util/UUID;
  #64 = Utf8               toString

在Person中一共有四个静态变量,finalStr1,finalStr2,staticStr,staticInt其中前两个被final修饰。
在Person的类初始化方法的字节码中发现没有finalStr2相关的初始化指令,原因是在执行之前finalStr2已经进行了赋值操作,因为finalStr2是显式赋值且赋值过程中不涉及方法调用,其显式赋的值"final String"在编译时期就已经确认,被存放在常量池#53的位置,然后在创建class实例后通过initialize_static_field函数进行赋值。

总结一波一个类的各种成员变量的初始化顺序:

  1. 被static修饰的全部变量赋默认值,在创建Class实例时;
  2. 被final static修饰的静态常量**(可以在常量池找到恒定值符号引用的常量)**在创建Class实例后,调用initialize_static_field进行赋值;
  3. 在Person类初始化时调用方法进行赋值
  4. 在创建类Person类实例时调用构造方法对非静态成员变量进行赋值。

参考:https://blog.51cto.com/u_10912795/2775854 类加载阶段之准备阶段

博主语雀地址:https://www.yuque.com/yangxiaofei-vquku/wmp1zm/mel3x8

简单就能了解JVM | OOP-Klass 二分模型 本文知识点 OOP和klass的概念 OOP和Klass源码 HSDB的使用 OOP和klass的概念 OOPS: 即普通对象指针,用来描述对象实例信息 Klass: Java类的C++对等体,用来描述Java类 总体上是多个OOP和一个Klass是对应的. 相当于一个类可以有多个实例 A a = new A() ; A a1 = new A(); a,a1 分别对应着不同的OOP, 对应着同一个Klass OOP和Klass源码 OOP中有很多模块, 其中.. 阅读详情

相关推荐

AGX Orin 部署PyTorch与Torchvision 2026避坑指南

本文提供了2026年在AGX Orin开发板上部署PyTorch与Torchvision的详细避坑指南。针对ARM64架构和JetPack 6.2系统,重点解决了Python 3.10环境配置、官方wheel包安装、torchvision源码编译等关键步骤,并分享了常见问题解决方案和性能优化技巧,确保深度学习环境稳定运行。

sky77的博客 752

动态代理类生成Hotspot VM干了什么

本来想着从头开始写JVM但是之前写了动态代理设计模式,而动态代理模式中又涉及到动态生成Class对象的过程,索性就先了解一下对象生成过程,在后续学习过程如果发现写的有问题在纠正和补充! 一、动态代理类生成主流程 1、类加载、代理类实现接口Class对象集合获取 2、校验是否为为接口(java是单继承而动态生成的代理李继承了Proxy所以其他的只能是接口) 3、判断接口是不是public的,如果不是public则要判断是否为同一个目录下的,如果不是同一目录下的则生成代理类结束 4、生成代理类包名..

bit-cafe 801

OAI数据集申请与实战:从权限获取到医学影像分析应用

本文详细介绍了OAI数据集的申请流程与医学影像分析实战应用。从NDA账户注册、数据下载技巧到骨盆X光关键点检测的完整实现,帮助医学研究者高效利用这一权威骨关节炎数据集。特别分享使用Pei Y标注数据训练U-Net模型的经验,包括预处理、注意力机制优化等实用技巧。

weixin_30275415的博客 478

java.lang.instrument解析

java.lang.instrument一、使用java agent二、运行原理2.1 绕不开的JVM TIClassFileLoadHookagent函数 (Agent_OnLoad、Agent_OnAttach、Agent_OnUnload)2.2 Instrument的实现2.2.1 premain 启动时加载2.2.2 agentmain 启动时加载2.3.3. ClassFileLoadHook回调实现在类加载时修改类的字节码修改内存已经存在的类三、sun.instrument 源码分析3.1 C

wanxiaoderen的博客 2352

比Synergy更轻量!Barrier鼠标共享工具实测:5分钟实现Win/Mac/Linux三设备无缝切换

本文实测了开源免费的Barrier鼠标共享工具,详细指导用户如何在5分钟内于Windows、macOS和Linux三台设备间实现一套键鼠的无缝切换。文章对比了其与Synergy的优势,提供了分平台安装、核心服务端-客户端配置、防火墙设置等完整教程,并分享了解决连接问题的关键技巧,是提升多设备办公效率的实用指南。

weixin_29304021的博客 594

ThinkPad T14 Gen 2与T14P系列对比

表格您的需求推荐机型日常办公、出差、文档处理、视频会议✅(够用、轻便、便宜)视频剪辑、3D建模、编程编译、AI训练、高色准设计✅(性能强、屏幕好、扩展佳)想要未来 5 年不换机✅T14p预算低于 5000 元✅T14 Gen 2(或考虑 T14 Gen 3/4)T14p 2025 款 ≠ 小改款,而是架构级升级它从“高性能笔记本”进化为“AI 移动工作站”,核心差异在于NPU + Ultra 平台 + 全面屏显 + 超长续航。如果您从事AI 开发、视频创作、工程仿真、多任务编程。

tecoes的博客 2734

java jdk 类加载机制_JDK源码阅读之类加载

java类加载类的生命周期(类加载过程)LLIUU+VPR加载(Loading)链接(Linking)验证(Verification)准备(Preparation)解析(Resolution)初始化(Initialization)使用(Using)卸载(Unloading) 类类加载器种类BootstrapClassLoader:C++编写,负责加载java核心类库Launcher.ExtClas...

weixin_36247891的博客 254

HANA-公司间销售ICS-IDOC系统配置-保姆级配置文档

合作伙伴编号:这个客户表示收票方(销售公司在生产公司中所对应的客户代码,卖方开票,买方收票),也就是咱们在配置消息类型输出的时候合作伙伴的类型的一个具体的客户编码,这个是站在发货工厂的角度去看的,也就是EDI 输出的 BP 合作伙伴的编号;在项目实施过程中经常会遇到关联方交易的问题,有公司间采购的业务场景,也会存在公司间销售的业务场景,本文将着重讲解公司间销售在SAP系统中的实现场景。在这个过程中,销售公司向客户开票产生应收,而生产公司向销售公司开票产生自己的应收,同时销售公司向生产公司产生应付。

我小时候很黑的博客 2389

JVM从源码分析类加载机制

流程图 源码分析 launcher 的实例化流程 1.AppClassLoader 和 ExtClassLoader 都继承URLClassLoader 2.getLauncher() 获取的是一个 单例类 Launcher 对象,保证了每一个虚拟机只有一个 Launcher 3…程序执行的结果说明了 自定义类 User的 类加载器 是 AppClassLoader, AppClassLoader的父加载器是ExtClassLoader , ExtClassLoader 的父加载器是 null,下面源码

IT_Rain的博客 359

开源鸿蒙具身机器人(ROS2框架):从内核驱动到智能算法的完整实践

本指导文档旨在为开发者提供完整的开源鸿蒙(OpenHarmony)具身机器人开发解决方案,涵盖从底层驱动到上层ROS2应用的全栈开发流程。文档结合OpenHarmony内核驱动移植技术和ROS2机器人操作系统,为具身机器人开发提供详细的技术指导。具身机器人(Embodied Robot)是指具有物理实体、能够感知环境并与之交互的智能机器人系统。感知-决策-执行闭环多模态传感器融合(视觉、听觉、触觉、力觉)实时环境感知与理解智能决策与规划精确运动控制执行具身智能特性物理世界交互能力。

m0_67505153的博客 1268

近五年体内微/纳米机器人赋能肿瘤精准治疗综述:以 GBM 为重点

摘要 微/纳米机器人在实体瘤治疗领域展现出突破性潜力,通过主动运动能力解决传统纳米药物递送效率低、渗透不足等核心问题。2021-2025年间形成三条技术路线:1) 磁驱、声驱等运动增强递送实现深层渗透;2) 与免疫治疗融合实现原位免疫唤醒;3) 针对胶质母细胞瘤开发跨屏障递送系统。研究重点从单一运动性能转向"运动-分布-疗效"的系统评价,需建立标准化指标评估运动学参数、肿瘤分布均匀性和安全性。未来挑战在于临床转化的可制造性、闭环操控及生物相容性优化,特别是对血脑屏障穿透等复杂场景的系统集

AllenLV的博客 5096

Hotspot JVM 底层C/C++ 源码 入门4--oop-klass模型

Hotspot虚拟机在内部使用两组类来表示Java的类和对象 oop(ordinary object pointer),描述对象的实例信息 klass,描述java类,是虚拟机内部Java类型结构 typedef class oopDesc* oop; typedef class instanceOopDesc* ...

ronghost的博客 696

MobaXterm简介和使用(服务器连接工具)

MobaXterm 是一个功能强大的远程计算工具,集成了许多常用的远程计算功能,包括 SSH、X11、RDP、VNC、FTP、SFTP 等。

清平乐的技术专栏 1936

JVM OOP-Klass源码分析

Hotspot采用OOP-Klass模型来描述一个Java对象,OOP是ordinary object pointer(普通对象指针),它用来表示对象的实例信息;Klass用来表示类的元数据。

YANYONG_的博客 250

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

java 占小狼_占小狼之-JVM-JVM源码分析之Java类加载过程

HotSpot对象模型HotSpot JVM并没有根据Java对象直接通过虚拟机映射到新建的C++对象,而是设计了一个oop/klass model,其中oop为Ordinary Object Pointer,用来表示对象的实例信息;klass用来保存描述元数据。Klass 关于为何要设计oop/klass这种二分模型的实现,一个原因是不想让每个对象都包含vtbl(虚方法表),其中oop中不含有任...

weixin_30104235的博客 240

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

java类加载过程_JVM源码分析之Java类的加载过程

乘着年轻,多学习背景最近对Java细节的底层实现比较感兴趣,如Java类文件是如何加载到虚拟机的,类对象和方法是以什么数据结构存在于虚拟机中?虚方法、实例方法和静态方法是如何调用的?本文基于openjdk-7的OpenJDK实现Java类在HotSpot的内部实现进行分析。HotSpot内存划分在HotSpot实现中,内存被划分成Java堆、方法区、Java栈、本地方法栈和PC寄存器几个部分:1、...

weixin_39978696的博客 396

深度学习工具包 DeepLearningToolbox

深度学习工具包Deprecation notice.-----This toolbox is outdated and no longer maintained.There are much better tools available for deep learning than this toolbox, e.g. [Theano](http://deeplearning.net/software/theano/), [torch](http://torch.ch/) or [tensorflow](http://www.tensorflow.org/)I would suggest you use one of the tools mentioned above rather than use this toolbox.Best, Rasmus.DeepLearnToolbox================A Matlab toolbox for Deep Learning.Deep Learning is a new subfield of machine learning that focuses on learning deep hierarchical models of data.It is inspired by the human brain's apparent deep (layered, hierarchical) architecture.A good overview of the theory of Deep Learning theory is[Learning Deep Architectures for AI](http://www.iro.umontreal.ca/~bengioy/papers/ftml_book.pdf)For a more informal introduction, see the following videos by Geoffrey Hinton and Andrew Ng.* [The Next Generation of Neural Networks](http://www.youtube.com/watch?v=AyzOUbkUf3M) (Hinton, 2007)* [Recent Developments in Deep Learning](http://www.youtube.com/watch?v=VdIURAu1-aU) (Hinton, 2010)* [Unsupervised Feature Learning and Deep Learning](http://www.youtube.com/watch?v=ZmNOAtZIgIk) (Ng, 2011)If you use this toolbox in your research please cite [Prediction as a candidate for learning deep hierarchical models of data](http://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=6284)```@MASTERSTHESIS\{IMM2012-06284, author = "R. B. Palm", title = "Prediction as a candidate for learning deep hierarchical models of data", year = "2012",}```Contact: rasmusbergpalm at gmail dot comDirectories included in the toolbox-----------------------------------`NN/` - A library for Feedforward Backpropagation Neural Networks`CNN/` - A library for Convolutional Neural Networks`DBN/` - A library for Deep Belief Networks`SAE/` - A library for Stacked Auto-Encoders`CAE/` - A library for Convolutional Auto-Encoders`util/` - Utility functions used by the libraries`data/` - Data used by the examples`tests/` - unit tests to verify toolbox is workingFor references on each library check REFS.mdSetup-----1. Download.2. addpath(genpath('DeepLearnToolbox'));Example: Deep Belief Network---------------------```matlabfunction test_example_DBNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit RBM and visualize its weightsrand('state',0)dbn.sizes = [100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);figure; visualize(dbn.rbm{1}.W'); % Visualize the RBM weights%% ex2 train a 100-100 hidden unit DBN and use its weights to initialize a NNrand('state',0)%train dbndbn.sizes = [100 100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);%unfold dbn to nnnn = dbnunfoldtonn(dbn, 10);nn.activation_function = 'sigm';%train nnopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.10, 'Too big error');```Example: Stacked Auto-Encoders---------------------```matlabfunction test_example_SAEload mnist_uint8;train_x = double(train_x)/255;test_x = double(test_x)/255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit SDAE and use it to initialize a FFNN% Setup and train a stacked denoising autoencoder (SDAE)rand('state',0)sae = saesetup([784 100]);sae.ae{1}.activation_function = 'sigm';sae.ae{1}.learningRate = 1;sae.ae{1}.inputZeroMaskedFraction = 0.5;opts.numepochs = 1;opts.batchsize = 100;sae = saetrain(sae, train_x, opts);visualize(sae.ae{1}.W{1}(:,2:end)')% Use the SDAE to initialize a FFNNnn = nnsetup([784 100 10]);nn.activation_function = 'sigm';nn.learningRate = 1;nn.W{1} = sae.ae{1}.W{1};% Train the FFNNopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.16, 'Too big error');```Example: Convolutional Neural Nets---------------------```matlabfunction test_example_CNNload mnist_uint8;train_x = double(reshape(train_x',28,28,60000))/255;test_x = double(reshape(test_x',28,28,10000))/255;train_y = double(train_y');test_y = double(test_y');%% ex1 Train a 6c-2s-12c-2s Convolutional neural network %will run 1 epoch in about 200 second and get around 11% error. %With 100 epochs you'll get around 1.2% errorrand('state',0)cnn.layers = { struct('type', 'i') %input layer struct('type', 'c', 'outputmaps', 6, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %sub sampling layer struct('type', 'c', 'outputmaps', 12, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %subsampling layer};cnn = cnnsetup(cnn, train_x, train_y);opts.alpha = 1;opts.batchsize = 50;opts.numepochs = 1;cnn = cnntrain(cnn, train_x, train_y, opts);[er, bad] = cnntest(cnn, test_x, test_y);%plot mean squared errorfigure; plot(cnn.rL);assert(er<0.12, 'Too big error');```Example: Neural Networks---------------------```matlabfunction test_example_NNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);% normalize[train_x, mu, sigma] = zscore(train_x);test_x = normalize(test_x, mu, sigma);%% ex1 vanilla neural netrand('state',0)nn = nnsetup([784 100 10]);opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samples[nn, L] = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.08, 'Too big error');%% ex2 neural net with L2 weight decayrand('state',0)nn = nnsetup([784 100 10]);nn.weightPenaltyL2 = 1e-4; % L2 weight decayopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex3 neural net with dropoutrand('state',0)nn = nnsetup([784 100 10]);nn.dropoutFraction = 0.5; % Dropout fraction opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex4 neural net with sigmoid activation functionrand('state',0)nn = nnsetup([784 100 10]);nn.activation_function = 'sigm'; % Sigmoid activation functionnn.learningRate = 1; % Sigm require a lower learning rateopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex5 plotting functionalityrand('state',0)nn = nnsetup([784 20 10]);opts.numepochs = 5; % Number of full sweeps through datann.output = 'softmax'; % use softmax outputopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex6 neural net with sigmoid activation and plotting of validation and training error% split training data into training and validation datavx = train_x(1:10000,:);tx = train_x(10001:end,:);vy = train_y(1:10000,:);ty = train_y(10001:end,:);rand('state',0)nn = nnsetup([784 20 10]); nn.output = 'softmax'; % use softmax outputopts.numepochs = 5; % Number of full sweeps through dataopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, tx, ty, opts, vx, vy); % nntrain takes validation set as last two arguments (optionally)[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');```[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/rasmusbergpalm/deeplearntoolbox/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

java加载分析_JVM 源码分析之 Java 类的加载过程

原标题:JVM 源码分析之 Java 类的加载过程(点击上方公众号,可快速关注)来源:占小狼,www.jianshu.com/p/252e27863822乘着年轻,多学习背景最近对Java细节的底层实现比较感兴趣,如Java类文件是如何加载到虚拟机的,类对象和方法是以什么数据结构存在于虚拟机中?虚方法、实例方法和静态方法是如何调用的?本文基于openjdk-7的OpenJDK实现Java类在Hot...

weixin_42540248的博客 199

JVM源码分析之Java类加载过程

背景 最近对Java细节的底层实现比较感兴趣,如Java类文件是如何加载到虚拟机的,类对象和方法是以什么数据结构存在于虚拟机中?虚方法、实例方法和静态方法是如何调用的?本文基于openjdk-7的OpenJDK实现Java类在HotSpot的内部实现进行分析。 HotSpot内存划分 在HotSpot实现中,内存被划分成Java堆、方法区、Java栈、本地方法栈和PC寄存器几个部分:

bxwbxw555的博客 283

占小狼之-JVM-JVM源码分析之Java类加载过程

JVM源码分析之Java类加载过程 背景 最近对Java细节的底层实现比较感兴趣,如Java类文件是如何加载到虚拟机的,类对象和方法是以什么数据结构存在于虚拟机中?虚方法、实例方法和静态方法是如何调用的?本文基于openjdk-7的OpenJDK实现Java类在HotSpot的内部实现进行分析。 HotSpot内存划分 在HotSpot实现中,内存被划分成Java堆、方法区

ApatheCrazyFan的博客 1790

JVM源码分析之Java类的加载过程

http://www.jianshu.com/p/252e27863822 背景 最近对Java细节的底层实现比较感兴趣,如Java类文件是如何加载到虚拟机的,类对象和方法是以什么数据结构存在于虚拟机中?虚方法、实例方法和静态方法是如何调用的?本文基于openjdk-7的OpenJDK实现Java类在HotSpot的内部实现进行分析。 HotSpot内存划分 在Ho

zdy0_2004的专栏 951

【转载】hotspot源码角度看OOP之类属性的底层实现(一)

hello,大家好,我是江湖人送外号[道格牙]的子牙老师。 最近看hotspo源码有点入迷。hotspot就像一座宝库,等你探索的东西太多了。每次达到一个新的Level回头细看,都有不同的感触。入迷归入迷,分享还是得分享。分享使大家夸我,使我快乐。^_^ 最近报名JVM小班的同学问我问的比较多的是类加载阶段对属性的处理那块。这块知识点还挺多,不局限于加载阶段: 加载阶段如何存储属性 准备阶段给属性赋初值,细节是什么 初始化阶段给静态属性赋值,细节是什么 创建对象时给非静态属性赋值,细节是什么

ziya_anan的博客 191

OpenJDK源码实战:从JVM调试到HotSpot修改

JVM是Java程序的运行基石,其底层实现HotSpot基于C++构建,本质是一个可编译、可调试、可修改的系统级程序。理解JVM原理不能止步于内存模型或GC算法等概念,而需深入OpenJDK源码,掌握从Java字节码执行、类加载、垃圾回收到JIT编译的全链路机制。通过GDB调试HotSpot、修改C++源码并重新构建JDK,开发者能真正验证理论、定位线上OOM与GC异常,并支撑JVM定制化与故障诊断工具开发。本文聚焦Linux环境下OpenJDK 21源码构建、HotSpot调试与真实问题闭环解决,覆盖源码

weixin_34175509的博客 315

「JVM 编译优化」javac 编译器源码解读

JVM 编译优化 - javac 编译器源码解读

三余知行 2703

java defineclass1_Java字节码检测:反射调用defineClass时出现NullPointerException

意图:我正在使用java.lang.instrument包为Java程序创建一些工具.我的想法是我通过这个系统使用字节码操作,以便在每个方法的开头和结尾添加方法调用.一般来说,修改后的Java方法看起来像:public void whateverMethod(){MyFancyProfiler.methodEntered("whateverMethod");//the rest of the me...

weixin_29451671的博客 579

JVM故障诊断与性能优化--jvm参数了解(二)

跟踪垃圾回收和类加载信息1.跟踪垃圾回收示例---读懂日志:package chapter3; /** * 创建实例,演示垃圾回收,进行4次minorGC * * @author shiker * 预设参数:-verbose:gc -Xms20M -Xmx20M(设置堆容量为20M) -Xmn10M(新生代容量10M) -XX:SurvivorRatio=8(survivor占比8:...

你好,我是橙序员 720

JVM系列之:关于HSDB的一点心得

之前未接触过 HSDB 工具,在深入学习反射时,研究其源码时需要了解生成的字节码文件,恰巧看到别人使用了 HSDB 工具,因此花时间学习了一番。 HSDB(Hotspot Debugger),是一款内置于 SA 中的 GUI 调试工具,可用于调试 JVM 运行时数据,从而进行故障排除。 HSDB发展 sa-jdi.jar 在 Java9 之前,JAVA_HOME/lib 目录下有个 sa-jdi.jar,可以通过如下命令启动HSDB(图形界面)及CLHSDB(命令行)。 java -cp /Library/

Herishwater的博客 2044
上一篇: JNI动态注册、静态注册实例及其实现原理分析
下一篇: jvm类加载子系统之链接阶段
躺平程序猿
博客等级 码龄8年 4870粉丝 71原创
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

躺平程序猿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值