java JVM

news2024/9/20 6:14:37

JVM的组成

Java虚拟机(JVM)是执行Java字节码的运行时环境。它由以下几个主要部分组成:

1. **类加载器(ClassLoader)**:
   - 负责加载Java类的字节码到JVM中,并进行链接和初始化。

关于Java的类加载器,以下是一些关键点,它们对于理解Java类如何被加载、链接和初始化非常重要:

1. **类加载过程**:
   - Java的类加载过程包括三个主要步骤:加载(Loading)、链接(Linking)、初始化(Initialization)。

2. **加载**:
   - 加载是类加载器读取类文件的二进制数据,并将其转换为方法区中的运行时数据结构的过程。

3. **链接**:
   - 链接包括验证(Verification)、准备(Preparation)和解析(Resolution)三个子阶段。验证确保加载的类信息符合JVM规范;准备负责为静态变量分配内存并设置默认初始值;解析将符号引用转换为直接引用。

4. **初始化**:
   - 初始化是为静态变量赋予正确的初始值,并执行静态代码块的过程。

5. **类加载器类型**:
   - Java提供了多种类型的类加载器:
     - **启动类加载器(Bootstrap ClassLoader)**:负责加载Java核心库,如`rt.jar`。
        加载lib目录下的类库
     - **扩展类加载器(Extension ClassLoader)**:负责加载扩展目录中的类库。
        加载ext目录下的类库
     - **系统类加载器(System ClassLoader)**:负责加载应用程序类路径(`-classpath`参数)上的类。
        也称为应用类加载器,负责加载用户类路径(-classpath参数或系统属性java.class.path)上指定的类库。
     - **自定义类加载器(User-Defined ClassLoader)**:应用程序可以自定义类加载器来控制类的加载过程。

6. **双亲委派模型(Parent Delegation Model)**:
   - 类加载器使用双亲委派模型来查找类。当一个类请求被加载时,它会首先委托给它的父类加载器去尝试加载这个类,只有当父类加载器无法完成这个请求时,子类加载器才会尝试自己去加载。

7. **类的唯一性**:
   - 在同一个Java虚拟机中,任何一个类只有一个实例,即使由不同的类加载器加载也是如此。

8. **类加载器的层次结构**:
   - 类加载器形成了一个层次结构,通常从启动类加载器到系统类加载器,再到自定义类加载器。

9. **安全性**:
   - 类加载器确保来自不同源的类是隔离的,例如,从网络上加载的类不会影响系统类加载器加载的类。

10. **资源优化**:
    - 类加载器允许应用程序在运行时动态地加载和卸载类,这有助于资源管理和优化。

11. **类卸载**:
    - 在某些情况下,如类不再被使用,JVM可以卸载这些类以释放内存。

12. **类加载器的实现**:
    - Java允许开发者通过继承`java.lang.ClassLoader`类来实现自定义的类加载器。

了解类加载器的工作原理对于Java开发者来说非常重要,特别是在需要动态加载类、隔离类版本、扩展应用程序功能或处理不同类路径场景时。

2. **运行时数据区(Runtime Data Areas)**:
   - 包括以下几个部分:
     - **堆(Heap)**:存储所有Java对象实例和数组。
     - **方法区(Method Area)**:存储已被虚拟机加载的类信息、常量、静态变量等。在Java 8之前,这部分被称为永久代(Permanent Generation,PermGen)。
     - **栈(Stack)**:每个线程都有自己的栈,用于存储局部变量和方法调用信息。
     - **程序计数器(Program Counter)**:每个线程都有一个独立的程序计数器,用于跟踪当前执行的字节码指令。
     - **本地方法栈(Native Method Stack)**:用于存储本地方法(如JNI调用)的调用信息。

3. **执行引擎(Execution Engine)**:
   - 负责解释执行字节码或通过即时编译器(JIT)将字节码编译为本地机器代码后执行。

4. **垃圾回收器(Garbage Collector, GC)**:
   - 负责自动回收不再使用的对象,以管理堆内存。

5. **本地接口(Native Interface)**:
   - 允许Java代码调用和使用本地应用程序编程接口(API),例如JNI(Java Native Interface)。

6. **即时编译器(Just-In-Time Compiler, JIT)**:
   - 将热点代码(经常执行的代码)编译为机器代码,以提高执行效率。

7. **内存管理器(Memory Manager)**:
   - 负责管理JVM的内存分配和回收。

8. **安全管理系统(Security Manager)**:
   - 控制对系统资源的访问,确保代码的安全性。

9. **线程调度器(Thread Scheduler)**:
   - 管理线程的创建、调度和执行。

10. **编译系统和解释器(Compiler and Interpreter)**:
    - 解释器负责直接执行字节码,而编译系统负责将字节码编译为本地代码。

11. **性能分析器(Profiler)**:
    - 用于监控和分析JVM的性能。

这些组件共同工作,为Java应用程序提供了一个稳定、安全且高效的运行环境。JVM的架构设计允许它在不同的操作系统和硬件平台上运行,确保了Java语言的跨平台特性。

