Java虚拟机(JVM)从入门到实战【上】
Java虚拟机(JVM)从入门到实战【上】,涵盖类加载,双亲委派机制,垃圾回收器及算法等知识点,全系列6万字。
一、基础篇
P1 Java虚拟机导学课程

P2 初识JVM
什么是JVM
Java Virtual Machine 是Java虚拟机。
JVM本质上是一个运行在计算机上的程序,职责是运行Java字节码文件。
因为计算机只能运行机器码,所以Java虚拟机负责将字节码转化为机器码。
JVM可以自动为对象和方法分配内存,具有自动的垃圾回收机制,回收不再使用的对象。
JVM的功能
JVM包含:内存管理、解释执行虚拟机指令、即时编译三大功能。

功能1:即时编译
Java语言如果不做优化,性能不如C和C++语言,因为C类语言可以将源代码文件直接通过编译和链接转化为机器码文件。

Java多了一步实时解释,目的是为了能够支持跨平台特性,将字节码指令解释为不同平台的机器码文件。

热点代码就是多次反复出现的代码,会被优化保存到内存中,再次执行可以直接调用。
常见的JVM


P3 Java虚拟机的组成
1.类加载器:把字节码文件的内容加载到内存中。
2.运行时数据区域(JVM管理的内存):负责管理JVM使用到的内存,比如创建对象和销毁对象。
3.执行引擎:即时编译器、解释器、垃圾回收器。执行引擎负责本地接口的调用。
4.本地接口。native方法,用C++编写。

字节码文件的组成
P4 正确打开字节码文件
字节码文件中保存了源代码编译之后的内容,以二进制方式存储,无法用记事本直接打开阅读。
可以通过NotePad++使用十六进制插件查看class文件:

推荐使用jclasslib工具查看字节码文件。
P5 基础信息
1.基础信息:包含魔数、字节码文件对应的Java版本号,访问标识。父类和接口。
文件是无法通过文件扩展名来确定文件类型的,文件扩展名可以随意修改,但不影响文件的内容。
文件是通过文件的头几个字节去校验文件的类型,如果软件不支持该种类型就会出错。
Java字节码文件中,将文件头称为magic魔数。

主副版本号指的是编译字节码文件的JDK版本号,主版本号用来标识大版本号。注意JDK1.2版本号是46,之后每升级一个大版本就加1。所以1.2之后大版本号计算方法是主版本号-44。比如主版本号为52,52-44=8,主版本号52就是JDK8。
版本号的作用主要是判断当前字节码的版本和运行时的JDK是否兼容。
报下面错误是兼容性出现问题。

方法1:升级IDEA编译的Jdk版本。(容易引发其它的兼容性问题)
方法2:改变依赖的版本,替换包名,降低版本。(工作中推荐选用该方法)
2.常量池:保存了字符串常量,类或接口名,字段名主要在字节码指令中使用。
3.字段:当前类或接口声明的字段信息。
4.方法:当前类或接口声明的方法信息字节码指令。
5.属性:类的属性,比如源码的文件名内部类的列表等。
P6 常量池和方法
常量池作用:避免相同内容重复定义,节省空间。
在常量池中存放1份字符串,在别处引用,节省空间。
常量池中的数据都有一个编号,编号从1开始。在字段或字节码指令中通过编号可以快速的找到对应的数据。
字节码指令中通过编号引用到常量池的过程称之为符号引用。
i=0;i=i++,问i的值为?答案:0

iconst_值,把操作数的值放入到操作数栈中。
istore_下标,弹出会把操作数栈中的数据弹出存放到局部变量表下标对应的数组中。操作数栈->局部变量表。
iload_下标,将局部变量表下标中的数据放入操作数栈。局部变量表->操作数栈。


iinc 1 by 1,将局部变量表中1号位置上的数据+1。

P7 字节码文件常见工具使用1
javap -v , javap是JDK自带的反编译工具,可以通过控制台查看字节码文件的内容,适合在服务器上看字节码文件内容。
使用步骤:
1.如果是jar包需要先使用jar -xvf命令解压。
2.输入javap -v 字节码文件名称,查看具体的字节码信息。
如果想查看哪个文件的字节码,只需要:javap -v 绝对路径,即可。
下载一个jclasslib Bytecode Viewer,选中源代码文件选择下面:


可以查看字节码:

P8 字节码文件常见工具使用2
阿里的arthas,Arthas是一款线上监控诊断产品,通过全局视角实时查看应用的load、内存、gc、线程的状态信息,并能在不修改应用代码的情况下,对业务问题进行诊断,大大提升线上问题排查的效率。
启动:java -jar arthas-boot.jar,会出现进程id,输入想进入的进程id。

监控面板,查看字节码信息,方法监控,类的热部署(线上某个类有问题,可以在不停机的情况下,把类的代码替换掉),内存监控,垃圾回收监控,应用热点定位。
当前系统的实时数据面板,按ctrl+c退出。

cls可以清除所有命令。
dashboard -i 2000 -n 3 :隔2秒,执行3次。

第1部分展示了每个线程的信息,第2部分展示了内存区,第3部分是运行中的配置信息。
dump 类的全限定名:dump已加载类的字节码文件到特定目录。
jad 类的全限定名:反编译已加载类的源码。
jad 包名.类名

通过arthas可以获取到当前运行的状态和字节码信息,甚至是反编译出来的源代码信息。

P9 类的生命周期加载阶段
总结:根据类的全限定名把字节码文件的内容加载并转换成合适的数据放入内存中,存放在方法区和堆上。

类的生命周期描述了一个类加载、使用、卸载的整个过程。
加载,连接,初始化,使用,卸载。

1.加载阶段第一步是类加载器根据类的全限定名通过不同渠道(本地文件,通过网络传输的类,动态代理生成)以二进制流的方式获取字节码信息。
2.类加载器在加载完类之后,Java虚拟机会将字节码中的信息保存到方法区中。
3.生成一个InstanceKlass对象,保存类的所有信息,里面还包含实现特定功能比如多态的信息。

4.Java虚拟机还会在堆中生成一份与方法区中数据类似的java.lang.Class对象。作用是在Java代码中获取类的信息以及存储静态字段的数据。

对于开发者来说,只需要访问堆中的Class对象,而不需要访问方法区中所有信息。
方法去是用C++代码写,堆区是java代码编写,在代码中可以获取到。
把方法区中能让开发者访问的资源拷贝到堆区中,Java虚拟机能很好控制好开发者访问数据的范围。开发者不能访问方法区,提升了安全性。
![]()
P10 类的生命周期连接阶段
总结:连接阶段:对魔数、版本号等进行验证,一般不需要程序员关注。准备阶段:为静态变量分配内存并设置初始值。解析阶段:将常量池中的符号引用(编号)替换为直接引用(内存地址)。
连接:
1.验证:验证内容是否满足Java虚拟机规范。


major是主版本号,>=常量一般是45,对jdk1.8来说最高版本号是52,对jdk8只能支持45-52之间的主版本号。副版本号不能大于0


2.准备:给静态变量赋初值。
准备阶段为静态变量(static)分配内存并设置初始值。

