JAVA—JVM详解_java jvm-程序员宅基地

技术标签: jvm  面试  java  阿里巴巴资料职业发展  后端  学习路线  开发语言  

JAVA—JVM详解

一、JVM

1、什么是JVM
  • JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的
  • 引入Java语言虚拟机后,Java语言在不同平台上运行时不需要重新编译。Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。
  • 通过JVM,Java实现了平台无关性,Java语言在不同平台运行时不需要重新编译,只需要在该平台上部署JVM就可以了。因而能实现一次编译多处运行。
2、JRE和JDK
  • JRE:Java Runtime Environment,也就是JVM的运行平台,联系平时用的虚拟机,大概可以理解成JRE=虚拟机平台+虚拟机本体(JVM)。类似于你电脑上的VMWare+适用于VMWare的Ubuntu虚拟机。这样我们也就明白了JVM到底是个什么。
  • JDK:Java Develop Kit,Java的开发工具包,JDK本体也是Java程序,因此运行依赖于JRE,由于需要保持JDK的独立性与完整性,JDK的安装目录下通常也附有JRE。目前Oracle提供的Windows下的JDK安装工具会同时安装一个正常的JRE和隶属于JDK目录下的JRE。
3、JVM位置

在这里插入图片描述

4、JVM体系结构
  • 简图:
    在这里插入图片描述
  • 详图:
    在这里插入图片描述

二、JVM详解

5、ClassLoader(类加载器)
(1)、类加载的机制的层次结构

每个编写的".java"拓展名类文件都存储着需要执行的程序逻辑,这些".java"文件经过Java编译器编译成拓展名为".class"的文件,“.class"文件中保存着Java代码经转换后的虚拟机指令,当需要使用某个类时,虚拟机将会加载它的”.class"文件,并创建对应的class对象,将class文件加载到虚拟机的内存,这个过程称为类加载,这里我们需要了解一下类加载的过程,如下:
在这里插入图片描述
在虚拟机提供了3种类加载器,引导(Bootstrap)类加载器、扩展(Extension)类加载器、系统(System)类加载器(也称应用类加载器),下面分别介绍

  • 启动(Bootstrap)类加载器
    启动类加载器主要加载的是JVM自身需要的类,这个类加载使用C++语言实现的,是虚拟机自身的一部分,它负责将 <JAVA_HOME>/lib路径下的核心类库或-Xbootclasspath参数指定的路径下的jar包加载到内存中,注意必由于虚拟机是按照文件名识别加载jar包的,如rt.jar,如果文件名不被虚拟机识别,即使把jar包丢到lib目录下也是没有作用的(出于安全考虑,Bootstrap启动类加载器只加载包名为java、javax、sun等开头的类)。

  • 扩展(Extension)类加载器
    扩展类加载器是指Sun公司(已被Oracle收购)实现的sun.misc.Launcher$ExtClassLoader类,由Java语言实现的,是Launcher的静态内部类,它负责加载<JAVA_HOME>/lib/ext目录下或者由系统变量-Djava.ext.dir指定位路径中的类库,开发者可以直接使用标准扩展类加载器。

  • 系统(System)类加载器
    也称应用程序加载器是指 Sun公司实现的sun.misc.Launcher$AppClassLoader。它负责加载系统类路径java -classpath或-D java.class.path 指定路径下的类库,也就是我们经常用到的classpath路径,开发者可以直接使用系统类加载器,一般情况下该类加载是程序中默认的类加载器,通过ClassLoader#getSystemClassLoader()方法可以获取到该类加载器。

  • 全流程简图
    在这里插入图片描述

    //代码详解
    public class TestClassLoader {
    public static void main(String[] args) {

        Person person_01 = new Person();
        Person person_02 = new Person();
        Person person_03 = new Person();
    
        //发现person_01,person_02,person_03的hashCode不一致,代表这三个实例化对象虽隶属于一个Class,即Person,但它们的hashCode值不一样,即代表内存空间的首地址不同,即这仨对象开辟在不同的内存空间。
        System.out.println(person_01.hashCode());
        System.out.println(person_02.hashCode());
        System.out.println(person_03.hashCode());
    
        //Person实例化对象person_01通过getClass()方法得到Class对象Person
        Class Person = person_01.getClass();
        //Person通过getClassLoader()方法得到系统类加载器
        ClassLoader myClassLoader = Person.getClassLoader();
        System.out.println(myClassLoader.hashCode());
        //加载器对象myClassLoader通过getParent()方法得到拓展类加载器
        ClassLoader myParentClassLoader = myClassLoader.getParent();
        System.out.println(myParentClassLoader.hashCode());
        //加载器对象myGPClassLoader通过getParent()方法得到引导类加载器
        ClassLoader myGPClassLoader = myParentClassLoader.getParent();
        System.out.println(myGPClassLoader.hashCode()); //发现报错,无法通过方法获取引导类加载器
    }
    

    }
    class Person{}