JDK8 JVM内存结构

在 JDK 8 中,JVM 的内存结构主要包括以下几个运行时数据区域:

1. **堆(Heap)**:
   - 堆是JVM中最大的一块内存区域,主要用于存储对象实例和数组。
   - 堆是垃圾回收器管理的主要区域,经常发生垃圾回收操作。

JVM堆是Java虚拟机中用于存储对象实例和数组的主要内存区域。以下是JVM堆中存储的一些主要内容:

1. **对象实例**:
   - 所有通过Java关键字`new`创建的对象实例都存储在堆中。

2. **数组**:
   - 所有的数组,无论其元素类型如何(原始类型或引用类型),都存储在堆中。

3. **实例变量**:
   - 对象的非静态成员变量也存储在堆中,与对象实例一起。

4. **匿名内部类**:
   - 匿名内部类(即使没有具体名称的内部类)的实例同样存储在堆中。

5. **反射对象**:
   - 通过Java反射API创建的类对象也存储在堆中。

6. **字符串常量**:
   - 尽管字符串常量可能会存储在字符串常量池中,但通过`new`操作符创建的字符串对象实例仍然存储在堆中。

7. **封装类对象**:
   - 封装类(如`Integer`、`Double`等)的实例存储在堆中。

8. **枚举实例**:
   - 枚举类型(`enum`)的实例也存储在堆中。

9. **异常对象**:
   - 当抛出异常时,异常对象的实例会存储在堆中,直到异常被处理。

10. **软引用、弱引用、虚引用**:
    - 这些引用类型指向的对象也存储在堆中,尽管它们可能在垃圾回收时被回收。

11. **动态代理对象**:
    - 使用Java动态代理API创建的代理对象实例存储在堆中。

12. **Java本地方法接口(JNI)对象**:
    - 通过JNI创建的对象实例同样存储在堆中。

JVM堆是垃圾回收器管理的主要区域,因为大多数对象都是短暂的,并在此处进行分配和回收。堆内存的大小可以通过JVM启动参数(如`-Xms`和`-Xmx`)进行配置。了解堆中存储的内容有助于开发者进行内存管理和性能优化。

JVM堆中的内存被划分为不同的代,主要是为了更有效地进行垃圾回收。主要分为以下两个部分:

1. **新生代(Young Generation)**:
   - 新生代是新创建的对象存储的地方。
   - 它通常占据堆内存的较小部分,并且被进一步划分为三个区域:
     - **Eden区**:大多数新对象首先被分配到Eden区。
     - **Survivor区**:为了能够回收内存,经过一次垃圾回收后仍然存活的对象会被复制到Survivor区。Survivor区有两个,通常被称为S0和S1,它们交替使用。
   - 新生代使用复制算法进行垃圾回收,称为Minor GC。这个过程涉及将存活的对象从Eden区和Survivor区复制到另一个Survivor区,然后清理Eden区和当前使用的Survivor区。
    Eden区和S0和S1内存划分比例值是8:1:1
    Eden区内存满了就会触发Minor GC
2. **老年代(Old Generation或Tenured Generation)**:
   - 老年代用于存储在新生代中经过多次垃圾回收后仍然存活的对象。
   - 这些对象通常已经存在较长时间,并且被认为不太可能在近期内变得垃圾。
   - 老年代占据堆内存的较大部分,并使用不同的垃圾回收算法,如标记-清除或标记-清除-整理算法。
   - 老年代的垃圾回收,称为Major GC或Full GC(如果涉及到整个堆的回收),通常比新生代的回收要慢,并且可能造成应用程序的停顿。
    线上项目中一定要尽量避免老年代内存占满,老年代内存满了后就会触发Full GC 会产生Stop the world,这个时候除了Full GC线程会继续执行,其他线程用户线程都会暂停,等待Full GC线程执行完,其他用户线程才会继续执行。

**晋升(Promotion)**:
GC扫一次没有清理,那么年龄就会+1,达到一定年龄(默认是15【CMS GC 默认是6岁】)了就会晋升到老年代。
对象在新生代中经过多次垃圾回收后,会根据其年龄(由垃圾回收次数决定)被晋升到老年代。这个过程有助于减少老年代中的对象数量,因为只有那些长期存活的对象才会被移动到那里。

**分代收集策略**:
JVM的分代垃圾回收策略基于这样一个观察:大多数对象都是短暂存在的,而只有少数对象会长期存活。通过在新生代和老年代使用不同的垃圾回收算法,JVM可以优化内存回收的效率和速度。

了解新生代和老年代的概念对于分析和优化Java应用程序的内存使用和垃圾回收性能至关重要。

2. **方法区(Method Area)**:
   - 也称为永久代(PermGen),但在JDK 8中已经被元空间(Metaspace)所取代。
   - 方法区用于存储类信息、常量、静态变量、方法字节码等数据。

