JVM知识

发布时间:2024年01月14日

JVM区域

程序计数器:定位当前线程执行代码在内存中的位置

栈内存(虚拟机栈):生命周期与线程相同,包括局部变量表,动态链接(被调用方法的实际地址),方法出口(方法返回值要返回到的地址)

本地方法栈:为虚拟机使用到的Native方法服务

Java堆:存放对象实例,物理上可以不连续,逻辑上连续即可,包括对象,数组,非静态变量

方法区:包括静态变量,常量,类的元数据等

垃圾收集器

Java垃圾收集器是Java虚拟机(JVM)的一部分,用于管理内存中不再被程序使用的对象,以释放内存资源。垃圾收集器主要负责以下任务:

  1. 对象的标记和回收:标记那些不再被引用的对象,并将它们回收,以释放内存空间。
  2. 内存碎片整理:将内存中的碎片化空间整理成连续的内存块,以减少垃圾回收的成本。
  3. 性能优化:不同的垃圾收集器采用不同的算法和策略,以满足不同应用场景的性能需求。

Java垃圾收集器的作用范围可以分为两个主要区域:

  1. 新生代(Young Generation):新生代主要用于存放新创建的对象。在新生代中,通常使用高效的垃圾收集算法,例如复制算法,以尽量减少对象的生命周期短,从而提高垃圾收集的效率。主要的新生代垃圾收集器有Serial收集器、ParNew收集器、Parallel Scavenge收集器等。
  2. 老年代(Tenured/Old Generation):老年代主要用于存放生命周期较长的对象。老年代中的垃圾收集器通常采用更复杂的算法,以处理老年代中的长寿命对象,以及进行内存碎片整理。主要的老年代垃圾收集器有Serial Old收集器、Parallel Old收集器、CMS(Concurrent Mark-Sweep)收集器、G1(Garbage-First)收集器等。

不同的Java应用可能需要选择不同的垃圾收集器来满足其性能和内存管理的需求。一些应用可能更关注低延迟,而另一些应用可能更关注吞吐量。因此,Java提供了不同的垃圾收集器,以便开发人员可以根据应用的特点进行选择和配置。

需要注意的是,Java的垃圾收集器是一个庞大的主题,不同版本的JVM可能会有不同的垃圾收集器,并且不同的垃圾收集器在性能和行为方面也有差异。因此,在选择和配置垃圾收集器时,需要考虑应用的具体需求和JVM版本的特点。

新生代和老年代

在Java的垃圾回收中,内存区域通常被划分为多个不同的区域,其中最常见的是新生代(Young Generation)和老年代(Tenured/Old Generation)。它们之间有以下主要区别:

  1. 存储对象的生命周期
    • 新生代:新生代主要用于存放新创建的对象,这些对象的生命周期通常较短。绝大多数对象在新生代中创建并在很短的时间内被回收。
    • 老年代:老年代用于存放生命周期较长的对象,这些对象可能已经在新生代中经历了一次或多次垃圾回收,但仍然存活。老年代中的对象生命周期较长,可能存活较长时间,直到程序结束或被明确回收。
  2. 垃圾回收算法
    • 新生代:为了提高垃圾回收的效率,新生代通常采用高效的垃圾回收算法,如复制算法或标记-整理算法。这些算法适用于对象生命周期短暂的场景。
    • 老年代:老年代中的对象通常比较稳定,因此垃圾回收算法通常更复杂,例如标记-清除算法和标记-整理算法。CMS(Concurrent Mark-Sweep)和G1(Garbage-First)是用于老年代的一些垃圾回收算法,它们旨在降低垃圾回收的停顿时间。
  3. 对象晋升
    • 新生代:如果一个对象在经历了几轮新生代垃圾回收后仍然存活,它可能会被晋升到老年代。
    • 老年代:老年代中的对象通常经历了多次垃圾回收,只有在经过多轮垃圾回收后,对象才会被真正清除。这也使得老年代中的对象生命周期相对较长。
  4. 内存空间大小
    • 新生代:新生代通常占据总堆内存的一小部分,因为新生代中的对象生命周期较短,不需要过多的内存。
    • 老年代:老年代通常占据总堆内存的大部分,因为它需要存储较长寿命的对象。
  5. 垃圾回收频率
    • 新生代:由于新生代中的对象生命周期短暂,垃圾回收频率通常较高。
    • 老年代:老年代中的垃圾回收频率较低,因为其中的对象生命周期较长。

