科普文:一文搞懂jvm(一)jvm概叙

123 篇文章 0 订阅
110 篇文章 0 订阅

概叙

        因篇幅限制,这里将分为多篇文章来讲解。

科普文:一文搞懂jvm(一)jvm概叙-CSDN博客

科普文:一文搞懂jvm原理(二)类加载器-CSDN博客

科普文:一文搞懂jvm原理(三)执行引擎-CSDN博客

科普文:一文搞懂jvm原理(四)运行时数据区-CSDN博客

科普文:一文搞懂jvm原理(三)执行引擎之垃圾回收器-CSDN博客

为什么要学习jvm?

        1.这是3年+的java程序员必备技能。

        2.理解“跨平台”、熟悉jvm组织结构、类的加载、双亲委派、对象在jvm中的生命周期、STD。

        3.最终目的只有一个:用jdk提供的工具分析排查解决java问题。

        你所遇到java问题示例

如何设置各种JVM参数,这些参数设置是否合理?
线上系统突然卡死,响应慢,甚至系统无法访问,有OOM,该如何分析解决???
如何解决线上JVM GC问题?
针对监控平台的告警,或者某些指标的“抽风/凸起/波谷/波峰”如何分析??
如何配合测试大佬,完成性能测试要求,确保你的项目性能指标达标??
如何让你的系统更快?
如何让你的系统避免出现性能瓶颈?
如何给产品大佬打包票,你的系统能支持他的业务要求?
如何向老板解释你为什么要买这么多服务器??
。。

jvm为什么是跨平台语言?

        JVM简介: JVM是Java Virtual Machine的缩写,中文翻译为Java虚拟机,是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。简单来说JVM是用来解析和运行Java程序的。随着Java7的正式发布,Java虚拟机的设计者们通过JSR-292规范基本实现在Java虚拟机平台上运行非Java语言编写的程序。

        Java有一个非常重要的特点“平台的无关性”,就是使用JVM实现的。Java语言只需要生成Jvm上运行的目标代码(字节码),就可以在多种平台上不加修改地运行,因为JVM本身屏蔽了与具体平台相关的信息,使得Java能够“一次编译,到处运行”。

        所以jvm根本不关心运行在其内部的程序到底是使用何种编程语言编写的,它只关心“字节码”文件。

        也就是说Java虚拟机拥有语言无关性,并不会单纯地与Java语言“终身绑定”,只要其他编程语言的编译结果满足并包含Java虚拟机的内部指令集、符号表以及其他的辅助信息,它就是一个有效的字节码文件,就能够被虚拟机所识别并装载运行

        结论:Java不是最强大的语言,但是JVM是最强大的虚拟机

jvm体系结构概叙

JVM介绍

        所谓虚拟机(Virtual Machine),就是一台虚拟的计算机,它是一款软件,用来执行一系列虚拟计算机指令。

        大体上虚拟机可以分为系统虚拟机和程序虚拟机:

  1. 大名鼎鼎的Virtual Box,VMware就属于系统虚拟机,它们完全是对物理计算机硬件的仿真(模拟),提供了一个可运行完整操作系统的软件平台
  2. 程序虚拟机的典型代表就是Java虚拟机,它专门为执行单个计算机程序而设计,在Java虚拟机中执行的指令我们称为Java字节码指令

        无论是系统虚拟机还是程序虚拟机,在上面运行的软件都被限制于虚拟机提供的资源中。

        

        JVM全称是Java Virtual Machine(java虚拟机)。它之所以被称之为是“虚拟”的,就是因为它仅仅是由一个规范来定义的抽象计算机。我们平时经常使用的Sun HotSpot虚拟机只是其中一个具体的实现(另外还有BEA JRockit、IBM J9等等虚拟机)。

        JVM的设计目标是提供一个基于抽象规格描述的计算机模型,为解释程序开发人员提供很好的灵活性,同时也确保Java代码可在符合该规范的任何系统上运行。JVM对其实现的某些方面给出了具体的定义,特别是对Java可执行代码,即字节码(Bytecode)的格式给出了明确的规格。这一规格包括操作码和操作数的语法和数值、标识符的数值表示方式、以及Java类文件中的Java对象、常量缓冲池在JVM的存储映象。这些定义为JVM解释器开发人员提供了所需的信息和开发环境。Java的设计者希望给开发人员以随心所欲使用Java的自由。

        JVM是java的核心和基础,在java编译器和os平台之间的虚拟处理器。它是一种基于下层的操作系统和硬件平台并利用软件方法来实现的抽象的计算机,可以在上面执行java的字节码程序。