特殊情况:final修饰的基本数据类型的静态变量,准备阶段直接会将代码中的值进行赋值。

3.解析:将常量池中的符号引用替换成指向内存的直接引用。
直接引用不再使用编号,而是使用内存中的地址进行访问具体的数据。
P11 类的生命周期初始化阶段
总结:执行静态代码块和静态变量的赋值。
初始化阶段会执行静态代码块中的代码,并为静态变量赋值。
初始化阶段会执行字节码文件中clinit(class init类的初始化)部分的字节码指令。

putstatic是给类中的静态字段赋值。静态字段的名字会从常量池中获取。值会从操作数栈中弹出。将操作数栈中的值赋值给常量池中的变量。
clinit方法中的执行顺序与Java中编写的顺序是一致的。
以下几种方式会导致类的初始化:
1.访问一个类的静态变量或者静态方法(如果变量时final修饰的并且等号右边是常量不会触发初始化,因为在连接阶段会直接赋常量值)
2.调用Class.forName(String className)
3.new一个该类的对象时。
4.执行Main方法的当前类。
ldc #9是从常量池中将字符串D加载到操作数栈中。

invokevirtual是调用Println方法打印操作数栈上的内容。
clinit指令在特定情况下不会出现:
1.无静态代码快且无静态变量赋值语句。
2.有静态变量的声明,但没有赋值语句。
3.静态变量的定义使用final关键字,这类变量会在准备阶段直接进行初始化。
注意下面:
1.直接访问父类的静态变量,不会触发子类的初始化
2.子类的初始化clinit调用之前,会先调用父类的clinit初始化方法。
下面这题因为B02是A02的子类,所以会先调用A02的方法再调用B02的方法。


如果去掉new,因为a是在A02中,所以直接访问A02中的变量即可。
3.数组的创建不会导致数组中元素的类进行初始化。
4.final修饰的变量如果赋值的内容需要执行指令才能得出结果,会执行clinit方法进行初始化。
P12 类加载器的分类
类加载器ClassLoader:是Java虚拟机提供给应用程序去实现获取类和接口字节码数据的技术。
类加载器:负责在类加载过程中的字节码获取并加载到内存中。通过加载字节码数据放入内存转换成byte[],接下来调用虚拟机底层方法将byte[]转换成方法区和堆中的数据。
本地接口JNI是Java Native Interface的缩写,允许java调用其它语言编写的方法。在hotspot类加载器中,主要用于调用Java虚拟机中的方法,这些方法使用C++编写。

企业级应用:SPI机制,类的热部署,Tomcat类的隔离。大量的面试题:什么是类的双亲委派机制,如何打破类的双亲委派机制,自定义类加载器。解决线上问题:使用Arthas不停机修复BUG,解决线上故障。
类加载器被分为2部分。

JDK9之后出现模块化,所以JDK9是分水岭。


P13 启动类加载器

Bootstrap:加载Java中最核心的类。
启动类加载器Bootstrap ClassLoader是由Hotspot虚拟机提供的,使用C++编写的类加载器。
默认加载Java安装目录/jre/lib下的类文件。
rt.jar是最核心的jar包。string,integer,long,日期类。
再Arthas中选择BootstrapClassLoaderDemo,输入sc -d 类名,sc是search class的简称,用来查看jvm已加载的类信息。-d可以输出当前类的详细信息,加载ClassLoader等详细信息。

如何让启动类加载器去加载用户jar包:
1.把要扩展的类打成jar包,放入jre/lib下进行扩展(不推荐,会要求名称符合规范)。
2.使用参数进行扩展。推荐,使用-Xbootclasspath/a:jar包目录/jar包名进行扩展。
P14 扩展和应用程序类加载器
扩展类加载器和应用程序类加载器都是JDK提供的,使用Java编写的类加载器。
它们源码都位于sun.misc.Launcher中,是一个静态内部类。继承自URLClassLoader。或者指定jar包将字节码文件加载到内存中。

默认加载Java安装目录/jre/lib/ext下的文件。
通过扩展类加载器去加载用户jar包:
1.放入/jre/lib/ext下进行扩展。
2.使用参数进行扩展。推荐,使用-Djava.ext.dirs=jar包目录进行扩展,这种方式会覆盖掉原始目录,可以用;(windows):(macos/linux)追加上原始目录。

应用程序类加载器加载的内容包含:启动类加载器和扩展类加载器。
P15 双亲委派机制
Java虚拟机中有多个类加载器,双亲委派机制的核心是解决一个类到底由谁加载的问题。
1.保证类加载的安全性:通过双亲委派机制避免恶意代码替换JDK中的核心类库。
2.避免重复加载:双亲委派机制可以避免同一个类多次加载。
双亲委派机制:当一个类加载器接收到加载类的任务时,会自底向上查看类是否加载过,如果加载过加载流程就结束了,把类的class对象返回即可;如果所有的加载器都没加载过,就会层层向上委派查看是否加载过,如果都没加载过,就会由顶向下尝试进行加载,如果一个类加载器,发现这个类在自己的加载路径中,就会选择去加载这个类。

一个类优先由启动类加载器加载,加载不了才交给扩展类加载器处理。因为底层代码是用C++编写。
如果类加载器返回的是null,说明是启动类加载器加载,因为启动类加载器底层是用C++编写。
每个Java实现的类加载器中保存了一个成员变量叫“父”Parent类加载器,可以理解为它的上级,不是继承关系。

下面很重要:
1.先描述双亲委派机制的流程。
2.然后描述类加载器之间的关系。
3.双亲委派机制的好处

P16 打破类的双亲委派机制 自定义类加载器
为什么打破:比如一个Tomcat程序中可以运行多个Web应用,如果两个应用中出现了相同限定名的类,比如Servlet类,Tomcat要保证这两个类都能加载并且它们应该是不同的类。
如果不打破双亲委派制,当应用类加载器加载Web应用1中的MyServlet之后,Web应用2中相同限定名的MyServlet类就无法被加载。
Tomcat为每一个web应用都单独生成了一个类加载器。

ClassLoader的原理:
1. loadClass方法是类加载的入口,提供了双亲委派机制,内部会调用findClass。根据全限定名去找到类,并把类的二进制信息加载进来。
2. findClass由类加载器子类实现,获取二进制数据调用defineClass,比如URLClassLoader会根据文件路径去获取类文件中的二进制数据。
3. defineClass在堆和方法区上创建包含类信息的对象。做一些类名的校验,然后调用虚拟机底层的方法将字节码信息加载到虚拟机内存中。
4.resolveClass执行类生命周期中的连接阶段。

通过loadClassData方法传递类的全限定名,找到字节码文件,加载到内存中,变成二进制数组。
byte[] data = loadClassData(name);
调用defineClass把二进制数组传入,在堆和方法区生成对应数据,完成加载阶段。
return defineClass(name,data,0,data.length);
如果不给自定义类加载器定义parent,它会默认parent为应用程序类加载器。

如果没传入parent,会自动默认传入系统类加载器。