3. **栈(Stack)**:
   - 每个线程都有自己的虚拟机栈,用于存储局部变量、方法参数、方法调用和返回值。方法开始的时候会进栈,方法执行完成会出栈,相当于清空了数据,所以不用进行GC操作。
   - 栈帧(Stack Frame)是栈的基本单位,每个方法调用都会创建一个新的栈帧。

4. **程序计数器(Program Counter)**:
   - 每个线程都有一个独立的程序计数器,用于记录当前线程执行的字节码指令地址。

5. **本地方法栈(Native Method Stack)**:
   - 用于存储本地方法(如JNI调用)的调用信息。

6. **元空间(Metaspace)**:
   - JDK 8中引入,用于替代JDK 7及以前版本的永久代。
   - 元空间不位于虚拟机内存中,而是使用本地内存,用于存储类的元数据信息。

7. **代码缓存(Code Cache)**:
   - 用于存储JIT编译器编译后的本地机器代码,以提高性能。

8. **运行时常量池(Runtime Constant Pool)**:
   - 属于方法区的一部分,用于存储类中的常量,如字符串字面量、数字常量等。

9. **直接内存(Direct Memory)**:
   - 不是JVM运行时数据区的一部分,但Java程序可以通过NIO操作直接内存。

在 JDK 8 中,除了上述内存区域,还引入了一些新的垃圾回收特性,如G1垃圾回收器的进一步优化,以及用于提高性能的JVM参数调整等。了解这些内存结构对于进行JVM调优和性能分析非常重要。


JVM调优参数

JVM 提供了一系列参数,可以通过命令行启动Java应用程序时进行调整。这些参数分为不同的类别,包括内存设置、垃圾收集器配置、性能监控和日志记录等。以下是一些常见的JVM参数:

### 1. 内存管理参数:
- `-Xms<size>`:设置JVM启动时的初始堆内存大小。
- `-Xmx<size>`:设置JVM可以使用的最大堆内存大小。
- `-Xss<size>`:设置每个线程的栈大小。
- `-XX:NewSize=<size>`:设置新生代的初始大小。设置新生代(Young Generation)的初始大小,但请注意,设置新生代大小可能会间接影响到老年代的大小,因为整个堆的大小(由 -Xmx 参数设置)是固定的。
- `-XX:MaxNewSize=<size>`:设置新生代的最大大小。同样,这会影响老年代可用的内存。

-XX:OldSize=<size>:直接设置老年代的初始内存大小。

java -XX:MaxOldSize=4g -jar YourApplication.jar

-XX:MaxOldSize=<size>:设置老年代的最大内存大小。

-XX:NewRatio=<value>:设置新生代与老年代的比率。例如,如果设置为3,意味着新生代占总堆大小的1/4,老年代占3/4。

-XX:SurvivorRatio=<value>:设置新生代中Eden区与Survivor区的比例。这个比例会影响到每次Minor GC后存活对象晋升到老年代的速率。

-XX:InitiatingHeapOccupancyPercent=<value>:设置触发老年代垃圾回收的堆占用阈值。当老年代的内存占用达到这个百分比时,会触发Full GC。

- `-XX:PermSize=<size>`(Java 8之前):设置永久代的初始大小。
- `-XX:MaxPermSize=<size>`(Java 8之前):设置永久代的最大大小。
- `-XX:MetaspaceSize=<size>`(Java 8及之后):设置元空间的初始大小。
- `-XX:MaxMetaspaceSize=<size>`(Java 8及之后):设置元空间的最大大小。

### 2. 垃圾收集器参数:
- `-XX:+UseSerialGC`:使用串行垃圾收集器。
- `-XX:+UseParallelGC`:使用并行垃圾收集器。
- `-XX:+UseConcMarkSweepGC`:使用CMS垃圾收集器。
- `-XX:+UseG1GC`:使用G1垃圾收集器。
- `-XX:+UseZGC`(Java 11及之后):使用ZGC垃圾收集器(实验性)。
- `-XX:+UseShenandoahGC`:使用Shenandoah垃圾收集器(实验性)。

### 3. 性能监控参数:
- `-XX:+PrintGC`:打印GC发生的情况。
- `-XX:+PrintGCDetails`:打印GC的详细日志。
- `-XX:+PrintGCDateStamps`:在GC日志中添加时间戳。
- `-Xloggc:<file>`:将GC日志输出到指定文件。
- `-XX:+UseGCLogFileRotation`:启用GC日志文件轮替。
- `-XX:NumberOfGCLogFiles=<files>`:指定GC日志文件的数量。
- `-XX:GCLogFileSize=<size>`:指定GC日志文件的大小。

### 4. JIT编译器参数:
- `-XX:+PrintCompilation`:打印JIT编译方法的信息。
- `-XX:+PrintInlining`:打印JIT编译过程中的内联信息。

### 5. 线程参数:
- `-XX:ThreadStackSize=<size>`:设置线程栈的大小。
- `-XX:+UseLargePages`(某些系统):使用大页内存,可以减少内存占用和提升性能。