(2)、双亲委派机制
  • 双亲委派模式要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器,请注意双亲委派模式中的父子关系并非通常所说的类继承关系,而是采用组合关系来复用父类加载器的相关代码,类加载器间的关系如下:
    在这里插入图片描述
  • 双亲委派机制工作原理为如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载。
  • 双亲委派机制优点:
    • 因为双亲委派是向上委托加载的,所以它可以确保类只被加载一次, 避免重复加载
    • Java的核心API都是通过引导类加载器进行加载的,如果别人通过定义同样路径的类比如java.lang.Integer,类加载器通过向上委托,两个Integer,那么最终被加载的应该是jdk的Integer类,而并非我们自定义的,这样就 避免了我们恶意篡改核心包的风险
6、沙箱安全机制
  • 沙箱机制就是将 Java 代码限定在虚拟机(JVM)特定的运行范围中,并且严格限制代码对本地系统资源访问,通过这样的措施来保证对代码的有效隔离,防止对本地系统造成破坏。沙箱主要限制系统资源访问,那系统资源包括什么?——CPU、内存、文件系统、网络。
  • 组成沙箱的基本组件:
    • 1.字节码校验器(bytecode verifier) :确保Java类文件遵循Java语言规范。这样可以帮助Java程序实现内存保护。但并不是所有的类文件都会经过字节码校验,比如核心类。
    • 2.类裝载器(class loader) :其中类装载器在3个方面对Java沙箱起作用
      防止恶意代码去干涉善意的代码;
      守护了被信任的类库边界;
      将代码归入保护域,确定了代码可以进行哪些操作。
7、本地方法栈(Native)
  • 程序中使用:private native void start0();
    1.凡是带了native关键字的,说明java的作用范围达不到了,回去调用底层c语言的库!
    2.会进入本地方法栈,然后去调用本地方法接口将native方法引入执行
  • 本地方法栈(Native Method Stack)
    内存区域中专门开辟了一块标记区域: Native Method Stack,负责登记native方法,在执行引擎( Execution Engine )执行的时候通过本地方法接口(JNI)加载本地方法库中的方法。
  • 本地方法接口(JNI)
    本地接口的作用是融合不同的编程语言为Java所用,它的初衷是融合C/C++程序, Java在诞生的时候是C/C++横行的时候,想要立足,必须有调用C、C++的程序,然后在内存区域中专门开辟了一块标记区域: Native Method Stack,负责登记native方法,在执行引擎( Execution Engine )执行的时候通过本地方法接口(JNI)加载本地方法库中的方法。
8、PC程序计数器(了解)

程序计数器: Program Counter Register

  • 每个线程都有一个程序计数器,是线程私有的,就是一个指针, 指向方法区中的方法字节码(用来存储指向像一条指令的地址, 也即将要执行的指令代码),在执行引擎读取下一条指令, 是一个非常小的内存空间,几乎可以忽略不计。
  • 为什么需要程序计数器?
    • 记录要执行的代码位置,防止线程切换重新执行字节码执行引擎修改程序计数器的值
9、方法区(Method Area)
  • 方法区是被所有线程共享,所有字段和方法字节码,以及一些特殊方法,如构造函数,接口代码也在此定义,简单说,所有定义的方法的信息都保存在该区域,此区域属于共享区间。
  • 静态变量(static)、常量(final)、类信息(构造方法、接口定义)(Class)、运行时的常量池存在方法区中,但是实例变量存在堆内存中,和方法区无关
