jvm八股

news2024/11/17 1:48:58

文章目录

  • 运行时数据区域
  • Java堆
    • 对象创建
    • 对象的内存布局
    • 对象的访问定位
      • 句柄
      • 直接指针
  • GC
    • 判断对象是否已死
      • 引用计数算法
      • 可达性分析算法
    • 引用的类别
    • 垃圾收集算法
      • 分代收集理论
      • 标记清除算法
      • 标记复制算法
      • 标记整理算法
    • 实现细节
      • 并发的可达性分析
    • 垃圾收集器
      • serial收集器
      • ParNew收集器
      • Parallel Scavenge收集器
      • Serial Old收集器
      • Parallel Old收集器
      • CMS(Concurrent mark sweep)收集器
      • G1收集器
  • 类加载机制
    • 类加载时机
    • 类加载过程
      • 加载
      • 验证
      • 准备
      • 解析
      • 初始化
    • 类加载器
      • 双亲委派模型
      • 破坏双亲委派模型

运行时数据区域

  • 程序计数器(线程私有):唯一一个在《Java虚拟机规范》中没有规定任何OutOfMemoryError情况的区域。如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是本地(Native)方法,这个计数器值则应为空(U ndefined)
  • Java虚拟机栈(线程私有):线程私有的,栈描述的是Java方法执行的线程内存模型。每个方法被执行的时候,Java虚拟机都 会同步创建一个栈帧用于存储局部变量表、操作数栈、动态连接、方法出口等信息。每一个方法被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。无论是正常return或是抛出异常都会导致方法结束,栈帧弹出。如果栈不可扩展,最终会StackOverFlowError,如果栈可扩展,则会OOM
    • 局部变量表:存储局部变量,编译时确定大小
    • 操作数栈:用于存放方法执行过程中产生的中间计算结果。另外,计算过程中产生的临时变量也会放在操作数栈中。
    • 动态连接:当一个方法要调用其他方法,需要将常量池中指向方法的符号引用转化为其在内存地址中的直接引用。动态链接的作用就是为了将符号引用转换为调用方法的直接引用,这个过程也被称为 动态连接
  • 本地方法栈(线程私有):线程私有的,类似Java虚拟机栈,本地方法栈则是为虚拟机使用到的本地(Native)方法服务,也会有StackOverFlowError或OOM
  • Java堆:线程共享的,此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存(之所以是“几乎”,比如经过逃逸分析变量不会逃逸的时候会分配在栈上而不是堆上)
  • 方法区:线程共享的,存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。JDK1.8之前实现为永久代,把收集器的分代设计扩展至方法区,收集器能够像管理Java堆一样管理这部分内存,省去专门为方法区编写内存管理代码的工作,但是这种设计导致了Java应用更容易遇到内存溢出的问题(受MaxPermSize参数的限制)。JDK1.8之后实现为元空间,使用的是本地内存,只受本机可用内存的限制,虽然元空间仍旧可能溢出,但是比原来出现的几率会更小。
  • 运行时常量池:是方法区的一部分,Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池表(Constant Pool Table),用于存放编译期生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。不过一般来说,还会把由符号引用翻译出来的直接引用也存储在运行时常量池中。运行时常量池还具备动态性,Java语言并不要求常量一定只有编译期才能产生,比如String.intern()
  • 字符串常量池:JVM 为了提升性能和减少内存消耗针对字符串(String 类)专门开辟的一块区域,主要目的是为了避免字符串的重复创建。JDK1.7之前字符串常量池放在方法区中,JDK1.7后为了提高回收率放在了Java堆中。
  • 直接内存:直接内存是一种特殊的内存缓冲区,并不在 Java 堆或方法区中分配的,而是通过 JNI 的方式在本地内存上分配的。JDK1.4加入的NIO,可以直接使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作,避免了在 Java 堆和 Native 堆之间来回复制数据。直接内存大小只受本机内存大小限制。

