Java后端八股----JVM篇

Java内存管理、线程并发与垃圾回收机制详解
本文解释了Java中线程抢占、内存优化(如本地内存和元空间)、NIO与IO的区别、类加载过程、垃圾回收规则(包括引用计数和分代回收),以及堆溢出的示例。特别强调了弱引用可能导致的问题和jdk1.8版本的特点。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

内存区域划分

本地方法栈
作用、抛出异常和虚拟机栈类似,但是本地方法栈为本地方法服务。(本地方法是非 java 代码实现的方法)

  • 程序计数器
    是一块较小的内存空间,可以看作当前线程执行字节码的指示器,是唯一没有内存溢出的区域。字节码解释器工作时通过改变计数器的值选取下一条执行指令。分支、循环、跳转、线程恢复等功能都需要依赖计数器。
    如果线程正在执行 Java 方法,计数器记录正在执行的虚拟机字节码指令地址。如果是本地方法,计数器值为 Undefined。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    上图中线程1,2如果资源被抢占了,则程序计数器记录一下执行的行号,等到资源就绪后会从记录的行号继续向后执行。
    在这里插入图片描述
  • Java 堆
    Java 堆是虚拟机所管理的内存中最大的一块。堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。它目的是为了存放对象实例,Java 里几乎所有的对象实例都在这里分配内存。
    Java 堆可以处于物理上不连续的内存空间中,但在逻辑上它应该被视为连续的。但对于大对象(例如数组),多数虚拟机实现出于简单、存储高效的考虑会要求连续的内存空间。
    如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,虚拟机将抛出 OutOfMemoryError 异常。
    在这里插入图片描述

在这里插入图片描述
Java8把静态变量以及常量放到了线程的本地内存原空间中(避免放在堆中不可控)。
在这里插入图片描述

  • Java 虚拟机栈
    是线程私有的,描述 Java 方法的内存模型。当有新线程创建时会分配一个栈空间,栈中元素用于支持虚拟机进行方法调用,每个方法在执行时都会创建一个栈帧存储方法的局部变量表、操作栈和方法出口等信息。每个方法从调用到执行完成,就是栈帧从入栈到出栈的过程
    线程请求的栈深度大于虚拟机允许的深度抛出 StackOverflowError;如果 JVM 栈允许动态扩展,栈扩展无法申请足够内存抛出 OutOfMemoryError。在这里插入图片描述

    在这里插入图片描述
    在这里插入图片描述
    👆图中第二种情况不太容易出现。
    在这里插入图片描述
Java JVM中 本地方法栈的作用 是每个线程单独一个还是共享的?

在Java虚拟机(JVM)中,本地方法栈(Native Method Stack)的作用是为每个线程调用本地方法(如通过JNI调用C/C++代码)时提供支持。每个线程都有自己独立的本地方法栈,而不是多个线程共享一个本地方法栈。

具体作用和机制

  • 线程独立:每个线程都有自己的本地方法栈,用于管理该线程调用本地方法时所需的本地变量、参数、返回地址等信息。这意味着本地方法栈是线程私有的。

  • 支持本地方法调用:当Java程序调用本地方法(即非Java语言实现的方法,如C/C++),这些方法的执行状态和上下文信息会存储在本地方法栈中。

  • 栈帧管理:与Java方法调用栈类似,本地方法栈在调用本地方法时会创建一个栈帧,保存相关的局部变量和调用信息。每个本地方法的执行对应一个栈帧。

  • 自动释放:本地方法栈的生命周期与线程相同,当线程结束时,其对应的本地方法栈也会被释放。

总之,Java JVM中的本地方法栈是为每个线程单独分配的,而不是多个线程共享的。

在这里插入图片描述

  • 方法区
    存储被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等等数据。
    虚拟机规范对方法区的约束宽松,除和堆一样不需要连续内存、可选择固定大小、可扩展外,还可以不实现垃圾回收。垃圾回收在方法区出现较少,主要针对常量池和类型卸载。
    如果方法区无法满足新的内存分配需求时,将抛出 OutOfMemoryError 异常。
    在这里插入图片描述
    在这里插入图片描述
    方法区存放在永久代的信息,方法区存的类的信息,jdk8之后存在了线程的元空间里。元空间的默认大小是没有上限的。
    在这里插入图片描述