### 6. 其他参数:
- `-Djava.security.egd=file:/dev/./urandom`:设置随机数生成器的熵源。
- `-XX:+HeapDumpOnOutOfMemoryError`:在发生OOM时生成堆转储。
- `-XX:HeapDumpPath=<path>`:指定堆转储文件的路径。

### 7. 实验性参数(可能在不同版本中有所不同):
- `-XX:+UnlockExperimentalVMOptions`:解锁实验性参数。
- `-XX:+UseContainerSupport`(Java 19及之后):启用对容器化环境的支持。

这些参数可以根据应用程序的具体需求和运行环境进行调整。建议在进行调优时,结合实际的性能测试结果和监控数据来决定最合适的参数配置。

垃圾回收机制

判断什么是垃圾

在Java中,判断一个对象是否成为垃圾,即是否可被垃圾回收器(Garbage Collector,GC)回收,主要基于以下条件:

1. **无法到达性(Unreachability)**:
   对象没有任何引用与之相连,即从GC Roots开始无法到达该对象。

2. **GC Roots的起点**:
   GC Roots是垃圾收集器进行可达性分析时的起始点,包括:
   - 静态字段(`static`字段)中的对象引用。
   - 局部变量表(栈帧中的局部变量)中的对象引用。
   - 活跃线程的引用,包括Java方法栈和本地方法栈。
   - 同步锁(`synchronized`关键字所创建的锁)的对象。
   - 被Java虚拟机引用的对象,如系统类加载器。

3. **可达性分析(Reachability Analysis)**:
   垃圾回收器会定期进行可达性分析,从GC Roots开始遍历所有可达对象。所有不可达的对象被认为是垃圾。

4. **finalize()方法**:
   如果对象没有被引用,并且没有被垃圾回收器回收,它可能会成为垃圾回收的候选对象。如果对象定义了`finalize()`方法,并且该方法还未被调用,垃圾回收器可能会调用这个方法。但自Java 9起,`finalize()`方法已被标记为过时,并推荐使用`Cleaner`类来实现类似功能。

5. **引用类型**:
   - **强引用(Strong References)**:普通的引用,如`Object obj = new Object();`。
   - **软引用(Soft References)**:`java.lang.ref.SoftReference`,当内存不足时会被回收。
   - **弱引用(Weak References)**:`java.lang.ref.WeakReference`,一旦成为弱引用,就会被垃圾回收器回收。
   - **虚引用(Phantom References)**:`java.lang.ref.PhantomReference`,无法通过虚引用访问对象,仅用于跟踪对象被回收的状态。

6. **回收时机**:
   即使对象已经被视为垃圾,垃圾回收器也不一定会立即回收它们。回收时机取决于多种因素,如GC算法、堆内存的使用情况等。

7. **回收过程**:
   对象被判定为垃圾后,GC会在合适的时机进行回收。在回收过程中,对象占用的内存会被释放。

开发者可以通过以下方式帮助JVM管理内存:
- 及时释放不再使用的引用,如将对象设置为`null`。
- 使用适当的数据结构和算法,减少内存占用。
- 避免内存泄漏,如避免循环引用或确保及时关闭资源。

记住,Java的垃圾回收是自动的,但开发者可以通过编写良好的代码来辅助JVM更高效地进行内存管理。
 

垃圾回收算法

标记-清除(Mark-Sweep):

首先标记所有需要回收的对象。
清除所有被标记的对象,释放内存。
缺点是会产生内存碎片,并且标记和清除过程可能较慢。

复制(Copying):
将内存分为两个相等的区域,每次只使用一个区域。
当一个区域满了,将存活的对象复制到另一个区域,并清空当前区域。
优点是解决了内存碎片问题,缺点是空间利用率降低。

标记-整理(Mark-Compact):
先进行标记阶段,找出存活对象。
然后将存活对象向内存的一端移动,并清空剩余区域。
优点是解决了内存碎片问题,缺点是移动对象可能较耗时。

增量回收(Incremental or Generational GC):
将堆分为不同的代(通常是新生代和老年代),并假设大部分对象都是短暂存活的。
新生代使用复制算法,老年代使用标记-清除或标记-整理算法。
增量回收尝试减少单次GC的停顿时间。

分代收集(Generational Collection):
基于增量回收的概念,进一步优化,将对象分配到不同代。
新生代对象存活率低,使用复制算法;老年代对象存活率高,使用标记-清除或标记-整理算法。

并发标记-清除(Concurrent Mark-Sweep):
允许垃圾回收器的某些阶段与应用程序并发运行,减少停顿时间。

G1(Garbage-First):
一种服务器端的垃圾回收算法,旨在提供可预测的停顿时间。
将堆分割成多个区域,并优先回收那些包含大量垃圾的区域。

ZGC(Z Garbage Collector):
一种低延迟垃圾回收器,使用彩色标记和并发处理。
它允许应用程序在非常大的堆上运行,同时保持低延迟。

