JVM高级特性

news2024/9/26 1:27:17

JVM高级特性

内存管理

jvm是自动内存管理的

运行时数据区域

Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域 有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而一直存在,有些区域则是 依赖用户线程的启动和结束而建立和销毁

线程独享
  • 虚拟机栈
    • 生命周期 与线程相同
    • 每个方法被执行的时候,Java虚拟机都 会同步创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态连接、方法出口等信 息。
    • 每一个方法被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
    • 局部变量表中的存储空间以局部变量槽(Slot)来表示,其中64位长度的long和 double类型的数据会占用两个变量槽,其余的数据类型只占用一个。当进入一个方法时,该方法在栈帧中的内存分配已经确定,且运行期间不会改变,内存分配是以变量槽为基本单位分配的
    • 两种异常:
      • StackOverflowError:栈深度不够
      • OutOfMemoryError(OOM):栈内存不够
  • 本地方法栈
    • 调用本地方法所存的具体数据(Native)
  • 程序计数器(PC)
    • 当前线程所执行的字节码的行号指示器
    • 是一块较小的内存空间,存储着下一条将要执行的指令的地址,是程序控制流的治时期
    • 存“行号“——指示要执行的下一条指令的值
    • 由于Java虚拟机的多线程是通过线程轮流切换、分配处理器执行时间的方式来实现的,在任何一 个确定的时刻,一个处理器(对于多核处理器来说是一个内核)都只会执行一条线程中的指令。因 此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程 之间计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存

程序计数器、虚拟机栈、本地方法栈 3 个区域随线程而生,随线程而灭

线程共享
  • 方法区
    • 存储已被虚拟机加载 的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据
    • JVM中内存最大的一种,是被所有线程共享的一块内存
    • 唯一目的就是存放对象实例,Java里几乎所有的对象实例都在此分配内存
    • Java堆是垃圾收集器管理的内存区域,由于现代垃圾收集器大部分都是基于分 代收集理论设计,所以会分为“新生代”“老年代”“永久代”“Eden空间”“From Survivor空 间”“To Survivor空间”等
    • 分配内存的角度看,所有线程共享的Java堆中可以划分出多个线程私有的分配缓冲区 (Thread Local Allocation Buffer,TLAB),以提升对象分配时的效率(先分配一部分内存栈使某一线程独占)
    • 将Java堆细分的目的只是为了更好的回收内存或更快的分配内存
对象的创建

在虚拟机为新生对象分配内存时,主要有两种方法:

  • 指针碰撞
    • 假设Java堆中内存是绝对规整的,所有被使用过的内存都被放在一 边,空闲的内存被放在另一边,中间放着一个指针作为分界点的指示器,那所分配内存就仅仅是把那 个指针向空闲空间方向挪动一段与对象大小相等的距离
  • 空闲列表
    • Java堆中的内存并不是规整的,虚拟机就必须维护一个列表,记录上哪些内存块是可用的,在分 配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录
  • 选择哪种分配方式由Java堆是否规整决定,而Java堆是否规整又由所采用 的垃圾收集器是否带有空间压缩整理(Compact)的能力决定。因此,当使用Serial、ParNew等带压缩 整理过程的收集器时,系统采用的分配算法是指针碰撞,既简单又高效;而当使用CMS这种基于清除 (Sweep)算法的收集器时,理论上就只能采用较为复杂的空闲列表来分配内存
  • 每个线程在Java堆中预先分配一小块内存,称为本地线程分配缓冲(Thread Local Allocation Buffer,TLAB),哪个线程要分配内存,就在哪个线程的本地缓冲区中分配,只有本地缓冲区用完 了,分配新的缓存区时才需要同步锁定。