10、栈
  • 栈:后进先出,每个线程都有自己的栈,栈内存主管程序的运行,生命周期和线程同步,线程结束,栈内存也就是释放

  • 对于栈来说,不存在垃圾回收问题,一旦线程结束,栈就结束.

  • 栈内存中运行:8大基本类型+对象引用+实例的方法.

  • 栈运行原理:栈桢

  • 栈满:StackOverflowError
    在这里插入图片描述

  • 栈细分4部分
    在这里插入图片描述

    例:int a=7;

  • 局部变量表:存放局部变量(a)

  • 操作数栈:存放操作数(7)

  • 动态链接:将符号引用转成直接引用(符号引用就是你知道调用了谁,直接引用就是你拿到可要调用的方法的地址)

  • 方法出口:方法结束

11、堆(重点)
  • 一个JVM只有一个堆内存,堆内存的大小是可以调节的,类加载器读取类文件后,一般会把类,方法,常量,变量,我们所有引用类型的真实对象,放入堆中。

  • 堆内存细分为三个区域:

    • 新生区(伊甸园区):Young/New
    • 养老区old
    • 永久区Perm
      在这里插入图片描述
新生区:类的诞生,成长和死亡的地方

分为:

  • 伊甸园区:所有对象都在伊甸园区new出来
  • 幸存0区和幸存1区:轻GC之后存下来的
老年区(养老区):多次轻GC存活下来的对象放在老年区
  • 真理:经过研究,99%的对象都是临时对象!
永久区

在这里插入图片描述
注意:

元空间:逻辑上存在,物理上不存在 ,因为:存储在本地磁盘内,不占用虚拟机内存
在这里插入图片描述
默认情况下,JVM使用的最大内存为电脑总内存的四分之一,JVM使用的初始化内存为电脑总内存的六十四分之一.

总结:
  • 栈:基本类型的变量,对象的引用变量,实例对象的方法
  • 堆:存放由new创建的对象和数组
  • 方法区:Class对象,static变量,常量池(常量)

三、JVM调优

12、使用JPofiler工具分析OOM原因

下载地址:https://www.ej-technologies.com/download/jprofiler/version_92

13、常见JVM调优参数

在这里插入图片描述

四、垃圾回收(GC)

  • 无法手动垃圾回收,只能手动提醒,等待JVM自动回收
  • GC的作用区在堆(Heap)和方法区中
  • JVM进行GC时,并不是统一对这三区域(新生区,幸存区,老年区)统一回收,回收都是新生代
    • 轻GC(普通GC)只针对于新生区,偶尔作用幸存区(在新生区满的情况下)
    • 重GC(全局GC)全局释放内存
14、常见垃圾回收算法
  • 1.引用计数算法
    原理是此对象有一个引用,即增加一个计数,删除一个引用则减少一个计数。垃圾回收时,只用收集计数为 0 的对象。此算法最致命的是无法处理循环引用的问题。

  • 2.复制算法
    此算法把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾回收时,遍历当前使用区域,把正在使用中的对象复制到另外一个区域中,同时回收未使用的对象。此算法每次只处理正在使用中的对象,因此复制成本比较小,同时复制过去以后还能进行相应的内存整理。
    优点:不会出现碎片化问题
    缺点:需要两倍内存空间,浪费
    在这里插入图片描述

  • 3.标记-清除算法
    此算法执行分两阶段。第一阶段从引用根节点开始标记所用存活的对象,第二阶段遍历整个堆,把未标记的对象清除。
    优点:不会浪费内存空间
    缺点:此算法需要暂停整个应用,同时,会产生内存碎片
    在这里插入图片描述

  • 4.标记-压缩算法
    此算法结合了“标记-清除”和“复制”两个算法的优点。也是分两阶段,第一阶段从根节点开始标记所有存活的对象,第二阶段遍历整个堆,清除未标记对象并且把存活对象“压缩”到堆的其中一块,按顺序排放。
    此算法避免了“标记-清除”的碎片问题,同时也避免了“复制”算法的空间问题。
    在这里插入图片描述

  • 总结:

    • 内存效率(时间复杂度):复制算法>标记清除算法>标记压缩算法
    • 内存效率整齐度:复制算法=标记压缩算法>标记清除算法
    • 内存利用率:标记清除算法=标记压缩算法>复制算法
