JVM 系列-2:虚拟机执行子系统

解决/usr/lib64/libstdc++.so.6和/lib64/libc.so.6版本过低问题 在CentOS7.0中编译好MySQL5.7.14之后,然后在CentOS6.5中初始化MySQL,执行:/usr/local/mysql/bin/mysqld --version出现如下问题:./bin/mysqld: /usr/lib64/libstdc++.so.6: version `GLIBCXX_3.4.15' not found (required by ./bin/mysqld) 阅读详情

1、类文件结构

Java虚拟机只与Class文件相关联,它规定了Class文件应该具有的格式,而不论该文件是由什么语言编写并编译而来。所以,任何语言只要能够最终编译成符合Java虚拟机要求的Class文件,就可以运行在Java虚拟机上面。就是说,不论是使用Java, Scala, Kotlin, Groovy还是其他语言,只要编译出的Class文件符合虚拟机规范,那么都可以被虚拟机执行。所以,实际上Java规范就是由Java语言规范和Java虚拟机规范两个独立的部分组成。

Class类文件是一种二进制文件,它包含了Java虚拟机指令集和符号表以及若干其他辅助信息。Class文件格式采用类似于C的伪结构来存储数据,这种结构只有两种数据类型:无符号数和表。无符号数属于基本数据类型,以u1,u2,u4,u8分别代表1字节、2字节、4字节和8字节的无符号数,无符号数可以用来描述数字、索引、数量值或者按照utf-8编码构成的字符串。

表是由多个无符号数或者其他作为表作为数据项构成的复合数据结构,所有表习惯性地以"_info"结尾,整个Class文件本质上是一张表。而所谓的表就对应于C++中的一个结构体,比如整个Class文件对应的结构体就是:

struct ClassFile {
    u4 magic;                // 识别Class文件格式,具体值为0xCAFEBABE,
    u2 minor_version;        // Class文件格式副版本号,
    u2 major_version;        // Class文件格式主版本号,
    u2 constant_pool_count;  // 常量表项个数,
    cp_info **constant_pool; // 常量表,又称变长符号表,
    u2 access_flags;         // Class的声明中使用的修饰符掩码,
    u2 this_class;           // 常数表索引,索引内保存类名或接口名,
    u2 super_class;          // 常数表索引,索引内保存父类名,
    u2 interfaces_count;     // 超接口个数,
    u2 *interfaces;          // 常数表索引,各超接口名称,
    u2 fields_count;         // 类的域个数,
    field_info **fields;     // 域数据,包括属性名称索引,
    u2 methods_count;        // 方法个数,
    method_info **methods;   // 方法数据,包括方法名称索引,方法修饰符掩码等,
    u2 attributes_count;         // 类附加属性个数,
    attribute_info **attributes; // 类附加属性数据,包括源文件名等。
};

上面结构体的各个变量的定义的顺序与Class文件中的存储顺序是一致的。下面我们用一个二进制编辑器打开Class文件并简单看下,Class文件中的存储如何与上面的结构体对应的。

Class二进制文件

根据结构体的定义,首先是magic字段,它是u4类型的,即4字节,对应于上图中的CAFEBABE;紧接着两个u2类型的字段,minor_version和major_version,用来表示Class文件的版本号,对应于上图中的00000031;然后是一个u2类型的constant_pool_count,用来表示常数表项个数,这里是0027,也就是有38个常量,因此常量从1开始计数的;接着常量个数的是变长符号表,这个符号表的长度就是之前常量的长度,即38,然后我们要按照常量的规则找到38个常量之后就是u2类型的访问标志。

那这38个常量又如何寻找呢?实际上,Class文件中总计规定了14种常量结构体,每种结构体都包含一个tag字段,它是u1类型的,即1个字节,并且存储在该结构体的首位。我们需要先根据该tag字段找到该常量的定义,然后才能确定接下来的几个字节是属于该常量的。比如上图中的第一个常量的tag是0A,我们可以查询相关的表知道它是CONSTANT_Fleddef_info类型的常量,该常量有3个字段:u1类型的tag, u2类型的index, u2类型的index,故我们可以确定常量长度之后的5个字节是属于第一个常量的。依次,分析下去我们可以得到第二个,第三个等常量的信息。得到了常量的信息之后,就可以获取上面结构体中其他的字段的信息。

其实,按照上面的分析,我们可以看出分析Class文件时一个非常机械的过程,因为它有固定的规则在里面,所以我们可以使用命令行工具javap来获取以上的信息。在实际开发过程中从字节码的角度分析问题的情形可能并不多,但是了解字节码中的一些指令,尤其是并发相关的指令,对学习和分析问题都大有裨益。

2、类加载机制

2.1 类加载过程

Java程序中的类加载是在运行期间完成的,我们可以使用Java预定义的加载器或者自定义加载器动态从各种渠道加载类并使用。最初将类加载器从虚拟机中分离出来是为了Applet等的动态加载,但是后来随着技术发展,动态加载被应用到各种场景中,比如移动端的插件化、热补丁、Tomcat的类加载等各种场景中,所以类加载是非常重要的一块内容。

一个类从被加载到虚拟机内存到卸载的整个生命周期包括:加载-验证-准备-解析-初始化-使用-卸载7个阶段。其中验证-准备-解析3个阶段称为连接。

加载发生在类被使用的时候,如果一个类之前没有被加载,那么就会执行加载逻辑,比如当使用new创建类、调用静态类对象和使用反射的时候等。加载过程主要工作包括:1).从磁盘或者网络中获取类的二进制字节流;2).将该字节流的静态存储结构转换为方法取的运行时数据结构;3).在内存中生成表示这个类的Class对象,作为方法区访问该类的各种数据结构的入口。

验证阶段会对加载的字节流中的信息进行各种校验以确保它符合JVM的要求。

准备阶段会正式为类变量分配内存并设置类变量的初始值。注意这里分配内存的只包括类变量,也就是静态的变量(实例变量会在对象实例化的时候分配在堆上),并且这里的设置初始值是指‘零值’,比如int类型的会被初始化为0,引用类型的会被初始化为null,即使你在代码中为其赋了值。

解析阶段是将常量池中的符号引用替换为直接引用的过程。符号引用与虚拟机实现的布局无关,引用的目标并不一定要已经加载到内存中。各种虚拟机实现的内存布局可以各不相同,但是它们能接受的符号引用必须是一致的,只要能正确定位到它们在内存中的位置就行。直接引用可以是指向目标的指针,相对偏移量或是一个能间接定位到目标的句柄。如果有了直接引用,那引用的目标必定已经在内存中存在。

初始化是执行类构造器<client>方法的过程。<client>方法是由编译器自动收集类中的类变量的赋值操作和静态语句块中的语句合并而成的。虚拟机会保证<client>方法执行之前,父类的<client>方法已经执行完毕。

2.2 类加载机制 双亲委派模型

类加载器用来根据类的全限定名获取描述此类的二进制字节流。我们可以把类加载器分成以下4种:

  1. 启动类加载器:存在于虚拟机中,使用C++编写,负责将<Java_Home>/lib下面的类库加载到内存中(比如rt.jar);
  2. 拓展类加载器:它负责将<Java_Home>/lib/ext或者由系统变量 java.ext.dir指定位置中的类库加载到内存中;
  3. 应用程序类加载器:它负责将用户类路径中指定的类库加载到内存中;
  4. 自定义类加载器:就是指用户自己定义的类加载器。

在Java种存在以上多种类加载器,它们之间通过一定的规则相互配合,这个规则就是双亲委派模型:每个类加载器收到类加载请求时,都会先将请求委派给父类加载器去完成,所以,加载请求会一直传递到最顶层的类加载器。只有当类父类加载器无法完成加载请求的时候,该加载器才会自己去加载。当然,这不是强制的,我们也可以完全使用自己的一套逻辑。但双清委派模型的好处就在于,假如你自定义了一个类java.lang.Object,那么当使用双亲委派模型来加载的时候,会由子加载器不断向上传递加载请求到启动类加载器进行加载,因此Object在各种类加载环境中都是同一个类。这保障了Java系统的稳定性。

3、虚拟机字节码执行引擎

虚拟机是相对于物理机而言的,它们的区别是物理机的执行引擎直接建立在处理器、硬件、指令集和操作系统层面上,而虚拟机的执行引擎是自己实现的。所以,执行引擎也是Java虚拟机最核心的组成部分之一。

执行引擎用来执行我们写的业务逻辑,而业务逻辑就是指一些方法,所以虚拟机执行引擎就是用来执行各个方法的。而方法是通过栈帧来描述的,方法的执行是用栈帧入栈和出栈来描述的,栈帧中存储了方法的局部变量表、操作数栈、动态连接和方法返回地址等信息。所以说,执行引擎就是用来执行各个栈帧的。在虚拟机执行的时候,只有最顶层的栈帧是有效的,与之关联的方法就称为当前方法,并且执行引擎运行的所有字节码都是针对当前栈帧的。

方法的信息存储在栈帧中,而栈帧中的方法信息是从Class文件中读取来的。回到之前的Class类文件结构部分,每个方法是通过结构体method_info来描述的:

struct method_info
{
    u2 access_flags;         //方法修饰符掩码
    u2 name_index;           //方法名在常数表内的索引
    u2 descriptor_index;     //方法描述符,其值是常数表内的索引
    u2 attributes_count;     //方法的属性个数
    attribute_info **attributes;    //方法的属性数据,
};

在method_info中又包含了一个属性表集合attribute_info类型的attributes,方法的局部变量表需要的空间大小和操作栈的深度等就记录在其中。局部变量表用于存放方法参数和方法内的局部变量,当方法是实例方法的时候,局部变量表的第0位会被用来传递方法所属对象的引用,即this。Java虚拟机执行引擎是基于栈的执行引擎,这里的栈就是操作数栈。操作数栈的深度也是记录在方法属性集合的Code属性中的。

执行引擎的执行过程

我们可以用下面的一个程序来说明以下在实际的执行过程中,Java执行引擎是如何工作的。

public int cal() {
    int a = 100;
    int b = 200;
    int c = 300;
    return (a + b) * c; // 1
}

首先会在编译期确定方法的栈深度和局部变量表的长度,栈深度是由计算的过程得到的,而局部变量表的长度等于1个this加3个局部变量即4。当程序执行到1处时,局部变量表内会被填充为this、100、 200和300。程序计数器会随着代码当前执行到的位置而不断更新。而此时因为没有进行任何计算,所以栈还是空的。

接下来就开始进行计算:首先会把a压入栈中(其实时把局部变量表里的值压入栈中);然后把b压入栈中;接着将栈顶的两个元素先出栈,相加之后再入栈,此时栈中只有一个计算结果300;接下来再把c压入栈中;然后,把栈顶的两个元素出栈,执行完乘法之后再入栈,所以最终栈中只有一个90000;最后,使用ireturn指令结束方法并将栈顶的元素返回给方法的调用者。

总结

以上就是虚拟机执行子系统的一个过程,包含了从编译出的Class文件,到被加载到内存中、验证、初始化等,到最终在虚拟机中被执行等完整的过程。这里只是总结和梳理了相关的基础的知识点,在虚拟机中实际的执行过程肯定远比我们上述的内容更加复杂和精彩。


Java 虚拟机系列文章,

本系列以及其他系列的文章均维护在 Github 上面:Github / Awesome-Java,欢迎 Star & Fork. 如果你喜欢这篇文章,愿意支持作者的工作,请为这篇文章点个赞?!

基于Playwright与反反爬技术的BOSS直聘数据爬取实战 本文将详细介绍如何使用Python最新技术栈(Playwright+Asyncio)构建一个高效、稳定的BOSS直聘数据爬虫系统。文章包含完整的技术选型分析、反爬机制破解方案、分布式爬虫架构设计以及数据处理存储方案,提供可直接运行的代码示例,并探讨爬虫开发中的法律与伦理问题。 阅读详情

相关推荐

详解各类以太网标准10BASE-T/100BASE-T4/100BASE-FX/1000BASE-X等

以太网是现实世界中最普遍的一种计算机网络。以太网有两类:第一类是经典以太网,第二类是交换式以太网,使用了一种称为交换机的设备连接不同的计算机。经典以太网是以太网的原始形式,运行速度从3-10 Mbps不等;而交换式以太网正是广泛应用的以太网,可运行在100Mbps、1000Mbps和10000Mbps高速率,分别以快速以太网、千兆以太网和万兆以太网的形式呈现

不积跬步,无以至千里! 5万+

面试题之JVM篇

java面试之JVM篇

weixin_58289438的博客 793

告别配置烦恼!保姆级教程:在Windows 10/11上为QT5.14.2配置MSVC2017编译器(附VS2022组件避坑指南)

本文提供了一份详细的保姆级教程,指导如何在Windows 10/11上为QT5.14.2配置MSVC2017编译器,包括VS2022组件避坑指南。从环境准备到关键配置,再到调试工具验证和Qt Creator中的编译器设置,一步步解决常见问题,帮助开发者快速搭建稳定可靠的开发环境。

weixin_30615767的博客 501

JVM扫盲-2:虚拟机执行子系统

1、类文件结构 Java虚拟机只与Class文件相关联,它规定了Class文件应该具有的格式,而不论该文件是由什么语言编写并编译而来。所以,任何语言只要能够最终编译成符合Java虚拟机要求的Class文件,就可以运行在Java虚拟机上面。就是说,不论是使用Java, Scala, Kotlin, Groovy还是其他语言,只要编译出的Class文件符合虚拟机规范,那么都可以被虚拟机执行。所以,实际...

weixin_34005042的博客 203

Eplan2022 复制已有的宏文件生成新的原理图宏文件

Eplan2022 新建原理图宏文件

haier123888的博客 1万+

【项目实战】Qt6 录制屏幕、录制声音生成MP4、AVI、MA3等(完整源码,可直接编译使用)

本文将详细介绍如何利用Qt6多媒体模块中的QMediaCaptureSession、QMediaRecorder、QScreenCapture、QAudioInput、QMediaFormat等功能,实现对屏幕和声音的录制,并生成常见的媒体格式文件,如MP4、AVI、MA3等。我们将探讨如何利用这些功能来实现灵活的视频和音频参数设置,以满足不同需求下的录制和生成要求。我们将重点讨论如何配置视频和音频参数,包括分辨率、帧率、编解码器选择、音频采样率、声道设置等。

yegu001的博客 4937

深入理解JVM三:虚拟机执行子系统

代码编译的结果从本地机器码转变为字节码,是存储格式发展的一小步,却是编程语言的一大步 一、无关性的基石 实现语言无关性的基础仍然是虚拟机和字节码存储格式,Java虚拟机不和包括Java在内的任何语言绑定,它只与Class语言这种特定的二进制文件格式所关联。任一门功能性语言都可以表示为一个能被Java虚拟机所接受的有效的Class文件。 1.1 Class类文件的结构 Class文件是一组以8位字节...

hunt_ing的博客 320

别再到处找包了!Keil5芯片支持包(Pack)最全管理指南:安装、更新、迁移与离线备份

本文详细解析了Keil5芯片支持包(Pack)的全生命周期管理,包括安装、更新、迁移与离线备份的工程化解决方案。针对STM32F10x等常见芯片,提供了高效获取Pack的策略、版本管理技巧及团队协作方案,帮助开发者解决常见的'Device Family Pack missing'等问题,提升嵌入式开发效率。

weixin_28729205的博客 539

深入理解Java虚拟机之虚拟机执行子系统(读书笔记)

类文件结构 1 Class类文件结构 Class文件的类结构是怎样的? 任何一个Class文件都对应着唯一一个类或接口的定义信息,反过来,类或接口不一定都得定义在文件里(也可通过类加载器直接生成)。 Class文件是一组以8位字节为基础单位的二进制流,各数据项紧密排列。空间占用8位以上的数据项按照高位在前(最高位字节在地址最低位、最低位字节在地址最高位)方式分割为若干个8位字节存储。  

BliNkShadOw的博客 1951

Unity 面试篇⚠️ | Unity性能优化好,山寨机都能跑~

Unity 面试篇⚠️ | Unity性能优化好,山寨机都能跑~

m0_58523831的博客 1万+

RK3568 HDMI输出调试:分辨率切换与EDID解析

厂商信息(Vendor ID)显示器型号(Product ID)支持的分辨率列表色彩特性音频支持情况检查项方法HPD 信号EDID 读取当前模式TMDS 时钟PHY 状态。

小灰灰的博客 1480

三、JVM-虚拟机执行子系统

1.类文件结构 1.1 每个class文件头的4个字节,为魔数magic number,作用:确定这个文件是否能被虚拟机接受的class文件。 基于安全的考虑。0xCAFEBABE 1.2 紧接着魔数的4个字节存储的是class文件的版本号:第5,第6个字节是次版本号(minor version), 第7,第8个字节是主版本号(major version)用于判断虚拟机是否支持该版本。 ...

chenbin1991smile的博客 227

Dify 第 2 篇 Dify 插件离线安装

Dify是一个支持插件扩展的AI平台,其插件市场提供5种类型插件(模型、工具、Agent策略、扩展、插件包)。由于默认pip安装速度慢,可通过dify-plugin-repackaging工具将插件转换为离线包(使用阿里云镜像加速)。该工具提供两种方式:1)自备Python环境执行脚本;2)使用Docker环境(推荐)。脚本支持从Dify市场或GitHub下载插件,并重新打包为含依赖的离线包,便于通过"本地插件"方式快速安装。

甘蓝的专栏 1258

【jvm系列-01】初识虚拟机与java虚拟机

为了解决java的跨平台性问题,

zhenghuishengq的博客 9756

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

Jvm系列-Jvm概述(一)

目录 1,什么是JVM? 2,JVM跨平台及原理 3,JVM的分类 4,JVM的位置 5,JVM的体系结构 6,Java代码的执行流程 7,JVM的架构模型 8,JVM的生命周期 9,三大商业虚拟机 1,什么是JVM? JVM 是 java虚拟机,是用来执行java字节码(二进制的形式)的虚拟计算机。 jvm是运行在操作系统之上的,与硬件没有任何关系。 2,JVM跨平台及原理 跨平台:由Java编写的程序可以在不同的操作系统上运行:一次编写,多处运行。 原理:编译之后的字节码

理科男同学 5万+

精雕软件(JDpaint) 5.5 绿色版

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

【金九银十】JVM虚拟机栈执行原理深入详解

前言: 你好,早上、中午、下午、晚上好。我是Java2B哥(微信搜Java2B)。一名无缘985,日常996工程师。 2B哥今天继续教大家JVM知识。这次章节为: 【金三银四-JVM系列】CMS收集器与GC日志分析定位问题详解 【金三银四】JVM虚拟机CMS和G1收集器详解 什么是JVM 相信很多小伙伴都非常熟悉了,JVM不就是虚拟机吗?那虚拟机又是什么了?不是JVM嘛! 这不废话嘛,其实JVM...

程序员Monkey的专栏 7059

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

jvm虚拟机-什么是jvm

最近看了一段时间jdk源码,在看源码的过程中对数据的存储不是很清楚,当时就在想数据到底存在哪里去了?以前只是看博客什么的对jvm有一个大概的了解,但是没有深入研究。经过一段时间的研究<<深入理解java虚拟机后>>,才决定写一些关于jvm的文章,不为别的只为了同为技术的程序员们少走一些弯路,因为这本书第一遍读起来晦涩难懂,我也是查阅了大量的资料才渐渐的理解了什么意思。同时也为了留下自己的脚印,以后几

wenhao_1992的博客 5856

JVM | 第2部分:虚拟机执行子系统《深入理解 Java 虚拟机》

第2部分主题为虚拟机执行子系统,以此延伸出 class 类文件结构、虚拟机类加载机制、虚拟机字节码执行引擎等相关内容;

多氯环己烷的博客 1411

【Jvm基础篇2】虚拟机执行子系统

在发生 Minor GC 之前,虚拟机必须先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,那这一次 Minor GC 可以确保是安全的。如果不成立,则虚拟机会先查看-XX: HandlePromotionFailure 参数的设置值是否允许担保失败(Handle Promotion Failure);如果允许,那会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试进行一次 Minor GC,尽管这次 Minor GC 是有风险的;

优快云站内信: https://i.youkuaiyun.com/#/msg/chat/qyj19920704 645

深入理解Java虚拟机-虚拟机执行子系统

文章目录类文件结构概述无关性的基石Class类文件的结构魔数与 Class 文件的版本常量池访问标志类索引、父类索引与接口索引集合字段表集合方法表集合属性表集合字节码指令简介公有设计和私有实现Class文件结构的发展虚拟机类加载机制概述类加载时机类加载过程加载验证准备解析初始化类加载器类与类加载器双亲委派模型类加载器的关系类加载器的作用类加载器的特点类加载器的隔离问题破坏双亲委派模型字节码执行引擎...

ThinkWon的博客 1万+

JVM经典书籍

JVM系列 学习JVM的相关资料 - 《深入理解Java虚拟机——JVM高级特性与最佳实践(第2版)》 基于最新JDK1.7,围绕内存管理、执行子系统、程序编译与优化、高效并发等核心主题对JVM进行全面而深入的分析,深刻揭示JVM的工作原理。以实践为导向,通过大量与实际生产环境相结合的案例展示了解决各种常见JVM问题的技巧和实践 《揭秘Java虚拟机:JVM设计原理与实现》 ...

luoyoub的博客 3万+

一篇文章带你全面了解JVM虚拟机,大厂面试再也不用慌了!

前言 java虚拟机(java virtual machine,JVM),一种能够运行java字节码的虚拟机。作为一种编程语言的虚拟机,实际上 不只是专用于Java语言,只要生成的编译文件匹配JVM对加载编译文件格式要求,任何语言都可以由JVM编译运行。 比如kotlin、scala等。 jvm有很多,不只是Hotspot,还有JRockit、J9等等 这里再给大家免费分享一波Java架构师学习资料,针对JAVA底层的架构技术 资料领取方式:点击这里【暗号:优快云】 JVM的基本结构 JVM由三

lubanziya的博客 3464

JVM-类加载子系统

本文和大家聊聊 JVM 的基本概念和类加载子系统。

DC1020的博客 1296

JVM 的执行子系统

JVM 的执行子系统。 一、Class类文件结构 1. JVM的平台无关性 与平台无关性是建立在操作系统上,虚拟机厂商提供了许多可以运行在各种不同平台的虚拟机,它们都可以载入和执行字节码,从而实现程序的一次编写,到处运行。 各种不同平台的虚拟机与所有平台都统一使用的程序存储格式——字节码(ByteCode)是构成平台无关性的基石,也是语言无关性的基础。Java 虚拟机不和包括 Java 在内的任何语言绑定,它只与“Class 文件”这种特定的二进制文件格式所关联,Class 文件中包含了 Java 虚拟机

IT王小二的博客 1224

Java虚拟机之JVM执行子系统

JVM执行子系统,主要是说虚拟机如何执行程序。本文楼主将带你去学习Class文件结构以及类加载过程。 1.Class类文件结构 看不懂 2.虚拟机类加载机制 代码编译的结果是从本地机器码转变为字节码,存储格式发展的一小步,确是编程语言发展的一大步。 2.1 类加载时机 类从被加载到虚拟机内存中开始,到卸载出内存为止,整个生命周期包括7个阶段。 加载、验证、准备、初始化和卸载这5个阶段的顺序是确定...

lonely_bin的博客 110

【JVM】详解执行引擎子系统、JIT即时编译原理

由于机器码都是由0和1组成的指令代码,可读性实在太差,所以慢慢的推出了指令,用于替代机器码的编码方式。在开发过程中遇到这样的问题,某个服务因为要扩容,原本按照之前的集群规模计算,再扩容1/4之一左右的机器是可以承载新的流量的,但后面启动之后出现了问题,新启动的机器网关那边分配转发流量之后,立马就宕机了,最开始因为是第一次碰到这样的问题,以为是机器或者程序中代码的问题,最后排查发现都没问题,后来尝试将扩容的机器数量从原本计划的1/4增加到1/3之后,流量平滑的被迁移到了新的机器,没有再出现宕机的故障。

blblccc 1283
上一篇: JVM 系列-3:虚拟机内存模型与高效并发
下一篇: JVM 系列-1:虚拟机内存管理
开发者如是说
博客等级 码龄10年 67粉丝 163原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值