Java虚拟机

  1. Java虚拟机是一台执行Java字节码的虚拟计算机,它拥有独立的运行机制,其运行的Java字节码也未必由Java语言编译而成。
  2. JVM平台的各种语言可以共享Java虚拟机带来的跨平台性、优秀的垃圾回器,以及可靠的即时编译器。
  3. Java技术的核心就是Java虚拟机(JVM,Java Virtual Machine),因为所有的Java程序都运行在Java虚拟机内部。

jvm作用:


        Java虚拟机就是二进制字节码的运行环境,负责装载字节码到其内部,解释/编译为对应平台上的机器指令执行。每一条Java指令,Java虚拟机规范中都有详细定义,如怎么取操作数,怎么处理操作数,处理结果放在哪里

jvm特点:


        跨平台:一次编译,到处运行;这是JVM 的主要特征之一,Java 程序在编译为字节码后可以在任何支持 JVM 的平台上运行,摆脱了硬件平台的束缚,实现了"一次编译,到处运行"的理想。
        自动内存管理(内存分配和回收):JVM 提供了自动的内存管理机制,包括内存分配、垃圾回收和内存优化。开发者无需手动分配和释放内存,JVM 会自动管理对象的生命周期和内存回收,通过垃圾回收器(Garbage Collector)自动回收不再使用的对象,避免了内存泄漏和悬挂指针等问题。
        即时编译:JVM 通过即时编译器将热点代码动态编译成本地机器码,提高程序的执行性能。编译器可以根据程序的运行情况进行优化,使得Java应用能随着运行事件的增长而获得更高的性能。

        

JVM的位置

        JVM运行在操作系统上,不与硬件直接交互。

JVM组织结构

           上图从类加载器、运行时数据区、执行引擎和垃圾回收器。它们分别对应了JVM的不同功能和模块,相互协作来执行Java程序。

        主要包括两个子系统和两个组件: Class loader(类装载器) 子系统,Execution engine(执行引擎) 子系统;Runtime data area (运行时数据区域)组件, Native interface(本地接口)组件。

  1. Java字节码:Java源代码经过编译后生成的字节码文件,它是一种与操作系统无关的中间代码。

  2. 类加载器(ClassLoader):JVM会使用类加载器将字节码文件加载到内存中,并在运行时动态地链接和加载类的定义。

  3. 运行时数据区(Runtime Data Area):JVM在内存中划分了几个不同的区域,用于存储不同类型的数据。

    • 方法区(Method Area):用于存储类的结构信息,如类的字段、方法、构造器等。

    • 堆(Heap):用于存储对象实例,以及数组等动态分配的数据。

    • 栈(Stack):用于存储方法的局部变量、参数、返回值和方法的调用信息。

    • 本地方法栈(Native Method Stack):用于存储Java程序调用本地方法的相关信息。

    • 程序计数器(Program Counter):用于记录当前线程执行的字节码指令的地址。

  4. 执行引擎(Execution Engine):JVM的核心组件之一,负责执行字节码指令。
    • 解释器(Interpreter):逐行解释执行字节码指令。

    • 即时编译器(Just-In-Time Compiler,JIT):将热点代码(被频繁执行的代码)编译为本地机器码,提高运行效率。

    • 垃圾回收器(Garbage Collector):JVM的另一个核心组件,负责自动的内存管理和垃圾回收。
      • 在堆中分配内存空间,并跟踪对象的引用关系。

      • 定期检查和回收不再使用的对象释放内存。

      • 不同的垃圾回收算法和策略,如标记-清除、复制、标记-整理等,可以根据实际情况进行选择。

  5. 本地方法接口和本地方法库:

    • java代码通过操作本地方法接口,即native修饰的接口,去操作本地方法库中的方法,从而完成对系统外部接口的调用。

    • 与native libraries交互,是其它编程语言交互的接口。Java里声明为native的方法多数在jdk/src/<platform>/native里可以找到。其中可以是share,也就是平台中立的代码;也可以是某个具体平台。这个native目录里的结构跟Java源码结构一样是按包名来组织的。不过需要提醒的是,这些native方法不是“JVM”的,是“类库”的,不在JVM里面。