Java堆

对象创建

  1. 类加载:遇到new指令,首先检查能否在常量池定位到该类的符号引用,检查类是否已经加载、解析、初始化,没有的话则先加载类
  2. 分配内存:类加载完成后,为对象分配内存,对象所需内存的大小在类加载完成后便可完全确定
    • 确定内存分配机制,常见算法有:指针碰撞、空闲列表
    • 保证内存分配是线程安全的:CAS、TLAB(Thread Local Allocation Buffer)
  3. 初始化:内存分配完成后,将内存空间初始化为零值
  4. 设置对象头(Object Header)
  5. 构造函数调用

对象的内存布局

内存布局划分为三个部份:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。

对象头包含两类信息:

  1. 对象自身的运行时数据(Mark Word):占据32或64bit大小,hashcode、GC分代年龄、锁状态标志、线程持有的锁、偏向锁ID、偏向时间戳等。
  2. 类型指针:对象指向它的类型元数据的指针,Java虚拟机通过这个指针 来确定该对象是哪个类的实例

对象的访问定位

主流访问方式有:句柄、直接指针

句柄

虚拟机划分出一块句柄池,reference保存句柄的地址,句柄保存对象实例数据和对象类型数据的指针。

好处在于当对象被移动的时候(GC的时候可能会发生),只会改变句柄中的实例数据指针,reference存放的句柄地址不用变更。

image-20240218153519049

直接指针

直接存储对象的地址,好处是只需要一次寻址就能访问到对象实例数据。

image-20240218153727375

GC

判断对象是否已死

引用计数算法

引用数+1则计数+1。简单的引用计数难以解决循环引用问题。

可达性分析算法

首先有一系列的GC Roots,从这些根出发,不可达的对象为已死。

固定作为GC Roots的对象包括:

  • 虚拟机栈中的对象
  • 本地方法栈中的对象
  • 方法区中的静态变量
  • 方法区中的常量
  • 虚拟机内部的引用,
  • 如基本数据类型对应的Class对象,一些常驻的异常对象(比如NullPointExcepiton、OutOfMemoryError)等,还有系统类加载器
  • 所有被synchronized持有的对象
  • 反映Java虚拟机内部情况的JM XBean、JVM TI中注册的回调、本地代码缓存等。

除了这些固定的GC Roots集合以外,根据用户所选用的垃圾收集器以及当前回收的内存区域不 同,还可以有其他对象“临时性”地加入,共同构成完整GC Roots集合。

引用的类别

  • 强引用:被强引用的对象永远不会被回收
  • 软引用:只被软引用的对象在OOM即将发生前会被回收
  • 弱引用:只被弱引用的对象在下次GC一定被回收
  • 虚引用:不影响对象的回收,但也不能从虚引用获取被引用的对象。只是用来当发生GC并且对象内存被回收的时候收到通知(通过从引用队列获取虚引用)。

关于虚引用,这里详细介绍一下。如上所述虚引用用来当发生GC并且对象内存被回收的时候收到通知,另一种在GC时收到通知的方法是重载对象的Object.finalize方法,这个方法在对象被确认可以GC的时候会被调用,用户可以重载这个方法做点事情,比如释放资源,但是finalize有很多问题:

  • 性能问题:GC是单线程执行,所有对象的finalize相当于串行执行,无法自定义执行finalize的线程,甚至可能造成死锁等问题
  • 灵活性问题:用户无法自定义对象的finalize是否被执行、各对象间finalize的顺序等
  • 对象复活问题:finalize中可以将对象重新强引用,阻止其被GC。这个问题会在下个小节更加详细介绍

因此官方推荐使用PhatomReference或者Cleaner来实现对象被GC后的工作。并且第三点「对象复活问题」是「为什么不用弱引用通知对象被GC,而是使用虚引用」的原因:结合弱引用的定义,经过测试发现,当对象确定只被弱引用,并且发生GC时,referent会从弱引用中删除,且弱引用进入引用队列。而finalize如果将对象复活,但此时弱引用已经进入引用队列,因此不能用弱引用来实现对象GC的通知,因为对象被复活了,没有被GC掉。