对象内存布局(对象在堆中是如何构成的)
  • 对象头
    • mark word

      • 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
    • 类型指针
      • 即对象指向它的类型元数据的指针,Java虚拟机通过这个指针 来确定该对象是哪个类的实例
    • 数组长度(如果对象为数组的话才会考虑到)
  • 示例数据
    • 是对象真正存储的有效信息,即我们在程序代码里面所定义的各种类型的字 段内容,无论是从父类继承下来的,还是在子类中定义的字段都必须记录起来。
  • 对齐填充
    • 并不是必然存在的,也没有特别含义,仅仅是占位符的作用。
      • 要求对象起始地址必须是8字节的整数倍,换句话说就是 任何对象的大小都必须是8字节的整数倍。对象头部分已经被精心设计成正好是8字节的倍数(1倍或者 2倍),因此,如果对象实例数据部分没有对齐的话,就需要通过对齐填充来补全。
对象的访问定位

创建对象自然是为了后续使用该对象,我们的Java程序会通过栈上的reference数据来操作堆上的具 体对象。但是reference类型只规定了一个指向对象的引用,并未定义具体方法,所以主要的实现还是由虚拟机

  • 句柄
    • 使用句柄访问的话,Java堆中将可能会划分出一块内存来作为句柄池,reference中存储的就 是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自具体的地址信息
    • 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
  • 直接指针
    • 使用直接指针访问的话,Java堆中对象的内存布局就必须考虑如何放置访问类型数据的相关 信息,reference中存储的直接就是对象地址,如果只是访问对象本身的话,就不需要多一次间接访问 的开销。
  • 两个比较
    • 使用句柄来访问的最大好处就是reference中存储的是稳定句柄地 址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而 reference本身不需要被修改。
    • 使用直接指针来访问最大的好处就是速度更快,它节省了一次指针定位的时间开销,由于对象访 问在Java中非常频繁,因此这类开销积少成多也是一项极为可观的执行成本

垃圾收集与内存分配

垃圾收集(Garbage Collection),垃圾 收集需要完成的三件事情:

  • 哪些内存需要回收
  • 什么时候回收
  • 如何回收
判断对象已死
  • 引用计数法
    • 脑门刻字法
      • 原理:在对象中添加一个引用计数器,每当有一个地方 引用它时,计数器值就加一;当引用失效时,计数器值就减一;任何时刻计数器为零的对象就是不可 能再被使用的。
      • 优缺点
        • 优点:原理简单,判定效率高,在大多数情况下都是一个不错的算法,经典案例比如:微软COM(Component Object Model)技术、使用ActionScript 3的FlashPlayer
        • 缺点:需要考虑很多例外情况,必须要配合大量额外处理才能保证正确工作,譬如单纯的引用计数 就很难解决对象之间相互循环引用的问题。
  • 可达性分析
    • 平地长树法
      • 原理:通过 一系列称为“GC Roots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为“引用链”(Reference Chain),如果某个对象到GC Roots间没有任何引用链相连, 或者用图论的话来说就是从GC Roots到这个对象不可达时,则证明此对象是不可能再被使用的
      • 图示如下:
        • 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
        • 在java里,固定可以作为GC Roots的对象包括以下:
          • 在虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如各个线程被调用的方法堆栈中使用到的 参数、局部变量、临时变量等。
          • 在方法区中类静态属性引用的对象,譬如Java类的引用类型静态变量
          • 在方法区中常量引用的对象,譬如字符串常量池(String Table)里的引用
          • 在本地方法栈中JNI(即通常所说的Native方法)引用的对象。
          • 所有被同步锁(synchronized关键字)持有的对象等
引用

java讲引用分为强引用(Strongly Re-ference)、软 引用(Soft Reference)、弱引用(Weak Reference)和虚引用(Phantom Reference)4种,引用强度依次降低

  • 强引用:
    • 最传统的“引用”的定义,是指在程序代码之中普遍存在的引用赋值,即类似“Object obj=new Object()”这种引用关系。无论任何情况下,只要强引用关系还存在,垃圾收集器就永远不会回收掉被引用的对象
  • 软引用:
    • 用来描述一些还有用,但非必须的对象。只被软引用关联着的对象,在系统将要发生内 存溢出异常前,会把这些对象列进回收范围之中进行第二次回收,如果这次回收还没有足够的内存, 才会抛出内存溢出异常
    • SoftReference类来实现软引用
  • 弱引用:
    • 用来描述那些非必须对象,但是它的强度比软引用更弱一些,被弱引用关联的对象只 能生存到下一次垃圾收集发生为止。当垃圾收集器开始工作,无论当前内存是否足够,都会回收掉只 被弱引用关联的对象。
    • WeakReference类来实现弱引用。
  • 虚引用:
    • 也称为“幽灵引用”或者“幻影引用”,它是最弱的一种引用关系。一个对象是否有虚引用的 存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚 引用关联的唯一目的只是为了能在这个对象被收集器回收时收到一个系统通知
    • PhantomReference类来实现虚引用。
