JVM之线程资源标记ResourceMark

Hotspot 方法调用之StubRoutines 源码解析 目录 一、StubRoutines 1、定义 2、initialize1和initialize2方法 二、ResourceMark 1、ResourceArea 2、构造和析构函数 三、BufferBlob 四、CodeBlob 1、定义 2、构造和flush方法 五、CodeBuffer 1、定义 2、构造和析构方法 3、copy_code_and_locs... 阅读详情

在前面介绍JVM的类加载中比如Field和Method的解析之前,都时要县创建一个ResourceMark对象,那么我们今天介绍下ResourceMark这个类。

ResourceMark类的主要成员以及方法

  1. ResourceMark继承StackObj,表示它是分配在栈上的, 它内部持有 ResourceMarkImpl实现类的引用.在构造函数中,会初始化ResourceMarkImpl类的构造方法传入当前线程的ResouceArea对象。
class ResourceMark: public StackObj {
  const ResourceMarkImpl _impl;
  // 构造方法
  ResourceMark(ResourceArea* area, Thread* thread) :
    _impl(area),
    _thread(thread),
    _previous_resource_mark(nullptr)
  {
    if (_thread != nullptr) {
      assert(_thread == Thread::current(), "not the current thread");
      _previous_resource_mark = _thread->current_resource_mark();
      _thread->set_current_resource_mark(this);
    }
  }
public:
  ResourceMark() : ResourceMark(Thread::current()) {}
  explicit ResourceMark(Thread* thread)
    : ResourceMark(thread->resource_area(), thread) {}
  explicit ResourceMark(ResourceArea* area)
    : ResourceMark(area, DEBUG_ONLY(Thread::current_or_null()) NOT_DEBUG(nullptr)) {}

  void reset_to_mark() { _impl.reset_to_mark(); }
};

 

ResourceMark的实现类ResourceMarkImpl构造函数和方法

  1. 构造函数传入当前线程的ResourceArea对象,保存到_area变量。 并调用SavedState构造函数初始化_saved_state变量。 然后再函数体重调用ResourceArea的activate_state校验当前资源状态,并且将_nesting变量加1。
class ResourceMarkImpl {
  ResourceArea* _area;   
  ResourceArea::SavedState _saved_state; // ResourceArea的快照
  
public:
  explicit ResourceMarkImpl(ResourceArea* area) :
  _area(area),
  _saved_state(area)
  {
    _area->activate_state(_saved_state);
  }
  explicit ResourceMarkImpl(Thread* thread)
    : ResourceMarkImpl(thread->resource_area()) {}
};

 SavedState是保存ResourceArea当前使用状态的快照。

class SavedState {
    friend class ResourceArea;
    Chunk* _chunk;
    char* _hwm;
    char* _max;
    size_t _size_in_bytes;

  public:
    SavedState(ResourceArea* area) :
      _chunk(area->_chunk),
      _hwm(area->_hwm),
      _max(area->_max),
      _size_in_bytes(area->_size_in_bytes)
    {}
  };

ResourceMarkImpl的析构函数

在ResouceMark对象是在栈上分配,所以它在生命周期结束后,也会调用 ResourceMarkImpl的析构函数,从而调用reset_to_mark执行重置ResouceArea到分配内存空间之前的快照状态,然后调用deactivate_state将_nesting将1.

~ResourceMarkImpl() {
    reset_to_mark();
    _area->deactivate_state将(_saved_state);
  }
    void reset_to_mark() const {
    _area->rollback_to(_saved_state);
  }

 reset_to_mark调用reset_to_mark函数,最终通过调用ResourceArea的 rollback_to函数并传入之前保存的内存资源快照_saved_state。

  • 首先判断UseMallocOnly是否只是用Malloc分配,如果是,则调用free_malloced_objects释放上一次分配的资源,这个默认是false,所以直接跳过。
  • 如果快照状态中当前的Chunk的_next指针不为空指针,执行set_size_in_bytes重置到Area中总的Chunk的字节数,然后调用当前的next_chop函数