运行时常量池(后续版本存储在元空间)

  • 是方法区的一部分。Class 文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池表,用于存放编译器生成的各种字面量与符号引用,这部分内容在类加载后存放到运行时常量池。
  • 运行时常量池相对于 Class 文件常量池的一个重要特征是动态性,Java 不要求常量只有编译期才能产生。
    例如 String 的 intern 方法。intern 方法是一个本地方法,作用是如果字符串常量池中已包含一个等于此 String 对象的字符串,则返回池中这个字符串的 String 对象的引用,否则将此 String 对象包含的字符串添加到常量池并返回此 String 对象的引用。

内存溢出和内存泄漏

  • 内存溢出 OutOfMemory,指程序在申请内存时,没有足够的内存空间供其使用。
  • 内存泄露 Memory Leak,指程序在申请内存后,无法释放已申请的内存空间,内存泄漏最终将导致内存溢出。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    👆 IO速度远小于NIO
    IO是用的IOStream NIO用的是FileChannel。
    在这里插入图片描述
    Java是没有权限之间读到系统内存的,是需要CPU进行状态切换然后读到Java堆内存再进行数据处理。
    在这里插入图片描述
    而直接内存就能解决上面的情况,在系统内存和堆内存中创建一块区域给双倍都有权限,这样进行拷贝的时候速度就能快上不少。
    在这里插入图片描述

对象创建的过程

① 当 JVM 遇到字节码 new 指令时,首先检查能否在常量池中定位到一个类的符号引用,并检查该类是否已被加载。
② 在类加载检查通过后虚拟机将为新生对象分配内存。
③ 内存分配完成后虚拟机将成员变量设为零值,保证对象的实例字段可以不赋初值就使用。
④ 设置对象头,包括哈希码、GC 信息、锁信息、对象所属类的类元信息等。
⑤ 执行 init 方法,初始化成员变量,执行实例化代码块,调用类的构造方法,并把堆内对象的首地址赋值给引用变量。
在这里插入图片描述

在这里插入图片描述

双亲委派模型

  • 双亲委派模型要求除了顶层的启动类加载器外,其余类加载器都应该有自己的父加载器。
  • 某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。
  • 类跟随它的加载器一起具备了有优先级的层次关系,确保某个类在各个类加载器环境中都是同一个,通过这种层级关可以避免类的重复加载,保证程序的稳定性。

具体含义

  • Java 虚拟机的第一个类加载器是 Bootstrap,这个加载器很特殊,它不是 Java 类,因此它不需要被别人加载,它嵌套在 Java 虚拟机内核里面,也就是 JVM 启动的时候 Bootstrap 就已经启动,它是用 C++ 写的二进制代码(不是字节码),它可以去加载别的类。

    • 这也是我们在测试时为什么发现 System.class.getClassLoader() 结果为 null 的原因,这并不表示 System 这个类没有类加载器,而是它的加载器比较特殊,是 BootstrapClassLoader,由于它不是 Java 类,因此获得它的引用肯定返回 null。
  • 委托机制具体含义
    当 Java 虚拟机要加载一个类时,到底派出哪个类加载器去加载呢?

    • 首先当前线程的类加载器去加载线程中的第一个类(假设为类 A)。
      注:当前线程的类加载器可以通过 Thread 类的 getContextClassLoader () 获得,也可以通过 setContextClassLoader () 自己设置类加载器。
    • 如果类 A 中引用了类 B,Java 虚拟机将使用加载类 A 的类加载器去加载类 B。
    • 还可以直接调用 ClassLoader.loadClass() 方法来指定某个类加载器去加载某个类。
  • 委托机制的意义: 防止内存中出现多份同样的字节码
    比如两个类 A 和类 B 都要加载 System 类:

    • 如果不用委托而是自己加载自己的,那么类 A 就会加载一份 System 字节码,然后类 B 又会加载一份 System 字节码,这样内存中就出现了两份 System 字节码。
    • 如果使用委托机制,会递归的向父类查找,也就是首选用 Bootstrap 尝试加载,如果找不到再向下。这里的 System 就能在 Bootstrap 中找到然后加载,如果此时类 B 也要加载 System,也从 Bootstrap 开始,此时 Bootstrap 发现已经加载过了 System 那么直接返回内存中的 System 即可而不需要重新加载,这样内存中就只有一份 System 的字节码了。

判断两个类是否相等
任意一个类都必须由类加载器和这个类本身共同确立其在虚拟机中的唯一性。两个类只有由同一类加载器加载才有比较意义,否则即使两个类来源于同一个 Class 文件,被同一个 JVM 加载,只要类加载器不同,这两个类就必定不相等。