P17 打破类的双亲委派机制 线程上下文类加载器
JDBC使用了DriverManager来管理项目中引入的不同数据库的驱动,比如mysql、oracle驱动。
DriverManager属于rt.jar是启动类加载器加载的。而用户jar包中的驱动需要由应用类加载器加载,这就违反了双亲委派机制。

DriverManager怎么知道jar包中要加载的驱动在哪里?
spi全称为(Service Provider Interface),是JDK内置的一种服务提供发现机制。
spi的工作原理:
1.(驱动jar包中)在ClassPath路径下的META-INF/service文件夹中,以接口的全限定名来命名文件名,对应的文件里面写该接口的实现。
2.使用ServiceLoader加载实现类

在驱动jar包中暴露出要让别人加载的类,放到固定的文件中(在META-INF/service下的文件中通过全限定名暴露);接下来在DriverManager中就会去使用这个ServiceLoader去加载文件中的类名,然后用类加载器去加载对应的类,创建对象。
SPI中如何获取到应用程序类加载器的?DriverManager是由启动类加载器加载,它怎么拿到应用程序类加载器?
因为SPI中使用了线程上下文中保存的类加载器进行类的加载,这个类加载器一般是应用程序类加载器。

1.启动类加载器加载DriverManager
2.在初始化DriverManager时,通过SPI机制加载jar包中的mysql驱动
3.SPI中利用了线程上下文类加载器(应用程序类加载器)去加载类并创建对象。
这种有启动类加载器加载的类,委派应用程序类加载器去加载类的方式,打破了双亲委派机制。
是否打破双亲委派机制?

没有打破双亲委派机制。因为JDBC只是在DriverManager加载完之后,通过初始化阶段出发了驱动类的加载,类的加载依然遵循双亲委派机制。
P18 打破双亲委派机制 osgi和类的热部署
同级之间的类加载器互相委托加载。OSGI还是用类加载器实现了热部署(在服务不停止的前提下,更新字节码文件到内存中)的功能。

1.jad命令反编译,然后可以使用其它编译器,比如vim来修改源码。
jad --source-only com.itheima.springbootclassfile.controller.UserController > /opt/jvm/UserController.java

2.记得添加-c参数让类加载器去编译。mc命令用来编辑修改过的代码。
mc -c 21b8d17c /opt/jvm/UserController.java -d /opt/jvm
3.用retransform命令加载新的字节码
retransform /opt/jvm/com/itheima/springbootclassfile/controller/UserController.class
注意事项:
1.程序重启之后,字节码文件会恢复,除非将class文件放入jar包中进行更新。
2.使用retransform不能添加方法或字段,也不能更新正在执行的方法。
P19 JDK9之后的类加载器
在JDK8及之前的版本中,扩展类加载器和应用程序类加载器的源码位于rt.jar包中的sun.misc.Launcher.java
JDK9引入了module的概念,类加载器在设计上发生了很多变化。
1.启动类加载器使用Java编写,位于jdk.internal.loader.ClassLoaders类中。Java中的BootClassLoader继承自BuiltinClassLoader实现从模块中找到要加载的字节码资源文件。
启动类加载器依然无法通过java代码获取到,返回的仍然是null,保持了统一。
2.扩展类加载器被替换为平台类加载器。
平台类加载器遵循模块化方式加载字节码文件,所以继承关系丛URLClassLoader变成了BuiltinClassLoader,BuiltinClassLoader实现了从模块中加载字节码文件。平台类加载器的存在更多的是为了与老版本的设计方案兼容,本身没有特殊的逻辑。
P20 运行时的数据区程序计数器
Java虚拟机在运行Java程序过程中管理的内存区域,称之为运行时数据区。
线程不共享:创建一个线程每个线程里都有一份程序计数器、Java虚拟机栈、本地方法栈对应的数据,自己的数据由自己维护,其它线程不能访问对方线程中的数据。
线程共享:放入任何数据,每个线程都能访问数据,共享。

应用场景:Java的内存分成哪几部分?详细介绍一下。
Java内存中哪些部分会内存溢出?
JDK7和8在内存结构上的区别是什么?
工作中的实际问题:内存溢出。
内存调优的学习路线:
1.了解运行时内存结构,了解JVM运行过程中每一部分的内存结构以及哪些部分容易出现内存溢出。
2.掌握内存问题的产生原因,学习代码中常见的几种内存泄露,性能问题的常见原因。
3.掌握内存调优的基本方法,学习内存泄露,性能问题等常见JVM问题的常规解决方法。
程序计数器(Program Counter Register):也叫PC寄存器,每个线程会通过程序计数器来记录接下来要执行的字节码指令的地址。
ifne 9 ,意思是将操作数栈中的数与0进行比较,如果相等执行6,如果不相等执行9。

程序计数器记录的是下一行字节码指令的地址(假如当前执行的是1,那程序计数器中记录的就是2)。
程序计数器可以控制程序指令的进行,实现分支、跳转、异常等逻辑。
在多线程的情况下,Java虚拟机需要通过程序计数器(线程不共享)记录CPU切换前执行到哪一句指令并继续解释运行。


程序计数器在运行中会出现内存溢出吗?
内存溢出指的是程序在使用某一块内存区域时,存放的数据需要占用的内存大小超过了虚拟机能提供的内存上限。
因为每个线程只存储一个固定长度的内存地址,程序计数器不会发生内存溢出。程序员无需对程序计数器做任何处理。
P21 栈局部变量表
Java虚拟机栈(Java Virtual Machine Stack)采用栈的数据结构来管理方法调用中的基本数据。先进后出(First In Last Out),每一个方法的调用使用一个栈帧来保存。
栈帧用来保存方法的基本信息。

当某个方法执行完栈帧就会被弹出。
Java虚拟机栈随着线程的创建而创建,而回收则会在线程的销毁时进行。由于方法可能会在不同线程中执行,每个线程都有一个自己的虚拟机栈。
栈帧的组成:
局部变量表:作用是在运行过程中存放所有的局部变量。
操作数栈:是栈帧中虚拟机在执行指令过程中用来存放临时数据的一块区域。
帧数据:包含动态链接、方法出口、异常表的引用。
局部变量表:作用是在方法执行过程中存放所有的局部变量。编译成字节码文件时就可以确定局部变量表的内容。
Nr表示的是变量的编号,按照生命的顺序,起始PC保存了从哪一行字节码指令开始可以访问这个局部变量,长度以生效那行到销毁那行计算。

栈帧中的局部变量表是一个数组,数组中的每一个位置称之为槽slot,long和double类型占2个槽,其他类型占用一个槽。

实例方法中的序号为0的位置存放的是this,指的是当前调用方法的对象,运行时会在内存中存放实例对象的地址。

方法参数也会保存在局部变量表中,其顺序与方法中参数的定义顺序一致。
局部变量表保存的内容有:实例方法的this对象,方法的参数,方法体中声明的局部变量。

为了节省空间,局部变量表中的槽可以复用,一旦某个局部变量不再生效,当前槽可以被复用。