回收方法区

方法区的垃圾收集主要回收两部分内容:废弃的常量和不再使用的类型。

  • 回收废弃常量
    • 与回收Java堆中的对象非常类似,当发生内存回收时,垃圾收集器判断确有必要(常量没有任何地方引用),那么该常量就将会被系统清理出常量池
  • 不再使用的类型
    • 判断一个类是否属于“不再被使用的类”要同时满足三个条件
      • 该类的所有实例都已经被回收,即Java堆中不存在该类及其任何派生自类的实例
      • 加载该类的类加载器已经被回收,这个条件除非是经过精心设计的可替换类加载器的场景,如 OSGi、JSP的重加载等,否则通常是很难达成的
      • 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方 法
    • Java虚拟机被允许对满足上述三个条件的无用类进行回收,这里说的仅仅是“被允许”,而并不是 和对象一样,没有引用了就必然会回收。
垃圾收集算法
  • 从如何判定对象消亡的角度出发,垃圾收集算法可以划分为“引用计数式垃圾收集”(Reference Counting GC)和“追踪式垃圾收集”(Tracing GC)两大类,这两类也常被称作“直接垃圾收集”和“间接垃圾收集” (已下所述皆为追踪式垃圾收集)

    • 分代收集理论

      ​ 建立在两个分代假说之上:

      • 弱分代假说(Weak Generational Hypothesis):绝大多数对象都是朝生夕灭的。
      • 强分代假说(Strong Generational Hypothesis):熬过越多次垃圾收集过程的对象就越难以消亡(熬过多次垃圾收集即被引用的次数很多或者一直在被引用,所以此类很难被回收)。
    • 垃圾收集器的一致的设计原则(由上面的两个假说奠定):

      • 收集器应该将Java堆划分 出不同的区域,然后将回收对象依据其年龄(年龄即对象熬过垃圾收集过程的次数)分配到不同的区 域之中存储。
      • 具体过程:
        • 将那些朝生夕灭的的对象放到一起,该区域每次垃圾收集的时候只需要考虑那些存活下来的即可,不需要耗费大量资源去标记被回收的对象,以较低代价回收到大量的空间;
        • 相对的,如果剩下的都是难以消亡的对象,那把它们集中放在一块, 虚拟机便可以使用较低的频率来回收这个区域,这就同时兼顾了垃圾收集的时间开销和内存的空间有 效利用。
    • 关于新生代和老年代

      • 在新生代中,每次垃圾收集 时都发现有大批对象死去,而每次回收后存活的少量对象,将会逐步晋升到老年代中存放
    • 跨代引用

      • 跨代引用假说(Intergenerational Reference Hypothesis):跨代引用相对于同代引用来说仅占极 少数
      • 存在互相引用关系的两个对象,是应该倾 向于同时生存或者同时消亡的
      • 根据这条假说,在碰到跨代引用时,只需在新生代上建立一个全局的数据结构(该结构被称 为“记忆集”,Remembered Set),这个结构把老年代划分成若干小块,标识出老年代的哪一块内存会 存在跨代引用。
    • 主要代:

      • 部分收集(Partial GC):指目标不是完整收集整个Java堆的垃圾收集,其中又分为
        • 新生代收集(Minor GC/Young GC):指目标只是新生代的垃圾收集
        • 老年代收集(Major GC/Old GC):指目标只是老年代的垃圾收集。目前只有CMS收集器会有单 独收集老年代的行为。另外请注意“Major GC”这个说法现在有点混淆,在不同资料上常有不同所指, 读者需按上下文区分到底是指老年代的收集还是整堆收集。
        • 混合收集(Mixed GC):指目标是收集整个新生代以及部分老年代的垃圾收集。目前只有G1收 集器会有这种行为。
      • 整堆收集(Full GC):收集整个Java堆和方法区的垃圾收集。
  • 标记-清除算法

    • 最早出现也是最基础的垃圾收集算法
    • 算法分为“标记”和“清除”两部分,过程如下:
      • 首先标记出所有需要回 收的对象,在标记完成后,统一回收掉所有被标记的对象,也可以反过来,标记存活的对象,统一回 收所有未被标记的对象(标记过程就是判定对象是否属于垃圾)。
    • 缺点:
      • ①执行效率不稳定,如果Java堆中包含大量对 象,而且其中大部分是需要被回收的,这时必须进行大量标记和清除的动作,导致标记和清除两个过 程的执行效率都随对象数量增长而降低
      • ②内存空间的碎片化问题,标记、清除之后会产生大 量不连续的内存碎片,空间碎片太多可能会导致当以后在程序运行过程中需要分配较大对象时无法找 到足够的连续内存而不得不提前触发另一次垃圾收集动作。
    • 图示如下:
      • 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
  • 标记-复制算法

    • 常被简称为复制算法
      • 基本思路:
        • 将可用 内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着 的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉
    • 优缺点:
      • 优点:不会产生内存碎片(因为每次都是对整个半区进行操作),只要移动堆定指针,按顺序即可,实现简单,运行搞笑
      • 缺点:特别浪费空间,因为每次回收都将可用内存缩小为原来的一半
    • 图示:
      • 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
    • 大部分都采用该方法去回收新生代(新生代中由98%的对象熬不过第一轮收集)
    • 半区复制分代算法的优化:Appel式回收
      • 具体做法
        • 把新生代分为一块较大的Eden空间和两块较小的 Survivor空间,每次分配内存只使用Eden和其中一块Survivor。发生垃圾搜集时,将Eden和Survivor中仍 然存活的对象一次性复制到另外一块Survivor空间上,然后直接清理掉Eden和已用过的那块Survivor空 间。
  • 标记-整理算法

    • 主要是针对老年代(因为老年代的存活率基本接近100%,此时使用标记复制的话,就需要全部复制,效率会降低)
    • 过程:
      • 主要分为两个过程,先标记,在整理,标记的过程与标记清楚的过程相同,整理的意思则是让所有存活的对象都向内存空间一端移动,然后直接清理掉边界以外的内 存
    • 标记-清除算法与标记-整理算法的本质差异在于前者是一种非移动式的回收算法,而后者是移动 式的
    • 图示:
      • 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
    • 移动回收后对象的风险决策:
      • 若移动存活对象,就与要面临“Stop The World”,即更新 所有引用这些对象的地方将会是一种极为负重的操作,而且这种对象移动操作必须全程暂停用户应用 程序才能进行
      • 同时,若不考虑移动和整理存活对象,那么也会跟标记清除一样产生内存碎片的问题,此时就只能依赖更为复杂的内存分配器和内存访问器来解决,譬如通过“分区空闲分配链 表”来解决内存分配问题(但这必定会影响吞吐量,同样影响效率)
    • 基于以上两点的解决方法:
      • 让虚 拟机平时多数时间都采用标记-清除算法,暂时容忍内存碎片的存在,直到内存空间的碎片化程度已经 大到影响对象分配时,再采用标记-整理算法收集一次,以获得规整的内存空间(基于标 记-清除算法的CMS收集器就是如此)

