JVM创建一个新对象到底有多大的代价?

本文通过实验探讨了JVM中创建对象的耗时情况,并对比了不同数量级的对象创建所带来的性能影响。结果显示,在实际应用中创建单个对象的耗时几乎可以忽略不计。

        好像是在听一人讲JVM性能调优方面事时, 知道了尽量减少JVM创建对象,因为那样会消耗不少资源. 有了这样的mindset后,自己编码也就尽可能地少创建对象了.

        但看jBPM时,发现每一个请求都要创建一个JbpmContext对象,纳闷的同时也反思先前的那个mindset的正确性,或者说在什么条件下是正确的.

        想到一个例子: Hibernate从数据库取出数据后,都是要以对象的形式来封装的, 那样的每次DB操作至少要创建一个对象的. 这样一想,JbpmContext的创建相比于Hibernate操作DB时对象的创建就可以忽略了.

        做个暴力实验来看下, 创建新对象时JVM的耗时:

        实验用的代码很简单, 如下所示:

for(int i=0;i< 10000;i++) {
	new Object();
}
 



    实验结果如下:

循环次数(也就是创建Object实例的次数)     耗时(毫秒)
1000000000                                             22094
100000000                                                 2282
10000000                                                     219
1000000                                                         31
100000                                                             0
10000                                                               0

现在用的机器配置: Pentium(R) 4 CPU 2.40GHz, 1G内存, 操作系统是 Windows XP(2002版本), SP3.

    从上面的结果看出: 只有在100万次时才有明显的耗时,而这时的耗时也才只31毫秒,而我做实验的机子很破的. 这样在真正的项目中,也就不必多考虑新建一个对象时的耗时了.

    这里只是做了个耗时的实验, 这个耗时背后又倒底做了些什么? 又有哪些是耗时的主犯呢? 抛砖引玉, 期待大家的讨论.