Shenandoah:
一种与应用程序并发运行的垃圾回收器,几乎没有停顿。
它使用全局并发标记和编译器支持的引用处理。

Epsilon:
一种无操作垃圾回收器,不执行任何垃圾回收,用于性能基准测试。

垃圾回收器

在JDK 8中,主要的垃圾回收器(Garbage Collectors,GC)有以下几种:

1. **Serial GC**:这是单线程的垃圾回收器,使用一个线程进行垃圾回收,适合内存资源受限的环境和小数据量的应用场景。它在新生代使用复制算法,在老年代使用标记-整理算法。

2. **ParNew GC**:ParNew是Serial GC的多线程版本,同样在新生代使用复制算法,适用于多CPU环境。它是许多运行在server模式下的虚拟机中首选的新生代收集器。

3. **Parallel Scavenge GC**:这个收集器是一个关注吞吐量的新生代收集器,使用复制算法。它提供了一些参数,如 `-XX:MaxGCPauseMillis` 用于控制最大垃圾收集停顿时间,以及 `-XX:GCTimeRatio` 用于设置吞吐量大小。

它的目标是达到一个可控的吞吐量(吞吐量=运行用户代码的时间 /(运行用户代码时间+垃圾收集时间)),例子:虚拟机一共运行了100分钟,垃圾收集器用了1分钟,用户代码运行时间99分钟,那么吞吐量就是99%。

4. **Parallel Old GC**:这是Parallel Scavenge收集器的老年代版本,使用多线程和标记-整理算法。它适用于注重吞吐量和CPU资源的场合。

5. **CMS (Concurrent Mark Sweep) GC**:CMS是一种以最小化GC停顿时间为目标的收集器,采用标记-清除算法。它的垃圾收集过程分为四个步骤:初始标记、并发标记、重新标记和并发清除。CMS在JDK 9中被标记为过时,并在JDK 14中被移除。

6. **G1 (Garbage-First) GC**:G1是一种服务器端的垃圾回收器,旨在提供可预测的停顿时间,同时保持高吞吐量。它将堆内存分割成多个区域,并优先回收那些包含大量垃圾的区域。G1在JDK 9之后成为默认的垃圾回收器。

7. **ZGC (Z Garbage Collector)**:虽然ZGC是在JDK 11中引入的,但它是一种低延迟的垃圾回收器,能够在保持高吞吐量的同时,将停顿时间控制在毫秒级别。ZGC支持最大16TB的堆内存,适合需要极低延迟和大堆内存的应用场景。

JDK 8的默认垃圾回收器组合是Parallel Scavenge GC用于新生代,Parallel Old GC用于老年代。开发者可以根据应用的具体需求和JVM的性能特性来选择合适的垃圾回收器。
 

Java 垃圾收集器(Garbage Collector,GC)日志分析工具

Universal JVM GC analyzer - Java Garbage collection log analysis made easy

GC Easy 是一款在线的 Java 垃圾回收日志分析工具,它通过机器学习技术辅助用户快速理解 GC 日志,定位内存泄漏和优化垃圾回收性能。使用 GC Easy 非常简单,用户只需上传 GC 日志文件,GC Easy 就会自动解析日志并生成包含多种图表和详细指标的分析报告,帮助用户直观地了解应用程序的内存使用情况和垃圾回收性能 。

打印GC日志

打印GC(Garbage Collection,垃圾回收)日志是监控和调优Java应用程序性能的重要手段。以下是一些常用的JVM参数,用于控制GC日志的打印:

1. **`-Xlog:gc*`**:
   这个参数会打印GC日志,`*`可以替换为更具体的日志选项,例如`-Xlog:gc`只打印GC日志,而`-Xlog:gc+heap=info`会打印GC和堆信息。

2. **`-XX:+PrintGC`**:
   启用这个参数会打印每次GC事件的简要信息。

3. **`-XX:+PrintGCDetails`**:
   如果需要更详细的GC日志,可以使用此参数,它会打印GC的详细日志,包括GC的类型、花费的时间、回收了多少内存等。

4. **`-XX:+PrintGCTimeStamps`**:
   这个参数会让GC日志包含时间戳,这对于分析GC事件的发生时间非常有用。

5. **`-XX:+PrintGCDateStamps`**:
   与`-XX:+PrintGCTimeStamps`类似,但这个参数会打印日期和时间戳。

6. **`-Xloggc:<file-path>`**:
   通过这个参数可以指定GC日志输出到文件而不是控制台。

7. **`-XX:+UseGCLogFileRotation`**:
   启用日志文件轮转,防止日志文件无限增长。

8. **`-XX:NumberOfGCLogFiles=<files>`**:
   设置轮转的日志文件数量。

9. **`-XX:GCLogFileSize=<size>`**:
   设置每个GC日志文件的最大大小。

10. **`-XX:HeapDumpPath=<path>`**:
    设置在发生OOM(Out of Memory)时Heap Dump文件的路径。