void rollback_to(const SavedState& state) {
    assert(_nesting > state._nesting, "rollback to inactive mark");
    assert((_nesting - state._nesting) == 1, "rollback across another mark");
    if (UseMallocOnly) {
      free_malloced_objects(state._chunk, state._hwm, state._max, _hwm);
    }
    if (state._chunk->next() != nullptr) { 
      assert(size_in_bytes() > state._size_in_bytes,
             "size: " SIZE_FORMAT ", saved size: " SIZE_FORMAT, size_in_bytes(), state._size_in_bytes);
      set_size_in_bytes(state._size_in_bytes);
      state._chunk->next_chop();
    } else {
      assert(size_in_bytes() == state._size_in_bytes, "Sanity check");
    }
    _chunk = state._chunk;      // Roll back to saved chunk.
    _hwm = state._hwm;
    _max = state._max;

    // Clear out this chunk (to detect allocation bugs)
    if (ZapResourceArea) {
      memset(state._hwm, badResourceValue, state._max - state._hwm);
    }
  }

释放当前Chunk后面的Chunk资源

next_chop调用chop释放当前Chunk的后面连接的Chunk的内存空间,可以是通过delete去释放空间.

void Chunk::next_chop() {
  _next->chop();
  _next = NULL;
}

void Chunk::chop() {
  Chunk *k = this;
  while( k ) {
    Chunk *tmp = k->next();
    // 清除当前的Chunk的内存
    if (ZapResourceArea) memset(k->bottom(), badResourceValue, k->length());
    delete k;                   // Free chunk (was malloc'd)
    k = tmp;
  }
}