在这里插入图片描述
在这里插入图片描述
一般我们自己写的类都是用第三种应用类加载器加载的。
在这里插入图片描述
加载前都进行上一任的委托,从下往上委托,像Student类最后会从最上面下到AppClassLoader进行加载。当Student类需要使用String类型的时候也会向上委托,发现最高层的BootStrap里lib下有这个类,就可以返回给AppClassLoader直接使用。
在这里插入图片描述
在这里插入图片描述

类加载的过程⭐

在这里插入图片描述
加载

  • 通过一个类的全限定类名获取对应的二进制字节流,将这个流所代表的静态存储结构转化为方法区的运行时数据区,然后在内存中生成对应该类的 Class 实例,作为方法区中这个类的数据访问入口。
    在这里插入图片描述
    加载的时候,使用Person的class对象作为一个访问Person类的接口,Heap堆访问对象方法的时候还是要调用方法区的方法进行使用。

验证

  • 确保 Class 文件的字节流符合约束。
  • 如果虚拟机不检查输入的字节流,可能因为载入有错误或恶意企图的字节流而导致系统受攻击。验证主要包含:文件格式验证、元数据验证、字节码验证、符号引用验证。
  • 验证通过后对程序运行期没有任何影响。如果代码已被反复使用和验证过,在生产环境就可以考虑关闭大部分验证缩短类加载时间。
    在这里插入图片描述
    准备
  • 为类静态变量分配内存并设置零值。
  • 该阶段进行的内存分配仅包括类变量,不包括实例变量。如果变量被 final 修饰,编译时 Javac 会为变量生成 ConstantValue 属性,准备阶段虚拟机会将变量值设为代码值。
    在这里插入图片描述
    图中的Object类会在初始化阶段赋值。
    解析
  • 将常量池内的符号引用替换为直接引用。
  • 符号引用以一组符号描述引用目标,可以是任何形式的字面量,只要使用时能无歧义地定位目标即可,引用目标不一定已经加载到虚拟机内存;直接引用是可以直接指向目标的指针、相对偏移量或能间接定位到目标的句柄,引用目标必须已在虚拟机的内存中存在。
    在这里插入图片描述
    解析阶段所做的就是把符号引用(图中的数字指向),变换为直接引用,使用指针直接指向执行方法。

初始化

  • 直到该阶段 JVM 才开始执行类中编写的代码。
  • 准备阶段时变量赋过零值,初始化阶段会根据程序员的编码去初始化类变量和其他资源。初始化阶段就是执行类构造方法中的 方法,该方法是 Javac 自动生成的。
    在这里插入图片描述
    第三条:如果子类调用了父类的静态属性,那么只会初始化父类的静态代码。👇在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

垃圾回收机制

  • 需要垃圾回收的原因:
    • Java 运行时的数据,比如对象,数组都会储存在 Java 堆中。但如果动态创建的对象没有得到及时回收造成持续堆积,就会使堆被占满,从而内存溢出。
  • 为了解决这个问题,Java 在后台创建一个守护进程,在内存紧张时回收堆中无用或者长时间没使用的的对象,保证程序正常运行。
  • 垃圾回收只会负责释放那些对象占有的内存。

在这里插入图片描述
回收垃圾一般是回收堆里的没有引用的数据。在这里插入图片描述
👆进行第二条指令之后 New String(“123”)的引用就成了0。
在这里插入图片描述
即使a,b都为null了之后
在这里插入图片描述
堆中的对象一样引用无法归零,引用计数法就失效了。
在这里插入图片描述
在这里插入图片描述
图中的第二段代码可以理解为 b.instance = new Demo();
方法区中的常量可以理解为👇

  • final引用本身不能被重新赋值,即不能指向另一个对象。
  • final引用指向的对象内容可以是可变的(如数组或集合)。
  • final引用常用于不可变对象,确保引用的稳定性。

GC Roots 一定可达。可作为 GC Roots 的对象:

  • 在虚拟机栈中引用的对象,如线程被调用的方法堆栈中的参数、局部变量等。
  • 在方法区中类静态属性引用的对象,如类的引用类型静态变量。
  • 在方法区中常量引用的对象,如字符串常量池中的引用。
  • 在本地方法栈中 JNI 即 Native 方法引用的对象。
  • JVM 内部的引用,如基本数据类型对应的 Class 对象,一些常驻异常对象,系统类加载器等。
  • 所有被 synchronized 同步锁持有的对象。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