总之,新生代和老年代在Java的内存管理中具有不同的角色和特点。新生代用于存放短生命周期的对象,采用高效的垃圾回收算法,而老年代用于存放长生命周期的对象,通常需要更复杂的垃圾回收策略。这种内存划分有助于提高垃圾回收的效率和降低停顿时间,同时有效地管理内存中的对象。

垃圾回收策略

垃圾回收策略是Java虚拟机(JVM)用来管理内存和自动回收不再被程序引用的对象的一种重要机制。不同的垃圾回收策略适用于不同的应用场景和需求,以下是一些常见的垃圾回收策略:

  1. 标记-清除算法(Mark and Sweep)
    • 该算法分为两个阶段:标记阶段和清除阶段。
    • 标记阶段:从根对象(如堆栈、静态变量)出发,标记出所有可达的对象。可达性分析通常采用深度优先搜索或广度优先搜索。
    • 清除阶段:遍历整个堆,清除未标记的对象,即不可达对象。
    • 优点:简单,适用于多种场景。
    • 缺点:容易产生内存碎片,垃圾回收时需要停止应用程序。
  2. 复制算法(Copying Algorithm):(新生代使用)
    • 通常用于新生代的垃圾回收。
    • 将堆分为两个区域,一半是存活对象,一半是空闲的。回收时将存活对象复制到空闲区域,然后清除原区域。
    • 优点:高效,减少内存碎片。
    • 缺点:浪费一半的内存,不适用于老年代。
  3. 标记-整理算法(Mark and Compact):(老年代使用)
    • 通常用于老年代的垃圾回收。
    • 标记阶段与标记-清除相同,但清除阶段不会简单地清除不可达对象,而是将存活对象移动到一端,然后清除掉未移动的部分。
    • 优点:减少内存碎片,适用于老年代。
    • 缺点:需要移动对象,回收时需要停止应用程序。
  4. 分代垃圾回收
    • 将堆分为新生代和老年代,采用不同的垃圾回收策略。
    • 新生代使用复制算法,因为对象生命周期短,容易产生垃圾。
    • 老年代使用标记-整理或标记-清除算法,因为对象生命周期较长,较不容易产生垃圾。
    • 优点:根据对象的生命周期采用不同的策略,提高效率。

不同的应用场景和需求会选择不同的垃圾回收策略。在实际应用中,可以根据应用程序的内存特点和性能需求来选择合适的垃圾回收器和策略。

新生代的垃圾回收算法–复刻算法

新生代(Young Generation)的垃圾回收通常使用复制(Copying)算法来进行垃圾回收。新生代是堆内存的一部分,主要用于存放新创建的对象。由于新生代的对象生命周期相对较短,因此适合使用复制算法进行垃圾回收。

复制算法的基本思想是将新生代划分为两个相等大小的区域,通常称为Eden区和两个Survivor区(通常称为S0和S1)。当对象被创建时,它们会被分配到Eden区。然后,垃圾回收器会定期检查Eden区中的存活对象,并将它们复制到其中一个Survivor区,同时清空Eden区。垃圾回收器会轮流使用两个Survivor区,每次将存活对象从Eden区复制到一个Survivor区,然后清空Eden区和之前使用的Survivor区。这个过程称为Minor GC(年轻代垃圾回收)。

Major GC | Full GC

老年代(Old Generation)垃圾回收的过程通常被称为 “Full GC”(Full Garbage Collection),也称为 “Major GC”。Full GC 是指对整个堆内存进行垃圾回收,包括新生代和老年代。它的主要目的是回收老年代中的存活对象,并进行内存整理,以减少内存碎片。