P22 栈操作数栈和帧数据
操作数栈是栈帧中虚拟机在执行指令过程中用来存放中间数据的一块区域。他是一种栈式的数据结构,如果一条指令将一个值压入操作数栈,则后面的指令可以弹出并使用该值。
在编译期就可以确定操作数栈的最大深度,从而执行时正确的分配内存大小。

帧数据:包含动态链接,方法出口,异常表的引用。
当前类的字节码指令引用了其它类的属性或者方法时,需要将符号引用(编号,比如#10)转换成对应的运行时常量池中的内存地址。动态链接就保存了编号到运行时常量池的内存地址的映射关系。
方法出口指的是方法在正确或异常结束时,当前栈帧会被弹出,同时程序计数器应该指向上一个栈帧中的下一条指令的地址。所以在当前栈帧中,需要存储此方法出口的地址。
异常表存放的是代码中异常的处理信息,包含异常捕获的生效范围以及异常发生后跳转到的字节码指令位置。

通过异常表可以知道要在什么范围内捕获异常,如果出现异常要跳转到哪一行。
P23 栈内存溢出
Java虚拟机如果栈帧过多,占用内存超过栈内存可以分配的最大大小就会出现内存溢出。
Java虚拟机内存溢出时会出现StackOverflowError的错误。
如果我们不指定栈的大小,JVM将创建一个具有默认大小的栈。大小取决于操作系统和计算机的体系结构。
Linux等操作系统一般是1MB。
一般一个线程的栈能容纳10000-11000个栈帧。创建一个方法会生成一个栈帧。

通过修改-Xss的参数可以让栈帧的大小调小:

对windows来说,JDK8测试最小值为180K,最大值为1024M。
如果局部变量过多,操作数栈的深度过大也会影响栈内存的大小。
![]()
Java虚拟机栈存储了Java方法调用时的栈帧,而本地方法栈存储的是native本地方法的栈帧。
P24 堆内存
一般Java程序中堆内存是空间最大的一块的内存区域,创建出来的对象都存在于堆上。
栈上的局部变量表中,可以存放堆上对象的引用。静态变量也可以存放堆对象的引用,通过静态变量就可以实现对象在线程之间的共享。

堆内存大小具有上限,当一直向堆中放入对象达到上限之后,会抛出OutOfMemory的错误。
可以通过:dashboard 进行访问,如果只能看内存只需要输入:memory
![]()
used是已经使用的堆内存,total是总共能使用的堆内存,max是虚拟机能分配的上限堆内存。

最后发现total还远没有达到max的量级就已经溢出了,所以不是当used=max=total的时候,堆内存就溢出。
如果不设置虚拟机的参数,max默认是系统内存的1/4,total默认是系统内存的1/64。在实际应用中一般需要设置total和max的值。
-Xmx4g表示最大堆内存的大小,-Xms4g表示total的大小。
为什么arthas中设置的heap堆大小与设置的值不一样呢?

arthas中的heap堆内存使用了JMX技术中内存获取方式,这种方式与垃圾回收器有关,计算的是可以分配对象的内存,而不是整个内存。
建议将-Xmx和-Xms设置为相同的值,这样程序启动后可使用的总内存就是最大内存,无需向java虚拟机再次申请,减少了申请与分配内存时间上的开销,也不会出现内存过剩后堆收缩的情况。
P25 方法区的实现
方法区是虚拟机中的虚拟概念,每款Java虚拟机在实现上各不相同。
JDK7及之前的版本将方法区存放在堆区域中的永久代空间,堆的大小由虚拟机参数控制。
JDK8之后的版本将方法区存放在元空间中,元空间位于操作系统维护的直接内存(占用操作系统的内存)中,默认情况下只要不超过操作系统承受的上限,可以一直分配。

方法区是存放基础信息的位置,线程共享,主要包含三部分内容:
1.类的元信息:保存了所有类的基本信息。
方法区是用来存储每个类的基本信息(元信息),一般称为InstanceKlass对象。在类的加载阶段完成。
InstanceKlass对象包含:基本信息,常量池,字段,方法,虚方法表。注意常量池和方法在虚拟机中会被单独摘出来,用单独的内存去存放,而在InstanceKlass中仅仅存储的是引用。
2.运行时常量池:保存了字节码文件中的常量池内容。
方法区除了存储类的元信息外,还存储了运行时的常量池。常量池中存放的是字节码中的常量池内容。
字节码文件通过编号查表的方式找到常量,被称为静态常量池。当常量池加载到内存中后,可以通过内存地址快速定位到常量池的内容,这种叫作运行时常量池。

JDK7大概11万次方法区溢出,JDK8运行上百万次程序也没有溢出。
![]()
3.字符串常量池:保存了字符串常量。
P26 方法去字符串常量池
字符串常量池存储在代码中定义的常量字符串内容。
运行时常量池与字符串常量池被拆分(因为JDK8之后方法区由永久代到元空间)。
结果false:

变量链接使用StringBuilder,StringBuilder的底层toString方法是new了一个strinng,所以放在堆。
因为字节码指令中d=a+b涉及到new,也就有对象产生,存放在堆。
结果true:

String.intern()方法是可以手动将字符串放入字符串常量池。
JDK7之后版本中由于字符串常量池在堆上,所以intern()方法会把第一次遇到的字符串的引用放入字符串常量池。

因为java是系统关键字会在启动时存放入字符串常量池。

JDK7之后版本中,静态变量存放在堆中的Class对象中,脱离了永久代。

P27 直接内存
JDK8后方法区的内容存在直接内存的元空间中。
直接内存不在虚拟机规范中存在,所以并不属于java运行时的内存区域。
在JDK1.4后引入NIO机制,使用直接内存。
Java堆中的对象如果不再使用要回收,回收时会影响对象的创建和使用。
IO操作比如读文件,需要先把文件读入直接内存(缓冲区)再把数据复制到Java堆中。
现在直接放入直接内存即可(减少了一次数据复制的开销),Java堆上维护直接内存的引用,减少数据复制开销。

设置直接内存区的大小:

P28 自动垃圾回收
内存泄露指的是不再使用的对象在系统中未被回收,内存泄露的积累导致内存溢出。

Java中为了简化对象的释放,引入了自动的垃圾回收(Garbage Collection简称GC)机制。通过垃圾回收器来对不再使用的对象完成自动的回收,垃圾回收器主要负责对堆上内存进行回收。
优点:降低程序员实现难度,降低对象回收bug的可能性。
缺点:程序员无法控制内存回收的及时性。
自动垃圾回收,应用场景:
1.解决系统僵死的问题。与频繁的垃圾回收有关。
2.性能优化。对垃圾回收器性能优化。
3.高频面试题:常见的垃圾回收器
P29 方法区的回收
线程不共享的部分(程序计数器、Java虚拟机栈、本地方法栈)来说,不需要使用垃圾回收机制进行回收。都是伴随线程的创建而创建,线程的销毁而销毁。方法的栈帧在执行完之后会自动弹出栈并释放掉对应的内存。

方法区中能回收的内容主要就是不再使用的类。判断一个类可以被卸载,需要同时满足下面三个条件:
1.此类所以实例对象都被回收,在堆中不存在任何该类的实例对象和子类对象。
2.加载类的类加载器已经被回收。
3.该类对应的java.lang.Class对象没有在任何地方被引用。

如果需要手动触发垃圾回收,可以调用System.gc()方法。
调用System.gc()方法并不一定会立即回收垃圾,仅仅向JAVA虚拟机发送一个垃圾回收的请求,具体是否需要执行垃圾回收Java虚拟机会自行判断。
P30 引用计数法
Java中的对象能否被回收,是根据对象是否被引用决定的。如果对象被引用了,说明该对象还在使用,不允许被回收。


执行main方法会在栈内存中创建一个栈帧。A a = new A()创建的实例对象A会被保存在堆内存中。

判断堆上对象是否被引用有2种方法:引用计数法和可达性分析法。
引用计数法会为每个对象维护一个引用计数器,当对象被引用时+1,取消引用时-1。
引用计数法缺点:1.每次引用和取消引用需要维护计数器,对系统性能存在影响。2.存在循环引用问题,当A引用B,B引用A会导致对象无法回收。

因为A引用B,B引用A出现循环引用问题,无法被回收。
-verbose:gc

P31 可达性分析法
可达性分析将对象分为2类:垃圾回收的根对象(GC Root)和普通对象。对象与对象之间存在引用关系。
下图中A到B再到C和D,形成了一个引用链,可达性分析算法指如果从某个到GC Root对象是可达的,对象就不可被回收。

能被称为GC Root对象的是下面4类对象:
1.线程Thread对象(创建线程之后,整个线程对象),引用线程栈帧中的方法参数、局部变量等。

2.系统类加载器加载的java.lang.Class对象。
Launcher包含应用程序类加载器和GC Root对象。
![]()
3.监视器对象,用来保存同步锁synchronized关键字持有的对象。
4.本地方法调用时使用的全局对象。

通过arthas和eclipse Memory Analyzer工具可以查看GC Root,MAT工具是eclipse推出的Java堆内存检测工具。
1.使用arthas的heapdump命令可将内存快照保存到本地磁盘中。
2.使用MAT工具打开堆内存快照文件。
3.选择GC Roots功能查看所有的GC Root。
P32 软引用
可达性算法中描述的对象引用,一般指的是强引用,即GCRoot对象对普通对象有引用关系,只要这层关系存在,普通对象就不会被回收。除了强引用外,Java还设计了其它引用方式:

1.软引用
2.弱引用
3.虚引用
4.终结器引用
软引用知识点如下:
软引用相对于强引用是一种比较弱的引用关系,如果一个对象只有软引用关联到它,当程序内存不足时,就会将软引用中的数据进行回收。
在JDK1.2版之后,提供了SoftReference类来实现软引用,软引用用于缓存中。
软引用的执行过程如下:
1.将对象使用软引用包装起来,new SoftReference<对象类型>(对象)。
2.内存不足时,虚拟机尝试进行垃圾回收。
3.如果垃圾回收仍不能解决内存不足的问题,回收软引用中的对象。
4.如果依然内存不足,抛出OutOfMemory异常。
把最大堆内存设置为200M,无法容纳2个100M的生成,所以当第2个100M生成时内存空间不足,会把软引用释放掉,所以第2次输出软引用的内容是null。

结果如下:

软引用中的对象如果在内存不足时回收,SoftReferece对象本身也需要被回收。如何知道哪些SoftReference对象需要回收呢?
SoftReference提供了一套队列机制:
1.软引用创建时,通过构造器传入引用队列。
2.在软引用中包含的对象被回收时,该软引用对象会被放入引用队列。
3.通过代码遍历引用队列,将SoftReference的强引用删除。


P33 弱虚终结器引用
弱引用:整体机制和软引用基本一致,区别在于弱引用包含的对象在垃圾回收时,不管内存够不够都会直接被回收。
在JDK1.2版之后提供了WeakReference类来实现弱引用,弱引用主要在ThreadLocal中使用。
弱引用对象本身也可以使用引用队列进行回收。
虚引用:当对象被垃圾回收器回收时可以接收到对应的通知。当对象被回收之后,对应的内存也应该被回收。

终结器引用:

P34 垃圾回收算法的评价标准
垃圾回收要做的有2件事;
1.找到内存中存活的对象。(可达性分析法,GC Root是否关联)
2.释放不再存活对象的内存,使得程序能再次利用这部分空间。

Java垃圾回收过程会通过单独的GC线程来完成,但是不管使用哪一种GC算法,都会有部分阶段需要停止所有的用户线程。这个过程称之为Stop The World简称STW,如果STW时间过长则会影响用户的使用。

判断GC算法是否优秀,可以从3个方面考虑:
1.吞吐量。吞吐量指的是CPU用于执行用户代码的时间与CPU总执行时间的比值,即吞吐量=执行用户代码时间/(执行用户代码时间+GC时间)。吞吐量数值越高,垃圾回收的效率就越高。
2.最大暂停时间。最大暂停指的是所有在垃圾回收过程中的STW时间最大值。最大暂停时间越短,用户使用系统时受到的影响就越短。
3.堆使用的效率。不同的垃圾回收算法,对堆内存的使用方式是不同的。标记清除法,可以使用完整的堆内存。复制算法会将堆内存一分为二,每次只使用一半内存。从堆使用效率上说,标记清除法要优于复制算法。

上面三种评价标准:堆使用效率、吞吐量、最大暂停时间不可兼得。
比如堆内存越大,最大暂停时间要越长。想要减少最大暂停时间,就会降低吞吐量。
不同的垃圾回收算法,适用于不同的场景。
P35 垃圾回收算法1
标记清除算法:
1.标记阶段:将所有存活的对象进行标记。Java中使用可达性分析算法,从GC Root开始通过引用链遍历出所有存活对象。
2.清除阶段:从内存中删除没有被标记的非存活对象。

优点:实现简单,只需要在第一阶段给每个对象维护标志位,第二阶段删除对象即可。
缺点:1.碎片化问题。由于内存是连续的,所有对象被删除之后,内存中会出现很多细小可用的内存单元。如果我们需要的是一个比较大的空间,很有可能这些内存单元的大小过小,无法进行分配。
2.分配速度慢。由于内存碎片存在,需要维护一个空闲链表,极有可能发生每次需要遍历到链表最后才能获得合适的内存空间。
复制算法:
复制算法的核心思想是:1.准备2块空间From空间和To空间,每次在对象分配阶段,只能使用其中一块空间(From空间)。2.在垃圾回收GC阶段,将From中存活对象复制到To空间。3.将两块空间的From和To名字互换。


复制算法的优点:1.吞吐量高。复制算法只需要遍历一次存活对象复制到To空间即可。比标记-整理算法少了一次遍历的过程,因此性能比较好,但是不如标记-清除算法,因为标记清除算法不需要进行对象的移动。
2.不会发生碎片化。复制算法在复制之后就会将对象按顺序存放入To空间,所以对象以外的区域都是可用空间,不存在碎片化内存空间。
缺点:内存使用效率低。每次只能让一半的内存空间来为创建对象使用。

标记整理算法:
标记整理算法也叫标记压缩算法,是对标记清理算法中容易产生内存碎片问题的一种解决方案。
核心思想分为2个阶段:
1.标记阶段。将所有存活对象进行标记。Java中使用可达性分析算法,从GC Root开始通过引用链遍历出所有存活对象。
2.整理阶段。将存活对象移动到堆的一端。清理掉存活对象的内存空间。

优点:1.内存使用效率高。整个堆内存都可以使用,不会像复制算法只能使用半个堆内存。
2.不会发生碎片化。在整理的阶段
缺点:整理阶段的效率不高。整理算法有很多种,比如Lisp2整理算法需要对整个堆中的对象搜索3次,整体性能不佳。可以通过Two-Finger、表格算法、ImmixGc等高效的整理算法优化此阶段的性能。
P36 垃圾回收算法 分代GC
分代垃圾回收算法是现代优秀的垃圾回收算法,会将上述描述的垃圾回收算法组合进行使用,其中应用最广的就是分代垃圾回收算法(Generational GC)。
分代垃圾回收将整个内存区域划分为年轻代和老年代。

在年轻代(新生代)Yong区中会被划分为Eden区(伊甸园区),幸存区(有2块)。

可以通过memory命令来查看内存,伊甸园区大概2G,幸存者区大概270M,老年代大概5G。

-Xms用来设置堆的初始大小即Total。-Xmx设置堆的最大大小即Max。-Xmn设置新生代的大小(包含伊甸园区和2块幸存者区)。-XX:SurvivorRatio可以设置伊甸园区和幸存者区的比例。-XX:+PrintGCDetails verbose:gc打印GC日志。

分代回收时,创建出来的对象,首先会被放入Eden伊甸园区。
随着对象在Eden区越来越多,如果Eden区满,新创建的对象已经无法放入,就会触发年轻代的GC,成为Minor GC或者Young GC。
Minor GC会把需要eden和From中需要回收的对象回收,把没有回收的对象放入To区。


如上图,当eden区满时想再往里放入对象,依然会发生Minor GC,此时会回收eden区和S1中的对象,并把eden和from区中剩余的对象放入S0。
每次Minor GC中都会为对象记录他的年龄,初始值为0,每次GC完会加1。


如果Minor GC后对象的年龄达到阈值(最大15,默认值和垃圾回收器有关),对象就会被晋升至老年代。
如果当老年代中空间不足,无法放入新的对象时(可能是由于年轻代被占满,因此有些对象没达到年龄的阈值,就会被放入老年代),先尝试minor gc如果还是不足,就会触发Full GC,Full GC会对整个堆进行垃圾回收。
如果Full GC依然无法回收掉老年代的对象,那么当对象继续放入老年代时,就会抛出Out Of Memory异常。
P37 垃圾回收器1
系统中大部分对象,都是创建出来之后很快就不再使用可以被回收的,比如用户获取订单数据,订单数据返回给用户之后就可以释放了。
老年代中会存放长期存活的对象,比如Spring的大部分bean对象,在程序启动之后就不会被回收了。
在虚拟机的默认设置中,新生代大小要远小于老年代的大小。

1.可以通过调整年轻代和老年代的比例来适应不同类型的应用程序,提高内存的利用率和性能(假如用户比较多,有大量用户会在同一时段访问订单数据,如果新生代设置小,大量数据创建,会频繁发生minor GC,所以我们希望这些生命周期短的数据能在)。
2.新生代和老年代使用不同的垃圾回收算法,新生代一般选择复制算法,老年代可以选择标记-清除和标记-整理算法,由程序员来选择灵活度高。
3.分代设计中允许只回收新生代(minor gc),如果能满足对象分配的要求,就不需要对整个堆进行回收(full gc),STW时间会减少。

垃圾回收器是垃圾回收算法的具体体现,由于垃圾回收器分为年轻代和老年代,除了G1之外的其他垃圾回收器必须成对组合进行使用。
JDK9之后推荐使用G1。

Serial 是一种单线程串行回收年轻代的垃圾回收器。
如果伊甸园区满了,垃圾回收线程就会回收垃圾,此时用户线程不能访问。
![]()
单CPU下吞吐量出色,多CPU吞吐量不佳,因为执行时只能但线程,跑在一个CPU上。
SerialOld 是Serial垃圾回收器的老年代版本,采用单线程串行回收。

注意是通过下面这行代码来设置垃圾回收器使用的参数。

P38 垃圾回收器2
parNew垃圾回收器
ParNew垃圾回收器本质上是对Serial在多CPU下的优化,使用多线程进行垃圾回收。
-XX:+UseParNewGc 年轻代使用ParNew回收器,老年代使用串行回收器。

CMS(Concurrent Mark Sweep)垃圾回收器 老年代
CMS垃圾回收器关注的是系统的暂停时间,允许用户线程和垃圾回收线程在某些步骤中同时执行,减少了用户线程的等待时间。
参数:XX:+UseConcMarkSweepGC

浮动垃圾问题:某些垃圾可能清理不掉。
CMS执行步骤:
1.初始标记,用极短的时间标记出GC Roots能直接关联到的对象。
2.并发标记,标记所有的对象,用户线程不需要暂停。
3.重新标记,由于并发标记阶段有些对象发生了变化,存在错标、漏标等情况,需要重新标记。
4.并发清理,清理死亡的对象,用户线程不需要暂停。

缺点:
1.CMS使用了标记-清除算法,在垃圾收集结束之后会出现大量内存碎片,CMS会在Full GC时进行碎片的整理。这样会导致用户线程暂停,可以使用-XX:CMSFullGCsBeforeCompaction=N参数调整N次Full GC之后再整理。
2.无法处理并发清理过程中产生的浮动垃圾,不能做到完全的垃圾回收。
3.如果老年代内存不足无法分配对象,CMS会退化成Serial Old单线程回收老年代。

P39 垃圾回收器3
Parallel Scavenge垃圾回收器,是JDK8默认的年轻代垃圾回收器,多线程并行回收,关注的是系统的吞吐量,具备自动调整堆内存大小(年轻代、老年代大小,年轻代内每一个组成部分,包括阈值等都会自动调整)的特点。


Parallel Old是为Parallel Scavenge收集器设计的老年代版本,利用多线程并发收集。
参数:-XX:+UseParallelGC或-XX:+UseParallelOldGC可以使用Parallel Scavenge + Parallel Old这种组合。

Parallel Scavenge允许手动设置最大暂停时间和吞吐量。Oracle官方建议在使用组合时,不要设置堆内存的最大值,垃圾回收器会根据最大暂停时间和吞吐量自动调整内存大小。

吞吐量设置为99,代表用户线程会执行99%的时间,垃圾回收线程仅会执行1%的时间。
最大暂停时间为1时的内存大小要远比为10时小,堆内存越小,回收的范围越小,回收时间更少,暂停时间更短。当最大暂停时间变短,会主动减小堆内存,减少最大停顿时间。
P40 g1垃圾回收器
JDK9之后默认的垃圾回收器是G1(Garbage First)垃圾回收器。
G1对老年代的清理会选择存活度最低的区域进行回收,这样可以保证回收效率最高,这也是G1(Garbage first)名称的由来。
Parallel Scavenge关注吞吐量,允许用户设置最大暂停时间,但是会减少年轻代可用空间的大小。
CMS关注暂停时间,但吞吐量会下降。
而G1设计目标就是将上述2种垃圾回收器的优点融合:
1.支持巨大的堆空间回收,具有较高的吞吐量。
2.支持多CPU并行垃圾回收。
3.允许用户设置最大暂停时间。

在G1出现之前的垃圾回收器,内存结构一般是连续的,如上图。
G1的整个堆会被划分成多个大小相等的区域,称之为区Region,区域不要求是连续的。分为Eden,Survivor,Old区。Region的大小可以通过堆空间大小/2048计算得到,也可以通过参数-XX:G1HeapRegionSize=32m指定(其中32m指定region大小为32M),Region size必须是2的指数幂,取值范围从1M到32M。

G1垃圾回收有2种方式:1.年轻代回收(Young GC)。2.混合回收(Mixed GC)。
年轻代回收(Young GC),回收Eden区和Survivor区中不用的对象。会导致STW,G1中可以通过参数-XX:MaxGCPauseMillis=n(默认200)设置每次垃圾回收时的最大暂停时间毫秒数,G1垃圾回收器会尽可能地保证暂停时间。
1.新创建的对象会存放在Eden区。当G1判断年轻代区不足(max默认60%,伊甸园区、Survivor),无法分配对象时需要回收时会执行Young GC。
2.标记出Eden和Survivor区域中的存活对象。
3.根据配置的最大暂停时间选择某些区域将存活对象复制到一个新的Survivor区中(年龄+1),情空这些区域。
G1在进行Young GC的过程中会去记录每次垃圾回收时每个Eden区和 Survivor区的平均耗时,以作为下次回收时的参考依据。这样就能根据配置的最大暂停时间计算出本次回收时最多能回收多少个Region区域了。


4.后续Young GC时与之前相同,只不过Survivor区中国存活对象会被搬运到另一个Survivor区。
5.当某个存活对象的年龄到达阈值(默认15),将被放入老年代。

6.部分对象如果大小超过Region的一半,会直接放入老年代,这类老年代被称为Humongous区。比如堆内存是4G,每个Region是2M,只要一个大对象超过了1M就被放入Humongous区,如果对象过大会横跨多个Region。

7.多次回收之后,会出现很多Old老年代区,此时总堆占有率达到阈值时(-XX:InitiatingHeapOccupancyPercent默认45%)会触发混合回收MixedGC。回收所有年轻代和部分老年代的对象以及大对象区。采用复制算法完成。

初始标记:标记Gc Roots引用的对象为存活。
用户线程:将第一步中标记的对象引用的对象,标记为存活。
最终标记:标记一些引用改变漏标的对象,不管新创建、不再关联的对象。
并发复制清理:将存活对象复制到别的Region不会产生内存碎片(优先回收存活度低的)。

如果清理过程中发现没有足够的空Region存放转移的对象,会出现Full GC。单线程执行标记-整理算法,此时会导致用户线程的暂停,所以尽量保证应该用的内存有一定多余的空间。

-XX:+UseG1GC打开G1的开关,JDK9之后默认打开。
-XX:MaxGCPauseMillis=毫秒值最大暂停的时间。

优点:对比较大的堆如超过6G的堆回收时,延迟可控(因为它会去判断应该回收哪个区域,而不是所有的区域)。采用复制算法,不会产生内存碎片。采用并发标记的SATB算法效率高。

相关文章:
Java虚拟机(JVM)从入门到实战【上】
Java虚拟机(JVM)从入门到实战【上】,涵盖类加载,双亲委派机制,垃圾回收器及算法等知识点,全系列6万字。 一、基础篇 P1 Java虚拟机导学课程 P2 初识JVM 什么是JVM Java Virtual Machine 是Java虚拟机。…...
SaaS 电商设计 (九) 动态化且易扩展的实现购物车底部弹层(附:一套普适的线上功能切量的发布方案)
目录 一.背景1.1 业务背景1.2 技术负债 二.技术目标三.方案设计3.1 解决移动端频繁发版3.1.1 场景分析3.1.2 技术方案 3.2 减少后端坏味道代码&无法灵活扩展问题3.2.1 通过抽象接口完成各自单独楼层渲染逻辑3.2.2 通过配置能力做到部分字段可配 四.升级上线(普适于高并发大…...
数据结构——lesson5栈和队列详解
hellohello~这里是土土数据结构学习笔记🥳🥳 💥个人主页:大耳朵土土垚的博客 💥 所属专栏:数据结构学习笔记 💥对于顺序表链表有疑问的都可以在上面数据结构的专栏进行学习哦~感谢大家的观看与…...
使用rsync同步服务器和客户端的文件夹
使用rsync同步服务器和客户端的文件夹 实现目的实验准备实验操作步骤服务器操作关闭防火墙和SELINUX安装rsync修改服务器配置文件/etc/rsync.conf创建服务器备份文件的目录创建rsync系统运行的用户修改备份文件的所有者和所属组创建rsync.passwd启动rsync服务并进行验证 客户端…...
计算机网络|Socket
文章目录 Socket并发socket Socket Socket是一种工作在TCP/IP协议栈上的API。 端口用于区分不同应用,IP地址用于区分不同主机。 以下是某一个服务器的socket代码。 其中with是python中的一个语法糖,代表当代码块离开with时,自动对s进行销毁…...
Python 使用 MyHDL库 实现FPGA板卡仿真验证
要使用 Python 结合 MyHDL 库实现 FPGA 板卡的仿真验证,您可以利用 MyHDL 提供的硬件描述语言和仿真功能来进行 FPGA 设计的验证。下面我将为您介绍一个简单的示例,演示如何使用 MyHDL 库进行 FPGA 设计的仿真验证。 步骤概述 编写 MyHDL 硬件描述&…...
解决SpringBoot集成WebSocket打包失败问题
前言 这几天在一个SpringBoot项目中使用WebSocket来用作客服聊天以及上传文件功能,项目在写的时候,以及在idea中跑的时候都非常完美,结果一打成jar包是,报错.在网上查了报错原因,原来是自己导入的WebSocket的jar与SpringBoot内置tomcat中的WebSocket的jar冲突,需要在打包时把S…...
i-vista五星测试标准
智能行车板块以八类场景评测汽车的 单车道纵向控制能力、 单车道横向控制能力、 单车道纵横向组合控制能力及换道辅助能力, 8类场景包括目标车静止、目标车低速、目标车减速、前车切入(新增场景)、直道居中行驶、直道驶入弯道、盲区无车、盲…...
初识Maven
介绍: web后端开发技术ApacheMaven是一个项目管理和构建工具,它基于项目对象模型(POM)的概念,通过一小段描述信息来管理项目的构建。安装:http://maven.apache.org/ Apache软件基金会,成立于19…...
16 Educational Codeforces Round 142 (Rated for Div. 2)C. Min Max Sort(递归、思维、dp)
C. Min Max Sort 很不错的一道题目,不过脑电波和出题人每对上, q w q 。 qwq。 qwq。 正难则反。 我们考虑最后一步是怎么操作的。 最后一步一定是对 1 1 1和 n n n进行操作 那么上一步呢? 上一步应该是对 2 2 2和 n − 1 n-1 n−1 以此类推…...
Mongodb安装配置
Mongodb安装配置 一、MongoDB简介二、Windows下MongoDB安装2.1.MongoDB下载2.2.安装MongoDB【解压版】2.2.1.解压2.2.2.创建和 bin 目录同级 data\db 目录来存储 MongoDB 产生的数据2.2.3.进入 bin 目录,cmd命令行窗口,使用命令的指定存储数据文件的形式…...
Linux常用操作命令大全
Linux常用操作命令大全 Linux,作为一款开源的操作系统,深受全世界开发者和系统管理员的喜爱。在Linux环境下,用户通过命令行界面可以执行各种操作,从而实现对系统的全面控制。本文将详细介绍Linux中常用的操作命令,帮助读者更好地理解和运用这些命令。 一、文件操作命令…...
CVPR2023 | 提升图像去噪网络的泛化性,港科大上海AILab提出 MaskedDenoising,已开源!
作者 | 顾津锦 首发 | AIWalker 链接 | https://mp.weixin.qq.com/s/o4D4mNM3jL6sYuhUC6VgoQ 当前深度去噪网络存在泛化能力差的情况,例如,当训练集噪声类型和测试集噪声类型不一致时,模型的性能会大打折扣。作者认为其原因在于网络倾向于过度…...
[python] dict类型变量写在文件中
在Python中,如果你想要将一个字典变量以具有可读性的格式写入文件,并且指定缩进为2个空格,你可以使用json模块来实现。json模块提供了一种很方便的方法来进行序列化和反序列化Python对象。下面是一个具体的示例: 字典变量以具有可…...
设计循环队列
文章目录 一、循环队列的构建二、判断是否为空三、判断队列是否满了四、队列插入五、队列的删除六、队列取头尾 设计循环队列 下面是队列提供的接口函数 typedef struct {int* a;int k;int front;int rear; } MyCircularQueue;MyCircularQueue* myCircularQueueCreate(int k) {…...
linux文件解压和压缩命令
linux文件解压和压缩命令 1.格式.zip 解压:unzip filename.zip 压缩:zip filename.zip directoryName 2.格式.rar 解压: #解压方式1(会在当前解压目录内产生一个以压缩包名字命名的目录,目录内是解压内容) …...
飞链云:让AI创造价值,让人类享受收益
我梦想有天,每个有能力的人都可以做自己喜欢的事情,都应该去做自己喜欢的事情,并且可以获得应有的收益。 有的人可以称之为“人”,有的人你得称他为鬼,有的人不如畜生。 如今社会,每个人都为了“生活”日…...
[NSSCTF 2nd]MyJs
做一题ejs原型链污染 首先是登录界面 源码里面提示了源码的路由 js不熟先审计一下 const express require(express); #导入Express框架,用于构建Web应用程序的服务器和路由 const bodyParser require(body-parser); #导入body-parser中间件,用于解析…...
NLP-词向量、Word2vec
Word2vec Skip-gram算法的核心部分 我们做什么来计算一个词在中心词的上下文中出现的概率? 似然函数 词已知,它的上下文单词的概率 相乘。 然后所有中心词的这个相乘数 再全部相乘,希望得到最大。 目标函数(代价函数࿰…...
Java学习--学生管理系统(残破版)
代码 Main.java import java.util.ArrayList; import java.util.Scanner;public class Main {public static void main(String[] args) {ArrayList<Student> list new ArrayList<>();loop:while (true) {System.out.println("-----欢迎来到阿宝院校学生管理系…...
日语AI面试高效通关秘籍:专业解读与青柚面试智能助攻
在如今就业市场竞争日益激烈的背景下,越来越多的求职者将目光投向了日本及中日双语岗位。但是,一场日语面试往往让许多人感到步履维艰。你是否也曾因为面试官抛出的“刁钻问题”而心生畏惧?面对生疏的日语交流环境,即便提前恶补了…...
springboot 百货中心供应链管理系统小程序
一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,百货中心供应链管理系统被用户普遍使用,为方…...
关于iview组件中使用 table , 绑定序号分页后序号从1开始的解决方案
问题描述:iview使用table 中type: "index",分页之后 ,索引还是从1开始,试过绑定后台返回数据的id, 这种方法可行,就是后台返回数据的每个页面id都不完全是按照从1开始的升序,因此百度了下,找到了…...
TRS收益互换:跨境资本流动的金融创新工具与系统化解决方案
一、TRS收益互换的本质与业务逻辑 (一)概念解析 TRS(Total Return Swap)收益互换是一种金融衍生工具,指交易双方约定在未来一定期限内,基于特定资产或指数的表现进行现金流交换的协议。其核心特征包括&am…...
linux 下常用变更-8
1、删除普通用户 查询用户初始UID和GIDls -l /home/ ###家目录中查看UID cat /etc/group ###此文件查看GID删除用户1.编辑文件 /etc/passwd 找到对应的行,YW343:x:0:0::/home/YW343:/bin/bash 2.将标红的位置修改为用户对应初始UID和GID: YW3…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
大语言模型(LLM)中的KV缓存压缩与动态稀疏注意力机制设计
随着大语言模型(LLM)参数规模的增长,推理阶段的内存占用和计算复杂度成为核心挑战。传统注意力机制的计算复杂度随序列长度呈二次方增长,而KV缓存的内存消耗可能高达数十GB(例如Llama2-7B处理100K token时需50GB内存&a…...
七、数据库的完整性
七、数据库的完整性 主要内容 7.1 数据库的完整性概述 7.2 实体完整性 7.3 参照完整性 7.4 用户定义的完整性 7.5 触发器 7.6 SQL Server中数据库完整性的实现 7.7 小结 7.1 数据库的完整性概述 数据库完整性的含义 正确性 指数据的合法性 有效性 指数据是否属于所定…...
基于IDIG-GAN的小样本电机轴承故障诊断
目录 🔍 核心问题 一、IDIG-GAN模型原理 1. 整体架构 2. 核心创新点 (1) 梯度归一化(Gradient Normalization) (2) 判别器梯度间隙正则化(Discriminator Gradient Gap Regularization) (3) 自注意力机制(Self-Attention) 3. 完整损失函数 二…...
CRMEB 中 PHP 短信扩展开发:涵盖一号通、阿里云、腾讯云、创蓝
目前已有一号通短信、阿里云短信、腾讯云短信扩展 扩展入口文件 文件目录 crmeb\services\sms\Sms.php 默认驱动类型为:一号通 namespace crmeb\services\sms;use crmeb\basic\BaseManager; use crmeb\services\AccessTokenServeService; use crmeb\services\sms\…...
