JVM--Java类加载器笔记
Java类加载器
代码经过编译变成了字节码打包成 Jar 文件。让 JVM 去加载需要的字节码,变成持久代/元数据区上的 Class 对象,接着执行程序逻辑。
类声明周期和加载过程
步骤:加载->链接(校验->准备->解析)->初始化->使用->卸载
-
加载:根据明确知道的 class 完全限定名, 来获取二进制 classfile 格式的字节流(找到文件系统中/jar 包中/或存在于任何地方的“
class 文件
”。 如果找不到二进制表示形式,则会抛出NoClassDefFound
错误。) -
校验:确保 class 文件里的字节流信息符合当前虚拟机的要求,不会危害虚拟机的安全。
- 在某个类的加载过程中,JVM 必须加载其所有的超类和接口。如果类层次结构有问题(例如,该类是自己的超类或接口,死循环了),则 JVM 将抛出
ClassCircularityError
。 而如果实现的接口并不是一个 interface,或者声明的超类是一个 interface,也会抛出IncompatibleClassChangeError
。
- 在某个类的加载过程中,JVM 必须加载其所有的超类和接口。如果类层次结构有问题(例如,该类是自己的超类或接口,死循环了),则 JVM 将抛出
-
准备:会创建静态字段, 并将其初始化为标准默认值(比如
null
或者0 值
),并分配方法表,即在方法区中分配这些变量所使用的内存空间。- 准备阶段并未执行任何 Java 代码。
- public static int i = 1;备阶段
i
的值会被初始化为 0,后面在类初始化阶段才会执行赋值为 1; - public static final int i = 1如果使用 final 作为静态常量,对应常量 i,在准备阶段就会被赋值 1;
-
解析:进入可选的解析符号引用阶段。 也就是解析常量池,主要有以下四种:类或接口的解析、字段解析、类方法解析、接口方法解析。
- 编写的代码中,当一个变量引用某个对象的时候,这个引用在
.class
文件中是以符号引用来存储的(相当于做了一个索引记录)。 - 在解析阶段就需要将其解析并链接为直接引用(相当于指向实际对象)。如果有了直接引用,那引用的目标必定在堆中存在。
- 加载一个 class 时, 需要加载所有的 super 类和 super 接口。
- 编写的代码中,当一个变量引用某个对象的时候,这个引用在
-
初始化: 必须在类的首次“主动使用”时才能执行类初始化。
- 初始化的过程包括执行:
- 类构造器方法
- static 静态变量赋值语句
- static 静态代码块
- 如果是一个子类进行初始化会先对其父类进行初始化,保证其父类在子类之前进行初始化。所以其实在 java 中初始化一个类,那么必然先初始化过
java.lang.Object
类,因为所有的 java 类都继承自 java.lang.Object。
- 初始化的过程包括执行:
类加载时机
触发类的初始化情况:
- 当虚拟机启动时,初始化用户指定的主类,就是启动执行的 main 方法所在的类;
- 当遇到用以新建目标类实例的 new 指令时,初始化 new 指令的目标类,就是 new 一个类的时候要初始化;
- 当遇到调用静态方法的指令时,初始化该静态方法所在的类;
- 当遇到访问静态字段的指令时,初始化该静态字段所在的类;
- 子类的初始化会触发父类的初始化;
- 如果一个接口定义了 default 方法,那么直接实现或者间接实现该接口的类的初始化,会触发该接口的初始化;
- 使用反射 API 对某个类进行反射调用时,初始化这个类,其实跟前面一样,反射调用要么是已经有实例了,要么是静态方法,都需要初始化;
- 当初次调用 MethodHandle 实例时,初始化该 MethodHandle 指向的方法所在的类。
同时以下几种情况不会执行类初始化:
- 通过子类引用父类的静态字段,只会触发父类的初始化,而不会触发子类的初始化。
- 定义对象数组,不会触发该类的初始化。
- 常量在编译期间会存入调用类的常量池中,本质上并没有直接引用定义常量的类,不会触发定义常量所在的类。
- 通过类名获取 Class 对象,不会触发类的初始化,Hello.class 不会让 Hello 类初始化。
- 通过 Class.forName 加载指定类时,如果指定参数 initialize 为 false 时,也不会触发类初始化,其实这个参数是告诉虚拟机,是否要对类进行初始化。Class.forName(“jvm.Hello”)默认会加载 Hello 类。
- 通过 ClassLoader 默认的 loadClass 方法,也不会触发初始化动作(加载了,但是不初始化)。
示例: 诸如 Class.forName(), classLoader.loadClass() 等 Java API, 反射API, 以及 JNI_FindClass 都可以启动类加载。 JVM 本身也会进行类加载。 比如在 JVM 启动时加载核心类,java.lang.Object, java.lang.Thread 等等。
类加载机制
类加载过程可以描述为“通过一个类的全限定名 a.b.c.XXClass 来获取描述此类的 Class 对象”,这个过程由“类加载器(ClassLoader)”来完成。这样的好处在于,子类加载器可以复用父加载器加载的类。
- 系统自带的类加载器分为三种:
- 启动类加载器(BootstrapClassLoader):由 JVM 内部实现的,在 Java 的 API 里无法拿到。
- 用来加载 Java 的核心类,是用原生 C++ 代码来实现的,可以看做是 JVM 自带的,在代码层面无法直接获取到启动类加载器的引用,所以不允许直接操作它。
- 例如:java.lang.String 是由启动类加载器加载的,所以 String.class.getClassLoader() 就会返回 null。但是后面可以看到可以通过命令行参数影响它加载什么。
- 扩展类加载器(ExtClassLoader):
- 负责加载 JRE 的扩展目录,lib/ext 或者由 java.ext.dirs 系统属性指定的目录中的 JAR 包的类,代码里直接获取它的父类加载器为 null(因为无法拿到启动类加载器)。
- 类加载器在 Oracle Hotspot JVM 里,都是在中
sun.misc.Launcher
定义的。一般都继承自URLClassLoader
类,这个类也默认实现了从各种不同来源加载 class 字节码转换成 Class 的方法。
- 应用类加载器(AppClassLoader):
- 负责在 JVM 启动时加载来自 Java 命令的 -classpath 或者 -cp 选项、java.class.path 系统属性指定的 jar 包和类路径。在应用程序代码里可以通过 ClassLoader 的静态方法 getSystemClassLoader() 来获取应用类加载器。如果没有特别指定,则在没有使用自定义类加载器情况下,用户自定义的类都由此加载器加载。
- 类加载器在 Oracle Hotspot JVM 里,都是在中
sun.misc.Launcher
定义的,一般都继承自URLClassLoader
类,这个类也默认实现了从各种不同来源加载 class 字节码转换成 Class 的方法。
- 自定义类加载器。
- 自定义类加载器都以应用类加载器作为父加载器。应用类加载器的父类加载器为扩展类加载器。这些类加载器是有层次关系的,启动加载器又叫根加载器,是扩展加载器的父加载器,但是直接从 ExClassLoader 里拿不到它的引用,同样会返回 null。
- 启动类加载器(BootstrapClassLoader):由 JVM 内部实现的,在 Java 的 API 里无法拿到。
- 类加载机制
- 双亲委托:当一个自定义类加载器需要加载一个类,比如 java.lang.String,先委托自己的父加载器去加载,父加载器如果发现自己还有父加载器,会一直往前找,这样只要上级加载器,比如启动类加载器已经加载了某个类比如 java.lang.String,所有的子加载器都不需要自己加载了。如果几个类加载器都没有加载到指定名称的类,那么会抛出 ClassNotFountException 异常。
- 负责依赖:如果一个加载器在加载某个类的时候,发现这个类依赖于另外几个类或接口,也会去尝试加载这些依赖项。
- 缓存加载:为了提升加载效率,消除重复加载,一旦某个类被一个类加载器加载,那么它会缓存这个加载结果,不会重复加载。
自定义类加载器示例
public class Hello {static {System.out.println("Hello Class Initialized!");}
}
import java.util.Base64;public class HelloClassLoader extends ClassLoader {public static void main(String[] args) {try {new HelloClassLoader().findClass("jvm.Hello").newInstance(); // 加载并初始化Hello类} catch (ClassNotFoundException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();} catch (InstantiationException e) {e.printStackTrace();}}@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {String helloBase64 = "yv66vgAAADQAHwoABgARCQASABMIABQKABUAFgcAFwcAGAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2N" +"hbFZhcmlhYmxlVGFibGUBAAR0aGlzAQALTGp2bS9IZWxsbzsBAAg8Y2xpbml0PgEAClNvdXJjZUZpbGUBAApIZWxsby5qYXZhDAAHAAgHABkMABoAGwEAGEhlb" +"GxvIENsYXNzIEluaXRpYWxpemVkIQcAHAwAHQAeAQAJanZtL0hlbGxvAQAQamF2YS9sYW5nL09iamVjdAEAEGphdmEvbGFuZy9TeXN0ZW0BAANvdXQBABVMamF2" +"YS9pby9QcmludFN0cmVhbTsBABNqYXZhL2lvL1ByaW50U3RyZWFtAQAHcHJpbnRsbgEAFShMamF2YS9sYW5nL1N0cmluZzspVgAhAAUABgAAAAAAAgABAAcACA" +"ABAAkAAAAvAAEAAQAAAAUqtwABsQAAAAIACgAAAAYAAQAAAAMACwAAAAwAAQAAAAUADAANAAAACAAOAAgAAQAJAAAAJQACAAAAAAAJsgACEgO2AASxAAAAAQAK" +"AAAACgACAAAABgAIAAcAAQAPAAAAAgAQ";byte[] bytes = decode(helloBase64);return defineClass(name,bytes,0,bytes.length);}public byte[] decode(String base64){return Base64.getDecoder().decode(base64);}}
- 两个没有关系的自定义类加载器之间加载的类是不共享的(只共享父类加载器,兄弟之间不共享),这样就可以实现不同的类型沙箱的隔离性
- 可以用多个类加载器,各自加载同一个类的不同版本,在这个基础上可以实现类的动态加载卸载,热插拔的插件机制等。
实用技巧
-
排查再找不到jar包的问题
-
结果可以看到三种类加载器各自默认加载了哪些 jar 包和包含了哪些 classpath 的路径
-
import java.lang.reflect.Field; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList;public class JvmClassLoaderPrintPath {public static void main(String[] args) {// 启动类加载器URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();System.out.println("启动类加载器");for(URL url : urls) {System.out.println(" ==> " +url.toExternalForm());}// 扩展类加载器printClassLoader("扩展类加载器", JvmClassLoaderPrintPath.class.getClassLoader().getParent());// 应用类加载器printClassLoader("应用类加载器", JvmClassLoaderPrintPath.class.getClassLoader());}public static void printClassLoader(String name, ClassLoader CL){if(CL != null) {System.out.println(name + " ClassLoader -> " + CL.toString());printURLForClassLoader(CL);}else{System.out.println(name + " ClassLoader -> null");}}public static void printURLForClassLoader(ClassLoader CL){Object ucp = insightField(CL,"ucp");Object path = insightField(ucp,"path");ArrayList ps = (ArrayList) path;for (Object p : ps){System.out.println(" ==> " + p.toString());}}private static Object insightField(Object obj, String fName) {try {Field f = null;if(obj instanceof URLClassLoader){f = URLClassLoader.class.getDeclaredField(fName);}else{f = obj.getClass().getDeclaredField(fName);}f.setAccessible(true);return f.get(obj);} catch (Exception e) {e.printStackTrace();return null;}} }
-
-
如何排查类的方法不一致的问题
- 假如确定一个 jar 或者 class 已经在 classpath 里了,但是却总是提示
java.lang.NoSuchMethodError
? - 很可能是加载了错误的或者重复加载了不同版本的 jar 包。
- 用前面的方法就可以先排查一下,加载了具体什么 jar,然后是不是不同路径下有重复的 class 文件,但是版本不一样。
- 假如确定一个 jar 或者 class 已经在 classpath 里了,但是却总是提示
-
怎么看到加载了哪些类,以及加载顺序?
- 假如有两个地方有 Hello.class,一个是新版本,一个是旧的,怎么才能直观地看到他们的加载顺序呢?
- 可以直接打印加载的类清单和加载顺序。
- 只需要在类的启动命令行参数加上
-XX:+TraceClassLoading
或者-verbose
即可,注意需要加载 Java 命令之后,要执行的类名之前,不然不起作用。例如:java -XX:+TraceClassLoading jvm.HelloClassLoader
-
怎么调整或修改 ext 和本地加载路径?
-
从前面的例子我们可以看到,假如什么都不设置,直接执行 java 命令,默认也会加载非常多的 jar 包,怎么可以自定义加载哪些 jar 包呢?比如我的代码很简单,只加载 rt.jar 行不行?答案是肯定的。
$ java -Dsun.boot.class.path="D:\Program Files\Java\jre1.8.0_231\lib\rt.jar" -Djava.ext.dirs= jvm.JvmClassLoaderPrintPath启动类加载器==> file:/D:/Program%20Files/Java/jdk1.8.0_231/jre/lib/rt.jar 扩展类加载器 ClassLoader -> sun.misc.Launcher$ExtClassLoader@15db9742 应用类加载器 ClassLoader -> sun.misc.Launcher$AppClassLoader@73d16e93==> file:/D:/git/studyjava/build/classes/java/main/==> file:/D:/git/studyjava/build/resources/main
- 我们看到启动类加载器只加载了 rt.jar,而扩展类加载器什么都没加载,这就达到了我们的目的。
- 命令行参数
-Dsun.boot.class.path
表示我们要指定启动类加载器加载什么,最基础的东西都在 rt.jar 这个包了里,所以一般配置它就够了。需要注意的是因为在 windows 系统默认 JDK 安装路径有个空格,所以需要把整个路径用双引号括起来,如果路径没有空格,或是 Linux/Mac 系统,就不需要双引号了。 - 参数
-Djava.ext.dirs
表示扩展类加载器要加载什么,一般情况下不需要的话可以直接配置为空即可。
-
-
怎么运行期加载额外的 jar 包或者 class 呢?
-
有时候在程序已经运行了以后,还想要再额外的去加载一些 jar 或类.简单说就是不使用命令行参数的情况下,怎么用代码来运行时改变加载类的路径和方式
-
假如说,在
d:/app/jvm
路径下,有刚才使用过的 Hello.class 文件,怎么在代码里能加载这个 Hello 类呢?- 一个是自定义 ClassLoader 的方式
- 还有一个是直接在当前的应用类加载器里,使用 URLClassLoader 类的方法 addURL,不过这个方法是 protected 的,需要反射处理一下,然后又因为程序在启动时并没有显示加载 Hello 类,所以在添加完了 classpath 以后,没法直接显式初始化,需要使用 Class.forName 的方式来拿到已经加载的Hello类(Class.forName(“jvm.Hello”)默认会初始化并执行静态代码块)。代码如下:
package jvm;import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader;public class JvmAppClassLoaderAddURL {public static void main(String[] args) {String appPath = "file:/d:/app/";URLClassLoader urlClassLoader = (URLClassLoader) JvmAppClassLoaderAddURL.class.getClassLoader();try {Method addURL = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);addURL.setAccessible(true);URL url = new URL(appPath);addURL.invoke(urlClassLoader, url);Class.forName("jvm.Hello"); // 效果跟Class.forName("jvm.Hello").newInstance()一样} catch (Exception e) {e.printStackTrace();}} }
执行以下,结果如下:
$ java JvmAppClassLoaderAddURL Hello Class Initialized!
结果显示 Hello 类被加载,成功的初始化并执行了其中的代码逻辑。
-
相关文章:
JVM--Java类加载器笔记
Java类加载器 代码经过编译变成了字节码打包成 Jar 文件。让 JVM 去加载需要的字节码,变成持久代/元数据区上的 Class 对象,接着执行程序逻辑。 类声明周期和加载过程 步骤:加载->链接(校验->准备->解析)-…...

【在Ubuntu部署Docker项目】— PROJECT#1
一、说明 让我们深入了解 Docker。用docker构建web服务器。我们正在计划开发JavaScript API,建立MySQL数据库,并创建一个 PHP 网站使用 API 服务。Php Node.js Mysql — DockerSeries — Episode#1 二、系统架构概述 我们要构建的容器,是三…...
【学习笔记】LOJ #6240. 仙人掌
毒瘤题😅 简单版本 CF235D Graph Game 首先,考虑建立圆方树,然后对于一个点双(简单环)上的两个点,有两条路径可以到达 和简单版本类似,考虑容斥。即枚举点对 i , j i,j i,j之间 哪些路径是联…...
java通过接口转发文件(上传下载)
java接口转发上传的文件 RequestMapping(value "/XXXX/fileUpload", method RequestMethod.POST) public String getFileUpload2(RequestParam("file") MultipartFile file, HttpServletRequest request) public static String hotMapPost3(String ur…...
Docker-部署docker-compose以及管理服务
部署docker-compose以及管理服务 文章目录 部署docker-compose以及管理服务[TOC] 前言一、docker-compose是什么?1、介绍2、 功能 二、安装docker-compose1.yum直接安装2.二进制安装3.pip安装 三、docker-compose部署服务1.编写docker-compose.yml文件 总结 前言 D…...
Android - Monkey 测试应用出现Crash报错IllegalStateException
问题描述 平时使用Lottie动画都是正常的,没出过这个crash问题,看下的报错信息,代码中文件夹也设置了,没看出来问题。 AndroidRuntime: java.lang.IllegalStateException: You must set an images folder before loading an imag…...

Spring源码分析 事务 实现原理
文章目录 什么是事务Spring事务管理Spring事务实现原理事务管理器事务定义事务的开启事务核心方法业务代码使用事务TransactionInterceptor 什么是事务 一般所指的事务是数据库事务,是指一批不可分割的数据库操作序列,也是数据库并发控制的基本单位。其…...

ADS-B及雷达显示终端8.3
新版本功能升级主要有如下: 1、地图更新 在上一版本8.2中使用的高程地图为由SRTM经过地形晕渲后,生成地形图片,然后对图片进行贴图,一一按规定位置、大小将地形图贴至底图上,而后在底图上进行二维矢量地图的绘制,包括…...
第二章:最新版零基础学习 PYTHON 教程(第二节 - Python 输入/输出–从 Python 控制台获取输入)
目录 Python 中的控制台是什么? 接受来自控制台的输入: 1. 将输入类型转换为整数:...

linux安装配置 flume
目录 一 解压安装包 二 配置部署 (1)修改配置 (2)下载工具 (3)创建配置文件 (4)启动监听测试 (5)flume监控文件 一 解压安装包 这里提供了网盘资源 链…...
SSM - Springboot - MyBatis-Plus 全栈体系(十五)
第三章 MyBatis 二、MyBatis 基本使用 4. CRUD 强化练习 4.1 准备数据库数据 首先,我们需要准备一张名为 user 的表。该表包含字段 id(主键)、username、password。创建SQL如下: CREATE TABLE user (id INT(11) NOT NULL AUT…...

win10默认浏览器改不了怎么办,解决方法详解
win10默认浏览器改不了怎么办,解决方法详解_蓝天网络 在使用Windows 10操作系统时,你可能会遇到无法更改默认浏览器的情况。这可能是因为其他程序或设置正在干扰更改。如果你也遇到了这个问题,不要担心,本文将为你提供详细的解决…...

C语言连接MySQL并执行SQL语句(hello world)
1.新建一个控制台项目 参考【VS2022 和 VS2010 C语言控制台输出 Hello World】VS2022 和 VS2010 C语言控制台输出 Hello World_vs2022源文件在哪_西晋的no1的博客-CSDN博客 2.安装MySQL 参考【MySQL 8.0.34安装教程】MySQL 8.0.34安装教程_西晋的no1的博客-CSDN博客 3.复制MySQ…...

react实现动态递增展示数字特效
在可视化展示界面时有一种场景,就是页面在初始化的时候,有些数字展示想要从某个值开始动态递增到实际值,形成一种动画效果。例如: 写一个数字递增的组件,有两种方式:1.固定步长,代码如下&#x…...

读取.nrrd和.dcm文件格式医学图片可视化与预处理
nrrd数据格式 MITK默认会将医学图像保存为格式为NRRD的图像,在这个数据格式中包含: 1、一个单个的数据头文件:为科学可视化和医学图像处理准确地表示N维度的栅格信息。 2、既能分开又能合并的图像文件。 nrrd_options输出 {u’dimension’:…...

VS CODE中的筛选器如何打开?
最近更新了vscode1.82版本,发现在git管理界面有一个“筛选器”功能,十分好用,后来关掉了,找了好久都没有找到办法打开这个筛选器功能,今天无意中不知道按到了哪个快捷键,打开了,就是下图这个&am…...

vue 多环境文件配置(开发,测试,生产)
1.经常我们在开发时候会有不同环境,要代理的路由等等都会出现不同 配置一下三个文件打包的时候,执行三个不同的指令就会打包不同的环境 npm run build:dev npm run build:test npm run build:prodpackage.json 中配置scripts 指令 以,env.development…...
在服务器上搭建pulseaudio的运行环境,指定其运行目录、状态目录和模块目录
如果想在搭建 PulseAudio 的服务器上指定其运行目录、状态目录和模块目录,可以通过修改 PulseAudio 的配置文件来实现。一般情况下所涉及的配置文件和相关选项如下所示: 1、配置文件路径:通常情况下,PulseAudio 的配置文件位于 /…...

[Qt]QListView 重绘实例之一:背景重绘
0 环境 Windows 11Qt 5.15.2 MinGW x64 1 系列文章 简介:本系列文章,是以纯代码方式实现 Qt 控件的重构,尽量不使用 Qss 方式。 《[Qt]QListView 重绘实例之一:背景重绘》 《[Qt]QListView 重绘实例之二:列表项覆…...

国庆周《Linux学习第二课》
Linux开篇指南针环境安装(第一课)-CSDN博客 Linux详细的环境安装介绍在上面 第一 环境准备过程 安装过程...
day52 ResNet18 CBAM
在深度学习的旅程中,我们不断探索如何提升模型的性能。今天,我将分享我在 ResNet18 模型中插入 CBAM(Convolutional Block Attention Module)模块,并采用分阶段微调策略的实践过程。通过这个过程,我不仅提升…...
【解密LSTM、GRU如何解决传统RNN梯度消失问题】
解密LSTM与GRU:如何让RNN变得更聪明? 在深度学习的世界里,循环神经网络(RNN)以其卓越的序列数据处理能力广泛应用于自然语言处理、时间序列预测等领域。然而,传统RNN存在的一个严重问题——梯度消失&#…...

Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility
Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility 1. 实验室环境1.1 实验室环境1.2 小测试 2. The Endor System2.1 部署应用2.2 检查现有策略 3. Cilium 策略实体3.1 创建 allow-all 网络策略3.2 在 Hubble CLI 中验证网络策略源3.3 …...

IoT/HCIP实验-3/LiteOS操作系统内核实验(任务、内存、信号量、CMSIS..)
文章目录 概述HelloWorld 工程C/C配置编译器主配置Makefile脚本烧录器主配置运行结果程序调用栈 任务管理实验实验结果osal 系统适配层osal_task_create 其他实验实验源码内存管理实验互斥锁实验信号量实验 CMISIS接口实验还是得JlINKCMSIS 简介LiteOS->CMSIS任务间消息交互…...

华为云Flexus+DeepSeek征文|DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建
华为云FlexusDeepSeek征文|DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建 前言 如今大模型其性能出色,华为云 ModelArts Studio_MaaS大模型即服务平台华为云内置了大模型,能助力我们轻松驾驭 DeepSeek-V3/R1,本文中将分享如何…...

20个超级好用的 CSS 动画库
分享 20 个最佳 CSS 动画库。 它们中的大多数将生成纯 CSS 代码,而不需要任何外部库。 1.Animate.css 一个开箱即用型的跨浏览器动画库,可供你在项目中使用。 2.Magic Animations CSS3 一组简单的动画,可以包含在你的网页或应用项目中。 3.An…...
现有的 Redis 分布式锁库(如 Redisson)提供了哪些便利?
现有的 Redis 分布式锁库(如 Redisson)相比于开发者自己基于 Redis 命令(如 SETNX, EXPIRE, DEL)手动实现分布式锁,提供了巨大的便利性和健壮性。主要体现在以下几个方面: 原子性保证 (Atomicity)ÿ…...

2025年渗透测试面试题总结-腾讯[实习]科恩实验室-安全工程师(题目+回答)
安全领域各种资源,学习文档,以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具,欢迎关注。 目录 腾讯[实习]科恩实验室-安全工程师 一、网络与协议 1. TCP三次握手 2. SYN扫描原理 3. HTTPS证书机制 二…...

通过 Ansible 在 Windows 2022 上安装 IIS Web 服务器
拓扑结构 这是一个用于通过 Ansible 部署 IIS Web 服务器的实验室拓扑。 前提条件: 在被管理的节点上安装WinRm 准备一张自签名的证书 开放防火墙入站tcp 5985 5986端口 准备自签名证书 PS C:\Users\azureuser> $cert New-SelfSignedCertificate -DnsName &…...
【FTP】ftp文件传输会丢包吗?批量几百个文件传输,有一些文件没有传输完整,如何解决?
FTP(File Transfer Protocol)本身是一个基于 TCP 的协议,理论上不会丢包。但 FTP 文件传输过程中仍可能出现文件不完整、丢失或损坏的情况,主要原因包括: ✅ 一、FTP传输可能“丢包”或文件不完整的原因 原因描述网络…...