JVM 8的启动参数

图片来自pixabay.com的Myriams-Fotos会员

1. JVM版本

本文所描述的启动参数在如下JVM版本测试通过,

java version "1.8.0_172"
Java(TM) SE Runtime Environment (build 1.8.0_172-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.172-b11, mixed mode)

启动参数的推荐值主要针对的场景为:4核CPU + 4G内存的机器配置,部署单应用,应用的堆内存使用量在1-2GB。

2. 服务器配置

参数名 说明 默认值 推荐值 备注
server Java Hotsport Server VM 64位机器默认为server选项 启用

3. 堆大小

参数名 说明 默认值 推荐值 备注
Xms 堆空间初始大小 若没有设置,则根据old+young计算得出 Xms2G 堆的大小根据物理内存配置,不大于所能提供的物理内存
Xmx 堆空间最大值 根据系统配置 Xmx3G
XX:NewRatio Old/Young的空间比例 2 XX:NewRatio=2
XX:SurvivorRatio Eden/Survivor的空间比例 8 XX:SurvivorRatio=8
XX:+UseAdaptiveSizePolicy 分代空间动态调整 启用 启用 年轻代占整个堆内存三分之一,Survivor区占整个年轻代十分之一。(为推荐值,实际JVM会动态进行调优)

4. 元空间和线程栈大小

参数名 说明 默认值 推荐值 备注
XX:MaxMetaspaceSize 元数据空间大小 无上限 XX:MaxMetaspaceSize=256M
Xss 线程栈空间大小 根据virtual memory计算而定 Xss256K 栈的大小根据应用所需并发线程数决定,1000线程*256KB = 256 MB。若线程数大于2000,可以考虑配置为128KB。

5. GC回收器选择

5.1 串行GC回收器

不推荐为生产环境的配置,主要是由于无法利用现代计算机的多核优势。

适合的应用场景,

  1. 对性能要求不要高的简单客户端应用,开发环境
  2. 堆内存设置不大(<200MB)
  3. 物理机器是单核CPU

5.2 并行GC回收器

此为JVM 8默认GC回收器。

适合的应用场景,

  1. 生产环境的后台应用服务器
  2. 系统配置较高,通常情况下至少四核(以目前的硬件水平为准)
  3. 应用程序运行时间较长,对吞吐量要求较高,应用程序使用的堆内存大于1G

5.3 并发GC回收器(年轻代Parallel GC, 老年代CMS GC)

不推荐,JDK9已经移除对CMS GC的支持

5.4 G1回收器

此为JVM 9默认GC回收器。

适合的应用场景,

  1. 生产环境的后台应用服务器
  2. 系统配置较高,通常情况下至少四核(以目前的硬件水平为准)
  3. 应用程序运行时间较长,对吞吐量要求较高,应用程序使用的堆内存大于4G

6. 日志和诊断信息

参数名 说明 默认值 推荐值 备注
XX:+PrintGCDetails
XX:+PrintGCDateStamps
Xloggc:./gc.log
XX:+PrintHeapAtGC
XX:+PrintTenuringDistribution
输出GC日志,以便监控查看 未开启 启用
XX:+HeapDumpOnOutOfMemoryError
XX:HeapDumpPath=./dump.hprof
在OOM时输出内存快照,以便后续问题调查 未开启 启用 相比PrintGCTimeStamps,使用PrintGCDateStamps会更加清晰易懂

7. 启动参数推荐样例

7.1 普通应用

  • 服务器配置:4核CPU + 4G内存,单应用部署。
  • 应用:堆内存使用量在500MB到1GB左右
java -server -Xms2G -Xmx2G -XX:MaxMetaspaceSize=256M -Xss256K -XX:NewRatio=2 -XX:SurvivorRatio=8 -XX:+UseAdaptiveSizePolicy -XX:+UseParallelGC -XX:+UseParallelOldGC -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:../logs/gc.log -XX:+PrintTenuringDistribution  -XX:+PrintHeapAtGC -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=../logs/dump.hprof -jar pphh.jar

7.2 大内存堆的应用

  • 服务器配置:4核CPU + 16G内存,单应用部署,有足够的物理内存
  • 应用:堆内存使用量在2GB左右
java -server -Xms4G -Xmx4G -XX:MaxMetaspaceSize=256M -Xss256K -XX:NewRatio=2 -XX:SurvivorRatio=8 -XX:+UseAdaptiveSizePolicy -XX:+UseG1GC -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:../logs/gc.log -XX:+PrintTenuringDistribution  -XX:+PrintHeapAtGC -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=../logs/dump.hprof -jar pphh.jar

7.3 大内存堆的应用(物理内存出现瓶颈)

  • 服务器配置:4核CPU + 4G内存,部署单应用,
  • 应用:堆内存使用量在2GB左右
  • 物理内存只有4G,应用的堆内存无法设置为4G,并启用JVM的UseStringDeduplication
