jvm

程序开发周期:编写,编译,运行

字节码

源代码.java -编译->字节码.class -运行解释->特定的机器码

字节码:字节码指令的操作码,固定为1字节;

字节码示例(Java字节码):格式为 指令(有特定含义)和操作数
0: aload_0
1: invokevirtual #16 // Method java/lang/Object.getClass:()Ljava/lang/Class;
4: pop
5: return

机器码示例(x86汇编):格式为 十六进制数字,每个数字对应着底层机器指令的操作码
8B 45 FC     mov eax, DWORD PTR [ebp-4]
83 C0 01     add eax, 0x1
89 45 FC     mov DWORD PTR [ebp-4], eax
C9           leave
C3           ret

类加载

当程序首次使用某个类时,类加载器会根据类的全限定名找到对应的字节码文件,并将其加载到内存中。这样可以避免在程序启动时一次性加载所有类,而是根据实际需要逐步加载,减少启动时间和内存消耗

加载时机, 启动时 main() 进件加载并初始化

Java类型

加载
查找字节流,在内存中生成Class对象。数组类直接JVM生成,其他类需要借助 类加载器 完成

链接:类合并到 JVM中

初始化

内存管理

内存布局

运行时内存空间的组织结构,关注内存空间的划分和管理

内存区域(Class 文件在内存中的分布)

原生内存:NIO,引入基于通道和缓冲区的IO方式,使用 Native 函数直接分配堆外内存,通过堆内的DirectByteBuffer 对象引用;受本机总内存和CPU寻址空间的限制导致 OutOfMemoryError

OutOfMemoryError

OutOfMemoryError,只影响JVM的单个线程,不会让JVM退出。终止线程引用的对象和不被任何线程引用的对象,在GC周期中释放内存,使得存活线程能继续执行

原生内存溢出
Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread

元空间内存溢出
Exception in thread "main" java.lang.OutOfMemoryError: Metaspace
元空间默认没有最大值限制,发生这个错误是你设置了最大值

堆内存溢出
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

达到GC开销限制
JVM判断它花了太多时间执行GC
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
满足下面所有条件时,错误会抛出

内存优化

堆内存
目标:

  1. 谨慎地创建对象并尽快丢弃它们,减少对象创建也提高GC效率
  2. 重用对象(线程局部变量、特殊对象引用和对象池),提高应用程序性能

工具:查看应用程序当前使用的对象,会消耗时间和资源,不用于测量应用程序的执行

减少内存使用

public class ImmutableObject {
	// 创建映射存储对象的标准化版本
	private static WeakHashMap<ImmutableObject, ImmutableObject> map = new WeakHashMap(); 
	public ImmutableObject canonicalVersion(ImmutableObject io) { 
		synchronized(map) { 
			ImmutableObject canonicalVersion = map.get(io); 
			if (canonicalVersion == null) { 
				map.put(io, new WeakReference(io)); 
				canonicalVersion = io; 
			} 
			return canonicalVersion; 
		} 
	} 
}

对象生命周期管理
实现方式

重用对象
对象重用:对象池和线程局部变量
缺点:被重用的对象会在堆中停留很长时间。如果堆中有大量对象,创建新对象的空间就更少了,因此GC操作会更频繁
堆中活跃对象对GC时间的影响:增加可能不止一个数量级(*10)
重用对象的原因:许多对象的初始化成本很高,权衡了增加的GC时间之后,还是重用对象的效率更高
JDK中满是使用线程局部变量的类,以避免重新分配某种类型的对象

这些例子的共同点

重用性能

不确定引用
更多地用于缓存 耗时的计算或数据库查询的结果,重用这个复杂对象,否则创建成本太高
引用(reference):一个引用(或对象引用)可以是任何类型的引用,比如强引用、弱引用和软引用等。一个普通实例变量指向一个对象,这就是一个强引用
不确定引用(indefinite reference):任何特殊类型的引用(例如,软引用或弱引用)。一个不确定引用实际上是一个对象实例(例如,SoftReference类的一个实例)
所引对象(referent):不确定引用的工作方式是,将另一个引用(几乎总是强引用)嵌入到不确定引用类的实例中。被封装的这个对象被称为所引对象

优点:一定会被垃圾回收
缺点:不确定引用本身会消耗内存;至少需要两个 GC 周期,// TODO 图
-XX:+PrintReferenceGC标志(默认为false)打印 GC 引用花费时间