分代回收算法也是垃圾回收算法的一种。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
一般不常用fullGC,只在新生代和老年代内存实在长期不足的时候才调用使用。
在这里插入图片描述
这里的清理标记的方法大多都是可达性分析算法(标记过程可能需要停等 STW)。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
重新标记好像等于对于标记的再确认。

垃圾收集器

新生代

Serial

  • 是一个使用复制算法的单线程工作的新生代收集器,它进行垃圾收集时必须暂停其他所有工作线程直到收集结束。(Stop the world)
  • Serial 是虚拟机运行在客户端模式下的默认新生代收集器,优点是简单高效,对于内存受限的环境它是所有收集器中最小的;对于单核处理器或处理器核心较少的环境来说,Serial 收集器由于没有线程交互开销,因此可获得最高的单线程收集效率。

ParNew

  • 是 Serial 的多线程版本,除了使用多线程进行垃圾收集外其余行为完全一致(所有控制参数、收集算法(复制算法)、Stop The World、对象分配规则、回收策略等)。
  • ParNew 是虚拟机运行在服务端模式下的默认新生代收集器,一个重要原因是除了 Serial 外只 有它能与 CMS 配合。自从 JDK 9 开始,ParNew 加 CMS 收集器的组合就不再是官方推荐的服务端模式下的收集器解决方案了,官方希望他能被 G1 完全取代。

Parallel Scavenge

  • 与 ParNew 类似,新生代收集器,基于标记 - 复制算法,是可以并行的多线程收集器。
  • 特点是它的关注点与其他收集器不同,CMS 等收集器的关注点是尽可能缩短收集时用户线程的停顿时间,而 Parallel Scavenge 的目标是达到一个可控制的吞吐量,吞吐量就是处理器用于运行用户代码的时间与处理器消耗总时间的比值。
  • 高吞吐量可以高效率地利用 CPU 时间,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务。
  • 自适应调节策略也是它区别于 ParNew 的一个重要特性。可以根据当前系统的运行情况收集性能监控信息,动态调整垃圾收集参数(Survivor-Eden 区比例,晋升老年代年龄等等),保证最大吞吐量。
老年代

Serial Old: Serial 收集器的老年代版本,使用 “标记 - 整理” 算法。
Parallel Old:Parallel Scavenge 收集器的老年代版本,使用多线程和 “标记 - 整理” 算法,吞吐量优先。

CMS(Concurrent Mark Sweep)

  • 以获取最短回收停顿时间为目标的收集器,如果希望系统停顿时间尽可能短以给用户带来更好的体验就可以使用 CMS。
  • 基于标记 - 清除算法,分为四个步骤:初始标记、并发标记、重新标记、并发清除。
    • 其中初始标记和重新标记仍然需要 STW(Stop The World,表示系统停顿),初始标记仅是标记 GC Roots 能直接关联到的对象,速度很快。
    • 并发标记就是从 GC Roots 的直接关联对象开始遍历整个对象图的过程,所有步骤中耗时最长但不需要停顿用户线程,可以与垃圾收集线程并发运行。
    • 重新标记则是为了修正并发标记期间因用户程序运作而导致标记产生变动的那一部分对象的标记记录,该阶段停顿时间比初始标记稍长,但远比并发标记短。
    • 清理标记阶段判断的已死亡对象,由于不需要移动存活对象,因此该阶段也可以与用户线程并发。
    • 由于整个过程中耗时最长的并发标记和并发清除阶段中,垃圾收集器都可以和用户线程一起工作,所以从总体上说 CMS 的内存回收过程是与用户线程并发执行的。
CMS垃圾回收 GPT版本

CMS垃圾回收的基本过程
CMS的垃圾回收过程可以分为以下几个阶段:

初始标记(Initial Marking):
  • 描述:这一步标记的是从GC Roots直接可达的对象。它只标记了老年代中与GC Roots直接相关的对象(即根对象),并不遍历整个对象图。
  • 停顿:此阶段会导致所有应用程序线程暂停,但由于只标记与GC Roots直接关联的对象,所以这个阶段很快。
并发标记(Concurrent Marking):
  • 描述:在这一步中,CMS开始从初始标记的对象出发,进行整个老年代的对象图遍历,标记出所有可达的对象。
  • 停顿:该阶段是并发执行的,垃圾回收线程和应用程序线程同时运行,因此不会造成应用程序停顿。
重新标记(Remark):
  • 描述:由于并发标记阶段,用户线程还在运行,可能导致对象引用关系发生变化,因此需要重新扫描那些在并发标记过程中发生变化的部分,以确保标记结果的准确性。
  • 停顿:此阶段会导致短暂的停顿,但比初始标记稍长。CMS使用了一些优化技术(例如增量更新和并发清理)以减少停顿时间。
