JVM Java虚拟机入门指南
文章目录
- 为什么学习JVM
- JVM的执行流程
- JVM的组成部分
- 类加载
- 运行时数据区
- 本地方法接口
- 执行引擎
- 垃圾回收
- 什么样的对象是垃圾呢
- 内存溢出和内存泄漏
- 定位垃圾的方法
- 对象的finalization机制
- 垃圾回收算法
- 分代回收
- 垃圾回收器
- JVM调优参数
- JVM调优工具
- Java内存泄漏排查思路
- CPU飙高排查方案与思路
为什么学习JVM
- JVM是Java的运行环境,优点是一次编译,到处运行。这是因为JVM是运行在操作系统上的,无论在什么操作系统都可以执行,所以常说Java是跨平台性的。
- 学习JVM能更深入的理解Java这门语言,理解Java语言底层代码的执行过程,为后期写出优质代码做好准备。比如很多时候一个问题需要深入字节码层次去分析才能得到准确的结论,字节码就是JVM的一部分。并且项目上线去排查一些程序log日志中无法呈现的问题,如:内存溢出等。
- 相较于C/C++Java不需要手动的去进行垃圾回收,但是正因为Java将内存控制交给JVM,一旦出现内存泄漏和溢出方面的问题,如果不了解JVM,是很难进行排查的。
JVM的执行流程
程序在执行前先要把Java代码转换成字节码(.class)文件,JVM需要将字节码文件通过一定方式的类加载器(ClassLoader)把文件加载到内存的运行时数据区(Runtime Data Area),而字节码文件是JVM的一套指令集规范,并不能直接由底层操作系统区执行,因此需要特定的命令解析器**执行引擎(Execution Engine)将字节码翻译成底层系统指令再交给CPU去执行,这个过程中需要调用其他语言的接口本地库接口(Native Interface)**来实现整个程序的功能。
JVM的组成部分
类加载、运行时数据区(内存区域)、本地方法接口、执行引擎。
类加载
-
加载:读取字节码文件,转换并存储,为每个类创建一个class类对象并存储在方法区中。
-
链接:
- 验证:检查被加载的类内部结构是否正确,对字节码文件格式进行验证,判断文件是否污染并对基本语法格式验证。
- 准备:为静态的变量分配内存,并设置默认初始值,不包含使用final修饰的static常量。
- 解析:将符号引用(方法名)转化为直接引用(使用指针指向地址),将字节码中的表现形式转为内存中的表现形式。
-
初始化:类的初始化,为类中定义的静态变量进行赋值。
-
类加载器分类:引导(启动)类加载器(C+)、扩展类加载器、应用程序类加载器(默认)、自定义类加载器。
-
双亲委派机制及其打破:如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父加载器还存在其父加载器,则继续向上委托,最终将到达顶层的启动类加载器,如果父类加载器可以完成类的加载任务,就成功返回,若无法完成加载任务,子加载器才会尝试自己去加载。如果都加载失败,则抛出异常ClassNotFoundException。 目的:为了确保加载系统类。优点:安全,可以避免用户自己编写的类替换Java的核心类库,并避免类重复加载。 打破: 通过集成ClassLoader类,重写loaclClass/findClass方法,实现自定义类加载。Tomcat就是自定义的类加载。
运行时数据区
- 程序计数器:线程私有的,内部保存的是字节码的行号,用于记录正在执行的字节码指令的地址。
- 字节码的行号:Java代码运行时,编译后的字节码文件是一行一行执行的,PC计数器就是记录当前线程执行的行号的,目的是其他线程抢占该线程后,下次接着之前执行的位置执行。
- Java虚拟机栈:线程私有的,随着线程创建而创建,随着线程销毁而死亡。每个线程在运行时所需要的内存就是虚拟机栈。每个栈都是由一个个栈帧组成,对应的每次方法调用占用的内存。每个栈帧:局部变量表,操作数栈,动态链接,方法返回地址。每个线程中只能有一个活动栈帧,对应着当前正在执行的那个方法。
- 本地方法栈:本地方法栈和Java虚拟机栈发挥的作用相似,区别在于Java虚拟机栈为虚拟机执行Java方法服务,而本地方法栈则为虚拟机使用到的Native方法服务,也就是执行native()方法,这些方法是C、C++写的。
- 堆:线程共享区域,主要保存对象的实例、数组等。
- 方法区:共享的内存区域,主要用来存储类信息、即时编译器编译后的信息以及运行时常量池。JVM启动时创建,关闭JVM释放。
- 常量池:是一张表,主要存储的是要执行的类名、方法名、参数类型、字面量等信息,JVM根据指令会在这张表中进行查找。
- 运行时常量池:常量池是.class文件中的,当类被加载时,它的常量池信息会放入运行时常量池,并将里面的符号地址变为真实地址(#1 #2 之类的)。
本地方法接口
简单地讲,一个 Native Method 是一个Java调用非Java代码的接囗。一个 Native Method 是这样一个Java方法:该方法的实现由非Java语言实现,比如C。特点:用native关键字修饰的方法称为一个本地方法,没有方法体。
为什么使用:因为Java在有些层次的任务使用Java实现起来不容易,Java语言需要与外部环境进行交互,直接访问操作系统接口即可,JVM本身开发也是在底层使用了C语言。
执行引擎
- 解释器:解释器有两种 ,一种是古老的字节码解释器:在执行时通过纯软件代码翻译字节码的执行,效率非常低下。另一种现在普遍使用的模板解释器:将每一条字节码和一个模板函数相关联,模板函数中直接产生这条字节码执行时的机器码,提高了解释器的性能。
- JIT即时编译器:可以将整个函数体编译成机器码,有效的避免函数体被解释执行,在重复执行时直接执行编译后的机器码即可,大大提升了执行效率。通俗的说就是如果遇到经常执行的字节码指令,只要执行过一次,将一些频繁执行的热点代码进行编译,并缓存到方法区中,后续再来执行就不需要翻译,可以直接取出对应的机器指令,性能更快,提高了执行效率。
垃圾回收
什么样的对象是垃圾呢
Java中的垃圾对象是指没有被任何引用变量所引用的对象。这些对象无法被访问,也无法被使用,因此它们占用内存空间而不被程序所使用,成为垃圾对象
内存溢出和内存泄漏
内存溢出指的是程序在申请内存时,由于没有足够的内存可用,而导致程序崩溃或者出现其他异常情况的现象。这通常是因为程序错误地使用了内存,例如未及时释放不需要的内存或者使用了太多内存资源,导致系统无法提供足够的内存来满足应用程序的需求。
内存泄漏指的是程序中存在一些对象或变量没有被垃圾回收器及时回收,导致这些对象一直占用着内存空间并最终耗尽可用内存的现象。通常是因为程序中存在不合理的设计或编码问题,例如忘记释放动态分配的内存、使用循环引用等等。还有就是打开了使用对象的东西,但是没有关闭,导致垃圾处理时认为对象处于运行状态,不会被回收处理,IO流close和jdbc链接close没有关闭。
两者区别在于,内存泄漏是程序代码中存在的开发问题,内存溢出则是由于系统资源有限造成的结果。需要解决内存泄漏问题,通常需要审查代码并进行调试,而需要解决内存溢出问题,则需要考虑优化应用程序,增加可用内存资源,并可能需要进行代码重新设计,以便更有效地使用和释放内存。
定位垃圾的方法
-
引用计数法:当一个对象被引用了一次,就在当前对象头上递增一次引用次数,如果这个对象的引用次数为0,代表这个对象可回收。比如创建一个demo对象,在JVM内存中会在栈中存储一个变量然后指向在堆中开辟对的一块空间来存储这个对象,引用计数法会给堆中的对象添加一个引用的参数ref=1,当demo=null,此时栈中的变量不会指向内存中的对象,ref变为0。引用计数法原理简单,效率也很高,但是目前主流的虚拟机中并没有选择这个算法来管理内存,主要原因是引用计数就很难解决对象之间相互循环引用的问题。当相互引用时ref次数增加了两次,此时如果demo=null,ref就会变为1,不会被识别为垃圾,这就是循环引用,会引发内存泄漏。
-
根搜索算法:目前的虚拟机都是通过可达性分析算法来确定哪些内容是垃圾,核心思想是沿着GC Root对象,遍历寻找关联着的对象就不是垃圾对象,扫描过程中,不能GC Root访问到该对象的就是可以被回收的。
-
GC Root可以是:虚拟机栈(栈帧中的本地变量表)中引用的对象。方法区中类静态属性引用的对象。方法区中常量引用的对象。本地方法栈中JNI(Native方法)引用的对象。
对象的finalization机制
对象的 finalization 机制是一种内存管理模式,它允许程序在对象被垃圾回收之前执行特定的清理和释放操作。在Java中,finalize() 方法是用于实现对象的 finalization 机制的。当一个对象变为垃圾之前,JVM会在内部自动调用其 finalize() 方法(如果该对象的 finalize() 方法未被重写,则不会执行任何操作),并在 finalize() 方法执行结束之后回收该对象。开发人员可以在 finalize() 方法中编写释放资源、关闭打开的文件、清除临时数据等操作,以便程序尽快回收不再使用的内存空间。
垃圾回收算法
- 标记清除: 使用GC Root标记处存活的对象,清除没有标记的对象。优点:标记和清除速度快。缺点:内存碎片化严重,内存不连贯。
- 标记复制: 将内存区域分为两块,当使用GC Root标记出存活的对象,将这些对象复制到另外一块之前清空的区域中。优点:当垃圾对象多的时候效率高,清理后内存没有碎片。缺点:需要两块内存空间,同一时刻只能使用一块空间,内存使用率较低。
- 标记整理:使用GC Root标记出存活的对象,清除没有标记的对象,将标记存活的对象向一端移动,避免了内存碎片化,但是由于移动,相较于标记清除性能是有一定影响。
分代回收
-
MinorGC(young GC):发生在新生代的垃圾回收,SWT时间短。
-
MixedGC:新生代+老年代垃圾部分区域垃圾回收,G1收集器特有。
-
FullGC:新生代+老年代完整垃圾回收,STW时间长,应尽量避免。
-
SWT(Stop The World):暂停所有应用程序线程,等待垃圾回收的完成。
垃圾回收器
- 串行垃圾回收器: Serial和SerialOld,单线程垃圾回收,堆内存较小。Serial作用于新生代,采用标记复制算法。SerialOld作用于老年代,采用标记整理算法。工作原理:垃圾回收时只有一个线程在工作,并且需要SWT。
- 并行垃圾回收器:Paraller New和Paraller Old,并行垃圾回收器。Paraller New作用于新生代,采用标记复制算法。Paraller Old作用于老年代,采用标记整理算法。这个垃圾回收器是JKD8中默认使用的,工作原理是垃圾回收时多个线程工作,Java应用中所有线程SWT。
- CMS(并发)垃圾回收器:主要是针对老年代的垃圾回收器,并发执行的,使用标记清除的垃圾回收器,是一款以获取最短停顿时间为目标的收集器,停顿时间短用户体验是比较良好的,最大的特点是在进行垃圾回收时,应用仍能正常运行。主要过程:
- 初始标记(SWT):标记直接与GC Root关联的对象 。
- 并发标记:标记与GC Root间接关联的对象。
- 重新标记:防止之前标记时有的垃圾被关联,漏标。
- 并发清理
- G1垃圾回收器:和其他垃圾回收器不同的是G1垃圾回收器是将堆区域分为多个区域,每个区域都可以充当eden、survivor、old、humongous(为大对象准备),采用的是标记复制算法进行垃圾回收。特点是响应时间与吞吐量兼顾,垃圾回收主要分为三个阶段新生代回收、并发标记、混合收集。如果回收的速度赶不上创建新对象的速度就会触发Full GC。
- 新生代垃圾回收:新生代的内存区域一般在G1堆中分配5%-6%,如果达到这个区间就会触发垃圾回收,使用标记复制算法将存活的对象复制到幸存者区中(挑出一个空闲区域),需要暂停用户线程。有新对象创建会将一块区域创建为eden区进行存储,之后进行垃圾回收时会将eden和幸存者区中存活的对象复制到另一个区域(幸存者区),超过15次的对象会复制到创建的老年区中。
- 并发标记:当老年代占总堆内存超过45%就会触发并发标记,并发标记就是将老年代中所有的存活对象标记出来,这个过程是并发的,无需暂停用户线程。
- 混合收集:在并发标记之后,会有一个重新标记阶段,用来解决标记阶段的漏标问题,此时需要swt。在回收老年代时,并不是一次将所有的老年代区域进行垃圾回收,而是有一个人为设置的预期的暂停时间,根据这个暂停时间优先回收价值高的区域(标记期间存活对象少,这个也是G1名称的由来),将这些回收价值高的老年代以及伊甸园,幸存者区一同进行一次垃圾回收,这就是混合收集,然后将伊甸园区和幸存者区中存活的对象放入新创建的幸存者区中,将老年代中存活的对象放入新创建的老年区中。
JVM调优参数
- 堆空间大小:-Xms -Xmx : 设置堆的初始大小和最大大小,为了防止垃圾收集器在初始大小和最大大小之间收缩堆,而产生额外的时间,通常将最大和初始大小设置为相同的值,不指定的话默认单位是字节。
- 堆空间设置多少合适:一般最大大小默认为物理内存的1/4,初始大小是物理内存的1/64,堆太小的话,可能会频繁导致GC,会产生stw,暂停用户线程,对空间大肯定好,但是也有风险,假如发生Full GC扫描整堆空间,暂停用户进程时间较长。
- 虚拟机栈的设置:-Xss : 默认值为1M,栈中一般存放栈帧,调用参数、局部变量表等,每个线程都会创建虚拟机栈,如果设置太大会导致线程数量减少,如果太小会导致栈内存溢出,一般建议设置256K或512K。
- 年轻代和老年代大小比例:-XXSurvivorRatio=8 表示survivor:eden=2:8,这是默认的比例,我们也可以设置增大eden区的大小,用来减少YGC发生的次数,但是虽然减少了,但是eden区满时占用空间大,导致释放缓慢,此时STW时间较长,因此还是需要根据程序情况去调优。
- 年轻代晋升老年代阈值:-XX:MaxTenuringThreshold=threshold 默认15,取值范围0-15
- 设置垃圾回收器:-XX:+useParallerGC,-XX:+useParalloldGC,-XX:+useG1GC
可以通过增大吞吐量来提高系统性能,可以通过这个设置并行垃圾回收器。
JVM调优工具
命令工具:jps查看进程状态、jstack查看进程内线程的堆栈信息、jmap查看堆转信息、jhat堆转储快照分析工具、jstatJVM统计检测工具。可视化工具:jconsole用于对JVM的内存线程,类的监控、
VisualVM能够监控线程内存情况。
-
命令工具: jmap:通过jmap =heap pid 显示Java堆的信息
jmap -dump:format=b,file=heap hprof pid,fomat=b表示以hprof=进制格式转Java堆的内存
file= 用于指定快照dump文件的文件名
使用以上命令生成一个进程或系统在某一时间的快照,比如在进程崩溃时甚至是任何时候,我们都可以通过工具将系统或进程的内存备份出来供调试分析使用。dump文件中包含了程序进行的模块信息,线程信息,堆栈调用信息,异常信息等数据,方便系统技术人员进行错误排查。jstat: jstat -gcutil pid 总结垃圾回收统计
jstat -gc pid 垃圾回收统计 -
jconsole: 通过java/bin/jconsole.ext可以直接打开线程信息。
VisualVM:目前只有1.8中有,高版本没有,通过java/bin/jvisualvm.exe打开。
Java内存泄漏排查思路
-
获取堆内存快照dump。
- 使用jmap命令获取运行中程序的dump文件,有的情况是内存一处之后程序中断了但是jmap只能打印运行中的程序,所以可以通过使用Vm参数获取dump文件。
- 使用VisualVM可以加载离线的dump文件。
-
VisualVM去分析dump文件。
-
通过查看堆信息的情况去定位内存溢出的问题。
-
找到对应代码,通过阅读上下文情况,进行修复即可。
CPU飙高排查方案与思路
- 使用top命令查看哪一个命令占用CPU较高,可以拿到相应的pid。
- 使用ps H =eo pid,tid,%cpu | grep 进程pid 可以找到进程中所有线程的信息。
- 使用jstack 进程id 打印当前进程的所有线程信息,将刚才进程的线程id转换为16进制的线程id(打印的线程信息的id是16进制的),然后根据相应的线程id,定位到问题代码的代码行。
相关文章:

JVM Java虚拟机入门指南
文章目录 为什么学习JVMJVM的执行流程JVM的组成部分类加载运行时数据区本地方法接口执行引擎 垃圾回收什么样的对象是垃圾呢内存溢出和内存泄漏定位垃圾的方法对象的finalization机制垃圾回收算法分代回收垃圾回收器 JVM调优参数JVM调优工具Java内存泄漏排查思路CPU飙高排查方案…...
【错误文档】This与Here的区别、主系表结构、如何合并两个句子、祈使句结构
目录 This与Here的区别 主系表结构 如何合并两个句子 祈使句结构 原句中文1: “就是这件。” 我的翻译: This is it. 正确翻译: 书上原句: Here it is! 正确解释: 两个翻译都对,只是强调点不同&…...

Java入门之JavaSe(韩顺平p1-p?)
学习背景: 本科搞过一段ACM、研究生搞了一篇B会后,本人在研二要学Java找工作啦~~(宇宙尽头是Java?)爪洼纯小白入门,C只会STL、python只会基础Pytorch、golang参与了一个Web后端项目,可以说项目小…...

TCP的连接和断开详解
目录 1.TCP基础知识 1.1.TCP 头格式 1.2.TCP协议介绍 1.3.UDP协议介绍 1.4.TCP 和 UDP 区别 1.5.TCP 和 UDP 应用场景 1.6.计算机网络相关术语(缩写) 2.TCP 连接建立:三次握手 2.1.TCP 三次握手过程 2.2.三次握手原理 2.3.异常分析…...

armbian ddns
参考https://mp.weixin.qq.com/s/0Uu_nbGH_W6vAYHPH4kHqg Releases jeessy2/ddns-go GitHub mkdir -p /usr/local/ddns-go cd /usr/local/ddns-gowget https://github.com/jeessy2/ddns-go/releases/download/v6.1.1/ddns-go_6.1.1_freebsd_armv7.tar.gztar zxvf ddns-go_…...

MQTT 服务器(emqx)搭建及使用
推荐阅读: MQTT 服务器(emqx)搭建及使用 - 哔哩哔哩 (bilibili.com) 一、EMQX 服务器搭建 1、下载EMQX https://www.emqx.com/zh/try?productbroker 官方中文手册: EMQX Docs 2、安装使用 1、该软件为绿色免安装版本,解压缩后即安装完…...
【flink状态管理(四)】MemoryStateBackend的实现
文章目录 1.基于MemoryStateBackend创建KeyedStateBackend1.1. 状态初始化1.2. 创建状态 2. 基于MemoryStateBackend创建OperatorStateBackend3.基于MemoryStateBackend创建CheckpointStorage 在Flink中,默认的StateBackend实现为MemoryStateBackend,本文…...

前端架构: 脚手架在前端研发流程中的意义
关于脚手架 脚手架又被成为 CLI (command-line interface)基于文本界面,通过中断输入命令执行常见的脚手架:npm, webpack-cli, vue-cli拿 npm 这个脚手架来说 在终端当中输入 npm 命令, 系统就会通过文本方式返回 npm 的使用方法它这种通过命令行执行的…...
Qt网络编程-QTcpServer的封装
简单封装Tcp服务器类,将QTcpServer移入线程 头文件: #ifndef TCPSERVER_H #define TCPSERVER_H#include <QObject>class QTcpSocket; class QTcpServer; class QThread; class TcpServer : public QObject {Q_OBJECT public:explicit TcpServer(…...

【MySQL】_JDBC编程
目录 1. JDBC原理 2. 导入JDBC驱动包 3. 编写JDBC代码实现Insert 3.1 创建并初始化一个数据源 3.2 和数据库服务器建立连接 3.3 构造SQL语句 3.4 执行SQL语句 3.5 释放必要的资源 4. JDBC代码的优化 4.1 从控制台输入 4.2 避免SQL注入的SQL语句 5. 编写JDBC代码实现…...
微信小程序编译出现 project.config.json 文件内容错误
问题描述: 更新微信开发工具后,使用微信开发工具编译时出现project.config.json 文件内容错误。 原因:当前使用的微信开发工具非稳定版本。 解决方法: 在 manifest.json中加入以下代码: "mp-weixin" : …...

一周学会Django5 Python Web开发-Django5创建项目(用命令方式)
锋哥原创的Python Web开发 Django5视频教程: 2024版 Django5 Python web开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili2024版 Django5 Python web开发 视频教程(无废话版) 玩命更新中~共计11条视频,包括:2024版 Django5 Python we…...

DockerUI如何部署结合内网穿透实现公网环境管理本地docker容器
文章目录 前言1. 安装部署DockerUI2. 安装cpolar内网穿透3. 配置DockerUI公网访问地址4. 公网远程访问DockerUI5. 固定DockerUI公网地址 前言 DockerUI是一个docker容器镜像的可视化图形化管理工具。DockerUI可以用来轻松构建、管理和维护docker环境。它是完全开源且免费的。基…...

UML之在Markdown中使用Mermaid绘制类图
1.UML概述 UML(Unified modeling language UML)统一建模语言,是一种用于软件系统分析和设计的语言工具,它用于帮助软件开发人员进行思考和记录思路。 类图是描述类与类之间的关系的,是UML图中最核心的。类图的是用于…...

Spring Boot + 七牛OSS: 简化云存储集成
引言 Spring Boot 是一个非常流行的、快速搭建应用的框架,它无需大量的配置即可运行起来,而七牛云OSS提供了稳定高效的云端对象存储服务。利用两者的优势,可以为应用提供强大的文件存储功能。 为什么选择七牛云OSS? 七牛云OSS提供了高速的…...

C++:二叉搜索树模拟实现(KV模型)
C:二叉搜索树模拟实现(KV模型) 前言模拟实现KV模型1. 节点封装2、前置工作(默认构造、拷贝构造、赋值重载、析构函数等)2. 数据插入(递归和非递归版本)3、数据删除(递归和非递归版本…...

npm淘宝镜像源换新地址
新的淘宝npm镜像源地址:https://registry.npmmirror.com 切换新的镜像源 npm config set registry https://registry.npmmirror.com然后再执行以下操作查看是否成功 npm config list如果没安装过淘宝镜像源的,则直接安装 npm install -g cnpm --regi…...
十大排序算法之线性时间非比较类排序
线性时间非比较类排序 线性时间的算法执行效率也较高,从时间占用上看,线性时间非比较类排序要优于非线性时间排序,但其空间复杂度较非线性时间排序要大一些。因为线性时间非比较类排序算法会额外申请一定的空间进行分配排序,这也…...
容器基础:Docker 镜像如何保证部署的一致性?
Docker 镜像如何通过固化基础环境、固化依赖性和固化软件启动流程保证部署的一致性 Docker 镜像通过以下三个方面保证部署的一致性: 1. 固化基础环境: 镜像包含构建应用程序所需的所有环境依赖项,例如操作系统、库和工具。构建镜像时,所有…...

爪哇部落算法组2024新生赛热身赛题解
第一题(签到): 1、题意: 2、题解: 我们观察到happynewyear的长度是12个字符,我们直接从前往后遍历0到n - 12的位置(这里索引从0开始),使用C的substr()函数找到以i开头的长度为12的字…...
Cursor实现用excel数据填充word模版的方法
cursor主页:https://www.cursor.com/ 任务目标:把excel格式的数据里的单元格,按照某一个固定模版填充到word中 文章目录 注意事项逐步生成程序1. 确定格式2. 调试程序 注意事项 直接给一个excel文件和最终呈现的word文件的示例,…...
Linux链表操作全解析
Linux C语言链表深度解析与实战技巧 一、链表基础概念与内核链表优势1.1 为什么使用链表?1.2 Linux 内核链表与用户态链表的区别 二、内核链表结构与宏解析常用宏/函数 三、内核链表的优点四、用户态链表示例五、双向循环链表在内核中的实现优势5.1 插入效率5.2 安全…...

PPT|230页| 制造集团企业供应链端到端的数字化解决方案:从需求到结算的全链路业务闭环构建
制造业采购供应链管理是企业运营的核心环节,供应链协同管理在供应链上下游企业之间建立紧密的合作关系,通过信息共享、资源整合、业务协同等方式,实现供应链的全面管理和优化,提高供应链的效率和透明度,降低供应链的成…...

深入理解JavaScript设计模式之单例模式
目录 什么是单例模式为什么需要单例模式常见应用场景包括 单例模式实现透明单例模式实现不透明单例模式用代理实现单例模式javaScript中的单例模式使用命名空间使用闭包封装私有变量 惰性单例通用的惰性单例 结语 什么是单例模式 单例模式(Singleton Pattern&#…...
sqlserver 根据指定字符 解析拼接字符串
DECLARE LotNo NVARCHAR(50)A,B,C DECLARE xml XML ( SELECT <x> REPLACE(LotNo, ,, </x><x>) </x> ) DECLARE ErrorCode NVARCHAR(50) -- 提取 XML 中的值 SELECT value x.value(., VARCHAR(MAX))…...

微信小程序云开发平台MySQL的连接方式
注:微信小程序云开发平台指的是腾讯云开发 先给结论:微信小程序云开发平台的MySQL,无法通过获取数据库连接信息的方式进行连接,连接只能通过云开发的SDK连接,具体要参考官方文档: 为什么? 因为…...
css3笔记 (1) 自用
outline: none 用于移除元素获得焦点时默认的轮廓线 broder:0 用于移除边框 font-size:0 用于设置字体不显示 list-style: none 消除<li> 标签默认样式 margin: xx auto 版心居中 width:100% 通栏 vertical-align 作用于行内元素 / 表格单元格ÿ…...
AspectJ 在 Android 中的完整使用指南
一、环境配置(Gradle 7.0 适配) 1. 项目级 build.gradle // 注意:沪江插件已停更,推荐官方兼容方案 buildscript {dependencies {classpath org.aspectj:aspectjtools:1.9.9.1 // AspectJ 工具} } 2. 模块级 build.gradle plu…...
JS设计模式(4):观察者模式
JS设计模式(4):观察者模式 一、引入 在开发中,我们经常会遇到这样的场景:一个对象的状态变化需要自动通知其他对象,比如: 电商平台中,商品库存变化时需要通知所有订阅该商品的用户;新闻网站中࿰…...

C/C++ 中附加包含目录、附加库目录与附加依赖项详解
在 C/C 编程的编译和链接过程中,附加包含目录、附加库目录和附加依赖项是三个至关重要的设置,它们相互配合,确保程序能够正确引用外部资源并顺利构建。虽然在学习过程中,这些概念容易让人混淆,但深入理解它们的作用和联…...