在一台 **64GB 内存的物理机或虚拟机** 上部署 Java 应用时,JVM 参数设置需要综合考虑: - 操作系统自身内存需求 - JVM 堆外内存(Metaspace、Direct Memory、Thread Stack 等) - 是否运行个服务 - GC 类型(推荐 G1GC) - 应用类型(高吞吐?低延迟?数据处理?) --- ## ✅ 一、基本原则 ### 🔹 1. 不要把全部 64G 都分配给 `-Xmx` JVM 堆只是应用内存的一部分。还需预留: | 内存用途 | 建议预留 | |--------|--------| | 操作系统缓存 / 进程管理 | 2~4 GB | | JVM 堆外内存(Metaspace、线程栈等) | 2~8 GB | | 直接内存(NIO Buffer、Netty Pool) | 1~4 GB | | 其他进程(如监控 agent、日志收集) | 1~2 GB | 👉 **建议最堆设为:50~56 GB** --- ### 🔹 2. 推荐使用 G1GC(Garbage-First GC) 适用于堆(> 4GB),可控制停顿时间,避免 CMS 的碎片问题。 --- ### 🔹 3. 避免超过压缩指针阈值(CompressedOops) - 在 64 位 JVM 中,当堆小于约 **32GB** 时,JVM 可启用 **CompressedOops** - 超过 ~32GB 后,对象引用从 4 字节变为 8 字节 → 内存开销增加 15~20% 📌 所以: - 如果你设 `-Xmx=40g` 或更高,**压缩指针会自动失效** - 但可通过 `-XX:ObjectAlignmentInBytes=8` + 小技巧让 CompressedOops 继续工作到 ~42.9GB(见后文说明) > 💡 实际上,现代 JVM(JDK 8u20+ / JDK 11+)支持通过调整对象对齐方式来延长 CompressedOops 的有效范围。 --- ## ✅ 二、推荐配置方案(单个 JVM 实例) 假设: - 主机总内存:64 GB - 单个 Java 服务(如 Spring Boot、Kafka、Elasticsearch 等) - 使用 JDK 8 或 JDK 11+ ```bash # 堆小:建议 50g ~ 56g -Xms50g -Xmx50g # 启用 G1GC -XX:+UseG1GC # 控制最 GC 停顿时间目标(软实时) -XX:MaxGCPauseMillis=200 # 提前触发并发标记周期(防止 late marking 导致 Full GC) -XX:InitiatingHeapOccupancyPercent=45 # 启用自适应 IHOP(智能预测并发启动时机) -XX:+G1UseAdaptiveIHOP # 新生代比例(G1 自适应,也可手动限定) -XX:G1NewSizePercent=30 -XX:G1MaxNewSizePercent=40 # 并发线程数(根据 CPU 核心数调整,比如 16 核以上) -XX:ConcGCThreads=8 -XX:ParallelGCThreads=16 # Metaspace 小(防止动态扩容影响性能) -XX:MetaspaceSize=512m -XX:MaxMetaspaceSize=1g # 关闭显式 GC 的干扰(除非你知道你在做什么) -XX:+ExplicitGCInvokesConcurrent # GC 日志输出(JDK 9+ 统一日志格式) -Xlog:gc*:file=/var/log/app/gc.log:time,uptime,level,tags:filecount=10,filesize=100M # 或 JDK 8 写法: # -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:gc.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=100M ``` --- ## ✅ 三、关于 “超过 32GB 还能用压缩指针吗?” —— 高级技巧 虽然默认情况下超过 ~32GB 会导致 `UseCompressedOops=false`,但在某些条件下仍可保留压缩指针: ### ✅ 方法:调整对象对齐和堆上限 ```bash -Xmx50g -XX:+UseG1GC -XX:+UseCompressedOops -XX:ObjectAlignmentInBytes=16 ``` > ⚠️ 注意:`ObjectAlignmentInBytes` 默认是 8,只能设为 2 的幂(8, 16, 32...),且会影响内存布局。 #### 效果: - 最支持堆小提升至约 **56GB ~ 57GB**(理论极限 ~64GB × 0.875) - 但代价是:**每个对象至少浪费 8 字节填充空间** - 实际收益可能不如预期,需压测验证 📌 **结论**:一般不推荐强行开启。宁愿接受指针膨胀,换取更的可用堆。 --- ## ✅ 四、线程栈与线程数估算 每个线程默认占用约 **1MB 栈空间**(由 `-Xss` 控制): ```bash -Xss1m # 默认值,可调小为 512k 以节省内存 ``` 若应用创建量线程(如 Tomcat 线程池、ForkJoinPool),需注意: | 线程数 | 总栈内存 | |-------|---------| | 1000 | 1 GB | | 2000 | 2 GB | 👉 建议: - 减少线程池小,改用异步/响应式编程 - 必要时降低 `-Xss` 到 `512k` --- ## ✅ 五、典型场景配置示例 ### 🌐 场景 1:Spring Boot Web 服务(中高并发) ```bash -Xms16g -Xmx16g -XX:+UseG1GC -XX:MaxGCPauseMillis=100 -XX:InitiatingHeapOccupancyPercent=40 -XX:G1NewSizePercent=30 -XX:G1MaxNewSizePercent=40 -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m -Xss512k -Xlog:gc*:file=logs/gc.log:time,uptime:filecount=10,filesize=100M ``` > ✅ 适合微服务架构中的普通业务服务。 --- ### 📊 场景 2:Elasticsearch / Kafka / Spark Executor(数据组件) ```bash -Xms50g -Xmx50g -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:InitiatingHeapOccupancyPercent=45 -XX:+G1UseAdaptiveIHOP -XX:G1HeapRegionSize=32m # 对象时可增 Region -XX:ConcGCThreads=10 -XX:ParallelGCThreads=20 -XX:MetaspaceSize=512m -XX:MaxMetaspaceSize=1g -Xlog:gc*:file=/var/log/es/gc.log:time,uptime,level,tags:filecount=10,filesize=100M ``` > ✅ 专用于需要缓存、高吞吐的数据节点。 --- ## ✅ 六、如何验证参数是否生效? ### 1. 查看实际使用的 GC 和堆小 ```bash jcmd <pid> VM.flags | grep -i "gc\|heap\|xmn\|xms" ``` 输出应包含: ``` -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -Xms50g -Xmx50g ``` ### 2. 查看是否启用 CompressedOops ```bash jcmd <pid> VM.system_properties | grep UseCompressedOops ``` 或查看 GC 日志开头是否有: ``` [Global flags] ... UseCompressedOops := true ``` --- ## ✅ 七、总结:64G 机器 JVM 设置建议 | 项目 | 推荐值 | |------|--------| | `-Xms` / `-Xmx` | `50g` ~ `56g`(留出系统和其他内存) | | GC 类型 | `-XX:+UseG1GC` | | 最停顿 | `-XX:MaxGCPauseMillis=200` | | 触发并发标记 | `-XX:InitiatingHeapOccupancyPercent=45` | | 新生代控制 | `-XX:G1NewSizePercent=30`, `-XX:G1MaxNewSizePercent=40` | | Metaspace | `-XX:MetaspaceSize=512m -XX:MaxMetaspaceSize=1g` | | 线程栈 | `-Xss512k`(如有量线程) | | GC 日志 | 必须开启,便于分析 | > ✅ **核心原则:不要把所有内存都塞给堆;优先保障稳定性和可观测性。** --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值