15、分代回收策略

在这里插入图片描述

  • 1.绝大多数刚刚被创建的对象会存放在Eden区
  • 2.当Eden区第一次满的时候,会触发MinorGC(轻GC)。首先将Eden区的垃圾对象回收清除,并将存活的对象复制到S0,此时S1是空的。
  • 3.下一次Eden区满时,再执行一次垃圾回收,此次会将Eden和S0区中所有垃圾对象清除,并将存活对象复制到S1,此时S0变为空。
  • 4.如此反复在S0和S1之间切换几次(默认15次)之后,还存活的对象将他们转移到老年代中。
  • 5.当老年代满了时会触发FullGC(全GC)
MinorGC
  • 使用的算法是复制算法
  • 年轻代堆空间紧张时会被触发
  • 相对于全收集而言,收集间隔较短
FullGC
  • 使用的算法一般是标记压缩算法
  • 当老年代堆空间满了,会触发全收集操作
  • 可以使用 System.gc()方法来显式的启动全收集
  • 全收集非常耗时
16、垃圾收集器

垃圾回收器的常规匹配:
在这里插入图片描述

  • 1.串行收集器(Serial)
    Serial 收集器是 Hotspot 运行在 Client 模式下的默认新生代收集器, 它的特点是:单线程收集, 但它却简单而高效
    在这里插入图片描述

  • 2.并行收集器(ParNew)
    ParNew 收集器其实是前面 Serial 的多线程版本
    在这里插入图片描述

  • 3.Parallel Scavenge 收集器
    与 ParNew 类似, Parallel Scavenge 也是使用复制算法, 也是并行多线程收集器. 但与其
    他收集器关注尽可能缩短垃圾收集时间不同, Parallel Scavenge 更关注系统吞吐量:
    系统吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)

  • 4.Serial Old 收集器
    Serial Old 是 Serial 收集器的老年代版本, 同样是单线程收集器,使用“标记-整理”算法

  • 5.Parallel Old 收集器
    Parallel Old 是 Parallel Scavenge 收集器的老年代版本, 使用多线程和“标记-整理”算
    法, 吞吐量优先

  • 6.CMS 收集器(Concurrent Mark Sweep)
    CMS是一种以获取最短回收停顿时间为目标的收集器(CMS又称多并发低暂停的收集器),
    基于”标记-清除”算法实现, 整个 GC 过程分为以下 4 个步骤:
    初始标记(CMS initial mark)
    并发标记(CMS concurrent mark: GC Roots Tracing 过程)
    重新标记(CMS remark)
    并发清除(CMS concurrent sweep: 已死对象将会就地释放, 注意:此处没有压缩)

  • 7.G1 收集器
    G1将堆内存“化整为零”,将堆内存划分成多个大小相等独立区域(Region),每一个Region都可以根据需要,扮演新生代的Eden空间、Survivor空间,或者老年代空间。收集器能够对扮演不同角色的Region采用不同的策略去处理,这样无论是新创建的对象还是已经存活了一段时间、熬过多次收集的旧对象都能获取很好的收集效果。

为什么要垃圾回收时要设计STW(stop the world)?

  • 如果不设计STW,可能在垃圾回收时用户线程就执行完了,堆中的对象都失去了引用,全部变成了垃圾,索性就设计了STW,快速做完垃圾回收,再恢复用户线程运行。

五、JMM(java内存模型)

JMM(java内存模型)Java Memory Model,本身是一个抽象的概念,不是真实存在的,它描述的是一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段、静态字段和构成数组对象的元素)的访问方式。
在这里插入图片描述
JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(Main Memory)中,每个线程都有一个私有的本地内存(Local Memory),本地内存中存储了该线程读/写共享变量的副本。