用一句话概括就是,虚引用用于通知对象的内存真正被回收,换句话来说,我认为如果对象不会被复活的话,弱引用可以替代虚引用。

关于Object.finalize方法,深入理解Java虚拟机一文中:

finalize()能做的所有工作,使用try-finally 或者其他方式都可以做得更好、 更及时,所以笔者建议大家完全可以忘掉Java语言里面的这个方法

垃圾收集算法

分代收集理论

  • 弱分代假说:绝大多数对象朝生夕灭,每次回收时只关注如何保留少量存活而不是去标记那些大量将要被回收的对象,就能以较低代价回收到大量的空间。对这部分的GC称为Young GC / Minor GC
  • 强分代假说:熬过越多次GC的对象越难消亡,虚拟机便可以使用较低的频率来回收这个区域,对这部分对象的GC称为Old GC / Major GC
  • 跨代引用假说:跨代引用相对于同代引用来说仅占极少数

跨代最直接的解决方法是扫描整个老年代,但是效率低下。依据跨代引用假说,只需要在新生代建立一个“记忆集”,将老年代划分为若干小块,标识出哪一块内存存在跨代引用,当发生minor GC的时候只需要将这些存在跨代引用的内存块加入GC Roots。

标记清除算法

标记所有需要回收的对象,然后统一回收这些对象。有两个缺点:标记清除的效率随着待回收对象的增加而降低、空间碎片化

标记复制算法

为了解决标记-清除算法面对大量可回收对象时执行效率低的问题,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后把原来那块内存一次清理掉。缺点是当存活的对象占大多数时,效率较低,并且能用的空间只有一半。

还有一种非针对新生代内存的标记复制,不需要根据1:1比例划分内存的Appel式回收,空间分为一块大的eden,两块小的survivor,平时只有eden和其中一块survivor工作,另一块survivor用于minor GC的时候存放存活的对象,然后一次性把eden和另一块survivor清空。如果survivor空间太小的话导致放不下存活对象,那么会触发分配担保机制,把这些存不下的对象直接进入老年代。

标记整理算法

标记-复制算法在对象存活率较高时就要进行较多的复制操作,效率将会降低。更关键的是,如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选用这种算法。

标记整理算法直接将存活对象往内存空间一端移动:

image-20240218224614487

如果在老年代中使用这样的策略,那么每次GC都会移动大量存活对象,而且必须暂停用户应用程序,即"Stop The World"。但是如果不移动对象的话,势必要增加内存管理的复杂度(比如使用空闲链表),降低了分配内存的效率,但不移动对象的话,STW的时间会更短。

实现细节

并发的可达性分析

三色标记法:对象图中的对象标记成三种颜色

  • 白色:初始状态,还没被扫描到的对象
  • 灰色:已经被扫描过,但是至少还有一个引用(这个对象的引用属性)没扫描
  • 黑色:已经被扫描过,并且这个对象的所有引用都已经扫描过。黑色对象是安全存活的,如果有其他对象指向了它,无需重新扫描。黑色对象不可能直接(不经过灰色对象)指向白色对象,除非用户程序并发修改引用,下面会说明这个问题
image-20240220004124142

以上情况1为正常标记完成的情况,最终白色对象将会被回收。情况2,3为在收集器标记的过程中,用户程序并发地将灰色对象对白色对象的引用取消了,并增加黑色对象对该白色对象的引用,由于黑色对象及其所有引用不会再被扫描,因此该白色对象无法被扫描,最终被错误地回收。

因此错误地回收对象,需要同时满足以下两个条件:

  • 赋值器插入了一条或多条从黑色对象到白色对象的新引用
  • 赋值器删除了全部从灰色对象到该白色对象的直接或间接引用