软引用
软引用本质上是一个大型的、最近最少使用的(LRU)对象池(对象大概率被重用),良好性能的关键是要确保它被及时清理。
软引用被清理的时机:所引对象必须没有被其他的强引用所引用。如果软引用是指向所引对象的唯一引用,那么当软引用最近没有被访问时,所引对象就会在下个GC周期被释放
如果完全耗尽内存或抖动(thrashing)过于严重,JVM会清理所有的软引用,否则会抛出OutOfMemoryError异常
当对象的数量不太多时,软引用的效果才会好(很容易填满堆),否则,还是要考虑使用更传统的、大小有界的对象池,并且以LRU缓存形式实现

弱引用
弱引用对象只有在它的所引对象仍然存在时才可以使用(在所引对象被回收后的下个GC周期,它就会被清理)

其他引用
// TODO

原生内存

整体内存的使用,监控和发现性能问题
JVM的内存占用(footprint)=原生内存+堆内存
应用程序使用原生内存:NIO
Java进程使用的原生内存与其他进程共享,为了获得最佳性能,你需要确保所有Java进程总的内存占用不超过机器的物理内存(加上你想为其他应用程序留出的内存)

测量内存占用
随着编译的代码增多,代码缓存会从初始值增长到最大值,这适用于 堆,元空间(初始值(提交的内存)增长到最大值(保留的内存))
线程栈空间在创建时就被完全分配了。每当JVM创建一个线程时,操作系统都会分配一些原生内存来存放线程栈,并向进程提交更多的内存(至少持续到线程退出)

# https://cloud.tencent.com/developer/article/1683708
sudo apt-get install smem
smem -r -p <PID>

最小化内存占用,限制内存使用量

原生内存跟踪,适用于 JVM 分配的内存
-XX:NativeMemoryTracking=off(default)|summary|detail 可以查看原生内存的信息
-XX:+PrintNMTStatistics标志(default false),JVM会在程序退出时输出原生内存的分配信息
summary-内存的使用情况

共享库原生内存
从架构的角度来看,NMT是HotSpot的一部分,HotSpot是运行程序的Java字节码的C++引擎。它位于JDK的下层,所以NMT无法跟踪JDK层面上任何东西分配的内存。JDK层面的分配来自共享库(通过调用System.loadLibrary()加载的共享库)
原生内存泄漏,即应用程序的RSS或工作集随着时间的推移不断增长,通常不会被NMT检测到
如果一个进程的工作集增长到10 GB,而NMT告诉我们JVM只提交了6 GB的内存,那我们就知道另外4 GB的内存一定是由原生库分配的
Profiler,能跟踪原生代码的内存分配,不能跟踪Java代码的

原生内存被大量使用

Linux 系统内存泄露
原生内存不进行压缩,碎片化严重,耗尽原生内存,排查:

  1. 应用程序会抛出OutOfMemoryError异常,表明原生内存已经耗尽。
  2. 如果查看进程的smaps文件,那么会发现它显示有许多小的(通常是64 KB)分配。
    补救的办法是将环境变量MALLOC_ARENA_MAX设置为像2或4这样小的数。该变量的默认值是系统的核心数乘以8(这也是这个问题在大型系统上比较常见的原因)。在这种情况下,原生内存仍然会变得碎片化,但不会太严重。

针对操作系统的JVM优化

操作系统分配的页面比物理内存能容纳的页面多很多,这就是为什么需要分页。地址空间的页面会被移入或移出交换空间(或其他存储,这跟页面中包含的内容有关)。这意味着,这些页面和它们当前存储在计算机RAM中的位置之间一定存在某种映射。这些映射有两种方式,所有的页面映射都保存在一个全局页表中(操作系统可以扫描这张表,以找到特定的映射),最常用的映射保存在转换后备缓冲区(translation lookaside buffer,TLB)中。TLB保存在快速缓存中,因此通过TLB条目访问页面比通过页表访问要快得多。
机器的TLB条目数量有限,因此最大限度地提高TLB条目的命中率就变得很重要了(它的功能是作为最近最少使用的缓存)。由于每个条目代表一页内存,因此增加应用程序使用的页面大小通常是有利的。如果每个页面能承载更多的内存,涵盖整个程序所需的TLB条目就会更少,需要时在TLB中找到一个页面的概率就更大。
大页必须在Java和操作系统两个层面上开启。在Java层面,-XX:+UseLargePages标志可以开启大页的使用,默认情况下这个标志是false。并非所有的操作系统都支持大页,开启大页的方式显然也不尽相同。

内存模型