JMM内存模型三大特性
  • 1、原子性
    使用 synchronized 互斥锁来保证操作的原子性
  • 2、可见性:
    volatile,会强制将该变量自己和当时其他变量的状态都刷出缓存。
    synchronized,对一个变量执行 unlock 操作之前,必须把变量值同步回主内存。
    final,被 final 关键字修饰的字段在构造器中一旦初始化完成,并且没有发生 this 逃逸(其它线程通过 this 引用访问到初始化了一半的对象),那么其它线程就能看见 final 字段的值。
  • 3、有序性
    源代码 -> 编译器优化的重排 -> 指令并行的重排 -> 内存系统的重排 ->最终执行的命令。
    重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。
    处理器在进行重排时必须考虑数据的依赖性,多线程环境线程交替执行,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性是无法确定的。
参考博客参考视频
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/m0_74823595/article/details/133723911

智能推荐

微信公众号开发之接收普通消息_微信公众号 接收普通消息-程序员宅基地

文章浏览阅读836次。这篇文章要讲的是微信中的接收普通消息;接收普通消息:顾名思义,它的作用就是接收普通用户发送的消息,并根据接收消息类型的不同,分为了六大接收接口;1 文本消息2 图片消息3 语音消息4 视频消息5 小视频消息6 地理位置消息7 链接消息 视频和小视频同属一类;它们都是通过接收xml文档接收,根据分析xml文档后根据类型判断并回复相应消息;一:文本消息;文本_微信公众号 接收普通消息

HDU 1272 小希的迷宫_小希的迷宫hdu1272 c语言-程序员宅基地

文章浏览阅读147次。链接:小希的迷宫刚开始拿到这道题的时候, 由于是并查集专题,满脑子想的都是怎么用并查集来做,但是我肯定这道题肯定是能用dfs来做的, 只需要判断是否有环即可, 。其实这道题很多方法都可以做,我用的dfs 100+ms过的。后来用并查集做了一下, 是60+ms, 并且并查集的代码要短很多, 以后遇到问题要先想一下!dfs.#include <iostream>#include..._小希的迷宫hdu1272 c语言

MT2300ACTR-VB_MOSFET产品应用与参数解析_actr开关是啥-程序员宅基地

文章浏览阅读29次。功率放大器:在需要放大电流的模块中,SUD50P04-09L-E3可以作为输出级别的功率放大器,提供高电流和低电阻的特性。- 电池管理:在电池管理模块中,可以使用SUD50P04-09L-E3来实现电池的充放电控制,提供强大的功率开关能力。综上所述,SUD50P04-09L-E3适用于开关电源、驱动电路、电池管理和功率放大器等领域模块。- 开关电源:可以应用于电源开关、DC-DC转换器和逆变器等电源模块中,用于实现高效的功率转换。- 阈值电压(Vth):-1.6V。- 最大耐压:-40V。_actr开关是啥

Lucene4.3.1拼写检查SpellChecker源码解析_spellchecker.suggestsimilar 排序-程序员宅基地

文章浏览阅读769次。Lucene4.3.1拼写检查SpellChecker源码解析_spellchecker.suggestsimilar 排序

stm32 AES256加密 串口IAP升级 bootloader程序通过上位机将keil生成的BIN文件进行AES加密_stm32软件加密方法-程序员宅基地

文章浏览阅读456次。本文从加密到解密再到升级的过程,详细阐述了整个流程,并提供了多种工具和说明文档,使得用户可以很方便地进行程序的加密和升级操作。在加密过程中,需要设置自己的秘钥。单片机接收到数据后,会根据预先设置好的秘钥,对数据进行解密还原,再进行程序升级。理论上,只要移植AES的.c和.h文件,并且你能将数据发送到单片机串口,就能用任意方式来对单片机进行升级,包括但不限于wifi,蓝牙,4G模块等。通过上位机将keil生成的BIN文件进行AES加密,得到新的加密文件,加密需要自己设置秘钥,加密升级包直接烧录不能运行。_stm32软件加密方法