Full GC 的发生通常是由于以下几种情况之一:

  1. 老年代空间不足:当老年代中的存活对象无法找到足够的连续内存空间来分配新对象时,触发 Full GC。
  2. 永久代(在旧版本的Java中,现在已被元数据区域(Metaspace)替代)空间不足:如果永久代或元数据区域中的空间不足,也可能触发 Full GC。
  3. 显式调用:应用程序代码中可以显式调用 System.gc() 方法来请求进行 Full GC,但这不是推荐的做法。

Full GC 通常会导致较长的停顿时间,因为它需要对整个堆内存进行扫描、标记、清除和整理操作。在某些情况下,停顿时间可能会对应用程序的性能和响应时间产生不利影响,因此需要谨慎考虑堆大小和垃圾回收策略,以减小 Full GC 的频率和影响。

为了减少 Full GC 的影响,可以采用以下措施:

  • 增加堆内存的大小,以减少 Full GC 的频率。
  • 使用合适的垃圾回收器和垃圾回收策略,如使用 G1 垃圾回收器可以降低停顿时间。
  • 优化应用程序代码,减少对象的创建和临时对象的使用,以降低内存压力。

总之,Full GC 是老年代垃圾回收的一种重要过程,它会对应用程序的性能产生影响(通常会导致较长的停顿时间,因为它需要对整个堆内存进行操作),因此需要合理配置和管理堆内存以减少 Full GC 的发生。

判断对象是否有效

  • 引用计数算法

    给对象添加一个引用计数器,每当一个地方用它时,计数器加1;当引用失效时,计数器减1,当计数器为0时回收。但是无法回收相互循环引用,所以Java没有使用这个方式

  • 根搜索算法(可达性算法)

    Java和C#都是采用这种算法。通过一系列的GC Root的对象(有一个GC Root Set)作为起始点,从这些节点开始向下搜索,搜索过的路径称为引用链,当一个对象到GC Root没有任何引用链时,证明可以被回收。

JVM参数

JVM参数有很多种,用于控制Java虚拟机的行为。以下是一些常见的JVM参数,它们用于不同的用途:

  1. 堆内存参数:
    • -Xmx<size>:设置Java堆的最大内存使用量,例如 -Xmx512m 表示最大堆内存为512MB。
    • -Xms<size>:设置Java堆的初始内存使用量,例如 -Xms256m 表示初始堆内存为256MB。
  2. 垃圾回收参数:
    • -XX:+UseSerialGC:使用串行垃圾回收器。
    • -XX:+UseParallelGC:使用并行垃圾回收器。
    • -XX:+UseConcMarkSweepGC:使用CMS垃圾回收器。
    • -XX:+UseG1GC:使用G1垃圾回收器。
  3. 性能调优参数:
    • -XX:MaxPermSize=<size>:设置永久代的最大内存使用量。
    • -XX:MaxMetaspaceSize=<size>:设置元空间的最大内存使用量。
    • -XX:MaxDirectMemorySize=<size>:设置直接内存的最大使用量。
  4. 运行模式参数:
    • -server:启用服务器模式,优化性能。
    • -client:启用客户端模式,用于开发和调试。
  5. 调试参数:
    • -Xdebug:启用远程调试。
    • -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=<address>:配置远程调试参数。
  6. 其他参数:
    • -D<property>=<value>:设置系统属性,例如 -Dfile.encoding=UTF-8
    • -verbose:gc:打印垃圾回收日志。
    • -XX:HeapDumpOnOutOfMemoryError:在内存溢出时生成堆转储文件。

这些只是一些常见的JVM参数示例,实际上有许多其他参数可用于微调Java应用程序的性能和行为。你可以根据你的需求和应用程序的性能要求来选择和配置适当的参数。

Java程序初始化策略

  • 父类静态代码块 -> 子类静态代码块 ->父类普通代码块 -> 父类构造方法 -> 子类普通代码块 -> 子类构造方法

  • 静态代码块和普通代码块都是类的一部分(类比成员变量),静态代码块是类属性,只会执行一次,普通代码块是对象属性,每构造一个对象,就会被隐式调用一次!

finally语句块在两种情况下不会被执行:

  • try语句块有return语句
  • try或catch语句块有System.exit(0)执行,0表示JVM正常退出,非0值表示异常退出
文章来源:https://blog.csdn.net/weixin_51164693/article/details/135574233
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。