Java Memory Model,多线程环境下的内存访问规则、操作顺序和同步机制。确保不同线程间的可见性、有序性和原子性

Java内存模型与堆内存的并发安全设计相关,但它涵盖了整个内存体系结构,包括主内存、工作内存和各种缓存等

  1. 主内存(Main Memory):主内存是所有线程共享的内存区域,用于存储对象的实例数据和静态变量等
  2. 工作内存(Working Memory):工作内存是每个线程独立拥有的内存区域,用于存储线程私有的栈帧和本地变量等。工作内存保存了从主内存中读取的副本,并对副本进行操作
  3. 内存屏障(Memory Barrier):内存屏障是一种同步机制,用于确保特定的内存操作顺序和可见性。在编写多线程代码时,通过插入内存屏障可以控制指令的执行顺序,以保证线程之间的协调和数据的正确更新
  4. 原子性操作(Atomicity):Java内存模型保证了一些特定操作的原子性,即这些操作要么完全执行成功,要么不执行。例如,对volatile变量的读写操作是具有原子性的
  5. 可见性(Visibility):Java内存模型确保在一个线程中对共享变量的修改对其他线程可见。使用同步机制(如锁、volatile关键字、synchronized块等)可以确保可见性

happens-before
让程序免于数据竞争的干扰,描述两个操作内存可见性:操作X happens-before Y,则 X 的结果对 Y 可见

  1. 加锁解锁、lovatile 读写
  2. 线程启动、第一个操作 / 线程最后一个操作、终止 / 线程中断、接收中断事件
  3. 构造器 最后一个操作、析构器第一个操作

内存模型底层实现:通过内存屏障 memory barrier 禁止指令重排
对于JIT 编译器,在 happens-before 向正编译的目标方法插入相应 读读、读写、写写 内存屏障
内存屏障使用具体的 CPU 指令。

内存模型关键字

JVM 实现 synchronized
字节码含 monitorenter 和 monitorexit 指令。退出方法时,正常返回或异常抛出,都需要解锁
可重复锁:同一线程每次持有锁,计数器+1
JVM锁实现

GC

GC:找出不被引用的对象,并回收对象的内存
垃圾:死亡对象占据的堆空间;不回收 栈内存中的引用变量(指向堆内存中对象的地址),它们由编译器和虚拟机自动管理。
比起 跟踪指针引起的BUG,垃圾回收更简单,用时更少
GC 的性能取决于,找到不使用的对象,回收其内存,压缩堆内存(不同回收器有不同的实现方式)

查找

找出不被引用的对象,策略:

// 可达性分析伪代码

algorithm markAndSweep():
    // 标记阶段
    // rootObjects 代表根对象集合
    mark(rootObjects)  // 从根对象开始,标记可达的对象

    // 清除阶段
    sweep()  // 清除未被标记的对象

function mark(object):
    if object is marked:
        return
    mark object as marked
    for each reference in object:
        mark(reference)

function sweep():
    for each object in heap:
        if object is marked:
            unmark object
        else:
            deallocate object

// 程序入口
function main():
    // 创建对象并建立引用关系
    obj1 = new Object()
    obj2 = new Object()
    obj3 = new Object()
    obj1.setReference(obj2)
    obj2.setReference(obj3)
    
    // obj1 是根对象,其他对象通过引用可达
    markAndSweep()
    
    // 假设 obj1 不再可达
    obj1 = null
    
    // 执行垃圾回收
    markAndSweep()

STW

问题:多线程环境下,很多线程会更新对象引用(应用更新对象,GC跟踪对象引用,GC移动对象),导致误报(引用设置为 null ,损失垃圾回收机会)或 漏报 (引用设置为未标记的对象,访问会崩溃)
解决:Stop-the-world、安全点

垃圾回收方式

分代回收

分代的原因是:大部分对象只存活小段时间,存活下来的小部分Java对象会存活很长一段时间

新生代 Minor GC

常见的垃圾收集器

垃圾回收器选择

应用程序(如REST服务器)中,考量单个请求的响应时间,

GC 算法
Serial垃圾回收器:程序运行在 32位JVM 的 Windows上,或者单核CPU(这样的Docker结构)上的默认回收器

触发GC
做性能监控或基准测试时需要

GC算法选择
取决于 1. 可用的硬件 2. 应用程序是什么样的 3. 应用程序的性能目标

GC 优化基本配置

对于很多应用程序来说,唯一需要优化的是选择合适的GC算法,如果需要的话,增加应用程序的堆大小。自适应大小允许JVM自动优化其行为,使用给定的堆,提供更好的性能