经典的垃圾收集器

CMS收集器

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器,CMS非常符合B/S的服务端

  • 主要步骤:

    CMS是基于标记-清除算法实现的

    • 初始标记(CMS initial mark)
      • 需要“Stop The World”,仅仅只是标记一下GC Roots能直接关联到的对象,速度很快
    • 并发标记(CMS concurrent mark)
      • 需要“Stop The World”,就是从GC Roots的直接关联对象开始遍历整个对 象图的过程,这个过程耗时较长但是不需要停顿用户线程,可以与垃圾收集线程一起并发运行
    • 重新标记(CMS remark)
      • 该阶段是为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,停顿时间通常会比初始标记阶段稍长一 些,但也远比并发标记阶段的时间短
    • 并发清除(CMS concurrent sweep)
      • 清理删除掉标记阶段判断的已经死亡的 对象,由于不需要移动存活对象,所以这个阶段也是可以与用户线程同时并发的。
  • 由于在整个过程中耗时最长的并发标记和并发清除阶段中,垃圾收集器线程都可以与用户线程一 起工作,所以从总体上来说,CMS收集器的内存回收过程是与用户线程一起并发执行的

    • 图示如下:
      • 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
  • 优点:并发收集、低停顿(故一些文档也称之为“并发低停顿收集器”)

  • 缺点:

    • ①CMS收集器对处理器资源非常敏感
    • ②由于CMS收集器无法处理“浮动垃圾”(Floating Garbage),有可能出现“Con-current Mode Failure”失败进而导致另一次完全“Stop The World”的Full GC的产生
    • CMS是一款基于“标记-清除”算法实现的收集器,会产生内存碎片