分配内存资源管理器Arena

  1. ResourceArea是继承Arena的资源管理器,ResourceMark是使用它去做内存资源分配。
    class ResourceArea: public Arena {
      friend class VMStructs;
    
    public:
      ResourceArea(MEMFLAGS flags = mtThread) :
        Arena(flags) DEBUG_ONLY(COMMA _nesting(0)) {}
    
      ResourceArea(size_t init_size, MEMFLAGS flags = mtThread) :
        Arena(flags, init_size) DEBUG_ONLY(COMMA _nesting(0)) {}

    Arena类的主要Field

class Arena : public CHeapObj<mtNone> {
protected:
  MEMFLAGS    _flags;         // 追踪内存的标识
  Chunk *_first;             // 第一个分配的chunk
  Chunk *_chunk;             // 当前 chunk
  char *_hwm, *_max;        // 高水位 and 当前chunk的最大水位

 MEMFLAGS是内存种类的枚举,主要是以下几个内存存储的类别

define MEMORY_TYPES_DO(f)                                    
  f(mtJavaHeap,       "Java Heap")   // Java 堆            
  f(mtClass,          "Class")  // java的class对象             
  f(mtThread,         "Thread")   // 线程对象
  f(mtThreadStack,    "Thread Stack")
  f(mtCode,           "Code")      // 生成的字节码
  f(mtGC,             "GC")                                   
  f(mtGCCardSet,      "GCCardSet")   // G1使用的卡表 
  f(mtCompiler,       "Compiler")                             
  f(mtJVMCI,          "JVMCI")                                 
  f(mtInternal,       "Internal")          

Chunk是分配内存资源管理者

Chunk是通过_next指针链接形成单向链表, _len记录当前Chunk的大小

class Chunk: CHeapObj<mtChunk> {
 private:
  Chunk*       _next;     // next指针指向下一个,形成链表
  const size_t _len;      //当前Chunk的大小
}

Arena构造函数初始化

  • Chunk继承了CHeapObj,它是重载new关键字,所以new Chunk就会再堆区分配对象并设置初始化大小为Chunk::init_size(从下面枚举可知初始大小为: 1k减去Chunk自身占用内存大小)。
  • 刚开始_first和_chunk变量是指向同一个chunk对象,_hwn初始化保存没有分配资源的位置. _max初始化为_hwn加当前chunk的大小len。
  • set_size_in_bytes设置可分配的字节数大小。
Arena::Arena(MEMFLAGS flag) : _flags(flag), _size_in_bytes(0) {
  _first = _chunk = new (AllocFailStrategy::EXIT_OOM, Chunk::init_size) Chunk(Chunk::init_size);
  _hwm = _chunk->bottom();   
  _max = _chunk->top();
  MemTracker::record_new_arena(flag);
  set_size_in_bytes(Chunk::init_size);
}

// 内存大小枚举
enum {
#ifdef _LP64
    slack      = 40,   // [RGV] Not sure if this is right, but make it a multiple of 8.
#else
    slack      = 24,  // suspected sizeof(Chunk) + internal malloc headers
#endif
    tiny_size  =  256  - slack, // Size of first chunk (tiny)
    init_size  =  1*K  - slack, // Size of first chunk (normal aka small)
    medium_size= 10*K  - slack, // Size of medium-sized chunk
    size       = 32*K  - slack, // Default size of an Arena chunk (following the first)
    non_pool_size = init_size + 32 // An initial size which is not one of above
  };

 Arena分配内存资源

  • 首先是64位内存对齐
  • 然后调用internal_amalloc进行分配内存
void* Amalloc(size_t x, AllocFailType alloc_failmode = AllocFailStrategy::EXIT_OOM) {
    x = ARENA_ALIGN(x); // 64位内初对齐
    // 校验内存对齐
    assert(is_aligned(_max, ARENA_AMALLOC_ALIGNMENT), "chunk end unaligned?");
    return internal_amalloc(x, alloc_failmode);
  }
  • 调用pointer_delta计算当前Chunk是有剩余的空间分配申请的x字节,则直接 _hwm 加上x,返回这次分配的_hwm指针。
  • 如果当前Chunk没有足够的剩余空间,则调用grow进行创建新的Chunk的进行分配内存空间。
void* internal_amalloc(size_t x, AllocFailType alloc_failmode = AllocFailStrategy::EXIT_OOM)  {
    assert(is_aligned(x, BytesPerWord), "misaligned size");
    if (pointer_delta(_max, _hwm, 1) >= x) {
      char *old = _hwm;
      _hwm += x;
      return old;
    } else {
      return grow(x, alloc_failmode);
    }
  }

 

  • 分配新的Chunk分配内存空间,空间大小取申请的x的64位对齐和Chunk::size(实际大小是32k-Chunk对象自身占用大小)
    void* Arena::grow(size_t x, AllocFailType alloc_failmode) {
      size_t len = MAX2(ARENA_ALIGN(x), (size_t) Chunk::size);
    
      Chunk *k = _chunk;        //记录之前的Chunk
      _chunk = new (alloc_failmode, len) Chunk(len);
    
      if (_chunk == NULL) {
        _chunk = k;  // 创建 Chunk失败,则恢复之前的Chunk指针
        return NULL;
      }
      if (k) k->set_next(_chunk);   // 将新的chunk设置_next指针
      else _first = _chunk;
      _hwm  = _chunk->bottom(); //保存chunk的最小地址
      _max =  _chunk->top();  // 保存chunk的最大地址
      set_size_in_bytes(size_in_bytes() + len);//设置Arena的所有Chunk的总的字节大小
      void* result = _hwm; // 返回Chunk开始分配的内存的void*指针
      _hwm += x; // 将_hwm加上x,那么就分配了[_hwn,_hwn+x]内存空间,返回result指针指向_hwn加x之前的地址指针
      return result;
    }

    Arena的分配内存是Chunk的链表组成

    总结

    本文主要分析栈上分配的ResouceMark,利用线程的ResourceArea进行分配前的快照保存以及内存分配,并利息ResourceMarkImpl的析构函数,释放当前Chunk后面分配的内存空间,并恢复分配前的内存快照的状态,

     

JVM 调优之 glibc 引发的内存泄露 malloc ? 这是申请内存的函数啊,为什么要申请这么多呢?难道没有释放? 于是想到用 pmap 命令看一下内存映射情况。 Pmap 提供了进程的内存映射,pmap 命令用于显示一个或多个进程的内存状态。其报告进程的地址空间和内存状态信息 执行了以下命令: pmap -x 1 | sort -n -k3 复制代码 发现了一些端倪: 有一些 64M 左右的内存分配,且越来越多。 glibc 搞不懂了,于是 google 了一下。发现是有这一类问题由于涉及许多底层基础知识,这里就大概解析一下,有兴趣的读 阅读详情

相关推荐

一个导致JVM物理内存消耗大的Bug

本文来自: PerfMa技术社区 PerfMa(笨马网络)官网 概述 最近我们公司在帮一个客户查一个JVM的问题(JDK1.8.0_191-b12),发现一个系统老是被OS Kill掉,是内存泄露导致的。在查的过程中,阴差阳错地发现了JVM另外的一个Bug。这个Bug可能会导致大量物理内存被使用,我们已经反馈给了社区,并得到快速反馈,预计在OpenJDK8最新版中发布(JDK11中也存在这个问题)。 PS:用户的那个问题最终也解决了,定位下来算是C2的一个设计缺陷导致大量内存被使用,安全性上没有得到保.

HeapDump性能社区的博客 2103

JVM篇-结合源码分析JVM堆内存&元空间的申请分配

一、堆内存的结构 1、结构图 ​ 我们可以看到官方给的这张图。可以看到其堆可以有两种分配类型:一种就是JVM申请提交的内存(committed)以及目前还没有使用到的内存(Virtual),另一种就是按照分代的概念:年轻代(Young Generation)老年代(Tenured Generation)。 ​ 同时根据这个结构图我们可以知道在代与代之间还有空出来暂时没有使用到的内存。 2、年轻代&老年代分配 ​ 我们可以看到在年轻代是有两个概念的划分的:Eden、Survivor,然后Surviv

Feverasa的博客 1392

Netty源码之arena、chunk、page、subpage概念

在本节我们将梳理一下有关内存分配几个非常重要的概念,分别是arena、chunk、page、subpage。 接下来我们看一下什么是arena: 在每个线程去申请内存的时候,他首先会通过ThreadLocal这种方式去获得当前线程的PoolThreadCache对象,然后调用他的allocate方法去申请内存,他一共分为两部分,一个是cache,在申请内存的时候首先会尝试从缓存中获取,另外一个就...

m0_37343985的博客 1238

Hotspot源码解析-第25章-类的初始化

Hotspot源码解析;JVM源码解析;JVM类的初始化;Java虚拟机源码解析

zhang527294844的博客 676

JVM 系列一:Native memory allocation 导致JVM Crash

JVM Crash抛出如下信息: # # There is insufficient memory for the Java Runtime Environment to continue. # Native memory allocation (malloc) failed to allocate 813056 bytes for Chunk::new # An error report fi

Kylin Soong Blog -> ksoong.org 6万+

云计算虚拟化实验(一)——虚拟机软件VMware的安装以及虚拟机系统的安装(Windows7+CentOS7)

主要内容: 1、虚拟机软件的安装(以VMware为例) 2、虚拟机系统的安装(Window7 + CentOS7) 3、虚拟主机网络互访 4、不同局域网内的虚拟主机互访(软路由——海蜘蛛) 0 ——首先介绍一下什么是虚拟化? 虚拟化,是指通过虚拟化技术将一台计算机虚拟为多台逻辑计算机。在一台计算机上同时运行多个逻辑计算机,每个逻辑计算机可运行不同的操作系统,并且应用程序都可以在

gg_asd的博客 1万+

Idea中Mark as为Sources和Resources的区别

1.Sources存放工程的源代码;Resources存放工程的资源文件,例如properties/XML等文件 2.个人的理解是,凡是被Mark as为Sources和Resources下面的代码或者文件可以被Idea自动编译,编译后都放在target/classes文件下面。classpath就是指的classes这个文件下面的资源

zhangustb 1万+

ST7789T3与CST816D驱动全解析,面试技巧第11篇:嵌入式面试场景问答 10 连发。

SPI模式下,SCLK、MOSI、CS、DC、RST为必需引脚;8080模式下需连接D0-D7、WR、RD等信号线。CST816D触摸芯片通常通过I2C接口通信,需连接SCL、SDA及INT(中断)引脚。确保电源引脚(VCC、GND)稳定供电,ST7789T3典型工作电压为3.3V。显示异常时检查时序参数,如SPI时钟频率(建议≤40MHz)、数据建立时间。初始化流程需发送一系列命令序列,包括设置像素格式、扫描方向、帧率控制等。扫描方向(MADCTL)可调整显示旋转角度,需根据实际硬件布局配置。

a967gsvj的博客 375

GenCollectedHeap的Gc策略MarkSweepPolicy

当内存堆管理器响应应用线程的一次内存分配请求失败时(就是没有足够的空闲内存),就会触发一次Gc来回收部分或所有的垃圾对象好腾出足够的空间来满足应用的所需内存,如果还不够则抛出OOM. MarkSweepPolicy的大体思路就是标记active的对象, 清理未被标记(非active)的对象MarkSweepPolicy作为内存堆管理器GenCollectedHeap的默认配置Gc策略, 针对这种内

xhh198781的专栏 2847

国产安全芯片在密码算法国密体系下的性能适配策略

国密体系即国家商用密码体系,是保障信息安全的核心技术体系。自20世纪90年代末起步,国密体系经历了从无到有、从弱到强的发展历程。1999年,国家密码管理局成立,标志着国密体系的正式建立。2000年,国密SM1算法首次发布,开启了国密算法的标准化进程。此后,SM2、SM3、SM4等算法陆续推出,形成了完整的对称与非对称密码算法体系。:2010年,国密标准正式纳入国家标准体系,国密算法在金融、政务、通信等关键领域的应用得到明确规范。

qq_40552871的博客 1356

全面解析:时延扩展与相干带宽、多普勒扩展与相干时间——无线通信基础

时延扩展与相干带宽 多径时延扩展与多径衰落 接收机所接收到的信号是通过不同的直射、反射、折射等路径到达接收机。由于电波通过各个路径的距离不同, 因而各条路径中发射波的到达时间不同,造成多径时延扩展。距离不同所以到达接收机的相位也不相同,不同相位的多个信号在接收端叠加, 如果同相叠加则会使信号幅度增强, 而反相叠加则会削弱信号幅度。 这样,接收信号的幅度将会发生急剧变化,就会产生多径衰落。 ...

king阿金 4万+

java线程锁标记_Java多线程(一)——基础概念/Thread/锁/AQS

AQS是什么:java.util.concurrnt包中lock包下有一个抽象类:AbstractQueuedSynchronizer简称为AQS。为实现阻塞锁和相关同步器提供一个框架,他是依赖于先进先出的等待队列。依靠单个原子int值来表示状态,该状态用于表示锁是获取中还是释放。通过给定的方法改变状态的值。定义了内部类ConditionObject拥有两种线程模式独占模式共享模式在LOCK包中的...

weixin_36054159的博客 344

匈牙利算法解决指派问题清晰流程

匈牙利算法解决指派问题清晰流程 百度词条上,指派问题(Assignment problem)是这么定义的:在满足特定指派要求条件下,使指派方案总体效果最佳。如:有若干项工作需要分配给若干人(或部门)来完成;有若干项合同需要选择若干个投标者来承包:有若干班级需要安排在若干教室里上课等。   一、做减法(归约): 行归约:每行元素减去该行最小元素。 列归约:每行元素减去该行最小元素。

siss0siss的博客 6万+

jvm疯狂吞占内存,罪魁祸首是谁

分享一篇公司小伙伴的关于jvm占用内存的技术文章 导读:JVM是Java Virtual Machine的缩写,中文名为Java虚拟机。它是一种用于计算设备的规范,是一个虚构出来的计算机,主要通过在实际的计算机上仿真模拟各种计算机功能来实现的。在实际运用过程中,易观技术人员注意到一台开发机上各个微服务进程占用内存很高,随即便展开了调查...... 现象:前段时间发现某台开发机上各个微服务进程占...

数据之路 1万+

手机移动端-纯js浏览器h5调用摄像头扫描识别解析 条形码+二维码

一、场景 h5原生js 浏览器端解决识别二维码条形码功能; 不借助Hbuilder.需要自己打包成APP,比如用Hbuilder打包,浏览器端项目h5无打包成app部署X不采用 不借助微信扫一扫,调用微信js-jdk有多麻烦,还要认证服务号,也不适用其它浏览器打开X不采用 二、思路 1.思路一:input(相机拍照)+条形码或二维码识别js支持库 通过h5-input[camera]调用相机进行拍照成图片,通过条形码等识别库解析出数据; <input @cha...

徐子厚的博客 3万+

在昇腾910B上,用kubeadm快速拉起一个双节点K8s集群(保姆级避坑指南)

本文详细介绍了在昇腾910B服务器上使用kubeadm快速搭建双节点Kubernetes集群的完整流程,包括环境准备、容器运行时配置、kubeadm初始化、节点加入与网络插件部署等关键步骤,特别针对ARM架构和昇腾芯片提供了优化配置和避坑指南。

weixin_29204749的博客 247

第04篇:Resources资源文件处理,再也不怕找不到文件了。

Java 的java.net.URL各种 URL 前缀的标准类和标准处理程序不足以满足所有对低级资源的访问。例如,没有URL可用于访问需要从类路径或相对ServletContext。于是乎这就给了Spring,封装继承多态,大展身手的展示了。怎么展示呢?在Spring中就是Resource接口,下面我们就看看Resource。我们利用Spring提供的能力,可以获取任何你想获取的文件,也可以使用通配符来模糊查询你要的文件。下面开始展示。

西魏陶渊明的博客 2143

基于Qt与PaddleOCR的跨平台OCR工具开发实战

本文详细介绍了基于Qt与PaddleOCR开发跨平台OCR工具的实战经验。通过Qt的跨平台特性和PaddleOCR的高效识别能力,开发者可以快速构建支持Windows、Linux和macOS的OCR工具。文章涵盖了环境配置、核心功能实现、性能优化及打包发布等关键环节,特别针对中文识别、内存管理和多线程处理等常见问题提供了解决方案。

weixin_30296405的博客 90

当Java虚拟机遇上Linux Arena内存池

故障案例一 系统环境: RHEL 6.8 64-bit(glibc 2.12)、Sun JDK 6u45 64-bit、WLS 10.3.6 故障现象: 这里引用一下客户当时发邮件时提出的问题描述吧。 下面pid 6287 weblogic进程占用7.6G的物理内存,之前只占用5G内存。我发现只有系统有空余的内存,就会被java给吃掉,为什么内存占用越来越多? 通过

京庐空间 3115

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

多线程的使用(二)

多线程

zyyy0的博客 620

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

基于JDK9从JVM源码研究类java.lang.Object

JAVA是面向对象语言,它的一个重要概念就是面向对象,而其所有类都继承于Object类,所有对象都实现这个类的方法。可以说,它的存在使得所有类都有了共同的祖先,接下来我们从源码来研究下这个神奇的Object类(本文的研究基于jdk-9.0.1)。

Ynter的分享 2547

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

Hotspot 类文件加载、链接和初始化

在熟悉《class文件和字节码解析》和《Java对象内存表示机制》了之后,我们对类有了更进一步的认识,那么类文件是如何变成Hotspot中的Klass了? 加载器机制  《类加载机制》一文件中,我们已知道java加载器双亲委派机制,也知道调用loadClass方法可自定义加载类。不管我们在JAVA中通过JNI的方式(ClassLoader JNI接口的实现源码在jdk/src/share/nati...

懒猫的脚印 1022

Java程序在Linux上运行虚拟内存耗用很大

Java程序虚拟内存很大,到底是怎么引起的

杀花千万可染唇间一点·落红十里难掩眸中春意 2万+
上一篇: JVM之线程资源标记ResourceMark
下一篇: Spring5源码-事务的创建、回滚、提交
java_lujj
博客等级 码龄4年 18粉丝 152原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值