并发清除(Concurrent Sweeping):
  • 描述:在标记完成后,CMS开始清除那些未被标记的对象,即所谓的“垃圾”对象,释放这些对象所占用的内存空间。
  • 停顿:该阶段也是并发执行的,与应用程序线程同时运行,因此不会造成应用程序停顿。
CMS的其他特点和问题
  • 并发重置(Concurrent Resetting):在并发清除阶段之后,CMS可能会进行重置,以准备下次垃圾回收。这也是并发进行的。

  • 内存碎片问题:CMS是一种“标记-清除”算法,它在清除过程中不对内存进行压缩和整理。因此,随着时间的推移,老年代中可能会出现大量的内存碎片。这些碎片可能会导致老年代无法为大对象分配足够的连续内存空间,从而触发Full GC。

  • 浮动垃圾(Floating Garbage):由于并发标记阶段和并发清除阶段应用程序线程仍在运行,因此可能会有一些垃圾在并发清除阶段没有被及时清理。这部分垃圾称为“浮动垃圾”,它们将在下一次GC中被清理。

  • Full GC:如果CMS无法获取足够的内存空间(例如,由于内存碎片或浮动垃圾的原因),可能会触发一次Full GC。Full GC会导致整个堆的垃圾回收,并伴随长时间的停顿。

  • 参数调优:CMS回收器有多个调优参数(如-XX:CMSInitiatingOccupancyFraction,用于控制在老年代占用多少比例内存时触发CMS回收)来帮助平衡停顿时间与内存使用效率。

G1

  • 是一款主要面向服务端的收集器,最初设计目标是替换 CMS,相应速度优先。
  • G1 可以面向堆内存任何部分来组成回收集进行回收,衡量标准不再是它属于哪个分代,而是哪块内存中存放的垃圾数量最多,回收受益最大,这就是 G1 的 MixedGC 模式。
  • 把 Java 堆划分为多个大小相等的独立区域(Region),每一个 Region 都可以根据需要扮演新生代的 Eden 空间、Survivor 空间或老年代空间。收集器能够对扮演不同角色的 Region 采用不同的策略处理,这样无论是新创建的对象还是已经存活了一段时间、熬过多次收集的旧对象都能获取很好的收集效果。
  • 跟踪各个 Region 里面的垃圾堆积的价值大小,也就是回收所获得的空间大小以及回收所需时间的经验值,在后台维护一个优先级列表,每次根据用户设定允许的收集停顿时间优先处理回收价值收益最大的 Region。这种回收方式保证了 G1 在有限的时间内获取尽可能高的收集效率。
  • 为了避免全堆扫描的发生,虚拟机为 G1 中每个 Region 维护了一个与之对应的 Remembered Set。虚拟机发现程序在对 Reference 类型的数据进行写操作时,会产生一个 Write Barrier 暂时中断写操作,检查 Reference 引用的对象是否处于不同的 Region 之中(在分代的例子中就是检查是否老年代中的对象引用了新生代中的对象),如果是,便通过卡表把相关引用信息记录到被引用对象所属的 Region 的 Remembered Set 之中。当进行内存回收时,在 GC 根节点的枚举范围中加入 Remembered Set 即可保证不对全堆扫描也不会有遗漏。
  • G1 从整体来看是基于 “标记 - 整理” 算法实现的收集器,从局部(两个 Region 之间)上来看是基于 “复制” 算法实现的。这意味着 G1 运行期间不会产生内存空间碎片,收集后能提供规整的可用内存。此特性有利于程序长时间运行,分配大对象时不会因为无法找到连续内存空间而提前触发下一次 GC。

G1 的运作过程:

  • 初始标记:标记 GC Roots 能直接关联到的对象并修改 TAMS (Nest Top Mark Start)指针的值,让下一阶段用户线程并发运行时能正确地在可用 Region 中分配新对象。该阶段需要 STW 但耗时很短,是借用进行 MinorGC 时同步完成的。
  • 并发标记:从 GC Roots 开始对堆中对象进行可达性分析,递归扫描整个堆的对象图,找出需要回收的对象。该阶段耗时长,但可与用户线程并发执行,当对扫描完成后还要重新处理 SATB 记录的在并发时有引用变动的对象。
  • 最终标记:对用户线程做一个短暂暂停,用于处理并发阶段结束后仍遗留下来的少量 SATB 记录。
  • 筛选回收:对各个 Region 的回收价值和成本排序,根据用户期望的停顿时间指定回收计划,可自由选择任意多个 Region 构成回收集然后把决定回收的那一部分的存活对象复制到空的 Region 中,再清理掉整个旧的 Region 的全部空间。该操作必须暂停用户线程,由多条收集器线程并行完成。