堆大小

堆总数大小

分代大小

元空间大小
JVM加载类时,它必须记录这些类的某些元数据。这些数据占据了一个单独的堆空间,叫作元空间(metaspace)。
元空间并不保存类的实例(例如Class对象)或者反射对象(例如Method对象),这些都保存在常规的堆中。元空间里的信息只在编译器和JVM运行时使用,它所保存的数据被称为类元数据(class metadata)。
并没有很好的方法来提前计算一个特定应用程序所需的元空间大小。元空间的大小与它所使用的类的数量成正比,所以更大的应用程序需要更大的区域。这是JDK技术提升让生活变得更轻松的另一个领域:优化永久代在过去相当普遍,而现在优化元空间相当罕见,主要原因是元空间大小的默认值非常宽裕。
元空间类似于常规堆的一个单独实例。它的大小是根据初始大小(-XX:MetaspaceSize=N)动态调整的,并会根据需要增长到最大值(-XX:MaxMetaspaceSize=N)。

控制并行
GC 多线程数量控制,-XX:ParallelGCThreads=N(不能设置 G1 GC 的线程数)
GC操作让应用程序停止运行,JVM 尽可能充分利用 CPU 资源,以减少停顿时间。
线程数量

GC 工具

JDK8 开启 GC 日志:-XX:+PrintGCDetails=true (default=false)
GC操作之间的时间:-XX:+PrintGCTimeStamps或-XX:+PrintGCDateStamps 时间戳(time stamp)是相对于0的(基于JVM启动的时间)值,而日期戳(date stamp)是实际的日期字符串
写入文件:-Xloggc:filename,default=标准输出
日志滚动:-XX:+UseGCLogFileRotation、-XX:NumberOfGCLogFiles=N和-XX:GCLogFileSize=N。默认情况下,UseGCLogFileRotation是关闭的。这个标志开启时,默认的文件数量是0(意味着无限多),默认的日志文件大小是0(意味着无限大)。因此,必须给以上标志设定值,才能让日志滚动按预期的方式工作。注意,如果给日志文件设置的大小不足8KB,那么这个数值会向上取整。

-Xloggc:gc.log -XX:+PrintGCTimeStamps -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFile=8 -XX:GCLogFileSize=8m

Java基本类型

8个基本类型

基本类型大小
boolean 占 1字节
Java 方法栈上的局部变量

Java 虚拟机规范中,boolean 类型被映射成 int 类型:true-1,false-0,Java 编译器遵守这个编码规则

编译优化

硬件视角:先解释执行字节码,将热点代码(反复运行到的)以方法为单位进行即时编译

运行效率
HotSpot 内置多个 JIT,取舍 编译时间和 生成代码的执行效率。默认线程池(线程大小根据CPU数量设置),分层编译:

Java 虚拟机不约束编译,由具体的虚拟机实现,现特指 Hotspot 虚拟机

JIT编译器指令重排
前提:单线程 as-if-serial,单线程下执行效果是顺序执行;如果两操作间存在数据依赖,不能调整顺序,否则改变程序语义

工具

VM

# 进程ID
[www@139-bee-dev ~]$ jps
26049 xxx.jar

# JVM 运行时间
[www@139-bee-dev ~]$ jcmd 26049 VM.uptime
26049:
3703522.021 s

# 系统属性
[www@139-bee-dev ~]$ jcmd 26049 VM.system_properties
26049:
#Thu Oct 19 14:45:39 CST 2023
java.runtime.name=Java(TM) SE Runtime Environment
java.protocol.handler.pkgs=org.springframework.boot.loader
sun.boot.library.path=/application/jdk1.8.0_221/jre/lib/amd64

# JVM 版本
[www@139-bee-dev ~]$ jcmd 26049 VM.version
26049:
Java HotSpot(TM) 64-Bit Server VM version 25.221-b11
JDK 8.0_221

# JVM 命令行
[www@139-bee-dev ~]$ jcmd 26049 VM.command_line
26049:
VM Arguments:
jvm_args: -Xms128m -Xmx780m -XX:+HeapDumpOnOutOfMemoryError -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=256m
java_command: /application/8201-early-warning/8201-early-warning-1.0.0-SNAPSHOT-2023-09-06_09_58.jar -jar --spring.profiles.active=dev --spring.cloud.consul.host=192.168.0.78 --spring.cloud.consul.port=32000 --spring.cloud.consul.discovery.acl-token=8dc1eb67-1f5f-4e10-ad9d-5e58b047647c
java_class_path (initial): /application/8201-early-warning/8201-early-warning-1.0.0-SNAPSHOT-2023-09-06_09_58.jar
Launcher Type: SUN_STANDARD