JVM的生命周期

        当启动一个Java程序时,一个虚拟机实例也就诞生了。当该程序关闭退出,这个虚拟机实例也就随之消亡。如果在同一台计算机上同时运行三个Java程序,将得到三个Java虚拟机实例。每个Java程序都运行于它自己的Java虚拟机实例中。

        JVM实例对应了一个独立运行的java程序,它是进程级别。

3.1、启动

        Java虚拟机的启动是通过引导类加载器(bootstrap class loader)创建一个初始类(initial class)来完成的,这个类是由虚拟机的具体实现指定的。

        启动一个Java程序时,一个JVM实例就产生了,任何一个拥有public static void main(String[] args)函数的class都可以作为JVM实例运行的起点。

3.2、运行

        main()作为该程序初始线程的起点,任何其他线程均由该线程启动。JVM内部有两种线程:守护线程和非守护线程,main()属于非守护线程,守护线程通常由JVM自己使用,java程序也可以标明自己创建的线程是守护线程

  1.         一个运行中的Java虚拟机有着一个清晰的任务:执行Java程序
  2.         程序开始执行时他才运行,程序结束时他就停止
  3.         执行一个所谓的Java程序的时候,真真正正在执行的是一个叫做Java虚拟机的进程

3.3、消亡

        当程序中的所有非守护线程都终止时,JVM才退出;若安全管理器允许,程序也可以使用Runtime类或者System.exit()来退出。

        有如下的几种情况:

  1.         程序正常执行结束
  2.         程序在执行过程中遇到了异常或错误而异常终止
  3.         由于操作系统用现错误而导致Java虚拟机进程终止
  4.         某线程调用Runtime类或System类的exit()方法,或Runtime类的halt()方法,并且Java安全管理器也允许这次exit()或halt()操作。
  5.         除此之外,JNI(Java Native Interface)规范描述了用JNI Invocation API来加载或卸载 Java虚拟机时,Java虚拟机的退出情况。

JVM运行原理

        操作系统装入JVM是通过jdk中java.exe来完成,通过下面4步来完成JVM环境。

4.1、JVM装入环境

        JVM提供的方式是操作系统的动态连接文件。既然是文件那就存在一个装入路径的问题,Java是怎么找这个路径的呢?下面基于Windows的实现的分析。

        首先查找jre路径,Java是通过GetApplicationHome api来获得当前的Java.exe绝对路径,c:\jdk1.7.0_45\bin\Java.exe,然后截取到绝对路径c:\jdk1.7.0_45\,判断c:\jdk1.7.0_45\bin\Java.dll文件是否存在,如果存在就把c:\jdk1.7.0_45\作为jre路径,如果不存在则判断c:\jdk1.7.0_45\jre\bin\Java.dll是否存在,如果存在这c:\jdk1.7.0_45\jre作为jre路径,如果不存在调用GetPublicJREHome查HKEY_LOCAL_MACHINE\Software\JavaSoft\Java Runtime Environment\“当前JRE版本号”\JavaHome的路径为jre路径。

        然后装载JVM.cfg文件。在我们的jdk目录中jre\bin\server和jre\bin\client都有JVM.dll文件存在,而Java正是通过JVM.cfg配置文件来管理这些不同版本的JVM.dll的。

        最后获得JVM.dll的路径,JRE路径+\bin+\JVM类型字符串+\JVM.dll就是JVM的文件路径了,但是如果在调用Java程序时用-XXaltJVM=参数指定的路径path,就直接用path+\JVM.dll文件做为JVM.dll的文件路径。

4.2、装载JVM.dll

        通过第一步已经找到了JVM的路径,Java通过LoadJavaVM来装入JVM.dll文件。装入工作很简单,就是调用Windows API函数:LoadLibrary装载JVM.dll动态连接库.然后把JVM.dll中的导出函数JNI_CreateJavaVM和JNI_GetDefaultJavaVMInitArgs挂接到InvocationFunctions变量的CreateJavaVM和GetDefaultJavaVMInitArgs函数指针变量上。JVM.dll的装载工作宣告完成。