可以由用户指定期望的停顿时间是 G1 的一个强大功能,但该值不能设得太低,一般设置为 100~300 毫秒比较合适。G1 不会存在内存空间碎片的问题,但 G1 为了垃圾收集产生的内存占用和程序运行时的额外执行负载都高于 CMS。

CMS 是 HotSpot 追求低停顿的第一次成功尝试,但还存在三个明显缺点:① 对处理器资源非常敏感,在并发阶段虽然不会导致用户线程暂停,但会降低总吞吐量。② 无法处理浮动垃圾,有可能出现并发失败而导致另一次 FullGC。③ 由于基于标记 - 清除算法,因此会产生大量空间碎片,给大对象分配带来麻烦。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
复制到s后会释放E,然后再重新分配Eden。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
上这时的垃圾回收就包括了新生代垃圾回收和老年代垃圾回收。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
当内存不够时会使用一次FullGC。
在这里插入图片描述

类加载机制

  • Java 程序运行过程
    首先通过 Javac 编译器将 .java 文件转为 JVM 可加载的 .class 字节码文件。编译过程分为:
    ① 词法解析,通过空格分割出单词、操作符、控制符等信息,形成 token 信息流,传递给语法解析器。
    ② 语法解析,把 token 信息流按照 Java 语法规则组装成语法树。
    ③ 语义分析,检查关键字使用是否合理、类型是否匹配、作用域是否正确等。
    ④ 字节码生成,将前面各个步骤的信息转换为字节码。
    之后通过即时编译器 JIT 把字节码文件编译成本地机器码。
    Java 程序最初都是通过解释器进行解释执行的,当虚拟机发现某个方法或代码块的运行特别频繁,就会认定其为热点代码,热点代码的检测主要有采样和计数器两种方式,为了提高热点代码的执行效率,虚拟机会把它们编译成本地机器码。
  • 类加载
    • Class 文件中的信息需要加载到虚拟机后才能使用。JVM 把描述类的数据从 Class 文件加载到内存,并对数据进行校验、解析和初始化,最终形成可以被虚拟机直接使用的 Java 类型,这个过程称为虚拟机的类加载机制。
    • Java 类的加载、连接和初始化都是在运行期间完成的,这增加了性能开销,但提供高扩展性,Java 动态扩展的特性就是依赖运行期动态加载和连接实现的。
    • 类型从被加载到卸出,整个生命周期经历加载、验证、准备、解析、初始化、使用和卸载七个阶段,其中验证、解析和初始化三部分称为连接。加载、验证、准备、初始化的顺序是确定的,解析则不一定:可能在初始化后再开始,这是为了支持 Java 的动态绑定。

引用类型

无论通过引用计数还是可达性分析判断对象是否存活,都和引用离不开关系。在 JDK1.2 之前引用的定义是:如果 reference 类型数据存储的数值代表另外一块内存的起始地址,那么就称该 reference 数据是代表某块内存、某个对象的引用。在 JDK 1.2 之后 Java 对引用的概念进行了扩充,按强度分为四种:
强引用:最传统的引用定义,指代码中普遍存在的引用赋值。任何情况下只要强引用存在,垃圾收集器就永远不会回收被引用的对象。
软引用:描述一些还有用但非必需的对象。只被软引用关联的对象,在系统将要发生内存溢出异常前,会把这些对象列进回收范围中进行二次回收,如果这次回收还没有足够的内存才会抛出 OOM 异常。
弱引用:描述非必需对象,引用强度比软引用更弱,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器开始工作时无论当前内存是否足够都会回收只被弱引用关联的对象。
虚引用:是最弱的引用关系。一个对象是否有虚引用存在,完全不会对其生存时间造成影响,也无法通过虚引用来取得一个对象实例。该引用的唯一目的就是为了能在这个对象被垃圾收集器回收时收到一个系统通知

在这里插入图片描述
在这里插入图片描述
弱引用容易造成内存泄漏的问题。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
.sh是在linux环境下的结尾,如果是windows环境结尾会是bat。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
👆目前只有jdk1.8中有。
在这里插入图片描述在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

通过上图可以得知,堆溢出的情况是在ToolDemo中的java的第26行
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值