11. **`-XX:+HeapDumpOnOutOfMemoryError`**:
    在发生OOM时自动生成Heap Dump。

以下是一个示例,展示如何使用这些参数启动Java应用程序:

```sh
java -Xmx1024m -Xms512m -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:/path/to/gc.log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/heapdump.hprof YourApplication.jar
```

在这个示例中,我们设置了堆的最大和初始大小,启用了详细GC日志和时间戳,并将GC日志输出到指定的文件。同时,如果发生OOM错误,会自动在指定路径生成Heap Dump文件。

请注意,这些参数可能会根据JDK的版本和实现有所不同,建议查阅具体版本的官方文档以获取最准确的信息。
 

演示案例

代码

public class TestGC {
   public static void main(String[] args) throws InterruptedException {
        final int STRING_COUNT = 100000; // 要生成的字符串数量
       List<String> arr = new ArrayList<>();
       
        for (int i = 0; i < STRING_COUNT; i++) {
            String replace = UUID.randomUUID().toString().replace("-", "");
            System.out.println(replace);
            arr.add(replace);
            replace = null;
        }
        arr.clear();
        System.gc();
        Thread.sleep(5000);
    }
}

设置jvm参数

-Xmx128m -Xms128m -XX:+UseParallelGC -XX:+UseParallelOldGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:gc.log

在Universal JVM GC analyzer - Java Garbage collection log analysis made easy 上传你的gc日志

然后分析

你会发现新生代内存空间占分配的128m栈内存的三分之一左右,老年代占三分之2左右。

通过下面这张图:

新生代内存满了之后,JVM会触发Minor GC(也称为Young GC),在这次垃圾回收过程中,不再有存活价值的对象会被回收,而仍然存活的对象会根据其年龄(age)进行处理。在HotSpot JVM中,对象在新生代中有一个年龄计数器,每次经历一次Minor GC后,如果对象仍然存活,其年龄计数器会增加。当对象的年龄达到一定的阈值(可以通过-XX:MaxTenuringThreshold参数设置,默认值通常是15),对象就会被晋升到老年代(Old Generation或Tenured Generation)。

晋升到老年代的对象会有更多的生存时间,JVM会认为这些对象的生命周期较长,因此不需要在每次Minor GC时都进行回收。老年代的垃圾回收(Major GC或Full GC)发生的频率远低于新生代,这样可以减少垃圾回收的开销。

总结来说,新生代内存满了会触发Minor GC,在此过程中,满足年龄条件的对象会被晋升到老年代,而未满足条件的存活对象则仍然留在新生代中。这个过程有助于JVM更有效地管理内存,减少垃圾回收的频率和成本。

 下图,平均GC执行时间,和最大GC执行时间

下图,发现Full GC 触发了一次,我们调优就是为了尽量避免Full GC的触发,System.gc()很大可能会触发,我们上面代码写了System.gc(),所以Full GC了一次,如果你去掉System.gc()代码,然后再分析日志,你会发现触发次数是0了。下面是FullGC触发了条件

Full GC(Full Garbage Collection)是JVM中的老年代垃圾回收,它是一种成本较高的垃圾回收操作,因为它会回收整个堆内存(包括新生代和老年代)。Full GC的触发条件通常包括以下几种情况:

1. **老年代空间不足**:当老年代中没有足够的内存空间去容纳新生代晋升的对象时,会触发Full GC 。
2. **Metaspace区内存达到阈值**:从JDK8开始,永久代(PermGen)被废弃,取而代之的是Metaspace。当Metaspace区域的内存使用达到一定阈值时,会触发Full GC 。
3. **统计得到的Minor GC晋升到老年代的平均大小大于老年代的剩余空间**:Hotspot为了避免新生代对象晋升到老年代导致空间不足,在进行Minor GC时会做一个判断,如果统计得到的晋升平均大小大于老年代剩余空间,则直接触发Full GC 。
4. **堆中分配很大的对象**:当创建一个很大的对象,且该对象需要的内存空间大于老年代的剩余空间时,会触发Full GC 。
5. **CMS GC时出现promotion failed和concurrent mode failure**:在使用CMS垃圾收集器时,如果出现晋升失败或并发模式失败,也会触发Full GC 。
6. **显式调用System.gc()**:虽然只是建议JVM进行Full GC,但在大多数情况下会增加Full GC的次数,导致系统性能下降 。

在进行Full GC时,JVM会尝试清理整个堆内存中的垃圾对象,这可能会导致应用程序的线程暂停,从而影响性能。因此,理解并监控Full GC的触发条件和频率对于优化Java应用程序的性能至关重要。开发者可以通过监控工具和调整JVM参数来优化垃圾回收过程,例如设置堆的最大大小、新生代与老年代的比例、使用合适的垃圾回收器等 。
 

JVM调优总结

 

JVM调优是一个综合性的过程,涉及到多个方面的参数调整,主要包括内存设置、垃圾收集器选择、性能监控等。以下是一些常见的JVM调优参数和策略:

1. **堆内存设置**:使用 `-Xms` 和 `-Xmx` 参数来设置JVM堆的初始大小和最大大小,这有助于优化内存使用并减少动态调整的开销。

2. **垃圾收集器选择**:根据应用的特点和需求,选择合适的垃圾收集器。例如,`-XX:+UseG1GC` 用于选择G1垃圾收集器,它适合于大堆内存和多核处理器的场景,可以提供平衡的吞吐量和较低的延迟。

3. **性能监控**:启用 `-XX:+PrintGCDetails` 参数打印详细的GC日志,这有助于监控垃圾收集的性能和优化垃圾收集策略。

4. **元空间(Metaspace)**:设置 `-XX:MetaspaceSize` 和 `-XX:MaxMetaspaceSize` 参数来控制元空间的大小,避免因元空间无限增长导致的问题。

5. **日志和监控**:使用 `-Xloggc` 将GC日志写入指定文件,并使用 `-XX:+UseGCLogFileRotation` 开启GC日志文件的轮替,以便于监控和分析。

6. **JVM性能调优**:使用 `-XX:+UseStringDeduplication` 开启字符串去重功能,减少堆内存的占用;使用 `-XX:+DisableExplicitGC` 禁用System.gc()的显式调用,避免可能的性能问题。

7. **同步优化**:在多线程应用中,合理使用同步机制,避免因过度同步导致的性能损耗。

8. **JIT编译器优化**:JIT编译器是JVM性能优化的重要手段,通过调整JIT编译器的参数和关闭不必要的优化,提高程序的执行效率。

9. **内存分析工具**:使用内存分析工具,如VisualVM和MAT,帮助定位内存泄漏问题并解决。

10. **监控工具**:使用jstat、jvisualvm、jconsole等JVM监控工具,监控和分析Java应用的性能。

需要注意的是,JVM调优应根据应用程序的具体需求和运行情况进行,逐步调整并观察每次调整的效果。此外,JVM调优是一个持续的过程,需要根据应用的运行情况不断进行优化和调整。
 

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

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

相关文章

C++基础练习

1》提示并输入一个字符串&#xff0c;统计该字符串中字母个数、数字个数、空格个数、其他字符的个数 1 #include<iostream>2 using namespace std;3 4 int main()5 {6 string str1; //定义字符串数据7 cout << "请输入一个字符串>>>" ;8…

三种常用的Word打印部分内容操作技巧

作为打工人&#xff0c;我们经常需要处理Word文档&#xff0c;有时还会遇到只需要打印文档中的部分内容而非整个文档的情况。为了高效地完成这一任务&#xff0c;Word提供了多种灵活的设置方法。本文将详细介绍三种常用的方法来帮助你实现只打印Word文档中的部分内容。 方法一&…

第一周学习--联邦学习

OUC读研--第一周 目录 1、课程学习 2、fedavg的算法实现 关于代码详解 1、client __init__ 方法 local_train 方法 2、server 3、get_dataset 函数定义 数据集加载 MNIST 数据集 CIFAR-10 数据集 返回值 使用示例 4、 main 代码解释 可能的改进点 5、models …

机器学习之 K-means算法的代码实现

K-means 算法简介 K-means 是一种常用的无监督学习算法&#xff0c;主要用于数据聚类。它的主要思想是将数据集中的数据分成 K 个簇&#xff08;Cluster&#xff09;&#xff0c;使得簇内的数据点尽可能相似&#xff0c;而簇间的差异尽可能大。K-means 算法的核心步骤包括初始…

关于类与构造函数继承的小挑战

