Java开发选服务器:8核16G真的适合你吗
"8核16G"是怎么变成默认选项的
不管什么业务,大家选服务器都是8核16G。
日活1000的小工具?8核16G。日活50万的电商后台?8核16G。一个内部管理系统?还是8核16G。
问为什么,答不上来。要么是"别人推荐的",要么是"云厂商默认就是这个"。
8核16G成了一种回避思考的安全选项------低了怕不够,高了嫌贵,8核16G卡在中间,心理上觉得应该没问题。
实际上,很多Java应用4核8G绑绑有余,有些8核16G反而不够用。关键是要算,不是猜。
先搞清楚Java应用怎么吃资源
JVM内存不等于你设的那个数
很多同学以为:分配16G内存,Java就能用16G。
不对。JVM进程实际占用的物理内存(RSS)通常比你设的堆内存(-Xmx)大不少,具体大多少取决于线程数、Metaspace使用情况、NIO直接内存等。常见的项目里,RSS大约是-Xmx的1.5到2倍。
JVM内存分布:
堆内存(-Xmx) ← 对象实例存放的地方,占大头
├── 新生代(Eden + Survivor)
└── 老年代
非堆内存 ← 容易被忽略
├── Metaspace ← 类元数据
├── 线程栈 ← 每个线程默认1MB
├── 直接内存 ← NIO用的DirectByteBuffer
└── JIT编译缓存
举个例子:
bash
# JVM参数
-Xmx2g -Xms2g -XX:MetaspaceSize=256m
堆设了2G,但实际进程可能占3-4G:
bash
# 查看Java进程实际内存
ps -p <PID> -o pid,rss,vsz,comm
PID RSS VSZ COMM
12345 3840000 12500000 java
RSS约3.7GB,比-Xmx的2G多了一倍左右。
所以选服务器内存的时候,别只看堆设了多少。堆内存 × 2,再留1-2G给操作系统,大概就是你要的服务器内存。
CPU和并发的关系
Spring Boot默认用Tomcat,线程池最大200个线程:
yaml
server:
tomcat:
threads:
max: 200
200个线程不意味着需要200个CPU核心。大部分Java Web应用是IO密集型的------线程在等数据库、等下游接口的时候会让出CPU,其他线程接着用。8核CPU通过时间片轮转,能"撑住"远超8个的并发线程。
实际看一下就知道了:
bash
top -bn1 | grep java
perl
PID %CPU %MEM COMMAND
12345 12.3 8.5 java
CPU才用了12%。8核远远用不满,4核可能都够。
不同业务场景的实际资源需求
以下是根据实际项目整理的参考,不是精确值,但能给个方向。
内部管理系统
erlang
特征:日活几十到几百,CRUD为主
CPU使用率:3-8%
实际RSS:1-1.5GB(堆512MB就够)
推荐:2核4G
JVM:-Xmx512m -Xms512m
普通业务API
erlang
特征:日活几千到几万,有数据库和Redis
CPU使用率:10-25%
实际RSS:2-4GB(堆1-2G)
推荐:4核8G
JVM:-Xmx2g -Xms2g
中型Web应用
erlang
特征:日活十万级,较高并发
CPU使用率:20-50%
实际RSS:4-8GB(堆2-4G)
推荐:8核16G
JVM:-Xmx4g -Xms4g
高并发API服务
特征:日活百万级,高并发
推荐:多实例4核8G 或 单台16核32G
大部分中小型Java Web应用,4核8G起步就够了。
一个真实的选型经历
之前帮一个做SaaS的朋友选服务器。Spring Boot应用,日活大约2000,主要是表单提交、数据查询、报表导出。
他本来要买8核16G,月费800左右。
我让他先开一台2核4G跑一下看数据。用Prometheus监控了3天:
erlang
CPU使用率:平均6%,峰值18%
内存RSS:约1.8GB,堆峰值约600MB
网络IO:峰值约2Mbps
2核4G绑绑有余。最后选了4核8G留余量,月费300多。一年省了将近6000块,他拿这个钱多买了一台做冷备,反而提升了可用性。
怎么判断你的配置够不够
压测
上线前最靠谱的方法。用wrk或JMeter模拟真实流量:
bash
# wrk简单压测:4线程100并发,持续60秒
wrk -t4 -c100 -d60s -s post.lua http://localhost:8080/api/query
bash
# JMeter复杂场景:多个接口混合压测
jmeter -n -t test_plan.jmx -l result.jtl -e -o ./report
压测时关注:
erlang
□ CPU峰值是否超过70%?超过考虑加核
□ RSS是否超过服务器内存的80%?超过考虑加内存
□ Full GC频率高不高?可能需要调JVM或加内存
□ 接口P99响应时间是否在可接受范围
上线后监控
压测模拟不了所有场景,上线后要持续看数据。
bash
# JVM堆内存使用情况(JDK 9+用jcmd替代jmap)
jcmd <PID> GC.heap_info
arduino
garbage-first heap total 3145728K, used 640000K [0x0000000700000000, 0x0000000800000000)
region size 1024K, 120 young (122880K), 6 survivors (6144K)
堆总共3G,用了640MB,使用率约20%。如果长期低于50%,说明堆设大了,可以减小。
bash
# GC统计(JDK 9+)
jcmd <PID> GC.stat
bash
# 或者用jstat(JDK 8常用,JDK 9+仍然可用但建议迁移到jcmd)
jstat -gcutil <PID> 5000
S0 S1 E O M CCS YGC YGCT FGC FGCT
0.00 32.15 45.67 28.91 95.32 92.1 128 1.234 2 0.456
重点看这几个:
- O列(老年代使用率):持续涨到80%以上,可能有内存泄漏
- FGC列(Full GC次数):频繁Full GC说明内存不够或有泄漏
- YGCT/YGC(年轻代GC耗时/次数):单次耗时过长说明年轻代可能设太大了
JVM参数和服务器配置怎么配合
服务器选好了,JVM参数也要配对。配错了硬件再好也白搭。
JDK 8
bash
java \
-Xmx3g -Xms3g \
-XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=256m \
-XX:+UseG1GC -XX:MaxGCPauseMillis=200 \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/var/log/java/heapdump.hprof \
-XX:+PrintGCDetails -XX:+PrintGCDateStamps \
-Xloggc:/var/log/java/gc.log \
-jar app.jar
JDK 11及以上
JDK 9开始GC日志参数换了,旧的-XX:+PrintGCDetails和-Xloggc会报警告甚至报错。用-Xlog统一替代:
bash
java \
-Xmx3g -Xms3g \
-XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=256m \
-XX:+UseG1GC -XX:MaxGCPauseMillis=200 \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/var/log/java/heapdump.hprof \
-Xlog:gc*:file=/var/log/java/gc.log:time,uptime:filecount=5,filesize=10m \
-jar app.jar
不同配置对应的JVM参数
2核4G: -Xmx1g -Xms1g
4核8G: -Xmx3g -Xms3g
8核16G: -Xmx6g -Xms6g
16核32G:-Xmx12g -Xms12g
经验法则:堆内存设为服务器内存的35%-40%。留够空间给Metaspace、线程栈、直接内存和操作系统。
8G的机器堆设6G甚至7G,系统内存会很紧张,容易触发OOM Killer------操作系统看你内存不够直接把进程杀了。那种排查起来很迷惑,Java日志里什么都没有,进程就消失了。
bash
# 检查是不是被OOM Killer杀了
dmesg | grep -i "oom\|kill"
如果看到类似Out of memory: Kill process 12345 (java),就是操作系统杀的,不是JVM的问题。
多实例比单机高配更靠谱
这是我想特别说的一点。
css
方案A:单台8核16G → 月费约600-800元
方案B:两台4核8G → 月费约300×2 = 600元
同样的钱,方案B多了一台机器的冗余。A机挂了服务就断了,B方案挂了一台另一台还撑着。
而且双实例可以搭负载均衡做滚动更新,发布不停机。
nginx
# nginx负载均衡
upstream backend {
server 10.0.1.10:8080;
server 10.0.1.11:8080;
}
server {
listen 80;
location / {
proxy_pass http://backend;
}
}
更新的时候先摘一台、更新、验证没问题、挂回去,再摘另一台。整个过程用户无感知。
除非你的应用有特殊的单机需求(比如本地缓存一致性),否则优先选多实例。
什么时候确实需要高配
也不是所有场景都适合低配。以下情况确实需要往上加:
JVM堆需要8G以上。 大量对象缓存在内存里,或者做复杂的数据处理和报表生成,堆要大。
CPU密集型计算。 图片处理、PDF生成、大量正则、复杂的规则引擎,CPU是瓶颈。
同一台机器跑多个服务。 应用+MySQL+Redis挤在一台机器上,资源被多个进程分摊。建议分开部署,别混在一起。
总结
| 场景 | 推荐配置 | 月费参考 |
|---|---|---|
| 内部管理系统 | 2核4G | 100-200元 |
| 普通业务API | 4核8G | 200-400元 |
| 中型Web应用 | 8核16G | 400-800元 |
| 高并发服务 | 多实例4核8G | 600-800元(双实例) |
大部分Java Web应用4核8G起步足够。省下来的钱不如多加一台做冗余。
选配置的逻辑就一句话:先压测再选配,先小后大按需升级。 不要拍脑袋选8核16G。根据实际数据做决定,既不浪费也不委屈。
下一篇聊:TCP拥塞控制对你的业务有什么影响?从Reno到BBR ------ 为什么带宽明明没跑满但传输速度就是上不去?