Garbage First收集器(G1收集器)

Garbage First(简称G1)收集器开创了收集 器面向局部收集的设计思路和基于Region的内存布局形式

  • G1是一款主要面向服务端应用的垃圾收集器
  • G1开创的基于Region的堆内存布局是它能够实现这个目标的关键
    • G1不再坚持固定大小以及固定数量的 分代区域划分,而是把连续的Java堆划分为多个大小相等的独立区域(Region)
  • 主要步骤:
    • 初始标记(Initial Marking)
      • 仅仅只是标记一下GC Roots能直接关联到的对象,并且修改TAMS 指针的值,让下一阶段用户线程并发运行时,能正确地在可用的Region中分配新对象。这个阶段需要 停顿线程,但耗时很短,而且是借用进行Minor GC的时候同步完成的,所以G1收集器在这个阶段实际 并没有额外的停顿
    • 并发标记(Concurrent Marking)
      • 从GC Root开始对堆中对象进行可达性分析,递归扫描整个堆 里的对象图,找出要回收的对象,这阶段耗时较长,但可与用户程序并发执行。当对象图扫描完成以 后,还要重新处理SATB记录下的在并发时有引用变动的对象。
    • 最终标记(Final Marking)
      • 对用户线程做另一个短暂的暂停,用于处理并发阶段结束后仍遗留 下来的最后那少量的SATB记录
    • 筛选回收(Live Data Counting and Evacuation)
      • 负责更新Region的统计数据,对各个Region的回 收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划,可以自由选择任意多个Region 构成回收集,然后把决定回收的那一部分Region的存活对象复制到空的Region中,再清理掉整个旧 Region的全部空间。这里的操作涉及存活对象的移动,是必须暂停用户线程,由多条收集器线程并行 完成的
  • G1不是仅仅面向低延迟,停顿用户线程能够最大幅度提高垃圾收集效率,为了保 证吞吐量所以才选择了完全暂停用户线程的实现方案
    • 图示:
      • 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

内存分配与回收策略

Java技术体系的自动内存管理,最根本的目标是自动化地解决两个问题:自动给对象分配内存以 及自动回收分配给对象的内存。

对象优先在Eden分配

大多数情况下,对象在新生代Eden区中分配。当Eden区没有足够空间进行分配时,虚拟机将发起 一次Minor GC(新生代垃圾收集)

虚拟机的内存分配,会将内存分为老年代与新生代,其中新生代又分为Eden和Survivor,一般Eden区与一 个Survivor区的空间比例是8∶1

大对象直接进入老年代