4.3、初始化JVM

        挂接到JNIENV(JNI调用接口)实例,获得本地调用接口,这样就可以在Java中调用JVM的函数了。调用InvocationFunctions->CreateJavaVM也就是JVM中JNI_CreateJavaVM方法获得JNIEnv结构的实例。

4.4、运行Java程序

        Java程序有两种方式一种是jar包,一种是class。运行jar(Java -jar XXX.jar)的时候,Java.exe调用GetMainClassName函数,该函数先获得JNIEnv实例然后调用Java类Java.util.jar.JarFileJNIEnv中方法getManifest()并从返回的Manifest对象中取getAttributes("Main-Class")的值即jar包中文件:META-INF/MANIFEST.MF指定的Main-Class的主类名作为运行的主类。之后main函数会调用Java.c中LoadClass方法装载该主类(使用JNIEnv实例的FindClass)。main函数直接调用Java.c中LoadClass方法装载该类。如果是执行class方法。main函数直接调用Java.c中LoadClass方法装载该类。

        然后main函数调用JNIEnv实例的GetStaticMethodID方法查找装载的class主类中“public static void main(String[] args)”方法,并判断该方法是否为public方法,然后调用JNIEnv实例的CallStaticVoidMethod方法调用该Java类的main方法。

JVM、JRE、JDK之间的关系

        从上图就能画出这个包含关系,想想看,有jre就能跑java代码了,为啥我们很多生产环境却装比jre大很多的jdk?其实也简单,因为我们现有条件下,对环境资源要求的不苛刻,几百兆的jdk无伤大雅;另外一个就是排查问题方便(其实很多时候开发是接触不到生产环境,运维大佬可能视情况而定用这些工具排查问题)。但是如果我们的环境换成、工控机床等嵌入式设备上时,就需要考虑是不是安装jre更合适。

JDK(Java Development Kit):


        JDK(Java开发工具包):JDK是开发Java应用程序所需的完整工具集合。它包括JRE,还包括用于编译、调试和运行Java代码的工具,如编译器(javac)、调试器(jdb)、Java文档生成器(javadoc)等。

        JDK是面向开发人员的,适用于编写、编译和调试Java代码。

JRE(Java Runtime Environment):


        Java运行时环境,包含了JVM,Java基础类库。是使用Java语言编写程序运行的所需环境。

        JRE允许您运行已编译的Java程序(.class文件),但是它通常不包括用于编译Java代码的工具。

JVM(Java Virtual Machine)


        Java虚拟机,JVM是Java应用程序运行的环境。它是一个虚拟的计算机,负责解释和执行Java字节码的虚拟机,它能够将这些字节码转换为特定操作系统能够理解和执行的指令。

        JVM是平台无关的,这意味着您编写的Java代码可以在安装了相应JVM的任何操作系统上运行。

        JVM是用来执行Java字节码的虚拟机,但如果没有Java标准类库,它可能无法正常执行包含对这些类库的依赖的程序。Java标准类库提供了许多Java程序所需的核心功能和工具类,包括输入输出、集合框架、网络通信等。如果程序依赖于这些标准类库的某些部分,而环境中没有这些类库的实现,那么程序可能无法正常运行或者会出现异常。

        一些简单的Java程序也许不需要大部分标准类库,但绝大多数实际的Java应用都依赖于这些类库的功能。所以,尽管JVM可以执行Java字节码,但缺少Java标准类库可能会导致程序无法运行或出现异常。

JVM发展历程


Sun Classic VM


        早在1996年Java1.0版本的时候,Sun公司发布了一款名为sun classic VM的Java虚拟机,它同时也是世界上第一款商用Java虚拟机,JDK1.4时完全被淘汰。

        这款虚拟机内部只提供解释器,没有即时编译器,因此效率比较低。

【即时编译器会把热点代码的本地机器指令缓存起来,那么以后使用热点代码的时候,效率就比较高】

        如果使用JIT编译器,就需要进行外挂。但是一旦使用了JIT编译器,JIT就会接管虚拟机的执行系统。解释器就不再工作,解释器和编译器不能配合工作。

        我们将字节码指令翻译成机器指令也是需要花时间的,如果只使用JIT,就需要把所有字节码指令都翻译成机器指令,就会导致翻译时间过长,也就是说在程序刚启动的时候,等待时间会很长。
        而解释器就是走到哪,解释到哪,现在Hotspot内置了此虚拟机。