只需要破坏其中任意一个条件,就不会发生对象被错误回收的情况,以下是两种解决方案:

  • 增量更新(破坏第一个条件):标记过程中新增黑色对象对白色对象的引用时,将其记录下来,等待并发扫描结束后,再以这些黑色对象为根重新扫描一次
  • 原始快照(破坏第二个条件):当灰色对象删除了指向白色对象的引用关系时,就将这个引用记录下来,等待并发扫描结束后,再以这些灰色对象为根重新扫描一次

增量更新和原始快照都通过写屏障实现。CMS使用增量更新,G1、Shenandoah使用原始快照。

垃圾收集器

serial收集器

基于标记-复制的新生代收集器,serial不仅仅是说明它只会使用一个处理器或一条收集线程去完成垃圾收集工作,更重要的是强调在它进行垃圾收集时,必须暂停其他所有工作线程,即STW。它是所有收集器里额外内存消耗最小的,而且收集几十兆甚至一两百兆的新生代,STW完全可以控制在十几、几十毫秒,最多一 百多毫秒以内。

ParNew收集器

基于标记-复制的新生代收集器,ParNew收集器实质上是Serial收集器的多线程并行版本,并与作为老年代收集器的CMS配合使用。ParNew收集器是激活CMS后的默认新生代收集器。

Parallel Scavenge收集器

也是基于标记-复制的新生代收集器。Parallel Scavenge收集器的目标则是达到一个可控制的吞吐量,主要适合在后台运算而不需要太多交互的分析任务。可以通过指定参数开启自适应的调节策略,以提供最合适的停顿时间或者最大的吞吐量。

Serial Old收集器

serial的老年版本,基于标记-整理算法的老年代收集器

Parallel Old收集器

Parallel Scavenge的老年版本,基于标记整理的老年代收集器。在注重吞吐量或者处理器资源较为稀缺的场合,都可以优先考虑Parallel Scavenge加Parallel Old收集器这个组合

CMS(Concurrent mark sweep)收集器

以最短STW时间为目标,基于标记-清除、增量更新的收集器,适合交互频繁型应用。运作过程包括四个步骤:

  1. 初始标记:需要STW,标记GC Roots能直接关联到的对象,速度很快
  2. 并发标记:从GC Roots的直接关联对象遍历整个对象图,耗时长,可以与用户程序并发运行
  3. 重新标记:需要STW,修正并发标记中因用户程序发生变动的标记,耗时比初始标记稍长
  4. 并发清除:清除回收对象,由于不需要移动对象因此可以与用户程序并发运行

缺点:

  1. 在并发回收阶段会因为CMS占用了一部分线程而导致应用程序变慢,降低总吞吐量
  2. 并发标记和并发清理过程中 CMS无法处理“浮动垃圾”,只能由下一次GC处理。重要的是这个过程中如果预留的内存无法满足程序分配新对象的需要,就会出现一次“并发失败”, 只能STW并临时启用Serial Old收集器来重新进行老年代的垃圾收集
  3. CMS由于使用标记-清除,会出现内存碎片,导致老年代还有很多剩余空间,但就是无法找,到足够大的连续空间来分配当前对象,最终触发Full GC。解决办法是Full GC的时候整理内存碎片

G1收集器

目的是实现停顿时间模型:支持指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间大概率不超过N毫秒这样的目标。

在G1之前的其他收集器,收集目标要么是全体新生代(Minor GC)、要么是全体老年代(Major GC)、要么是整个Java堆(Full GC)。而G1是Mixed GC模式,面向整个堆,衡量标准不再是它属于哪个分代,而是哪块内存中存放的垃圾数量最多,回收收益最大

将连续的Java堆划分成多个大小相等的独立区域(region),region是最小回收单元,每一个Region都可以根据需要动态地扮演着新生代的Eden空间、Survivor空间,或者老年代空间。Region中还有一类特殊的Humongous区域,专门用来存储大对象(大小超过一半region大小的对象),G1的大多数行为都把Humongous Region作为老年代。