大对象就是指需要大量连续内存空间的Java对象,最典型的大对象便是那种很长的字符串,或者 元素数量很庞大的数组

  • 避免大对象的原因:
    • 在分配空间时,它容易 导致内存明明还有不少空间时就提前触发垃圾收集,以获取足够的连续空间才能安置好它们,
    • 当复 制对象时,大对象就意味着高额的内存复制开销
  • HotSpot虚拟机提供了**-XX:PretenureSizeThreshold** 参数,指定大于该设置值的对象直接在老年代分配
    • 目的就是避免在Eden区及两个Survivor区 之间来回复制,产生大量的内存复制操作。
长期存活的对象将进入老年代

HotSpot虚拟机中多数收集器都采用了分代收集来管理堆内存,那内存回收时就必须能决策哪些存 活对象应当放在新生代,哪些存活对象放在老年代中。

  • 解决
    • 虚拟机会给每个对象定义了一个对 象年龄(Age)计数器,存储在对象头中
    • 过程:
      • 对象在Eden中诞生,若经过第一次 Minor GC后仍然存活,并且能被Survivor容纳的话,该对象会被移动到Survivor空间中,并且将其对象 年龄设为1岁
      • 对象在Survivor区中每熬过一次Minor GC,年龄就增加1岁,当它的年龄增加到一定程 度(默认为15),就会被晋升到老年代中
      • 对象晋升老年代的年龄阈值,可以通过参数-XX: MaxTenuringThreshold设置
动态对象年龄判断

为了能更好地适应不同程序的内存状况,HotSpot虚拟机并不是永远要求对象的年龄必须达到 XX:MaxTenuringThreshold才能晋升老年代,如果在Survivor空间中相同年龄所有对象大小的总和大于 Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到-XX: MaxTenuringThreshold中要求的年龄。

空间分配担保
  • 在发生Minor GC之前,虚拟机必须先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,若该条件成立,那这一次Minor GC可以确保是安全的
  • 若不成立,虚拟机会先查看 XX:HandlePromotionFailure参数的设置值是否允许担保失败(Handle Promotion Failure)
    • 若允许,那会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大 于,将尝试进行一次Minor GC,尽管这次Minor GC是有风险的
    • 若小于,或者-XX: HandlePromotionFailure不允许冒险,那这时就要改为进行一次Full GC。

虚拟机性能监控、故障处理工具

jps:虚拟机进程状况工具
jstat:虚拟机统计信息监视工具
jinfo:Java配置信息工具
jmap:Java内存映射工具
jhat:虚拟机堆转储快照分析工具
jstack:Java堆栈跟踪工具

虚拟机执行子系统