Exact VM


为了解决上一个虚拟机问题,jdk1.2时,Sun提供了此虚拟机。

Exact Memory Management:准确式内存管理

也可以叫Non-Conservative/Accurate Memory Management
虚拟机可以知道内存中某个位置的数据具体是什么类型。
具备现代高性能虚拟机的维形

热点探测(寻找出热点代码进行缓存)
编译器与解释器混合工作模式
只在Solaris平台短暂使用,其他平台上还是classic vm,英雄气短,终被Hotspot虚拟机替换

HotSpot VM(重点)


HotSpot历史

最初由一家名为“Longview Technologies”的小公司设计
1997年,此公司被Sun收购;2009年,Sun公司被甲骨文收购。
JDK1.3时,HotSpot VM成为默认虚拟机
目前Hotspot占有绝对的市场地位,称霸武林。

不管是现在仍在广泛使用的JDK6,还是使用比例较多的JDK8中,默认的虚拟机都是HotSpot
Sun/oracle JDK和openJDK的默认虚拟机
因此本课程中默认介绍的虚拟机都是HotSpot,相关机制也主要是指HotSpot的GC机制。(比如其他两个商用虚机都没有方法区的概念)

从服务器、桌面到移动端、嵌入式都有应用。

名称中的HotSpot指的就是它的热点代码探测技术。

通过计数器找到最具编译价值代码,触发即时编译或栈上替换
通过编译器与解释器协同工作,在最优化的程序响应时间与最佳执行性能中取得平衡


BEA JRockit(商用三大虚拟机之一)


专注于服务器端应用:它可以不太关注程序启动速度,因此JRockit内部不包含解析器实现,全部代码都靠即时编译器编译后执行。

大量的行业基准测试显示,JRockit JVM是世界上最快的JVM

使用JRockit产品,客户已经体验到了显著的性能提高(一些超过了70%)和硬件成本的减少(达50%)。
优势:全面的Java运行时解决方案组合

JRockit面向延迟敏感型应用的解决方案JRockit Real Time提供以毫秒或微秒级的JVM响应时间,适合财务、军事指挥、电信网络的需要
Mission Control服务套件,它是一组以极低的开销来监控、管理和分析生产环境中的应用程序的工具。
2008年,JRockit被Oracle收购。

Oracle表达了整合两大优秀虚拟机的工作,大致在JDK8中完成。整合的方式是在HotSpot的基础上,移植JRockit的优秀特性。

高斯林:目前就职于谷歌,研究人工智能和水下机器人

IBM的J9(商用三大虚拟机之一)


全称:IBM Technology for Java Virtual Machine,简称IT4J,内部代号:J9

市场定位与HotSpot接近,服务器端、桌面应用、嵌入式等多用途VM广泛用于IBM的各种Java产品。

目前,有影响力的三大商用虚拟机之一,也号称是世界上最快的Java虚拟机。

2017年左右,IBM发布了开源J9VM,命名为openJ9,交给Eclipse基金会管理,也称为Eclipse OpenJ9

OpenJDK -> 是JDK开源了,包括了虚拟机

KVM和CDC/CLDC Hotspot
Oracle在Java ME产品线上的两款虚拟机为:CDC/CLDC HotSpot Implementation VM

KVM(Kilobyte)是CLDC-HI早期产品


目前移动领域地位尴尬,智能机被Android和iOS二分天下。

KVM简单、轻量、高度可移植,面向更低端的设备上还维持自己的一片市场

智能控制器、传感器
老人手机、经济欠发达地区的功能手机
所有的虚拟机的原则:一次编译,到处运行。

Azul VM


前面三大“高性能Java虚拟机”使用在通用硬件平台上

这里Azul VW和BEA Liquid VM是与特定硬件平台绑定、软硬件配合的专有虚拟机:高性能Java虚拟机中的战斗机。

Azul VM是Azul Systems公司在HotSpot基础上进行大量改进,运行于

Azul Systems公司的专有硬件Vega系统上的Java虚拟机。

每个Azul VM实例都可以管理至少数十个CPU和数百GB内存的硬件资源,并提供在巨大内存范围内实现可控的GC时间的垃圾收集器、专有硬件优化的线程调度等优秀特性。