G1停顿时间模型的实现就建立在:它将Region作为单次回收的最小单元,这样可以有计划地避免在整个Java堆中进行全区域的垃圾收集。

具体地说,G1收集器去跟踪各个Region里面的垃圾堆积的“价值”大小,然后在后台维护一个优先级列表,每次根据用户设定允许的收集停顿时间,优先处理回收价值收益最大的那些Region,保证了G1收集器在有限的时间内获取尽可能高的收集效率。

G1收集器的运行过程大致为四个步骤:

  1. 初始标记:STW,标记GC Roots,修改TAMS
  2. 并发标记:与用户程序并发执行,扫描整个对象图
  3. 最终标记:STW,处理原始快照
  4. 筛选回收:STW,对各个Region的回 收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划,将回收的Region中存活的对象移动到空Region并清空回收的Region

只有并发标记没有STW,它并非纯粹地追求低延迟,官方给它设定的目标是在延迟可控的情况下获得尽可能高的吞吐量。可以由用户指定期望的停顿时间是G1收集器很强大的一个功能,设置不同的期望停时间,可使得G1在不同应用场景中取得关注吞吐量和关注延迟之间的最佳平衡。

G1 vs CMS

  • G1将整块年轻代/老年代划分为一个个region,并根据回收价值回收,这样G1能够更加灵活地控制 GC 停顿时间
  • G1使用标记整理,避免了内存碎片。另外CMS不使用标记整理是因为耗时太长,G1通过回收性价比高的region,灵活控制停顿时间。
  • G1需要使用记忆集解决跨代引用,占整个堆内存大,而且 G1 中维护记忆集的成本较高,带来了更高的执行负载

类加载机制

类加载时机

加载、链接、初始化、使用、卸载

image-20240220050611491

必须进行类初始化的情况有:

  • 使用new、读写类的静态字段(非final)、调用类的静态方法
  • 对类进行反射调用
  • 初始化类的时候,父类也要初始化
  • 执行main方法的主类
  • MethodHandle
  • 含有default方法的接口的实现类初始化的时候,先初始化接口

类加载过程

加载

加载三步骤:

  1. 通过类的全限定名获取类的二进制字节流
  2. 将字节流所代表的静态存储结构转化为方法区的运行时数据结构
  3. 在内存中生成Class对象,作为方法区数据结构的访问入口

验证

包括文件格式验证、元数据验证、字节码验证、符号引用验证。

如果代码事先经过验证,可以用参数关闭类加载过程中的验证,以缩短加载时间。

准备

为类静态变量分配内存,并初始化为零值,在初始化阶段才会putstatic设置为用户指定的初始值。而对于final的静态变量,则会在准备阶段就会初始化成用户指定的初始值。

解析

解析阶段是Java虚拟机将常量池内的符号引用替换为直接引用的过程。

初始化

Java虚拟机才真正开始执行类中编写的Java程序代码,初始化阶段就是执行类构造器<clinit>()方法的过程,这个类构造器方它是Javac编译器的自动生成物,由自动收集类中的所有类变量的赋值动作和static块的语句合并产生的。其中static块中只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的static块可以赋值,但是不能访问:

public class Test { 
  static {
    i = 0; // ok
    System.out.print(i); // not ok
    static int i = 1; 
  }
}

虚拟机保证了clinit在多线程环境下的同步,如果多个线程同时去初始化一个类,那么只会有其中一个线程去执行这个类的clinit方法,如果clinit有耗时操作,那么可能会造成多个线程阻塞。clinit最好耗时短或者提前加载。即同一个类加载器下,一个类型只会被初始化一次。

类加载器

类加载过程中,「通过一个类的全限定名来获取描述该类的二进制字节流」这个动作放到虚拟机外部去实现,让应用程序自己决定如何获取所需的类,实现的代码称为类加载器,可以用于实现类层次划分、OSGi、程序热部署、代码加密等技术。

