Java 内存区域详解
1 常见面试题
1.1 基本问题
- 介绍下Java内存区域(运行时数据区)
- Java对象的创建过程(五步,建议能够默写出来并且要知道每一步虚拟机做了什么)
- 对象的访问定位的两种方式(句柄和直接指针两种方式)
1.2 拓展问题
- String类和常量池
- 8种基本类型的包装类和常量池
2 概述
对于Java程序员来说,在虚拟自动内存管理机制下,不再需要像C/C++程序开发程序员这样为每一个new操作去写对应的delete/free操作,不容易出现内存泄漏和内存溢出问题。正是因为Java程序员把内存控制的权力交给Java虚拟机,一旦出现内存泄漏和溢出方面的问题,如果不了解虚拟机是怎样使用内存的,那么排查错误将会是一个非常艰巨的任务。
3 运行时数据区域
Java虚拟机在执行Java程序的过程中会把它管理的内存划分成若干个不同的数据区域。JDK1.8和之前的版本略有不同,下面会介绍到。
jdk1.8之前:
jdk1.8:
线程私有的:
- 程序计数器
- 虚拟机栈
- 本地方法栈
线程共享的
- 堆
- 方法区
- 直接内存(非运行时数据区的一部分)
3.1 程序计数器
程序计数器是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。字节码解释器工作时通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等功能都需要依赖这个计数器来完成。
另外,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各线程之间计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。
从上面的介绍中我们知道程序计数器主要有两个作用:
- 字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。
- 在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。
注意:程序计数器是唯一一个不会出现 OutOfMemoryError
的内存区域,它的生命周期随着线程的创建而创建,随着线程的结束而死亡。
3.2Java虚拟机栈
与程序计数器一样,Java虚拟机栈也是线程私有的,它的生命周期和线程相同,描述的是Java方法执行的内存模型,每次方法调用的数据都是通过栈传递的。
Java内存可以粗糙的区分为堆内存(Heap)和栈内存(Stack),其中栈就是现在说的虚拟机栈中局部变量表部分。(实际上,Java虚拟机栈是由一个个栈帧组成,而每个栈帧中拥有:局部变量表、操作数栈、动态链接、方法出口信息。)
局部变量表主要存放了编译期可知的各种数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,它不同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)
Java虚拟机栈会出现两种错误:StackOverFlowError和OutOfMemoryError。
StackOverFlowError
: 若 Java 虚拟机栈的内存大小不允许动态扩展,那么当线程请求栈的深度超过当前 Java 虚拟机栈的最大深度的时候,就抛出 StackOverFlowError 错误。OutOfMemoryError
: 若 Java 虚拟机堆中没有空闲内存,并且垃圾回收器也无法提供更多内存的话。就会抛出 OutOfMemoryError 错误。
Java虚拟机栈也是线程私有的,每个线程都有各自的Java虚拟机栈,而且随着线程的创建而创建,随着线程的死亡而死亡
扩展:那么方法/函数如何调用?
Java栈可用类比数据结构中栈,Java栈中保存的主要内容是栈帧,每一次函数调用都会有一个对应的栈帧被压入Java栈,每一个函数调用结束后,都会有一个栈帧被弹出。
Java方法有两种返回方式:
1.return语句。
2.抛出异常。
不管哪种返回方式都会导致栈帧被弹出。
3.3 本地方法栈
和虚拟机栈所发挥的作用非常相似,区别是:虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机是用到的Native方法服务。在HotSpot虚拟机中和Java虚拟机栈合二为一。
本地方法被执行的时候,在本地方法栈也会创建一个栈帧,用于存放该本地方法的局部变量表、操作数栈、动态链接、出口信息。
方法执行完毕后相应的栈帧也会出栈并释放内存空间,也会出现StackOverFlowError和OutOfMemoryError两种错误。
3.4 堆
Java虚拟机所管理的内存中最大的一块,Java堆是所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。
Java世界中“几乎”所有的对象都在堆中分配,但是,随着JIT编译期的发展和逃逸分析技术逐渐成熟,栈上分配、标量替换优化技术将会导致一些微妙的变化,所有的对象都分配到堆上也渐渐变得不那么“绝对”了。从JDK1.7开始已经默认开启逃逸分析。如果某些方法中的对象引用没有被返回或者未被外面使用(也就是未逃逸出去),那么对象可以直接在栈上分配内存。
Java堆是垃圾收集器管理的主要区域,因此也被称作GC堆。从垃圾回收的角度,由于现在收集器基本都采用分代垃圾收集算法,所以Java堆还可以细分为:新生代和老年代:再细致一点有:Eden空间、From Survivor、To Survivor空间等。进一步划分的目的是更好地回收内存,或者更快地分配内存。
在JDK7版本以及JDK7版本之前,堆内存被通常分为下面三部分:
1.新生代内存(Young Generation)
2.老生代(Old Generation)
3.永生代(Permanent Generation)
JVM堆内存结构-JDK7
Java8版本之后方法区(HopSpot的永生代)被彻底移除了(JDK1.7就已经开始了),取而代之的是元空间,元空间使用的是直接内存。
JVM堆内存结构-JDK8
上图所示的Eden区、两个Survivor区都属于新生代(为了区分,这两个Survivor区域按照顺序被命名为from和to),中间一层属于老年代。
大部分情况,对象都会首先在Eden区域分配,在一次新生代垃圾回收后,如果对象还存活,则会进入s0或者s1,并且对象的年龄还会增加1(Eden区->Survivor区后对象的初始年龄变为1),当它的年龄增加到一定程度(默认为15岁),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 -XX:MaxTenuringThreshold
来设置。
修正(issue552):“Hotspot遍历所有对象时,按照年龄从小到大对其所占用的大小进行累积,当累积的某个年龄大小超过了survivor区的一半时,取这个年龄和MaxTenuringThreshold中更小的一个值,作为新的晋升年龄阈值”。
动态年龄计算的代码如下
uint ageTable::compute_tenuring_threshold(size_t survivor_capacity) {//survivor_capacity是survivor空间的大小size_t desired_survivor_size = (size_t)((((double) survivor_capacity)*TargetSurvivorRatio)/100);size_t total = 0;uint age = 1;while (age < table_size) {total += sizes[age];//sizes数组是每个年龄段对象大小if (total > desired_survivor_size) break;age++;}uint result = age < MaxTenuringThreshold ? age : MaxTenuringThreshold;...
}
堆这里最容易出现的就是 OutOfMemoryError 错误,并且出现这种错误之后的表现形式还会有几种,比如:
OutOfMemoryError: GC Overhead Limit Exceeded
: 当JVM花太多时间执行垃圾回收并且只能回收很少的堆空间时,就会发生此错误。java.lang.OutOfMemoryError: Java heap space
:假如在创建新的对象时, 堆内存中的空间不足以存放新创建的对象, 就会引发java.lang.OutOfMemoryError: Java heap space
错误。(和本机物理内存无关,和你配置的内存大小有关!)- ......
3.5 方法区
方法区与 Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然 Java 虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做 Non-Heap(非堆),目的应该是与 Java 堆区分开来。
方法区也被称为永久代。很多人都会分不清方法区和永久代的关系,为此我也查阅了文献。
3.5.1 方法区和永久代的关系
《Java 虚拟机规范》只是规定了有方法区这么个概念和它的作用,并没有规定如何去实现它。那么,在不同的 JVM 上方法区的实现肯定是不同的了。 方法区和永久代的关系很像 Java 中接口和类的关系,类实现了接口,而永久代就是 HotSpot 虚拟机对虚拟机规范中方法区的一种实现方式。 也就是说,永久代是 HotSpot 的概念,方法区是 Java 虚拟机规范中的定义,是一种规范,而永久代是一种实现,一个是标准一个是实现,其他的虚拟机实现并没有永久代这一说法。
3.5.2 常用参数
JDK 1.8 之前永久代还没被彻底移除的时候通常通过下面这些参数来调节方法区大小
-XX:PermSize=N //方法区 (永久代) 初始大小
-XX:MaxPermSize=N //方法区 (永久代) 最大大小,超过这个值将会抛出 OutOfMemoryError 异常:java.lang.OutOfMemoryError: PermGen
相对而言,垃圾收集行为在这个区域是比较少出现的,但并非数据进入方法区后就“永久存在”了。
JDK 1.8 的时候,方法区(HotSpot 的永久代)被彻底移除了(JDK1.7 就已经开始了),取而代之是元空间,元空间使用的是直接内存。
下面是一些常用参数:
-XX:MetaspaceSize=N //设置 Metaspace 的初始(和最小大小)
-XX:MaxMetaspaceSize=N //设置 Metaspace 的最大大小
与永久代很大的不同就是,如果不指定大小的话,随着更多类的创建,虚拟机会耗尽所有可用的系统内存。
3.5.3 为什么要将永久代(PermGen)替换为元空间(MetaSpace)呢?
1.整个永久代有一个 JVM 本身设置固定大小上限,无法进行调整,而元空间使用的是直接内存,受本机可用内存的限制,虽然元空间仍旧可能溢出,但是比原来出现的几率会更小。
当你元空间溢出时会得到如下错误:
java.lang.OutOfMemoryError: MetaSpace
你可以使用 -XX:MaxMetaspaceSize
标志设置最大元空间大小,默认值为 unlimited,这意味着它只受系统内存的限制。-XX:MetaspaceSize
调整标志定义元空间的初始大小如果未指定此标志,则 Metaspace 将根据运行时的应用程序需求动态地重新调整大小。
2.元空间里面存放的是类的元数据,这样加载多少类的元数据就不由 MaxPermSize
控制了, 而由系统的实际可用空间来控制,这样能加载的类就更多了。
3.在 JDK8,合并 HotSpot 和 JRockit 的代码时, JRockit 从来没有一个叫永久代的东西, 合并之后就没有必要额外的设置这么一个永久代的地方了。
3.6 运行时常量池
运行时常量池是方法区的一部分。Class 文件中除了有类的版本、字段、方法、接口等描述信息外,还有常量池表(用于存放编译期生成的各种字面量和符号引用)
既然运行时常量池是方法区的一部分,自然受到方法区内存的限制,当常量池无法再申请到内存时会抛出 OutOfMemoryError 错误。
修正(issue747,reference):
- JDK1.7之前运行时常量池逻辑包含字符串常量池存放在方法区, 此时hotspot虚拟机对方法区的实现为永久代
- JDK1.7 字符串常量池被从方法区拿到了堆中, 这里没有提到运行时常量池,也就是说字符串常量池被单独拿到堆,运行时常量池剩下的东西还在方法区, 也就是hotspot中的永久代 。
- JDK1.8 hotspot移除了永久代用元空间(Metaspace)取而代之, 这时候字符串常量池还在堆, 运行时常量池还在方法区, 只不过方法区的实现从永久代变成了元空间(Metaspace)
相关问题:JVM 常量池中存储的是对象还是引用呢?: https://www.zhihu.com/question/57109429/answer/151717241 by RednaxelaFX
3.7 直接内存
直接内存并不是虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域,但是这部分内存也被频繁地使用。而且也可能导致 OutOfMemoryError 错误出现。
JDK1.4 中新加入的 NIO(New Input/Output) 类,引入了一种基于通道(Channel) 与缓存区(Buffer) 的 I/O 方式,它可以直接使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样就能在一些场景中显著提高性能,因为避免了在 Java 堆和 Native 堆之间来回复制数据。
本机直接内存的分配不会受到 Java 堆的限制,但是,既然是内存就会受到本机总内存大小以及处理器寻址空间的限制。
4 HotSpot虚拟机对象探秘
通过上面的介绍我们大概知道了虚拟机的内存情况,下面我们来详细的了解一下 HotSpot 虚拟机在 Java 堆中对象分配、布局和访问的全过程。
4.1 对象的创建
下图便是Java对象创建的过程
4.1.1 类加载检查
虚拟机遇到一条 new 指令时,首先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否已被加载过、解析和初始化过。如果没有,那必须先执行相应的类加载过程。
4.1.2 分配内存
在类加载检查通过后,接下来虚拟机将为新生对象分配内存。对象所需的内存大小在类加载完成后便可确定,为对象分配空间的任务等同于把一块确定大小的内存从 Java 堆中划分出来。分配方式有 “指针碰撞” 和 “空闲列表” 两种,选择哪种分配方式由 Java 堆是否规整决定,而 Java 堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。
内存分配的两种方式:(补充内容,需要掌握)
选择以上两种方式中的哪一种,取决于 Java 堆内存是否规整。而 Java 堆内存是否规整,取决于 GC 收集器的算法是"标记-清除",还是"标记-整理"(也称作"标记-压缩"),值得注意的是,复制算法内存也是规整的
内存分配并发问题(补充内容,需要掌握)
在创建对象的时候有一个很重要的问题,就是线程安全,因为在实际开发过程中,创建对象是很频繁的事情,作为虚拟机来说,必须要保证线程是安全的,通常来讲,虚拟机采用两种方式来保证线程安全:
- CAS+失败重试: CAS 是乐观锁的一种实现方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。虚拟机采用 CAS 配上失败重试的方式保证更新操作的原子性。
- TLAB: 为每一个线程预先在 Eden 区分配一块儿内存,JVM 在给线程中的对象分配内存时,首先在 TLAB 分配,当对象大于 TLAB 中的剩余内存或 TLAB 的内存已用尽时,再采用上述的 CAS 进行内存分配
4.1.3 初始化零值
内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),这一步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。
4.1.4 设置对象头
初始化零值完成之后,虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息。 这些信息存放在对象头中。 另外,根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式。
4.1.5 执行init方法
在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但从 Java 程序的视角来看,对象创建才刚开始,<init>
方法还没有执行,所有的字段都还为零。所以一般来说,执行 new 指令之后会接着执行 <init>
方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来。
4.2 对象的内存布局
在 Hotspot 虚拟机中,对象在内存中的布局可以分为 3 块区域:对象头、实例数据和对齐填充。
Hotspot 虚拟机的对象头包括两部分信息,第一部分用于存储对象自身的运行时数据(哈希码、GC 分代年龄、锁状态标志等等),另一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是那个类的实例。
实例数据部分是对象真正存储的有效信息,也是在程序中所定义的各种类型的字段内容。
对齐填充部分不是必然存在的,也没有什么特别的含义,仅仅起占位作用。 因为 Hotspot 虚拟机的自动内存管理系统要求对象起始地址必须是 8 字节的整数倍,换句话说就是对象的大小必须是 8 字节的整数倍。而对象头部分正好是 8 字节的倍数(1 倍或 2 倍),因此,当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。
4.3 对象的访问定位
建立对象就是为了使用对象,我们的 Java 程序通过栈上的 reference 数据来操作堆上的具体对象。对象的访问方式由虚拟机实现而定,目前主流的访问方式有①使用句柄和②直接指针两种:
1.句柄: 如果使用句柄的话,那么 Java 堆中将会划分出一块内存来作为句柄池,reference 中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息;
对象的访问定位-使用句柄
2.直接指针: 如果使用直接指针访问,那么 Java 堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,而 reference 中存储的直接就是对象的地址。
对象的访问定位-直接指针
这两种对象访问方式各有优势。使用句柄来访问的最大好处是 reference 中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而 reference 本身不需要修改。使用直接指针访问方式最大的好处就是速度快,它节省了一次指针定位的时间开销。
5 重点补充内容
5.1 String类和常量池
String对象的两种创建方式
String str1 = "abcd";//先检查字符串常量池中有没有"abcd",如果字符串常量池中没有,则创建一个,然后 str1 指向字符串常量池中的对象,如果有,则直接将 str1 指向"abcd"";
String str2 = new String("abcd");//堆中创建一个新的对象
String str3 = new String("abcd");//堆中创建一个新的对象
System.out.println(str1==str2);//false
System.out.println(str2==str3);//false
这两种不同的创建方法是有差别的。
- 第一种方式是在常量池中拿对象;
- 第二种方式是直接在堆内存空间创建一个新的对象。
记住一点:只要使用 new 方法,便需要创建新的对象。
再给大家一个图应该更容易理解,图片来源:What is Java String Pool? | DigitalOcean:
String 类型的常量池比较特殊。它的主要使用方法有两种:
- 直接使用双引号声明出来的 String 对象会直接存储在常量池中。
- 如果不是用双引号声明的 String 对象,可以使用 String 提供的 intern 方法。String.intern() 是一个 Native 方法,它的作用是:如果运行时常量池中已经包含一个等于此 String 对象内容的字符串,则返回常量池中该字符串的引用;如果没有,JDK1.7之前(不包含1.7)的处理方式是在常量池中创建与此 String 内容相同的字符串,并返回常量池中创建的字符串的引用,JDK1.7以及之后的处理方式是在常量池中记录此字符串的引用,并返回该引用。
String s1 = new String("计算机");String s2 = s1.intern();String s3 = "计算机";System.out.println(s2);//计算机System.out.println(s1 == s2);//false,因为一个是堆内存中的 String 对象一个是常量池中的 String 对象,System.out.println(s3 == s2);//true,因为两个都是常量池中的 String 对象
字符串拼接:
String str1 = "str";String str2 = "ing";String str3 = "str" + "ing";//常量池中的对象String str4 = str1 + str2; //在堆上创建的新的对象 String str5 = "string";//常量池中的对象System.out.println(str3 == str4);//falseSystem.out.println(str3 == str5);//trueSystem.out.println(str4 == str5);//false
尽量避免多个字符串拼接,因为这样会重新创建对象。如果需要改变字符串的话,可以使用 StringBuilder 或者 StringBuffer。
5.2 String s1 = new String("abc");这句话创建了几个字符串对象?
将创建 1 或 2 个字符串。如果池中已存在字符串常量“abc”,则只会在堆空间创建一个字符串常量“abc”。如果池中没有字符串常量“abc”,那么它将首先在池中创建,然后在堆空间中创建,因此将创建总共 2 个字符串对象。
验证:
String s1 = new String("abc");// 堆内存的地址值String s2 = "abc";System.out.println(s1 == s2);// 输出 false,因为一个是堆内存,一个是常量池的内存,故两者是不同的。System.out.println(s1.equals(s2));// 输出 true
结果:
false
true
5.3 8种基本类型的包装类和常量池
Java 基本类型的包装类的大部分都实现了常量池技术,即 Byte,Short,Integer,Long,Character,Boolean;前面 4 种包装类默认创建了数值[-128,127] 的相应类型的缓存数据,Character创建了数值在[0,127]范围的缓存数据,Boolean 直接返回True Or False。如果超出对应范围仍然会去创建新的对象。 为啥把缓存设置为[-128,127]区间?(参见issue/461)性能和资源之间的权衡。
public static Boolean valueOf(boolean b) {return (b ? TRUE : FALSE);
}
private static class CharacterCache { private CharacterCache(){}static final Character cache[] = new Character[127 + 1]; static { for (int i = 0; i < cache.length; i++) cache[i] = new Character((char)i); }
}
两种浮点数类型的包装类 Float,Double 并没有实现常量池技术。
Integer i1 = 33;Integer i2 = 33;System.out.println(i1 == i2);// 输出 trueInteger i11 = 333;Integer i22 = 333;System.out.println(i11 == i22);// 输出 falseDouble i3 = 1.2;Double i4 = 1.2;System.out.println(i3 == i4);// 输出 false
Integer 缓存源代码:
/**
*此方法将始终缓存-128 到 127(包括端点)范围内的值,并可以缓存此范围之外的其他值。
*/public static Integer valueOf(int i) {if (i >= IntegerCache.low && i <= IntegerCache.high)return IntegerCache.cache[i + (-IntegerCache.low)];return new Integer(i);}
应用场景:
- Integer i1=40;Java 在编译的时候会直接将代码封装成 Integer i1=Integer.valueOf(40);,从而使用常量池中的对象。
- Integer i1 = new Integer(40);这种情况下会创建新的对象。
Integer i1 = 40;Integer i2 = new Integer(40);System.out.println(i1==i2);//输出 false
Integer 比较更丰富的一个例子:
Integer i1 = 40;Integer i2 = 40;Integer i3 = 0;Integer i4 = new Integer(40);Integer i5 = new Integer(40);Integer i6 = new Integer(0);System.out.println("i1=i2 " + (i1 == i2));System.out.println("i1=i2+i3 " + (i1 == i2 + i3));System.out.println("i1=i4 " + (i1 == i4));System.out.println("i4=i5 " + (i4 == i5));System.out.println("i4=i5+i6 " + (i4 == i5 + i6)); System.out.println("40=i5+i6 " + (40 == i5 + i6));
结果:
i1=i2 true
i1=i2+i3 true
i1=i4 false
i4=i5 false
i4=i5+i6 true
40=i5+i6 true
解释:
语句 i4 == i5 + i6,因为+这个操作符不适用于 Integer 对象,首先 i5 和 i6 进行自动拆箱操作,进行数值相加,即 i4 == 40。然后 Integer 对象无法与数值进行直接比较,所以 i4 自动拆箱转为 int 值 40,最终这条语句转为 40 == 40 进行数值比较。
参考
- 《深入理解 Java 虚拟机:JVM 高级特性与最佳实践(第二版》
- 《实战 java 虚拟机》
- Java SE Specifications
- http://www.pointsoftware.ch/en/under-the-hood-runtime-data-areas-javas-memory-model/
- https://dzone.com/articles/jvm-permgen-–-where-art-thou
- https://stackoverflow.com/questions/9095748/method-area-and-permgen
- 深入解析String#intern深入解析String#intern - 美团技术团队
相关文章:
Java 内存区域详解
1 常见面试题 1.1 基本问题 介绍下Java内存区域(运行时数据区)Java对象的创建过程(五步,建议能够默写出来并且要知道每一步虚拟机做了什么)对象的访问定位的两种方式(句柄和直接指针两种方式)…...
jEasyUI 创建学校课程表
jEasyUI 创建学校课程表 引言 随着信息技术的飞速发展,教育行业也迎来了数字化转型的浪潮。学校课程表的创建和管理作为教育信息化的重要组成部分,其效率和准确性直接影响到学校的教学秩序。jEasyUI,作为一款优秀的开源UI框架,凭借其易用性、灵活性和丰富的组件,成为了许…...
利用 OpenCV 进行棋盘检测与透视变换
利用 OpenCV 进行棋盘检测与透视变换 1. 引言 在计算机视觉领域,棋盘检测与透视变换是一个常见的任务,广泛应用于 摄像机标定、文档扫描、增强现实(AR) 等场景。本篇文章将详细介绍如何使用 OpenCV 进行 棋盘检测,并…...
git-提交时间和作者时间的区别
1.介绍 定义介绍 提交时间(Committer Date):决定了提交在 Git 历史中的位置,通常影响 GitHub 上提交显示的顺序。 作者时间(Author Date):虽然不影响提交的排序,但在每个提交详情页…...
解决双系统开机显示gnu grub version 2.06 Minimal BASH Like Line Editing is Supported
找了好多教程都没有用,终于解决了!!我是因为ubuntu分区的时候出问题了 问题描述: 双系统装好,隔天开机找不到引导项,黑屏显示下列 因为我用的D盘划分出来的部分空闲空间,而不是全部,…...
基于Flask的京东商品信息可视化分析系统的设计与实现
【Flask】基于Flask的京东商品信息可视化分析系统的设计与实现(完整系统源码开发笔记详细部署教程)✅ 目录 一、项目简介二、项目界面展示三、项目视频展示 一、项目简介 系统能够灵活地执行SQL查询,提取出用于分析的关键数据指标。为了将这…...
期权帮|股指期货中的套期保值如何操作?
锦鲤三三每日分享期权知识,帮助期权新手及时有效地掌握即市趋势与新资讯! 股指期货中的套期保值如何操作? 一、股指期货中的套期保值准备阶段 确定套保需求,投资者依据市场预判与投资组合分析,决定是否套保。 &…...
用Chrome Recorder轻松完成自动化测试脚本录制
前言 入门自动化测试,录制回放通常是小白测试首先用到的功能。而录制回放工具也一直是各大Web自动化测试必然会着重提供的一块功能。 早期WinRunner、QTP这样的工具,自动化测试可以说是围绕录制回放开展的。近年像Selenium也提供有录制工具 Selenium IDE,Playwright也包含…...
C/C++面试知识点总结
目录 1. 指针1.1 智能指针1.2 指针和引用的区别1.3 数组和指针的区别1.4 数组指针和指针数组的区别1.5 迭代器和指针的区别1.6 strcpy 和 memcpy 的区别 2. 内存管理与分配2.1 内存分配与存储区2.2 malloc / free2.3 volatile和extern的区别2.4 拷贝构造函数2.5 预处理、编译、…...
springboot三层架构详细讲解
目录 springBoot三层架构 0.简介1.各层架构 1.1 Controller层1.2 Service层1.3 ServiceImpl1.4 Mapper1.5 Entity1.6 Mapper.xml 2.各层之间的联系 2.1 Controller 与 Service2.2 Service 与 ServiceImpl2.3 Service 与 Mapper2.4 Mapper 与 Mapper.xml2.5 Service 与 Entity2…...
助力DeepSeek私有化部署服务:让企业AI落地更简单、更安全
在数字化转型的浪潮中,越来越多的企业选择私有化部署AI技术,以保障数据安全、提升业务效率并实现自主可控。DeepSeek作为行业领先的AI开源技术,其技术可以支持企业私有化部署,企业需要一站式服务私有化部署,涵盖硬件采…...
Mac book Air M2 用VMware安装 Ubuntu22.04
安装 VMware Fusion 下载 Ubuntu 安装VMware 完成之后运行新建 将对应Ubuntu 版本拖拽 如图 选择第一个回车 选绿色 回车 为空 相关命令行 sudo apt install net-tools sudo apt install ubuntu-desktop sudo reboot 常用命令行 uname uname -a clear ll ifconfig (查…...
Spring Boot接收参数的19种方式
Spring Boot是一个强大的框架,允许开发人员通过多种方式接收和处理参数。无论是HTTP请求参数、路径变量,还是请求体中的数据,Spring Boot都能提供灵活的处理方式。本文将介绍19种不同的方式来接收参数。 1. 查询参数(Query Param…...
Linux firewalld 常用命令
本文参考RedHat官网文章How to configure a firewall on Linux with firewalld。 Firewalld 是守护进程名,对应命令为firewall-cmd。帮助详见以下命令: $ firewall-cmd --helpUsage: firewall-cmd [OPTIONS...]General Options-h, --help Pr…...
火语言RPA--Excel插入空行
【组件功能】:在Excel内指定的位置插入空行 配置预览 配置说明 在第n行之前 支持T或# 填写添加插入第n行之前行号。 插入n行 支持T或# 插入多少行。 Sheet页名称 支持T或# Excel表格工作簿名称。 示例 Excel插入空行 描述 在第3行之后插入3行。 配置 输…...
纷析云开源版- Vue2-增加字典存储到localStorage
main.js //保存字典数据到LocalStorage Vue.prototype.$api.setting.SystemDictType.all().then(({data}) > {loadDictsToLocalStorage(data) })新增 dictionary.js 放在 Utils文件夹里面 // 获取字典数据 export function getDictByType(dictType) {const dicts JSON.par…...
LangChain-基础(prompts、序列化、流式输出、自定义输出)
LangChain-基础 我们现在使用的大模型训练数据都是基于历史数据训练出来的,它们都无法处理一些实时性的问题或者一些在训练时为训练到的一些问题,解决这个问题有2种解决方案 基于现有的大模型上进行微调,使得它能适应这些问题(本…...
机器学习在脑卒中预测中的应用:不平衡数据集处理方法详解
机器学习在脑卒中预测中的应用:不平衡数据集处理方法详解 目录 引言 脑卒中的全球影响机器学习在医疗预测中的挑战类别不平衡问题的核心痛点数据预处理与特征选择 数据来源与清洗缺失值处理方法类别特征编码特征选择技术处理类别不平衡的四大方法 SMOTE(合成少数类过采样技术…...
Spring Boot项目@Cacheable注解的使用
Cacheable 是 Spring 框架中用于缓存的注解之一,它可以帮助你轻松地将方法的结果缓存起来,从而提高应用的性能。下面详细介绍如何使用 Cacheable 注解以及相关的配置和注意事项。 1. 基本用法 1.1 添加依赖 首先,确保你的项目中包含了 Spr…...
飞书API
extend目录下,API <?php // ---------------------------------------------------------------------- // | 飞书API // ---------------------------------------------------------------------- // | COPYRIGHT (C) 2021 http://www.jeoshi.com All rights reserved. …...
杨校老师课堂之信息学奥赛结构体操作使用经典题集锦汇总
C基础:结构体数组综合训练 员工信息处理系统题目描述输入描述输出描述解题思路参考代码 员工信息处理系统 题目描述 在一家企业中,员工信息的准确性和时效性是日常人事管理工作的关键。由于企业员工数量众多,手动统计与更新员工信息不仅耗费大量时间&a…...
交互编程工具之——Jupyter
Jupyter 是什么? Jupyter 是一个开源的交互式编程和数据分析工具,广泛应用于数据科学、机器学习、教育和研究领域。其核心是 Jupyter Notebook(现升级为 JupyterLab),允许用户在一个基于浏览器的界面中编写代码、运行…...
Redis常见问题排查
redis连接不上去,ERR max number of clients reached redis默认最大连接是10000,如果出现连接泄露或者被服务器被攻击可能会出现连接数超过限制。 Redis 的 INFO 命令可以提供服务器的统计信息,其中包括当前客户端连接数。这是获取连接数最…...
Hadoop初体验
一、HDFS初体验 1. shell命令操作 hadoop fs -mkdir /itcast hadoop fs -put zookeeper.out /itcast hadoop fs -ls / 2. Web UI页面操作 结论: HDFS本质就是一个文件系统有目录树结构 和Linux类似,分文件、文件夹为什么上传一个小文件也这…...
深入解析C++26 Execution Domain:设计原理与实战应用
一、Domain设计目标与核心价值 Domain是C26执行模型的策略载体,其核心解决两个问题: 执行策略泛化:将线程池、CUDA流等异构调度逻辑抽象为统一接口策略组合安全:通过类型隔离避免不同执行域的策略污染 // Domain类型定义示例&a…...
基于Flask的租房信息可视化系统的设计与实现
【Flask】基于Flask的租房信息可视化系统的设计与实现(完整系统源码开发笔记详细部署教程)✅ 目录 一、项目简介二、项目界面展示三、项目视频展示 一、项目简介 随着互联网的快速发展,租房市场日益繁荣,信息量急剧增加ÿ…...
TensorFlow v2.16 Overview
TensorFlow v2.16 Overview 一、模块 Modules二、类 Classes三、函数 Functions TensorFlow v2.16.1 Overview 一、模块 Modules 模块是TensorFlow中组织代码的一种方式,将相关的功能和类封装在一起,方便用户使用和管理。每个模块都提供了特定领域的公共…...
网页版的俄罗斯方块
1、新建一个txt文件 2、打开后将代码复制进去保存 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>俄…...
Vue3 状态管理 - Pinia
目录 1. 什么是Pinia 2. 手动添加Pinia到Vue项目 3. Pinia的基础使用 4. getters实现 5. action异步实现 6. storeToRefs工具函数 7. Pinia的调试 8. Pinia的持久化插件 1. 什么是Pinia Pinia 是 Vue 专属的最新状态管理库 ,是 Vuex 状态管理工具的替代品 …...
Arduino 第十六章:pir红外人体传感器练习
Arduino 第十六章:PIR 传感器练习 一、引言 在 Arduino 的众多有趣项目中,传感器的应用是非常重要的一部分。今天我们要学习的主角是 PIR(被动红外)传感器。PIR 传感器能够检测人体发出的红外线,常用于安防系统、自动…...
伯克利 CS61A 课堂笔记 10 —— Trees
本系列为加州伯克利大学著名 Python 基础课程 CS61A 的课堂笔记整理,全英文内容,文末附词汇解释。 目录 01 Trees 树 Ⅰ Tree Abstraction Ⅱ Implementing the Tree Abstraction 02 Tree Processing 建树过程 Ⅰ Fibonacci tree Ⅱ Tree Process…...
Springboot 高频面试题
以下是Spring Boot的高频面试题及答案和底层原理解释: 基础概念 什么是Spring Boot,其主要特点是什么? 答案: Spring Boot本质上是一个建立在Spring框架之上的快速应用开发框架。其主要特点包括: 启动器:一…...
从零开始玩转TensorFlow:小明的机器学习故事 2
你好,TensorFlow!——从零开始的第一个机器学习程序 1. 为什么要写这个“Hello, TensorFlow!”? 无论学习什么新语言或新框架,“Hello World!”示例都能帮助我们快速确认开发环境是否就绪,并掌握最基本的使用方式。对…...
第四届图像、信号处理与模式识别国际学术会议(ISPP 2025)
重要信息 会议官网:www.icispp.com 会议时间:2025年3月28-30日 会议地点:南京 简介 由河海大学和江苏大学联合主办的第四届图像、信号处理与模式识别国际学术会议(ISPP 2025) 将于2025年3月28日-30日在中国南京举行。会议主…...
阿里云通过docker安装skywalking及elasticsearch操作流程
系统 本文使用系统为 Alibaba Cloud Linux 3.2104 LTS 64位 配置为 4核8G PS:最低配置应为2核4G,配置过低无法启动 安装docker 1.卸载旧版本docker yum remove docker \docker-client \docker-client-latest \docker-common \docker-latest \docker-…...
Linux·spin_lock的使用
自旋锁 内核当发生访问资源冲突的时候,可以有两种锁的解决方案选择: 一个是原地等待一个是挂起当前进程,调度其他进程执行(睡眠) Spinlock 是内核中提供的一种比较常见的锁机制,自旋锁是“原地等待”的方…...
企业内部真题
文章目录 前端面试题:一个是铺平的数组改成树的结构问题一解析一问题一解析二前端面试题:for循环100个接口,每次只调3个方法一:使用 `async/await` 和 `Promise`代码解释(1):代码解释(2):1. `fetchApi` 函数2. `concurrentFetch` 函数3. 生成 100 个接口地址4. 每次并…...
MySQL基本操作——包含增删查改(环境为Ubuntu20.04,MySQL5.7.42)
1.库的操作 1.1 创建数据库 语法: 说明: 大写的表示关键字 [] 是可选项 CHARACTER SET: 指定数据库采用的字符集 COLLATE: 指定数据库字符集的校验规则 1.2 创建案例 创建一个使用utf8字符集的db1数据库 create database db1 charsetutf8; …...
程序代码篇---Python指明函数参数类型
文章目录 前言简介一、函数参数的类型指定1. 基本类型提示2. 默认参数3. 可变参数4. 联合类型(Union)5. 可选类型(Optional)6. 复杂类型 二、返回值的类型指定1. 基本返回类型2. 无返回值(None)3. 返回多个…...
AIGC视频扩散模型新星:SVD——稳定扩散的Video模型
大家好,这里是好评笔记,公主号:Goodnote,专栏文章私信限时Free。本文详细介绍慕尼黑大学携手 NVIDIA 等共同推出视频生成模型 Video LDMs。NVIDIA 在 AI 领域的卓越成就家喻户晓,而慕尼黑大学同样不容小觑,…...
MySql面试宝典【刷题系列】
文章目录 一、Mysql 的存储引擎 myisam 和 innodb 的区别。二、MySQL数据库作发布系统的存储,一天五万条以上的增量,预计运维三年,怎么优化?三、对于大流量的网站,您采用什么样的方法来解决各页面访问量统计问题?四、锁的优化策略…...
银河麒麟系统安装mysql5.7【亲测可行】
一、安装环境 cpu:I5-10代; 主板:华硕; OS:银河麒麟V10(SP1)未激活 架构:Linux 5.10.0-9-generic x86_64 GNU/Linux mysql版本:mysql-5.7.34-linux-glibc2.12-x86_64.ta…...
CTF-内核pwn入门1: linux内核模块基础原理
本文由A5rZ在2025-2-18-21:00编写 1.可加载内核模块是什么? 内核可加载模块(*.ko 文件)是内核的一种扩展机制,可以在不重启系统的情况下加载和卸载代码。它们允许动态地向内核添加新的功能或支持。 以下是一些内核模块常见的功能&…...
第4章 4.1 Entity Framework Core概述
4.1.1 什么是ORM ORM (object tralstional mapping ,对象关系映射)中的“对象”指的就是C#中的对象,而“关系”是关系型数据库,“映射”指搭建数据库与C#对象之间的“桥梁”。 比如使用ORM ,可以通过创建C#对象的方式把数据插入数据库而不需…...
【C语言】自定义类型:联合体和枚举
1. 联合体 1.1 联合体类型的声明 像结构体一样,联合体也是由一个或者多个成员构成,这些成员可以是不同的类型。 但是编译器只为最大的成员分配足够的内存空间。联合体的特点是所有成员共用同一块内存空间。所以联合体也叫:共用体。 给联合…...
企业组网IP规划与先关协议分析
目录 一、IP编址 1、IP地址组成 2、IP地址表达 3、IP 地址分类 4、IP地址类型 5、IP网络通信 6、子网掩码 7、默认子网掩码 8、IP 地址规划 9、有类IP编制缺陷 10、VLSM 11、变长子网掩码案例 12、网关 13、无类域间路由 一、IP编址 网络层位于数据链路层与传输层之间…...
数据结构之【顺序表简介】
1.顺序表的概念 顺序表 是 用一段物理地址连续的存储单元 依次 存储数据元素的线性结构 一般情况下采用数组存储 2.顺序表的结构 既然顺序表可以用来存储数据元素, 那就少不了 增删查改 的操作 此时,单一地只创建数组满足不了上述操作 创建相应的结构…...
如何调用 DeepSeek API:详细教程与示例
目录 一、准备工作 二、DeepSeek API 调用步骤 1. 选择 API 端点 2. 构建 API 请求 3. 发送请求并处理响应 三、Python 示例:调用 DeepSeek API 1. 安装依赖 2. 编写代码 3. 运行代码 四、常见问题及解决方法 1. API 调用返回 401 错误 2. API 调用返回…...
一周学会Flask3 Python Web开发-flask3模块化blueprint配置
锋哥原创的Flask3 Python Web开发 Flask3视频教程: 2025版 Flask3 Python web开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili 我们在项目开发的时候,多多少少会划分几个或者几十个业务模块,如果把这些模块的视图方法都写在app.py…...
vxe-table实现动态列
vxe-table实现动态列 1.动态列解释2.解决步骤2.1将后端返回的动态列表头,按照格式拼接在固定列表头上2.2将后端返回的列表数据按照键值对格式组装 1.动态列解释 正常列表是有固定的列;我的需求是,最初只知道表格的固定两列,查询数…...