2010年,Azul Systems公司开始从硬件转向软件,发布了自己的Zing JVM,可以在通用x86平台上提供接近于Vega系统的特性。

Liquid VM


高性能Java虚拟机中的战斗机。

BEA公司开发的,直接运行在自家Hypervisor系统上

Liquid VM即是现在的JRockit VE(Virtual Edition)。

Liquid VM不需要操作系统的支持,或者说它自己本身实现了一个专用操作系统的必要功能,如线程调度、文件系统、网络支持等。

随着JRockit虚拟机终止开发,Liquid vM项目也停止了

Apache Marmony


Apache也曾经推出过与JDK1.5和JDK1.6兼容的Java运行平台Apache Harmony。

它是IElf和Intel联合开发的开源JVM,受到同样开源的Open JDK的压制,Sun坚决不让Harmony获得JCP认证,最终于2011年退役,IBM转而参与OpenJDK

虽然目前并没有Apache Harmony被大规模商用的案例,但是它的Java类库代码吸纳进了Android SDK。

Micorsoft JVM


微软为了在IE3浏览器中支持Java Applets,开发了Microsoft JVM。

只能在window平台下运行。但确是当时Windows下性能最好的Java VM。

1997年,Sun以侵犯商标、不正当竞争罪名指控微软成功,赔了Sun很多钱。微软WindowsXP SP3中抹掉了其VM。现在Windows上安装的jdk都是HotSpot

Taobao JVM


由AliJVM团队发布。阿里,国内使用Java最强大的公司,覆盖云计算、金融、物流、电商等众多领域,需要解决高并发、高可用、分布式的复合问题。有大量的开源产品。

基于OpenJDK开发了自己的定制版本AlibabaJDK,简称AJDK。是整个阿里Java体系的基石。

基于OpenJDK Hotspot VM发布的国内第一个优化、深度定制且开源的高性能服务器版Java虚拟机。

创新的GCIH(GCinvisible heap)技术实现了off-heap,即将生命周期较长的Java对象从heap中移到heap之外,并且GC不能管理GCIH内部的Java对象,以此达到降低GC的回收频率和提升GC的回收效率的目的。
GCIH中的对象还能够在多个Java虚拟机进程中实现共享
使用crc32指令实现JvM intrinsic降低JNI的调用开销
PMU hardware的Java profiling tool和诊断协助功能
针对大数据场景的ZenGC
taobao vm应用在阿里产品上性能高,硬件严重依赖inte1的cpu,损失了兼容性,但提高了性能

目前已经在淘宝、天猫上线,把Oracle官方JvM版本全部替换了。


Dalvik VM


谷歌开发的,应用于Android系统,并在Android2.2中提供了JIT,发展迅猛。

Dalvik VM只能称作虚拟机,而不能称作“Java虚拟机”,它没有遵循 Java虚拟机规范

不能直接执行Java的Class文件

基于寄存器架构,不是jvm的栈架构。

执行的是编译以后的dex(Dalvik Executable)文件。执行效率比较高。

它执行的dex(Dalvik Executable)文件可以通过class文件转化而来,使用Java语法编写应用程序,可以直接使用大部分的Java API等。
Android 5.0使用支持提前编译(Ahead of Time Compilation,AoT)的ART VM替换Dalvik VM。

Graal VM(未来虚拟机)


2018年4月,Oracle Labs公开了GraalvM,号称 “Run Programs Faster Anywhere”,勃勃野心。与1995年java的”write once,run anywhere”遥相呼应。

GraalVM在HotSpot VM基础上增强而成的跨语言全栈虚拟机,可以作为“任何语言”的运行平台使用。语言包括:Java、Scala、Groovy、Kotlin;C、C++、Javascript、Ruby、Python、R等

支持不同语言中混用对方的接口和对象,支持这些语言使用已经编写好的本地库文件

工作原理是将这些语言的源代码或源代码编译后的中间格式,通过解释器转换为能被Graal VM接受的中间表示。Graal VM提供Truffle工具集快速构建面向一种新语言的解释器。在运行时还能进行即时编译优化,获得比原生编译器更优秀的执行效率。

如果说HotSpot有一天真的被取代,Graalvm希望最大。但是Java的软件生态没有丝毫变化。
 

  • 25
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

-无-为-

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

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

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

打赏作者

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

抵扣说明:

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

余额充值