题目 /* 使用构造函数将电动汽车&#xff08;称为 EV&#xff09;作为 Car 的子 “类 ”来实现。除了品牌和当前速度外&#xff0c;EV 还具有当前电池电量&#xff08;百分比&#xff09;&#xff08;“charge ”属性&#xff09;&#xff1b;实现一个 “chargeBattery ”方法…

Vitis AI 基本认知(Tiny-VGG 项目代码详解)

目录 1. 简介 1.1 Tiny-VGG 1.2 data 目录结构 2. 代码分析 2.1 Import packages 2.2 Dataset 2.3 Train step 2.4 Vali & Test step 2.5 Ceate model 2.6 Compile model 2.6.1 计算 loss 2.6.2 计算平均值 3.6.3 计算准确度 2.7 训练循环 2.7.1 自定义训练…

BLE蓝牙协议详解

BLE蓝牙协议详解 1、BLE协议栈 1、协议栈结构 蓝牙LE协议栈按功能分为三个层&#xff1a;Controller、Host和Application Profiles and Services。 HCI event是按BLE Spec标准设计的&#xff0c;是BLE Controller和Host用来交互的事件&#xff1b;GAP event是BLE host定义的…

环境配置 --- miniconda安装torch报错OSError: [WinError 126] 找不到指定的模块

环境配置 — miniconda安装torch报错OSError: [WinError 126] 找不到指定的模块。 CSDN 原因&#xff1a;fbegmm.dll文件出现问题 解决方案&#xff1a; 使用依赖分析工具https://github.com/lucasg/Dependencies/releases/tag/v1.11.1 检测报错提示的那个dll文件发现哪个文…

Nuclei:开源漏洞扫描器

Nuclei 拥有灵活的模板系统&#xff0c;可以适应各种安全检查。 它可以使用可自定义的模板向多个目标发送请求&#xff0c;确保零误报并实现跨多台主机的快速扫描。 它支持多种协议&#xff0c;包括 TCP、DNS、HTTP、SSL、文件、Whois、Websocket 等。 特征 模板库&#xf…

Java中的定时器(Timer)

目录 一、什么是定时器? 二、标准库中的定时器 三、实现定时器 一、什么是定时器? 定时器就像一个"闹钟"&#xff0c;当它到达设定的时间后&#xff0c;就会执行预定的代码。 例如&#xff0c;我们在TCP的超时重传机制中讲过&#xff0c;如果服务器在规定的时间…

XDMA - AXI4 Memory Mapped

目录 1. What is SG DMA2. Descriptor3. Transfer for H2CStep 1. The host prepares stored data and creates descriptors in main memoryStep 2. The host enables DMA interruptsStep 2. The driver initializes DMA with descriptor start addressStep 3. The driver writ…

数据结构(邓俊辉)学习笔记】串 06——KMP算法:构造next[]表

文章目录 1. 递推2. 算法3. 实现 1. 递推 接下来的这节&#xff0c;我们就来讨论 next 查询表的构造算法。我们将会看到非常有意思是&#xff0c; next 表的构造过程与 KMP 主算法的流程在本质上是完全一样的。 在这里&#xff0c;我们不妨采用递推策略。我们只需回答这样一个…

带你深入浅出新面经:十六、十大排序之快速排序

此为面经第十六谈&#xff01;关注我&#xff0c;每日带你深入浅出一个新面经。 我们要了解面经要如何“说”&#xff01; 很重要&#xff01;很重要&#xff01;很重要&#xff01; 我们通常采取总-分-总方式来阐述&#xff01;&#xff08;有些知识点&#xff0c;你可以去…

python脚本请求数量达到上限,http请求重试问题例子解析

在使用Python的requests库进行HTTP请求时&#xff0c;可能会遇到请求数量达到上限&#xff0c;导致Max retries exceeded with URL的错误。这通常发生在网络连接不稳定、服务器限制请求次数、或请求参数设置错误的情况下。以下是一些解决该问题的策略&#xff1a; 增加重试次数…

【负载均衡式在线OJ】项目设计

文章目录 程序源码用到的技术项目宏观结构代码编写思路 程序源码 https://gitee.com/not-a-stupid-child/online-judge 用到的技术 C STL 标准库。Boost 准标准库(字符串切割)。cpp-httplib 第三方开源网络库。ctemplate 第三方开源前端网页渲染库。jsoncpp 第三方开源序列化…

栈和队列有何区别?

栈和队列是两种常见的数据结构&#xff0c;它们分别用于解决不同类型的问题。在程序设计中&#xff0c;栈和队列都是非常重要的数据结构&#xff0c;因为它们可以帮助我们解决很多实际的问题。 栈&#xff1a; 首先&#xff0c;让我们来讨论栈, 栈是一种后进先出&#xff08;…

学NLP不看这本书等于白学!一书弄懂NLP自然语言处理(附文档)

随着人工智能技术的飞速发展&#xff0c;自然语言处理成为了计算机科学与人工智能领域中不可或缺的关键技术之一。作为一名长期致力于人工智能和自然语言处理研究的学者&#xff0c;今天给大家推荐的这本《自然语言处理&#xff1a;大模型理论与实践》正是学NLP自然语言非常牛逼…

黑神话悟空用什么编程语言

《黑神话&#xff1a;悟空》作为一款备受瞩目的国产单机动作游戏&#xff0c;其背后的开发涉及了多种编程语言和技术。根据公开信息和游戏开发行业的普遍做法&#xff0c;可以推测该游戏主要使用了以下几种编程语言&#xff1a; C&#xff1a; 核心编程语言&#xff1a;作为《黑…

【C++ Primer Plus习题】5.7

问题: 解答: #include <iostream> #include <string> using namespace std;typedef struct _Car {string brand;int year; }Car;int main() {int count 0;cout << "请问你家有多少辆车呢?" << endl;cin >> count;cin.get();Car* ca…

Java 入门指南:Java IO流 —— 序列化与反序列化

序列化 序列化是指将对象转换为字节流的过程&#xff0c;以便能够将其存储到文件、内存、网络传输等介质中&#xff0c;或者在不同的进程、网络或机器之间进行数据交换。 序列化的逆过程称为反序列化&#xff0c;即将字节流转换为对象。过反序列化&#xff0c;可以从存储介质…