java -server -Xms3G -Xmx3G -XX:MaxMetaspaceSize=256M -Xss256K -XX:NewRatio=2 -XX:SurvivorRatio=8 -XX:+UseAdaptiveSizePolicy -XX:+UseG1GC -XX:+UseStringDeduplication -XX:+PrintStringDeduplicationStatistics -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:../logs/gc.log -XX:+PrintTenuringDistribution  -XX:+PrintHeapAtGC -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=../logs/dump.hprof -jar pphh.jar

7.4 JVM7的普通应用配置

  • 服务器配置:4核CPU + 4G内存,单应用部署
  • 应用:堆内存使用量在500MB到1GB左右
java -server -Xms2G -Xmx2G -XX:MaxPermSize=256M -Xss256K -XX:NewRatio=2 -XX:SurvivorRatio=8 -XX:+UseAdaptiveSizePolicy -XX:+UseParallelGC -XX:+UseParallelOldGC -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:../logs/gc.log -XX:+PrintTenuringDistribution  -XX:+PrintHeapAtGC -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=../logs/dump.hprof -jar pphh.jar

8. 参考资料

一个JedisConnectionException的异常跟踪调查

图片来自pixabay.com的spencerlikestorun会员

1. 问题缘起

有一个web服务应用一直在稳定运行,直到最近一个月,陆续三次在晚上出现了JedisConnectionException的异常,服务集群几乎在同一时间段都报同一错误,无法正常运行,重启后一切正常。

redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool
   at redis.clients.util.Pool.getResource(Pool.java:50)
   at redis.clients.jedis.JedisPool.getResource(JedisPool.java:86)
    …
Caused by: java.util.NoSuchElementException: Timeout waiting for idle object
    at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:449)

2. 现场处理和初步调查结论

  • JedisConnectionException异常所报告的问题是,无法正常获取连接,通过查看redis连接池配置(maxTotal=200)和当时的并发流量(并发数小于5),没有发现不对和特别的地方。
  • 通过netstat查看网络TCP连接,redis连接数也不多(小于20),正常连接中,在redis服务器端可以观察到有连接正在陆续正常地新建和关闭,网络没有问题。
  • 查看zabbix和pinpoint监控,主站应用的CPU使用率有升高,不过在正常范围内,但响应时间有飙高(长达6秒),预计是获取redis连接阻塞的原因(在redis客户端设置阻塞时间为6秒)。
  • 为了能够保证主站恢复使用,调查一段时间后,决定重启应用,然后检查所有服务,一切恢复正常使用。

3. 调查方向

根据现象,确定如下几个调查方向,

  1. 查看redis的访问代码实现,是否有redis连接泄漏?并尝试是否可以本地开发环境重现同样问题。
  2. 查看线上配置,是否配置不正确或者未加载的情况?
  3. 查看网络原因,是否由于因为防火墙导致单机连接有限制?
    • 查看iptables
    • 使用tcpdump查看TCP流量
  4. 查看主站应用的请求访问日志和运行日志,查看异常前后有无特别的请求访问?
    • 请求访问日志 localhost_access_log.2018-12-28
    • 运行日志 catalina.out-20181228

Java数据库访问的分片技术架构实现

图片来自pixabay.com的spencerlikestorun会员

最近和朋友同事在一起聊天,问起我正在做的项目-数据访问中间件,很多人都有一丝疑惑,这不是重复造轮子的事情么?业界里ORM框架有mybatis/hiberate,分库分表有sharding-jdbc/mycat等等,你们为什么还要新做一个框架?新做的框架有什么不一样?

对于这些问题,我希望通过本文能够帮助解决这些疑惑。本文主要介绍了数据库访问的技术架构,然后讨论了分片在不同架构层级的技术实现方案,对标业界各个数据库访问中间件,相互进行比较,分析各种架构的实现差别和优缺点。

1. 什么是数据库分片

传统关系型数据库集中存储数据到单一节点,单表可以存储达数亿行的数据记录,通过主从备份作为灾备方案,保证数据的安全性,这基本可以覆盖大多数的应用场景。但是,随着互联网技术的发展,海量数据和高并发访问的应用场景日益增多,单表数据记录在突破一定阈值之后,其性能和可用性大幅下降。为了解决这个问题,将单一节点的数据拆分存储到多个数据库或表,即分库分表,使得关系型数据库能够存储的数据量阈值上限扩大1-2个数量级,从而满足业务需求。

数据库的分片拆分有两种方式,

  1. 按照业务划分的垂直拆分,将不同业务、不同模块的数据拆分为不同表。
  2. 按照容量平衡的水平拆分,将同一表的数据按照一定平衡策略,存储到不同数据库和表中。