# JVM 调优标志
[www@139-bee-dev 9311-mortgage-service]$ jcmd 26049 VM.flags -all
26049:
[Global flags]
     intx ActiveProcessorCount                      = -1                                  {product}
	 bool HeapDumpOnOutOfMemoryError               := true                                {manageable}
# 修改标识:明确指定,一些标志(特别是与GC相关的标志)会影响另一些标志的最终值;如果没有令人信服的理由,那么任何一个标志都不应该更改
# 非默认值标识,其值来源于 1. 命令行设置 2. 其他标识影响 3. JVM自动计算;product 表示所有平台都有这个标识;manageable 标志的值可以在运行期间动态更改;C2 diagnostic 标志可以为编译器工程师提供诊断输出,以了解编译器如何运行
# jinfo 可以修改标识,只对 manageable 修饰的标识有效

线程

# jstack:线程栈轨迹、线程持有锁;死锁检测
[www@139-bee-dev ~]$ jstack 26049
2023-10-19 15:02:49
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.221-b11 mixed mode):

"Attach Listener" #137 daemon prio=9 os_prio=0 tid=0x00007faab0003800 nid=0x88aa waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"kafka-coordinator-heartbeat-thread | operation_info" #84 daemon prio=5 os_prio=0 tid=0x00007faa6c07e800 nid=0x6631 waiting on condition [0x00007faa527c8000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x00000000d2d17048> (a java.util.concurrent.locks.ReentrantLock$FairSync)
        at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(AbstractQueuedSynchronizer.java:870)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1199)
        at java.util.concurrent.locks.ReentrantLock$FairSync.lock(ReentrantLock.java:224)
        at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:285)
        at org.apache.kafka.clients.consumer.internals.ConsumerNetworkClient.poll(ConsumerNetworkClient.java:249)
        at org.apache.kafka.clients.consumer.internals.ConsumerNetworkClient.pollNoWakeup(ConsumerNetworkClient.java:306)
        at org.apache.kafka.clients.consumer.internals.AbstractCoordinator$HeartbeatThread.run(AbstractCoordinator.java:1386)
        - locked <0x00000000d2ce6550> (a org.apache.kafka.clients.consumer.internals.ConsumerCoordinator)

"kafka-coordinator-heartbeat-thread | risking_info" #85 daemon prio=5 os_prio=0 tid=0x00007faa580a1000 nid=0x6630 in Object.wait() [0x00007faa528c9000]
   java.lang.Thread.State: TIMED_WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        at org.apache.kafka.clients.consumer.internals.AbstractCoordinator$HeartbeatThread.run(AbstractCoordinator.java:1418)
        - locked <0x00000000d2c51560> (a org.apache.kafka.clients.consumer.internals.ConsumerCoordinator)

[www@139-bee-dev ~]$ jstack 26049 > thread_dump.txt
[www@139-bee-dev ~]$  grep Thread.State thread_dump.txt | awk '{print $2$3$4$5}' | sort | uniq -c
     45 RUNNABLE
      2 TIMED_WAITING(onobjectmonitor)
      8 TIMED_WAITING(parking)
      4 TIMED_WAITING(sleeping)
      2 WAITING(onobjectmonitor)
     17 WAITING(parking)

类数量和编译信息

jstat
GC 和 类加载信息

第12章,第4章

ASM
字节码分析和修改框架,应用于 代码覆盖测试工具 JaCoCo,Java8 Lambda 的适配器类
生成新的 class文件,修改已有的 class文件
// 编译
javac Foo.java
// 查看字节码 -p打印私有方法和字段;-v class文件;常量池;字段区域;方法区域
javap -p -v Foo

GC

第五章

jmap
Eclipse Memory Analyzer Open Source Project | The Eclipse Foundation
Java堆分析器,计算对象的保留大小,查看谁在阻止垃圾收集器收集对象

[vmroot@101-efq application]$ whereis java
java: /usr/local/java /usr/local/java/jdk1.8.0_151/bin/java /usr/local/java/jdk1.8.0_151/jre/bin/java

# 生成 Java 堆转储文件
1. ps - ef | grep <xxx>  Java 进程,启动信息
2. jmap -dump:file=heap_dump.hprof <pid>  Java自带工具
3. -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/dump.hprof 启用时 JVM参数