类文件结构
虚拟机类加载机制

Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最 终形成可以被虚拟机直接使用的Java类型,这个过程被称作虚拟机的类加载机制

  • 一个类的生命周期
    • 加载 (Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化 (Initialization)、使用(Using)和卸载(Unloading),其中验证、准备、解析三个部分统称 为连接(Linking)
    • 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
    • 在上图中,加载、验证、准备、初始化和卸载这几个阶段的顺序是确定的,但解析就不一定了,它在某些情况下可以在初始化阶段之后再开始, 这是为了支持Java语言的运行时绑定特性(也称为动态绑定或晚期绑定)
    • 同时注意,按部就班地“开始”,而不是按部就班地“进行”或按部就班地“完成”,强调这点是因为这些阶段通常都 是互相交叉地混合进行的
    • 类初始化阶段的六个有且只有的情况:
      • 遇到new、getstatic、putstatic或invokestatic这四条字节码指令时
      • 使用java.lang.reflect包的方法对类型进行反射调用的时候

在上图中,加载、验证、准备、初始化和卸载这几个阶段的顺序是确定的,但解析就不一定了,它在某些情况下可以在初始化阶段之后再开始, 这是为了支持Java语言的运行时绑定特性(也称为动态绑定或晚期绑定)

  • 同时注意,按部就班地“开始”,而不是按部就班地“进行”或按部就班地“完成”,强调这点是因为这些阶段通常都 是互相交叉地混合进行的
  • 类初始化阶段的六个有且只有的情况:
    • 遇到new、getstatic、putstatic或invokestatic这四条字节码指令时
    • 使用java.lang.reflect包的方法对类型进行反射调用的时候

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

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

相关文章

基于STM32的智能仓储管理系统教程

目录 引言环境准备智能仓储管理系统基础代码实现:实现智能仓储管理系统 物品识别与追踪模块环境监控模块数据处理与存储模块用户界面与远程管理应用场景:仓储管理与优化常见问题与解决方案收尾与总结 引言 随着电子商务和物流行业的快速发展&#xff…

C++手撕简易vector

提前准备工作 由于vector跟string不同&#xff0c;vector是可以存储不同类型的变量的容器&#xff0c;因此实现类模板是肯定的 在原本的STL的vector容器中&#xff0c;主要成员变量有&#xff0c;start&#xff0c;finish&#xff0c;和 end_of_storage 所以 template<cl…

leetcode 2415.反转二叉树的奇数层

1.题目要求: 给你一棵 完美 二叉树的根节点 root &#xff0c;请你反转这棵树中每个 奇数 层的节点值。例如&#xff0c;假设第 3 层的节点值是 [2,1,3,4,7,11,29,18] &#xff0c;那么反转后它应该变成 [18,29,11,7,4,3,1,2] 。 反转后&#xff0c;返回树的根节点。完美 二叉…

SolverLearner:提升大模型在高度归纳推理的复杂任务性能,使其能够在较少的人为干预下自主学习和适应

SolverLearner&#xff1a;提升大模型在高度归纳推理的复杂任务性能&#xff0c;使其能够在较少的人为干预下自主学习和适应 提出背景归纳推理&#xff08;Inductive Reasoning&#xff09;演绎推理&#xff08;Deductive Reasoning&#xff09;反事实推理&#xff08;Counterf…

npm ERR! missing script: serve

报错原因&#xff1a;我这里是因为跑错命令了&#xff0c;我用的npm run serve 解决办法&#xff1a;去package.json文件里面找到对应的serve命令运行即可&#xff0c;每个系统都不太一样&#xff0c;如果还不行可以看看是不是项目终端搞错了&#xff0c;比如我这个项目有两个前…

行为型设计模式3:模板方法/备忘录/解释器/迭代器

设计模式&#xff1a;模板方法/备忘录/解释器/迭代器 (qq.com)

无需提示的思考链推理:深度探索大型语言模型的内在能力

人工智能咨询培训老师叶梓 转载标明出处 在人工智能领域&#xff0c;提升大模型&#xff08;LLMs&#xff09;的推理能力一直是研究的重点。传统的方法主要依赖于特定的提示技术&#xff0c;例如少量样本或零样本的思考链&#xff08;CoT&#xff09;提示。这些方法虽然有效&am…

力扣爆刷第169天之TOP200五连刷111-115(课程表、单词搜索、归并)

力扣爆刷第169天之TOP200五连刷111-115&#xff08;课程表、单词搜索、归并&#xff09; 文章目录 力扣爆刷第169天之TOP200五连刷111-115&#xff08;课程表、单词搜索、归并&#xff09;一、207. 课程表二、LCR 125. 图书整理 II三、402. 移掉 K 位数字四、79. 单词搜索五、9…

Python自动化办公2.0:重塑工作效率的未来

在现代办公环境中&#xff0c;自动化技术和数据分析已经成为提升工作效率和决策质量的关键。随着Python编程语言的发展&#xff0c;我们迎来了“Python自动化办公2.0”时代&#xff0c;这一时代不仅包括强大的数据分析工具&#xff0c;还涵盖了酷炫的可视化技术和前沿的机器学习…

【卷积神经网络】卷积层详解【数学+python代码】

1、简介 学习目标&#xff1a; 掌握卷积计算过程掌握特征图大小计算方法掌握PyTorch卷积层API 基本概念&#xff1a; ①在计算机视觉领域&#xff0c;往往我们输入的图像都很大&#xff0c;使用全连接网络的话&#xff0c;计算的代价较高。 另外图像也很 难保留原有的特征 &am…

科技赋能生活——便携气象站

传统气象站往往庞大而复杂&#xff0c;需要专业人员维护&#xff0c;它小巧玲珑&#xff0c;设计精致&#xff0c;可以轻松放入背包或口袋&#xff0c;随身携带&#xff0c;不占空间。无论是城市白领穿梭于高楼大厦间&#xff0c;还是户外爱好者深入山林湖海&#xff0c;都能随…

numpy如何按等长分割数组

numpy如何按等长分割数组 1、效果 2、流程 1、分割数组 2、转列表3、代码 # -*- coding: utf-8 -*-""" @contact: 微---信 1257309054 @file: test.py @time: 2024/8/03 19:46 @author: LDC """ import numpy as np# 假设arr是需要分割的nump…

virtualbox7安装centos7.9配置静态ip

1.背景 我大概在一年之前安装virtualbox7centos7.9的环境&#xff0c;但看视频说用vagrant启动的窗口可以不用第三方工具(比如xshell、secure等)连接centos7.9&#xff0c;于是尝鲜试了下还可以&#xff0c;导致系统文件格式是vmdk了&#xff08;网上有vmdk转vdi的方法&#xf…

ChatGLM3-6B模型部署微调实战

准备 教程 视频教程 https://www.bilibili.com/video/BV1ce411J7nZ?p14&vd_source165c419c549bc8d0c2d71be2d7b93ccc 视频对应的资料 https://pan.baidu.com/wap/init?surlAjPi7naUMcI3OGG9lDpnpQ&pwdvai2#/home/%2FB%E7%AB%99%E5%85%AC%E5%BC%80%E8%AF%BE%E3%8…

Keil5.40因为Jlink驱动闪退问题

现象 Cannot load driver ‘C:\Keil_v5\ARM\Segger\JL2CM3.dll 原因 由于Jlink为盗版&#xff0c;导致闪退。 具体为JLinkARM.dll这个插件搞鬼。 这个插件的来源为 也就是我们装Jlink驱动的时候&#xff0c;勾选了这个选项&#xff0c;而导致这个毒瘤插件进入Keil&#xff…

代码题-01_顺序表_基础知识

线性表 线性表&#xff08;list&#xff09;:零个或多个相同数据元素的有限序列 线性表是逻辑结构&#xff08;元素之间一对一相邻关系&#xff09;按存储方式分为 顺序表链表 顺序表 顺序表的定义 静态分配&#xff08;使用数组存数据&#xff09; 有溢出风险 typedef…

特定领域软件架构-系统架构师(三十七)

软件架构复用 有三个阶段&#xff1a; 首先构造/获取可复用的软件资产其次管理这些资产&#xff08;构件库&#xff09;最后针对这些需求&#xff0c;从这些资产中选择可复用的部分&#xff0c;满足需求应用系统。 特定领域软件架构 DSSA&#xff08;Domain Specific softwa…

(四)activit5.23.0修复跟踪高亮显示BUG

一、先看bug 在 &#xff08;三&#xff09;springboot2.7.6集成activit5.23.0之流程跟踪高亮显示 末尾就发现高亮显示与预期不一样&#xff0c;比如上面的任务2前面的箭头没有高亮显示。 二、分析原因 具体分析步骤省略了&#xff0c;主要是ProcessInstanceHighlightsResour…

饿了么冰杯外卖爆涨350%,“冰+X”激发酒饮即时零售夏季增长加速

近日&#xff0c;饿了么联合尼尔森IQ共同发布的《2024夏季即时零售冰品酒饮消费洞察报告》显示&#xff0c;大暑前后冰杯外卖量同比去年增长350%&#xff0c;冰杯搭配啤酒等酒水饮料的外卖量也同比增长约300%。 报告综合多渠道零售数据和案例分析&#xff0c;剖析了冰品酒饮在…

基于51单片机的车窗控制系统proteus仿真

地址&#xff1a; https://pan.baidu.com/s/1w5qrAvn1cUK7ZX2GJvWBQw 提取码&#xff1a;1234 仿真图&#xff1a; 芯片/模块的特点&#xff1a; AT89C52/AT89C51简介&#xff1a; AT89C52/AT89C51是一款经典的8位单片机&#xff0c;是意法半导体&#xff08;STMicroelectro…