首先在Java虚拟机中任意一个类的唯一性由类加载器和类本身共同确定,比较两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提下才有意义,只要加载它们的类加载器不同,那这两个类就必定不相等。

双亲委派模型

在虚拟机的视角,只有两种类加载器:

  • 启动类加载器:是虚拟机自身的一部份
  • 其他类加载器:由Java语言实现,独立于虚拟机,并且全部继承自ClassLoader

自JDK1.2以来,Java一直保持着三层类加载器、双亲委派的类加载架构。

JDK8及以前的版本绝大多数Java程序都会使用到以下3个系统提供的类加载器来进行加载:

  • 启动类加载器:加载JAVA_HOME\lib目录和-Xbootclasspath指定路径的类库
  • 扩展类加载器:加载JAVA_HOME\lib\ext目录的扩展类库(Java9后被天然具有扩展能力的模块化机制取代)
  • 应用程序类加载器:加载用户类路径上的所有类库,如果用户没有定义自己的类加载器,一般情况下这个就是程序中默认的类加载器
image-20240220112016294

这里类加载器之间的关系通常不是继承,而是使用组合来复用父加载器的代码。

双亲委派模型的工作过程:类加载器将收到的类加载请求先交给父加载器完成,最终传送到启动类加载器。如果父加载器无法完成这个请求(它的搜索范围内没有找到所需的类),子加载器才会尝试自己去完成加载。

双亲委派模型这种工作模式的好处是,具备了一种带有优先级的层次关系,比如java.lang.Object是由启动类加载器加载的,无论哪个类加载器来加载这个类,最终都由启动类加载器加载,那么即使用户自己也编写了一个名为java.lang.Object的类,运行时加载的也是rt.jar里面的那个java.lang.Object,保证了Java类型体系中最基础的行为,避免造成混乱。

破坏双亲委派模型

如果不想破坏双亲委派模型,那么自定义加载器的时候,重写ClassLoaderfindClass方法即可。如果想打破的话,那么需要重写loadClass方法,目的是重写加载类的流程。

比如 Tomcat 服务器为了能够优先加载 Web 应用目录下的类,然后再加载其他目录下的类,就自定义了类加载器 WebAppClassLoader 来打破双亲委托机制。这也是 Tomcat 下 Web 应用之间的类实现隔离的具体原理:

  • CommonClassLoader 是为了实现公共类库(可以被所有 Web 应用和 Tomcat 内部组件使用的类库)的共享和隔离
  • CatalinaClassLoader用于加载 Tomcat 自身的类,为了隔离 Tomcat 本身的类和 Web 应用的类
  • SharedClassLoader 作为 WebAppClassLoader 的父加载器,专门来加载 Web 应用之间共享的类比如 Spring、Mybatis
  • 每个 Web 应用都会创建一个单独的 WebAppClassLoader,各个 WebAppClassLoader 实例之间相互隔离,进而实现 Web 应用之间类的隔离

但是单纯依靠自定义类加载器没办法满足某些场景的要求,例如,有些情况下,高层的类加载器需要加载低层的加载器才能加载的类。

比如应用程序中的业务类实现了Spring中的接口,这些接口属于Web 应用之间共享的,由SharedClassLoader加载,并且Spring内部需要访问这些业务实现类,但是SharedClassLoader无法找到业务类,因此就需要用到线程上下文类加载器(ThreadContextClassLoader),当 Spring 需要加载业务类的时候,它不是用自己的类加载器,而是用当前线程的上下文类加载器。线程线程上下文类加载器的原理是将一个类加载器保存在线程私有数据里,跟线程绑定,然后在需要的时候取出来使用。这个类加载器通常是由应用程序或者容器(如 Tomcat)设置的,一般的格式如下:

try {
  // 保存原始ClassLoader
  ClassLoader curCl = Thread.currentThread().getContextClassLoader();
  // 获取并设置所需的ClassLoader
  ClassLoader cl = 比如WebAppClassLoader;
  Thread.currentThread().setContextClassLoader(cl);
  // 使用设置的ClassLoader加载一些类,处理一些框架内的逻辑等
  loadClass();
} finally {
  // 还原
  Thread.currentThread().setContextClassLoader(curCl);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1507061.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

手机短信恢复 - 如何在 Android 手机上恢复删除的短信

手机每天发送和接收的短信数以亿计&#xff0c;而Android消息丢失每天都在发生。 Android短信恢复对于那些在设备中保存了一些重要信息的人来说显得至关重要。首先&#xff0c;我们向您推荐奇客数据恢复安卓版&#xff0c;这款软件能够高效、安全地恢复已删除的短信&#xff0…

结构指针的使用

结构指针的使用 指针类型变量&#xff1a; 指针类型&#xff0c;是变量类型的一种&#xff0c;它是专门用来存储变量的地址的。 例如 int *p; 表示p是一个指针变量&#xff0c;它用来存储某个整型变量的地址。 int a5; int *p&a; 这样&#xff0c;就将整型变量a的地…

有什么针对新闻媒体行业的安全解决方案

对媒体行业而言&#xff0c;门户网站是最易受到攻击的地方。常见的攻击方式有网页篡改、挂马和被植入暗链等。门户网站作为新闻媒体对外的第一扇门&#xff0c;通常承载了大量的流量&#xff0c;一旦遭到攻击&#xff0c;造成的影响会更具有可怕的“传播力”。那么我们应该如何…

Tree Shaking:优化前端项目的利器

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

1、功能安全入门之内功心法ISO26262介绍

目录 0. 对于安全规范的认知 0.1 ISO26262简介 0.2 功能安全的必要性 2. ISO26262第1部分基本术语 2. ISO26262第2部分-功能安全管理 3. ISO26262第3部分-概念阶段 4. ISO26262第4部分-产品开发:系统层面 5. ISO26262第5部分-产品开发:硬件层面 6. ISO26262第6部分-…

使用VBA快速梳理多层级族谱(组织架构)

实例需求&#xff1a;族谱&#xff08;或者公司组织架构等&#xff09;都是典型的带有层级关系数据&#xff0c;例如下图中左侧表格所示。 A列为层级&#xff08;准确的讲是B列成员的层级&#xff09;&#xff0c;从一开始递增B列和C列为成员直接的父&#xff08;/母&#xff…

搭建一款实用的个人IT工具箱——it-tools

一、it-tools介绍 IT-Tools是一款开源的个人工具箱&#xff0c;专为IT从业人员打造&#xff0c;支持Docker私有化部署&#xff0c;包含众多实用的IT工具。其功能丰富多样&#xff0c;涵盖二维码生成、数据格式转换、MAC地址生成等&#xff0c;可满足用户多样化的需求。 二、本…

armv8/armv9 MMU深度学习

目录 1、MMU概念介绍2、虚拟地址空间和物理地址空间2.1、(虚拟/物理)地址空间的范围2.2、物理地址空间有效位(范围)2.2.1、页表翻译相关寄存器的配置 3、Translation regimes4、地址翻译/几级页表&#xff1f;4.1、思考&#xff1a;页表到底有几级&#xff1f;4.2、以4KB granu…

《日期类》的模拟实现

目录 前言&#xff1a; 头文件类与函数的定义Date.h 实现函数的Date.cpp 测试Test.cpp 运行结果&#xff1a; 前言&#xff1a; 我们在前面的两章初步学习认识了《类与对象》的概念&#xff0c;接下来我们将实现一个日期类&#xff0c;是我们的知识储备更加牢固。 头文件…

Android Gradle 开发与应用 (五) : 基于Gradle 8.2,创建Gradle插件

1. 前言 本文介绍在Android中&#xff0c;如何基于Gradle 8.2&#xff0c;创建Gradle插件。 1.1 本文环境 Android Studio 版本 : Android Studio Hedgehog | 2023.1.1Gralde版本 : gradle 8.2 使用 Android Gradle 插件升级助理 Android Gradle 插件版本说明 1.2 为什么要写…

【C语言】——详解操作符(下)

【C语言】——详解操作符&#xff08;下&#xff09; 前言七、关系操作符八、逻辑操作符8.1、& 与运算符8.2、 | 或运算符 九、条件操作符十、逗号表达式十一、下标引用与函数调用操作符11.1、[ ] 下标引用操作符11.2、( ) 函数调用操作符 十二、 结构成员操作符12.1、…

转移表回调函数实现

回调函数实现 计算器的模拟&#xff08;函数指针数组的使用&#xff09;&#xff08;回调函数&#xff09; 简化 冗余 老的代码的问题就是 冗余 写死 不能完成不同的任务 函数调用的时候只需要知道地址就可以 calc计算器 这里也称之为转移表 #define _CRT_SECURE_NO_WAR…

朴素贝叶斯 | 多分类问题

目录 一. 贝叶斯公式的推导二. 朴素贝叶斯1. 离散的朴素贝叶斯朴素贝叶斯导入示例 离散的朴素贝叶斯训练 2. 连续的朴素贝叶斯3. 伯努利朴素贝叶斯4. 多项式朴素贝叶斯4.1 Laplace平滑4.2 Lidstone平滑 三. 概率图模型1. 贝叶斯网络(Bayesian Network)1.1 全连接贝叶斯网络1.2 …

【Redis系列】深入了解 Redis:一种高性能的内存数据库

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

分布式搜索elasticsearch

1.初识elasticsearch 1.1.了解ES 1.1.1.elasticsearch的作用 elasticsearch是一款非常强大的开源搜索引擎&#xff0c;具备非常多强大功能&#xff0c;可以帮助我们从海量数据中快速找到需要的内容 例如&#xff1a; 在GitHub搜索代码 在电商网站搜索商品 在百度搜索答案…

25 使用块的网络 VGG【李沐动手学深度学习v2课程笔记】

目录 1. VGG块 2. VGG网络 3. 训练模型 4. 小结 虽然AlexNet证明深层神经网络卓有成效&#xff0c;但它没有提供一个通用的模板来指导后续的研究人员设计新的网络。 与芯片设计中工程师从放置晶体管到逻辑元件再到逻辑块的过程类似&#xff0c;神经网络架构的设计也逐渐变得…

【Linux】文件系统扩展——软硬链接

目录 对文件建立软硬链接 软链接 硬链接 对文件建立软硬链接 对 log 文件建立软链接&#xff1a; ln -s log log.soft.link 对 test 文件建立硬链接&#xff1a; ln test test.hard.link log.soft.link 和 test.hard.link 在 Linux 中都只是文件名&#xff0c;为了方便…

Math类 --Java学习笔记

Math 代表数学&#xff0c;是一个工具类&#xff0c;里面提供的都是对数据进行操作的一些静态方法 Math提供的常用方法

springBoot--静态资源映射

静态资源映射 前言1、通过继承 WebMvcConfigurerAdapter 来实现2、在 application.properties 配置 前言 在 web 开发中&#xff0c;静态资源的访问是必不可少的&#xff0c;如图片、js、css等资源的访问 1、通过继承 WebMvcConfigurerAdapter 来实现 即如果使用了 EnableWe…

web前端框架

目前比较火热的几门框架: React React是由Facebook(脸书)开发和创建的开源框架。React 用于开发丰富的用户界面&#xff0c;特别是当您需要构建单页应用程序时。它是最强大的前端框架。 弊端: 您不具备 JavaScript 的实践知识&#xff0c;则建议不要使用 React。同样&#x…