# mat 打开堆转储文件

JMC
java mission control,JVM 性能监控,组件 Java Flight Recorder,收集Java 性能数据
不依赖安全点机制

命令
Oracle Help Center - Search-java8 vm

更近一步
JVM 异常处理
JVM 泛型支持
运行内存故障

性能分析

性能分析工具通过 Socket 或 JVM 原生接口交互,可能会产生大量数据,给工具开启并发GC
性能分析两种模式

  1. 采样 1. Oracle Developer Studio分析器 2. 开源项目async-profiler 火焰图
  2. 探查:引起性能偏差 ; Oracle Developer Studio 检查线程的执行模式,而不是方法上的等待时间
    1. 线程阻塞是不是性能问题的原因,需要检查它们为什么被阻塞
    2. 通过正在阻塞的方法或者线程时间线分析,可以分辨阻塞的线程
  3. 如果原生分析器显示主要占用CPU资源的是GC时间,那么优化垃圾回收器是正确的选择。如果它显示编译线程占用大量时间,那么这通常不会影响应用程序的性能。

JFR

Java飞行记录器(Java Flight Recorder)是JVM的一项功能,可以对运行中的应用程序进行轻量级性能分析,对性能影响最多1%,可通过 jcmd 来配置
JFR会记录异常事件,如线程因为等待锁而被阻塞的事件,保存到循环缓冲区(只展示最近事件)
Java Mission Control(jmc),检查 JFR 记录

JIT 编译

调整热点代码阈值

调整 Code Cache 大小
JIT编译的代码存储在 Code Cache中

调整编译器线程数,或选择适当的编译器模式
client 默认一个编译线程,server default 2个;分层编译根据CPU个数配置

JVM参数

# 查找
man java | grep "heap size"

# java 默认值配置
[vmroot@101-efq application]$ java -XX:+PrintFlagsFinal |grep HeapDumpOnOutOfMemoryError
     bool HeapDumpOnOutOfMemoryError                = false                               {manageable}

The best HotSpot JVM options and switches for Java 11 through Java 17 (oracle.com)

下面是一些常用的JVM参数设置,可以用于调整Java虚拟机的行为和性能:

  1. -Xms: 设置JVM的初始堆大小。
    例如:-Xms512m 表示初始堆大小为512MB。
  2. -Xmx: 设置JVM的最大堆大小。
    例如:-Xmx1024m 表示最大堆大小为1GB。
  3. -Xss: 设置每个线程的栈大小。
    例如:-Xss256k 表示每个线程的栈大小为256KB。
  4. -XX:NewRatio: 设置年轻代(Young Generation)与老年代(Old Generation)的比例。
    例如:-XX:NewRatio=2 表示年轻代与老年代的比例为1:2。
  5. -XX:MetaspaceSize: 设置元空间(Metaspace)的初始大小。
    例如:-XX:MetaspaceSize=128m 表示元空间的初始大小为128MB。
  6. -XX:MaxMetaspaceSize: 设置元空间的最大大小。
    例如:-XX:MaxMetaspaceSize=256m 表示元空间的最大大小为256MB。
  7. -XX:+UseParallelGC: 启用并行垃圾回收器。
    例如:-XX:+UseParallelGC 表示启用并行垃圾回收器。
  8. -XX:+UseConcMarkSweepGC: 启用并发标记清除垃圾回收器。
    例如:-XX:+UseConcMarkSweepGC 表示启用并发标记清除垃圾回收器。
  9. -XX:MaxGCPauseMillis: 设置垃圾回收的最大停顿时间。
    例如:-XX:MaxGCPauseMillis=100 表示垃圾回收的最大停顿时间为100毫秒。
  10. -XX:+DisableExplicitGC: 禁用显式调用System.gc()方法触发垃圾回收。
    例如:-XX:+DisableExplicitGC 表示禁用显式调用System.gc()方法。

这些只是一些常用的JVM参数设置,具体的参数根据应用需求和系统环境可能会有所不同。可以根据具体情况进行调整和优化,以获得最佳的性能和资源利用。

Docker

Docker 容器只是操作系统的一个进程(可能受资源限制),后期 Java8 可以自动设置合理的内存和CPU数量

参考文档

https://dmetzler.github.io/troubleshooting-java-apps-in-k8s/
https://www.baeldung.com/jvm-parameters
https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/clopts001.html
https://github.com/peachyy/jvmdump4k8s