AAAI 2024 | 浙大&优图提出DiAD:第一个基于扩散模型的多类异常检测工作-程序员宅基地

文章浏览阅读561次,点赞2次,收藏3次。点击下方卡片,关注“CVer”公众号AI/CV重磅干货,第一时间送达点击进入—>【异常检测和扩散模型】微信交流群扫码加入CVer知识星球,可以最快学习到最新顶会顶刊上的论文idea和CV从入门到精通资料,以及最前沿项目和应用!发论文,强烈推荐!在CVer微信公众号后台回复:DiAD,即可下载论文pdf和代码链接!快学起来!DiAD:第一个基于扩散模型架构的多类异常检测工作(来自浙大,腾讯优图..._基于扩散模型的异常检测

随便推点

华三设备升级教程_华三ap升级 dir-程序员宅基地

文章浏览阅读6k次,点赞2次,收藏39次。这里写自定义目录标题欢迎使用Markdown编辑器欢迎使用Markdown编辑器第一种方法:利用网线和电脑(FTP服务器端),设备做PC端get文件升级首先,在H3C官网中下载最新版本的交换机文件。在网上下载3CDaemon软件。点击3CDaemon,进入点击设置FTP服务器。进入界面后点击FTP设置,在上传/下载目录选择新版本所在目录;点击FTP用户,可设置用户名和密码,连接上传时会用到。登入H3C交换机,可用配置线在console口登录,也可以远程登录。登录后在全局底下查看交换机版本:_华三ap升级 dir

Linux任务计划 crontab_计划任务 linux crontab-程序员宅基地

文章浏览阅读258次。crontab命令从输入设备读取指令,并将其存放于crontab文件中,以供之后读取和执行。通常,crontab储存的指令被守护进程激活,crond为其守护进程,crond常常在后台运行,每一分钟会检查一次是否有预定的作业需要执行。通过crontab命令,我们可以在固定的间隔时间执行指定的系统指令或shell脚本。crontab命令常见于Unix和类Unix的操作系统之中(Linux就属于类Unix操作系统),用于设置周期性被执行的指令。crontab的启动。..._计划任务 linux crontab

4k分辨率是多少(真4k与假4k区别)-程序员宅基地

文章浏览阅读8.3k次。真4k与假4k区别

最好的Scrum工具 - 在一个页面中自动完成整个过程_scrum process-程序员宅基地

文章浏览阅读731次。虽然scrum是一个有效且经过验证的软件开发框架,但为了充分利用scrum,您需要以提高灵活性和更好地适应项目和团队的方式定制或调整它。的Scrum过程画布是一个争球的管理工具。它在一页的流程画布中呈现可操作的scrum活动。团队成员执行管理和完成软件项目的活动。Scrum Process Canvas是完全可定制的,允许您将其他活动(例如某些会议)和流程交付(例如特定的日志)添加到Scr..._scrum process

逆向(异或)_reverse异或操作-程序员宅基地

文章浏览阅读1.3k次,点赞5次,收藏8次。异或(XOR)是一种逻辑运算符,常用于计算机科学和电子工程中。异或操作符通常用符号 “^” 表示。异或操作的定义如下:当两个操作数的位值不同时,异或结果为1。当两个操作数的位值相同时,异或结果为0。例如,对于两个二进制数1和0进行异或操作:异或操作还具有以下一些重要性质:1.结合律:(A XOR B) XOR C = A XOR (B XOR C)2.交换律:A XOR B = B XOR A3.自反性:A XOR A = 04.零元素:A XOR 0 = A。_reverse异或操作

winPE下的PECMD命令详解 -----PECMD.INI 文件配置(2)_pecmd.ini编写教程-程序员宅基地

文章浏览阅读2.5w次。[IFEX]格式:IFEX ,[命令1][!命令2]功能:依据条件表达式是否成立,成立则执行命令1,不成立则执行命令2。参数: ■条件对[可用内存]或[磁盘可用空间]或[按键]或[数值变量]或[文件目录]的判断。 ■可用内存MEM数值。 ■磁盘可用空间R:_pecmd.ini编写教程

推荐文章

热门文章

相关标签