深入探讨Java虚拟机(JVM):执行流程、内存管理和垃圾回收机制
目录
什么是JVM?
JVM 执行流程
JVM 运行时数据区
堆(线程共享)
Java虚拟机栈(线程私有)
什么是线程私有?
程序计数器(线程私有)
方法区(线程共享)
JDK 1.8 元空间的变化
运行时常量池
内存布局中的异常问题
1. Java堆溢出
2. 虚拟机栈和本地方法栈溢出
JVM 类加载
1. 类加载过程
加载
验证
准备
解析
初始化
双亲委派模型
垃圾回收机制
死亡对象的判断算法
引用计数算法
可达性分析算法
垃圾回收算法
标记-清除算法(Mark and Sweep):
复制算法(Copying Garbage Collection):
标记-整理算法(Mark and Compact):
分代算法(Generational Garbage Collection):
什么是JVM?
JVM(Java虚拟机)是Java编程语言的关键组成部分,它是一种虚拟计算机环境,用于执行Java程序。JVM的主要作用是将Java源代码编译成与特定计算机硬件无关的字节码,并在运行时将这些字节码转换为机器码,以便在不同平台上运行Java应用程序。
在JVM的运行环境中,Java程序能够实现跨平台的特性,因为它们不需要直接与底层操作系统进行交互,而是依赖JVM来处理与硬件的交互。这使得Java成为一种高度可移植和可扩展的编程语言。
JVM的关键功能包括:
- 类加载:JVM负责加载Java类的字节码文件,通过类加载器实现这一任务。
- 内存管理:JVM自动管理内存分配和垃圾回收,以确保应用程序不会出现内存泄漏和溢出。
- 字节码执行:JVM解释或编译Java字节码,将其转换为本地机器码以执行应用程序。
- 多线程支持:JVM提供多线程支持,允许并发执行Java应用程序的部分或全部代码。
- 垃圾回收:JVM使用垃圾回收机制来自动释放不再被引用的内存,以提高内存利用率。
JVM 是 Java 运行的基础,也是实现一次编译到处执行的关键,那么 JVM 是如何执行的呢?
JVM 执行流程
程序在执行之前先要把java代码转换成字节码(class文件),JVM 首先需要把字节码通过一定的方式 类加载器(ClassLoader) 把文件加载到内存中 运行时数据区(Runtime Data Area) ,而字节码 文件是 JVM 的一套指令集规范,并不能直接交个底层操作系统去执行,因此需要特定的命令解析器 执行引擎(Execution Engine)将字节码翻译成底层系统指令再交由CPU去执行,而这个过程中需要调 用其他语言的接口 本地库接口(Native Interface) 来实现整个程序的功能,这就是这4个主要组成部分的职责与功能。
总结来看, JVM 主要通过分为以下 4 个部分,来执行 Java 程序的,它们分别是:
1. 类加载器(ClassLoader)
2. 运行时数据区(Runtime Data Area)
3. 执行引擎(Execution Engine)
4. 本地库接口(Native Interface)
JVM 运行时数据区
JVM 运行时数据区域也叫内存布局,但需要注意的是它和 Java 内存模型((Java Memory Model,简称JMM)完全不同,属于完全不同的两个概念,它由以下5大部分组成:
堆(线程共享)
堆的作用:程序中创建的所有对象都在保存在堆中。
我们常见的 JVM 参数设置 -Xms10m 最小启动内存是针对堆的,-Xmx10m 最大运行内存也是针对堆的。
ms 是 memory start 简称,mx 是 memory max 的简称。
堆里面分为两个区域:新生代和老生代,新生代放新建的对象,当经过一定 GC 次数之后还存活的对象会放入老生代。新生代还有 3 个区域:一个 Endn + 两个 Survivor(S0/S1)。
垃圾回收的时候会将 Eden 中存活的对象放到一个未使用的 Survivor 中,并把当前的 Endn 和正在使用的 Survivor 清楚掉。
Java虚拟机栈(线程私有)
Java 虚拟机栈的作用:Java 虚拟机栈的生命周期和线程相同,Java 虚拟机栈描述的是 Java 方法执行的 内存模型:每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。咱们常说的堆内存、栈内存中,栈内存指的就是虚拟机栈。
Java 虚拟机栈中包含了以下 4 部分:
1. 局部变量表: 存放了编译器可知的各种基本数据类型(8大基本数据类型)、对象引用。局部变量 表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局 部变量空间是完全确定的,在执行期间不会改变局部变量表大小。简单来说就是存放方法参数 和局部变量。
2. 操作栈:每个方法会生成一个先进后出的操作栈。
3. 动态链接:指向运行时常量池的方法引用。
4. 方法返回地址:即在方法执行完成后将控制返回到调用方法的指令位置。方法出口通常用于支持方法调用的返回操作。
什么是线程私有?
由于JVM的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现,因此在任何一个确定的时刻,一个处理器 (多核处理器则指的是一个内核) 都只会执行一条线程中的指令。因此为了切换线程后能恢复到正确的执行位置,每条线程都需要独立的程序计数器,各条线程之间计数器互不影响,独立存储。我们就把类似这类区域称之为"线程私有"的内存。
程序计数器(线程私有)
程序计数器的作用:用来记录当前线程执行的行号的。
程序计数器是一块比较小的内存空间,可以看做是当前线程所执行的字节码的行号指示器。
如果当前线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;
如果正在执行的是一个Native方法,这个计数器值为空。 程序计数器内存区域是唯一一个在JVM规范中没有规定任何OOM情况的区域!
方法区(线程共享)
方法区的作用:用来存储被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据的。
在《Java虚拟机规范中》把此区域称之为“方法区”,而在 HotSpot 虚拟机的实现中,在 JDK 7 时此区域叫做永久代(PermGen),JDK 8 中叫做元空间(Metaspace)。
汽车(Java虚拟机规范):在这个比喻中,汽车代表了Java虚拟机规范,它定义了Java虚拟机的基本结构和功能,其中包括了方法区的概念。
动能提供装置(方法区):这相当于汽车的一个重要部分,就像Java虚拟机中的方法区一样。方法区是用于存储类的结构信息、静态变量、常量池、方法的字节码等的内存区域。它提供了Java应用程序所需的关键信息。
发动机和电机(永久代和元空间):在不同类型的汽车中,动能提供装置可以有不同的实现。对于燃油车,它使用发动机作为动能提供装置,而电动汽车使用电机。同样地,Java虚拟机可以使用永久代或元空间来实现方法区。
永久代(PermGen):就像燃油车使用汽油发动机一样,一些早期版本的Java虚拟机使用永久代作为方法区的实现。永久代有一些限制,如固定大小,可能会导致内存溢出问题。
元空间(Metaspace):与之不同,元空间是Java虚拟机规范的一种新实现方式。它更灵活,不再受到永久代的限制,可以动态调整大小,避免了一些与永久代相关的问题。
总之,永久代和元空间都是Java虚拟机规范中对方法区的不同实现方式,就像汽油发动机和电动机都是动能提供装置的不同实现一样。选择使用哪种实现方式取决于Java虚拟机的版本和配置,以及应用程序的需求。这个比喻很好地概括了它们之间的关系。
JDK 1.8 元空间的变化
1. 对于 HotSpot 来说,JDK 8 元空间的内存属于本地内存,这样元空间的大小就不在受 JVM 最大内存的参数影响了,而是与本地内存的大小有关。
2. JDK 8 中将字符串常量池移动到了堆中。
运行时常量池
运行时常量池是方法区的一部分,存放字面量与符号引用。
字面量 : 字符串(JDK 8 移动到堆中) 、final常量、基本数据类型的值。
符号引用 : 类和结构的完全限定名、字段的名称和描述符、方法的名称和描述符。
内存布局中的异常问题
1. Java堆溢出
Java堆用于存储对象实例,只要不断的创建对象,并且保证GC Roots到对象之间有可达路径来避免来 GC清除这些对象,那么在对象数量达到最大堆容量后就会产生内存溢出异常。
我们可以设置JVM参数-Xms:设置堆的最小值、-Xmx:设置堆最大值。下面我们来看一个 Java堆OOM的测试,测试以下代码之前先设置 Idea 的启动参数,如下图所示:
JVM 参数为:-Xmx20m -Xms20m -XX:+HeapDumpOnOutOfMemoryError
示例:
public class Main {static class OOMObject {}public static void main(String[] args) {List<OOMObject> list =new ArrayList<>();while(true) {list.add(new OOMObject());}}
}
以上程序的执行结果如下:
Java堆内存的OOM异常是实际应用中最常见的内存溢出情况。当出现Java堆内存溢出时,异常堆栈信 息"java.lang.OutOfMemoryError"会进一步提示"Java heap space"。当出现"Java heap space"则很明 确的告知我们,OOM发生在堆上。
此时要对Dump出来的文件进行分析,以MAT为例。分析问题的产生到底是出现了内存泄漏(Memory Leak)还是内存溢出(Memory Overflow)
内存泄漏 : 内存泄漏是指在程序中分配了一些内存(通常是用于存储对象或数据),但在后续的程序执行中,无法释放或回收这些内存,导致内存消耗不断增加,最终可能导致程序性能下降或崩溃。
内存溢出 : 内存溢出(Memory Overflow)是指在程序运行时,尝试分配的内存超出了可用的物理内存或虚拟内存的范围,导致程序无法继续正常执行的情况。内存溢出通常是由于程序内存管理不当或程序本身存在缺陷引起的。内存溢出会导致程序崩溃或异常终止。
2. 虚拟机栈和本地方法栈溢出
由于我们HotSpot虚拟机将虚拟机栈与本地方法栈合二为一,因此对于HotSpot来说,栈容量只需要由-Xss参数来设置。
关于虚拟机栈会产生的两种异常:
1. 如果线程请求的栈深度大于虚拟机所允许的最大深度,会抛出StackOverFlow异常
2. 如果虚拟机在拓展栈时无法申请到足够的内存空间,则会抛出OOM异常
示例:
观察StackOverFlow异常(单线程环境下)
/**
* JVM参数为:-Xss128k
*/
public class Test {private int stackLength = 1;public void stackLeak() {stackLength++;stackLeak();}public static void main(String[] args) {Test test = new Test();try {test.stackLeak();} catch (Throwable e) {System.out.println("Stack Length: " + test.stackLength);throw e;}}
}
会出现如下运行结果:
出现StackOverflowError异常时有错误堆栈可以阅读,比较好找到问题所在。如果使用虚拟机默认参数,栈深度在多多数情况下达到1000-2000完全没问题,对于正常的方法调用(包括递归),完全够用。
如果是因为多线程导致的内存溢出问题,在不能减少线程数的情况下,只能减少最大堆和减少栈容量的 方式来换取更多线程。
/**
* JVM参数为:-Xss2M
*/
public class Test {private void dontStop() {while(true) {}}public void stackLeakByThread() {while(true) {Thread thread = new Thread(new Runnable() {@Overridepublic void run() {dontStop();}});thread.start();}}public static void main(String[] args) {Test test = new Test();test.stackLeakByThread();}
}
以上代码运行电脑可能会崩,记得保存所有工作~~🤗
JVM 类加载
1. 类加载过程
对于一个类来说,它的生命周期是这样的:
其中前 5 步是固定的顺序并且也是类加载的过程,其中中间的 3 步我们都属于连接,所以对于类加载来说总共分为以下几个步骤:
1. 加载
2. 连接
1. 验证
2. 准备
3. 解析
3. 初始化
下面我们来看看每个步骤的具体执行内容~~😁
加载
“加载”(Loading)阶段是整个“类加载”(Class Loading)过程中的一个阶段,它和类加载 Class Loading 是不同的,一个是加载 Loading 另一个是类加载 Class Loading,所以不要把二者搞混了。
在加载 Loading 阶段,Java虚拟机需要完成以下三件事情:
1)通过一个类的全限定名来获取定义此类的二进制字节流。
2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
3)在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
验证
验证是连接阶段的第一步,这一阶段的目的是确保Class文件的字节 流中包含的信息符合《Java虚拟机 规范》的全部约束要求,保证这些信息被当作代码运行后不会危害虚拟机自身的安全。
验证选项: 文件格式验证,字节码验证,符号引用验证...
准备
准备阶段是正式为类中定义的变量(即静态变量,被static修饰的变量)分配内存并设置类变量初始值的阶段。
比如此时有这样一行代码:
public static int value = 123;
它是初始化 value 的 int 值为 0,而非 123。
解析
解析阶段是 Java 虚拟机将常量池内的符号引用替换为直接引用的过程,也就是初始化常量的过程。
初始化
初始化阶段,Java 虚拟机真正开始执行类中编写的 Java 程序代码,将主导权移交给应用程序。初始化 阶段就是执行类构造器方法的过程。
双亲委派模型
Java虚拟机(JVM)的双亲委派模型是一种类加载机制,用于确保Java类的加载和安全性。这个模型基于"委派"原则,它指定了JVM在加载类时首先尝试委派给父类加载器,只有在父类加载器无法加载类时才会由子类加载器尝试加载。这个模型有助于确保类的一致性、避免重复加载、并提高安全性。
下面是双亲委派模型的关键概念:
-
父类加载器(Parent Class Loader):在JVM中,有一个层次结构的类加载器链,根类加载器(Bootstrap Class Loader)位于最顶层,然后是扩展类加载器(Extension Class Loader),最后是应用程序类加载器(Application Class Loader)。父类加载器负责加载核心Java类库(如java.lang包中的类),并向下委派加载请求。
-
子类加载器(Child Class Loader):自定义的类加载器通常是子类加载器,它们负责加载应用程序特定的类。当一个类加载器收到加载请求时,它首先将请求委派给其父类加载器,如果父类加载器无法找到类,子类加载器才会尝试加载。
-
双亲委派规则:当类加载器收到加载类的请求时,它首先检查是否已经加载过该类。如果没有,它将委派给其父类加载器进行加载。这一过程递归继续,直到达到根类加载器。如果根类加载器仍然找不到类,那么会由底层的类加载器尝试加载。这种层层委派的方式确保了类的唯一性和一致性。
双亲委派模型的优点包括:
-
避免了类的重复加载:如果一个类已经被一个类加载器加载,那么它不会被另一个类加载器重复加载,这有助于节省内存和资源。
-
安全性:通过限制用户自定义类加载器的能力,双亲委派模型可以确保核心Java类库的安全性,防止恶意代码替代标准类库。
-
保持类的一致性:双亲委派模型可以确保不同类加载器加载的类是相同的,避免了类的混乱和不一致性。
总之,双亲委派模型是Java类加载的关键机制之一,它有助于保持类加载的安全性和一致性,同时提高了性能和资源利用率。在自定义类加载器时,通常建议遵循双亲委派模型,以确保类加载行为的一致性~~😁
启动类加载器:加载 JDK 中 lib 目录中 Java 的核心类库,即$JAVA_HOME/lib目录。
扩展类加载 器。加载 lib/ext 目录下的类。
应用程序类加载器:加载我们写的应用程序。
自定义类加载器:根据自己的需求定制类加载器。
垃圾回收机制
垃圾回收机制(Garbage Collection,简称GC)是Java虚拟机(JVM)的一个关键特性,用于管理和回收不再被程序使用的内存,以确保内存的有效使用和防止内存泄漏。垃圾回收机制的主要目标是自动释放不再引用的对象,以便释放内存并减少内存泄漏的风险。
在 Java 中,所有的对象都是要存在内存中的(也可以说内存中存储的是一个个对象),因此我们将内存回收,也可以叫做死亡对象的回收。
死亡对象的判断算法
引用计数算法
每个对象都带有一个引用计数器,每当有一个引用指向对象时,计数器加1,每当引用不再指向对象时,计数器减1。当对象的引用计数器减到零时,表示对象不再被引用,即可认为该对象是垃圾,可以被回收。
引用计数法的一些关键概念和示例:
引用计数器:每个对象都包含一个整数计数器,用于记录对象被引用的次数。
引用操作:当新的引用指向对象时,引用计数器加1;当引用不再指向对象时,引用计数器减1。
垃圾对象:当一个对象的引用计数器降到零时,表示对象不再被引用,可以被认为是垃圾对象,即可被回收。
内存回收:引用计数法的内存回收是实时的,即当引用计数器降到零时,立即回收对象所占用的内存。
但是JVM中没有选用引用计数法来管理内存,最主要的原因就是引用计数法无法解决对象的循环引用问题,下面是一个循环引用示例
class Node {Node next;Node() {next = null;}void setNext(Node node) {next = node;}
}public class ReferenceCycleExample {public static void main(String[] args) {Node node1 = new Node();Node node2 = new Node();// 创建循环引用node1.setNext(node2);node2.setNext(node1);// 现在node1和node2互相引用,但引用计数法无法检测到这一点// 尝试释放引用node1 = null;node2 = null;// 即使没有引用指向这两个对象,它们仍然相互引用,无法被回收,造成内存泄漏}
}
在上述示例中,Node
类表示一个简单的链表节点,其中的next
字段指向下一个节点。在main
方法中,我们创建了两个节点node1
和node2
,并通过setNext
方法相互引用,形成了循环引用。尽管在后续代码中将node1
和node2
设置为null
,但它们仍然相互引用,因此无法被引用计数法正确回收,导致内存泄漏。
这个示例突出了引用计数法的一个主要问题:它无法处理循环引用的情况。因为引用计数法只关注引用计数,而不考虑对象之间的引用关系,因此循环引用可能导致对象的引用计数永远不会降为零,即使对象已经不再被程序使用,也无法被正确回收。这就是为什么现代垃圾回收器通常使用基于可达性分析的算法,如标记-清除,以解决这个问题。
可达性分析算法
可达性分析算法是JVM用于标记和回收不再被程序引用的对象的核心机制。它通过以下步骤来确定哪些对象仍然是可达的,哪些是不可达的:
根集合(Root Set):JVM从一组根对象开始,这些根对象包括程序的主方法中的局部变量、静态变量(类变量)中引用的对象以及已经在执行过程中被分配的对象。
标记阶段:在标记阶段,JVM从根集合出发,通过引用链遍历对象,将它们标记为活动对象(可达)。引用链包括字段、数组元素、方法参数等。如果对象可以通过引用链访问到,它被认为是可达的。
清除阶段:在标记阶段之后,未被标记为活动对象的对象被认为是不可达的垃圾,将被回收。这些对象占用的内存将被释放,以供将来分配新对象。
内存区域
对于可达性分析算法,主要关注堆内存的分配和回收。
堆内存(Heap):堆是用于存储对象的主要内存区域。在可达性分析算法中,堆被划分为不同的代,通常包括新生代(Young Generation)、幸存区(Survivor Space)、老年代(Old Generation)。
新生代:新分配的对象通常位于新生代。在新生代中,有三个区域:一个Eden区和两个幸存区(通常称为S0和S1)。对象首先分配到Eden区,然后在垃圾回收发生时,存活的对象会被移动到幸存区。幸存区之间也会发生对象的复制。
老年代:在对象经历多次幸存区的复制后,它们最终会被晋升到老年代。老年代主要用于存储生命周期较长的对象。
栈内存(Stack):栈内存用于存储方法调用的局部变量、操作数栈和方法调用的返回地址。它的生命周期与方法的调用和返回密切相关,不用于存储对象。
可达性分析与内存区域的关系
可达性分析算法主要影响堆内存的使用和回收。当可达性分析算法标记不再被引用的对象为垃圾时,这些对象所占用的堆内存将被释放。可达性分析还帮助JVM确定哪些对象需要在不同代之间进行移动,以优化内存使用。
新生代和老年代之间的对象移动与可达性分析密切相关,因为幸存区中的对象在不同代之间移动。幸存区的复制和清理操作是为了确保年轻代内存的高效利用。老年代的管理也依赖于可达性分析,以及在必要时执行Full GC(Full Garbage Collection)来回收老年代的垃圾。
垃圾回收算法
垃圾回收算法是Java虚拟机(JVM)用于管理内存中对象的方法,以便回收不再被引用的对象,从而释放内存资源。不同的垃圾回收算法适用于不同的情况和内存分配模式。以下是四种常见的垃圾回收算法的详细讲解:
标记-清除算法(Mark and Sweep):
-
标记阶段:在此阶段,垃圾回收器从一组根对象(通常包括程序的主方法中的局部变量、静态变量等)出发,通过引用链遍历对象图,并将可达对象标记为活动对象。这些对象不会被回收。
-
清除阶段:在标记阶段之后,所有未被标记为活动对象的对象被认为是垃圾,将被回收。此时,垃圾回收器会释放垃圾对象占用的内存。
-
优点:简单明了,适用于任何内存分配模式。
-
缺点:可能会产生内存碎片,回收效率较低,会引发应用程序停顿。
像这样子我们把上面的黑色垃圾都打上标记,垃圾回收器就会释放这些内存。
复制算法(Copying Garbage Collection):
-
新生代划分:堆内存被划分为两个相等的区域,通常称为Eden区和幸存区(S0和S1)。
-
标记和复制阶段:新对象首先分配到Eden区。当Eden区满时,执行标记和复制阶段。在这个阶段,垃圾回收器标记所有活动对象,然后将它们复制到另一个幸存区。幸存区之间也会进行对象的复制。
-
清理阶段:清理阶段将不再被引用的对象所占用的区域标记为空闲,并将Eden区和一个幸存区的对象互换。
-
优点:有效避免了内存碎片,回收效率高,适用于新生代的短生命周期对象。
-
缺点:需要额外的内存来进行复制操作,老年代对象无法直接受益。
如上示例,假如a和c对象被认为是垃圾,它会把剩下的对象(我们这个示例只剩下了b对象)先复制一份到s0上,然后把Eden一整个区域内存全部释放,这样有效避免了内存碎片,回收效率高,适用于新生代的短生命周期对象。
标记-整理算法(Mark and Compact):
-
标记阶段:与标记-清除算法相同,标记所有可达对象。
-
整理阶段:在清理阶段,不会简单地清除垃圾对象,而是将存活的对象向一端移动,然后清除掉未移动的垃圾对象。这样可以减少内存碎片。
-
优点:避免了内存碎片,回收效率高,适用于堆中包含较多长生命周期对象的情况。
-
缺点:需要对象移动操作,可能引发一定的停顿。
分代算法(Generational Garbage Collection):
-
堆分代:堆内存被分为不同的代,通常包括新生代和老年代。新分配的对象通常位于新生代。
-
新生代回收:在新生代中,通常采用复制算法,因为新生代的对象生命周期短。垃圾回收频繁发生,但效率高。
-
老年代回收:在老年代中,通常采用标记-清除或标记-整理算法,因为老年代的对象生命周期较长。垃圾回收不太频繁,但可能导致较长的停顿。
-
优点:根据对象的生命周期进行不同的回收,提高了效率和响应时间。
-
缺点:需要管理不同代的对象,引入了复杂性。
当前 JVM 垃圾收集都采用的是"分代收集(Generational Collection)"算法,这个算法并没有新思想,只是根据对象存活周期的不同将内存划分为几块。一般是把Java堆分为新生代和老年代。在新生代中,每次垃圾回收都有大批对象死去,只有少量存活,因此我们采用复制算法;而老年代中对象存活率高、没有额外空间对它进行分配担保,就必须采用"标记-清理"或者"标记-整理"算法。
新生代(Young Generation):
特点:新生代是堆内存的一部分,通常用于存储具有较短生命周期的对象。由于大多数对象在创建后很快就变得不可达,因此新生代采用了一种高效的垃圾回收策略。
回收策略:新生代通常采用复制算法(Copying Garbage Collection)作为主要的垃圾回收策略。这意味着新生代被划分为三个区域:Eden区和两个幸存区(S0和S1)。新对象首先分配到Eden区。当Eden区满时,发生垃圾回收,将存活的对象复制到其中一个幸存区,同时清理掉Eden区。随着垃圾回收的进行,对象会在幸存区之间进行复制,最终存活的对象会晋升到老年代。
优点:新生代的复制算法有效避免了内存碎片,回收效率高。
缺点:复制算法需要额外的内存来存储复制后的对象,老年代对象无法直接受益,可能导致内存使用效率不高。
老年代(Old Generation):
特点:老年代用于存储具有较长生命周期的对象,这些对象在多次垃圾回收后仍然存活。
回收策略:老年代通常采用标记-清除(Mark and Sweep)或标记-整理(Mark and Compact)算法作为主要的垃圾回收策略。这些算法涉及标记不再被引用的对象,然后清除或整理内存。
标记-清除算法(Mark and Sweep):标记不再被引用的对象,然后清除垃圾对象,但可能会导致内存碎片。
标记-整理算法(Mark and Compact):标记不再被引用的对象,然后将存活的对象向一端移动,清除未移动的垃圾对象,以减少内存碎片。
优点:老年代的标记-清除或标记-整理算法能够处理具有较长生命周期的对象,并在垃圾回收时避免内存碎片。
缺点:可能引发较长的垃圾回收停顿,影响应用程序的响应时间。
写在最后
通过本博客,我们深入探讨了Java虚拟机(JVM)的工作原理和内部机制。我们从JVM的基本概念开始,了解了其执行流程和运行时数据区域的重要性。我们探讨了JVM内存管理的关键方面,包括堆内存、栈内存、方法区、运行时常量池等,以及Java堆溢出和虚拟机栈溢出等内存问题的解决方法。
我们还深入研究了JVM的类加载过程,了解了加载、验证、准备、解析和初始化等步骤,以及双亲委派模型的重要性。此外,我们介绍了JDK 1.8中元空间的变化和垃圾回收机制的工作原理。
在垃圾回收方面,我们讨论了死亡对象的判断算法,引用计数算法和可达性分析算法等关键概念。我们还介绍了不同的垃圾回收算法,包括标记-清除、复制、标记-整理和分代算法,以及它们在不同内存区域的应用。
最后,我们希望这篇博客能够帮助您更深入地理解Java虚拟机的内部工作原理,从而更好地编写高性能和可靠的Java应用程序。JVM作为Java生态系统的核心组件,其深入理解对于Java开发人员来说是至关重要的。感谢您的阅读!😁
相关文章:

深入探讨Java虚拟机(JVM):执行流程、内存管理和垃圾回收机制
目录 什么是JVM? JVM 执行流程 JVM 运行时数据区 堆(线程共享) Java虚拟机栈(线程私有) 什么是线程私有? 程序计数器(线程私有) 方法区(线程共享) JDK 1.8 元空…...

3D 碰撞检测
推荐:使用 NSDT场景编辑器快速搭建3D应用场景 轴对齐边界框 与 2D 碰撞检测一样,轴对齐边界框 (AABB) 是确定两个游戏实体是否重叠的最快算法。这包括将游戏实体包装在一个非旋转(因此轴对齐)的框中&#…...

Unity Canvas动画不显示的问题
问题描述: 我通过角色创建了一个walk的动画,当我把这个动画给到Canvas里面的一个image上,这个动画就不能正常播放了,经过一系列的查看我才发现,canvas里面动画播放和非canvas得动画播放,他们的动画参数是不一样的。一个…...

NSSCTF2nd与羊城杯部分记录
文章目录 前言[NSSCTF 2nd]php签到[NSSCTF 2nd]MyBox[NSSCTF 2nd]MyHurricane[NSSCTF 2nd]MyJs[NSSCTF 2nd]MyAPK羊城杯[2023] D0nt pl4y g4m3!!!羊城杯[2023]ezyaml羊城杯[2023]Serpent羊城杯[2023]EZ_web羊城杯[2023]Ez_misc总结 前言 今天周日,有点无聊没事干&a…...

数据库(一) 基础知识
概述 数据库是按照数据结构来组织,存储和管理数据的仓库 数据模型 数据库系统的核心和基础是数据模型,数据模型是严格定义的一组概念的集合。因此数据模型一般由数据结构、数据操作和完整性约束三部分组成。数据模型主要分为三种:层次模型,网状模型和关…...

vue从零开始学习
npm install慢解决方法:删掉nodel_modules。 5.0.3:表示安装指定的5.0.3版本 ~5.0.3:表示安装5.0X中最新的版本 ^5.0.3: 表示安装5.x.x中最新的版本。 yarn的优点: 1.速度快,可以并行安装 2.安装版本统一 项目搭建: 安装nodejs查看node版本:node -v安装vue clie : np…...

dji uav建图导航系列(三)模拟建图、导航
前面博文【dji uav建图导航系列()建图】、【dji uav建图导航系列()导航】 使用真实无人机和挂载的激光雷达完成建图、导航的任务。 当需要验证某一个slam算法时,我们通常使用模拟环境进行测试,这里使用stageros进行模拟测试,实际就是通过模拟器,虚拟一个带有传感器(如…...

PixelSNAIL论文代码学习(1)——总体框架和平移实现因果卷积
文章目录 引言正文目录解析README.md阅读Setup配置Training the model训练模型Pretrained Model Check Point预训练的模型训练方法 train.py文件的阅读model.py文件阅读h12_noup_smallkey_spec模型定义_base_noup_smallkey_spec模型实现一、定义因果卷积过程通过平移实现因果卷…...

Python大数据处理利器之Pyspark详解
摘要: 在现代信息时代,数据是最宝贵的财富之一,如何处理和分析这些数据成为了关键。Python在数据处理方面表现得尤为突出。而pyspark作为一个强大的分布式计算框架,为大数据处理提供了一种高效的解决方案。本文将详细介绍pyspark…...

S905L3A(M401A)拆解, 运行EmuELEC和Armbian
关于S905L3A / S905L3AB S905Lx系列没有公开资料, 猜测是Amlogic用于2B的芯片型号, 最早的 S905LB 是 S905X 的马甲, 而这个 S905L3A/S905L3AB 则是 S905X2 的马甲, 因为在性能评测里这两个U的得分几乎一样. S905L3A/S905L3AB 和 S905X2, S905X3 一样 GPU 是 G31, 相比前一代的…...

stack和queue容器
1 stack 基本概念 概念:stack是一种先进后出(First In Last Out,FILO)的数据结构,它只有一个出口 栈中只有顶端的元素才可以被外界使用,因此栈不允许有遍历行为 栈中进入数据称为 — 入栈 push 栈中弹出数据称为 — 出栈 pop 2 stack 常用…...

面向对象基础
文章目录 面向对象基础一.面向对象介绍二.设计对象并使用三.封装四.This关键字五.构造方法六.标准的Javabean类七.对象内存图八.基本数据类型和引用数据类型九.成员和局部 面向对象基础 一.面向对象介绍 面向:拿,找 对象:能干活的东西 面向对象编程:找东西来做对应的事情 …...

spring集成mybatis
1、新建一个javaEE web项目 2、加入相关依赖的坐标 <dependencies><!--数据系列:mybatis,mysgl,druid数据源,junit--><!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --><dependency><groupId>mysql</grou…...

抽象轻松c语言
目 c语言 c程序 c语言的核心在于语言,语言的作用是进行沟通,人与人之间的信息交换 人与人之间的信息交换是会有信息空白(A表达信息,B接受信息,B对信息的处理会与A所以表达的信息具有差距,这段差距称为信…...

Redis布隆过滤器原理
其实布隆过滤器本质上要解决的问题,就是防止很多没有意义的、恶意的请求穿透Redis(因为Redis中没有数据)直接打入到DB。它是Redis中的一个modules,其实可以理解为一个插件,用来拓展实现额外的功能。 可以简单理解布隆…...

写代码时候的命名规则、命名规范、命名常用词汇
版权声明 这个大部分笔记是观看up主红桃A士的视频记录下来的,因为本人在学习的过程中也经常出现类似的问题,并且觉得Up主的视频讲解很好,做此笔记反复学习,若有侵权请联系删除,此推荐视频地址:【改善丑陋的…...

Linux之iptables防火墙
一.网络安全技术 ①入侵检测系统(Intrusion Detection Systems):特点是不阻断任何网络访问,量化、定位来自内外网络的威胁情况,主要以提供报警和事后监督为主,提供有针对性的指导措施和安全决策依据,类 似于…...

启动服务报错:Command line is too long Shorten command line for xxx or also for Spri
ommand line is too long. Shorten command line for ProjectApprovalApplication or also for Spring Boot default configuration. 启动springboot 项目的时候报错 解决方案: 点击提示中的:default:然后在弹出窗口中选择:JAR xx…...

docker安装elasticsearch、kibana
安装过程中,遇到最大的问题就是在安装kibana的时候发现 一直连接不上 elasticsearch。最后解决的问题就是 我通过 ifconfig en0 | grep inet| awk {print $2} 在mac中找到本机的ip,然后去到kibana容器中 修改 vi config/kibana.yml中的elasticsearch.hos…...

前端 CSS - 如何隐藏右侧的滚动条 -关于出现过多的滚动条导致界面不美观
1、配置 HTML 标签,隐藏右侧的滚动条 CSS 配置:下面两个一起写进进去,适配 IE、火狐、谷歌浏览器 html {/*隐藏滚动条,当IE下溢出,仍然可以滚动*/-ms-overflow-style:none;/*火狐下隐藏滚动条*/overflow:-moz-scroll…...

2.神经网络的实现
创建神经网络类 import numpy # scipy.special包含S函数expit(x) import scipy.special # 打包模块 import pickle# 激活函数 def activation_func(x):return scipy.special.expit(x)# 用于创建、 训练和查询3层神经网络 class neuralNetwork:# 初始化神经网络def __init__(se…...

合宙Air724UG LuatOS-Air LVGL API控件-键盘 (Keyboard)
键盘 (Keyboard) LVGL 可以添加触摸键盘,但是很明显,使用触摸键盘的话必须要使用触摸的输入方式,否则无法驱动键盘。 示例代码 function keyCb(obj, e)-- 默认处理事件lvgl.keyboard_def_event_cb(keyBoard, e)if(e lvgl.EVENT_CANCEL)the…...

pytorch深度学习实践
B站-刘二大人 参考-PyTorch 深度学习实践_错错莫的博客-CSDN博客 线性模型 import numpy as np import matplotlib.pyplot as pltx_data [1.0, 2.0, 3.0] y_data [2.0, 4.0, 6.0]def forward(x):return x * wdef loss(x, y):y_pred forward(x)return (y_pred - y) ** 2# …...

直方图反向投影(Histogram Backprojection)
直方图反向投影(Histogram Backprojection)是一种在计算机视觉中用于对象检测和图像分割的技术。它的原理基于图像的颜色分布,允许我们在一幅图像中找到与给定对象颜色分布相匹配的区域。这个技术常常用于图像中的目标跟踪、物体识别和图像分…...

day32 泛型 数据结构 List
一、泛型 概述 JDK1.5同时推出了两个和集合相关的特性:增强for循环,泛型 泛型可以修饰泛型类中的属性,方法返回值,方法参数, 构造函数的参数 Java提供的泛型类/接口 Collection, List, Set,Iterator 等 …...

DW-AHB Central DMAC
文章目录 AHB Central DMAC —— Design Ware AHB Central DMAC —— Design Ware AHB(Adavenced High-performace BUS) Central DMAC(Direct Memory Access Controller) : 一个高性能总线系统。 作用:在嵌入式系统种连接高速设备,如处理器内存&#x…...

JavaScript设计模式(四)——策略模式、代理模式、观察者模式
个人简介 👀个人主页: 前端杂货铺 🙋♂️学习方向: 主攻前端方向,正逐渐往全干发展 📃个人状态: 研发工程师,现效力于中国工业软件事业 🚀人生格言: 积跬步…...

JS画布的基本使用
直线 <!DOCTYPE html> <html> <head> <meta charset"utf-8"> <title></title> <style> #myname{ border: 1px solid red; /* background: linear-gradient(to righ…...

c++ set/multiset
set/multiset 集合,一个单个,一个多个(multi)。两个库都是"set"。 https://blog.csdn.net/fckbb/article/details/130917681 对象创建 set(const Pred& compPred(),const A& alA()):创建空集合。set(const set& x):…...

多线程与高并发——并发编程(4)
文章目录 四、阻塞队列1 基础概念1.1 生产者消费者概念1.2 JUC阻塞队列的存取方法2 ArrayBlockingQueue2.1 ArrayBlockingQueue的基本使用2.2 生产者方法实现原理2.2.1 ArrayBlockingQueue的常见属性2.2.2 add方法2.2.3 offer方法2.2.4 offer(time,unit)方法2.2.5 put方法2.3 消…...