服务器时区和JAVA进程时区不一致问题解决

[TOC]

问题说明

JAVA进程在运行过程中发现和当前时间相差8小时,检查服务器时间和互联网的北京时间一致,由此推测操作系统时区不对,经过查看操作系统时区,发现时区正确,通过jinfo命令查看Java进程发现时区不是东八区,由此找到原因,在此把排查过程做简要记录,便于后续遇到问题快速解决。

中国跨越了东五区、东六区、东七区、东八区、东九区五个时区,一般都统一采用东八区计时时间。

查看操作系统当前时间

[root@swk-204 ~]# date
Fri Jan 25 19:28:28 CST 2019
[root@swk-204 ~]# date "+%Y-%m-%d %H:%M:%S"
2019-01-25 19:28:36
[root@swk-204 ~]# 

查看操作系统当前时区

方式一

[root@swk-204 ~]# date -R
Fri, 25 Jan 2019 19:04:13 +0800

-0800表示西八区,是美国旧金山所在的时区,+0800表示东八区,是中国上海所在的时区

方式二

[root@swk-204 ~]# date "+%Z"
CST

方式三

[root@swk-204 ~]# date
Fri Jan 25 19:05:43 CST 2019

方式四

[root@engine ~]# cat /etc/localtime 
TZif2 
     °þǜɺ'pʕ˛Z򞸶p ~h!Iap"^J#)Cp$Gg%_򤦉&񂮨+(У񿿂q侐LMTCDTCSTTZif2 
                                                           ÿÿÿÿ°þÿÿÿÿǜÿÿÿÿɺ'pÿÿÿÿʕÿÿÿÿ˛Z򞸶p ~h!Iap"^J#)Cp$Gg%_򤦉&񂮨+(У񿿂q侐LMTCDTCST
CST-8

时区为CST-8

查看JAVA组件进程时区

方式一

通过jvisualvm即可得到

2019-01-25_193830.png

方式二

通过组件名称得到进程号

[root@engine bin]# jps -lm |grep zkui
1728 ./zkui-2.0-SNAPSHOT-jar-with-dependencies.jar

通过jinfo命令查看运行中jvm的全部参数

[root@engine bin]# jinfo 1728
Attaching to process ID 1728, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.71-b15
Java System Properties:

java.runtime.name = Java(TM) SE Runtime Environment
java.vm.version = 25.71-b15
sun.boot.library.path = /usr/java/jdk1.8.0_71/jre/lib/amd64
java.vendor.url = http://java.oracle.com/
java.vm.vendor = Oracle Corporation
path.separator = :
file.encoding.pkg = sun.io
java.vm.name = Java HotSpot(TM) 64-Bit Server VM
sun.os.patch.level = unknown
sun.java.launcher = SUN_STANDARD
user.country = US
user.dir = /iflytek/server/zkui2.0
java.vm.specification.name = Java Virtual Machine Specification
java.runtime.version = 1.8.0_71-b15
java.awt.graphicsenv = sun.awt.X11GraphicsEnvironment
os.arch = amd64
java.endorsed.dirs = /usr/java/jdk1.8.0_71/jre/lib/endorsed
java.io.tmpdir = /tmp
line.separator = 

java.vm.specification.vendor = Oracle Corporation
os.name = Linux
sun.jnu.encoding = UTF-8
java.library.path = /usr/java/packages/lib/amd64:/usr/lib64:/lib64:/lib:/usr/lib
java.specification.name = Java Platform API Specification
java.class.version = 52.0
sun.management.compiler = HotSpot 64-Bit Tiered Compilers
os.version = 2.6.32-431.el6.x86_64
user.home = /root
user.timezone = GMT
java.awt.printerjob = sun.print.PSPrinterJob
file.encoding = UTF-8
java.specification.version = 1.8
user.name = root
java.class.path = ./zkui-2.0-SNAPSHOT-jar-with-dependencies.jar
java.vm.specification.version = 1.8
sun.arch.data.model = 64
sun.java.command = ./zkui-2.0-SNAPSHOT-jar-with-dependencies.jar
java.home = /usr/java/jdk1.8.0_71/jre
user.language = en
java.specification.vendor = Oracle Corporation
awt.toolkit = sun.awt.X11.XToolkit
java.vm.info = mixed mode
java.version = 1.8.0_71
java.ext.dirs = /usr/java/jdk1.8.0_71/jre/lib/ext:/usr/java/packages/lib/ext
sun.boot.class.path = /usr/java/jdk1.8.0_71/jre/lib/resources.jar:/usr/java/jdk1.8.0_71/jre/lib/rt.jar:/usr/java/jdk1.8.0_71/jre/lib/sunrsasign.jar:/usr/java/jdk1.8.0_71/jre/lib/jsse.jar:/usr/java/jdk1.8.0_71/jre/lib/jce.jar:/usr/java/jdk1.8.0_71/jre/lib/charsets.jar:/usr/java/jdk1.8.0_71/jre/lib/jfr.jar:/usr/java/jdk1.8.0_71/jre/classes
java.vendor = Oracle Corporation
file.separator = /
java.vendor.url.bug = http://bugreport.sun.com/bugreport/
sun.io.unicode.encoding = UnicodeLittle
sun.cpu.endian = little
sun.cpu.isalist = 

VM Flags:
Non-default VM flags: -XX:CICompilerCount=15 -XX:InitialHeapSize=1056964608 -XX:MaxHeapSize=16888365056 -XX:MaxNewSize=5629280256 -XX:MinHeapDeltaBytes=524288 -XX:NewSize=352321536 -XX:OldSize=704643072 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:+UseParallelGC 
Command line:  

通过grep关键字进行过滤

[root@engine bin]# jinfo 1728 |grep user.timezone
user.timezone = GMT

时区为GMT

问题分析

知识储备

通过查询相关资料:

  1. GMT(Greenwich Mean Time,格林威治标准时间): 是指位于英国伦敦郊区的皇家格林尼治天文台的标准时间,因为本初子午线被定义在通过那里的经线。
  2. UTC(Universal Time/Temps Cordonné 世界标准时间)
  3. CST可以为如下4个不同的时区的缩写:美国、澳大利亚、古巴或中国的标准时间
    • 美国中部时间:Central Standard Time (USA) UT-6:00
    • 澳大利亚中部时间:Central Standard Time (Australia) UT+9:30
    • 中国标准时间:China Standard Time UT+8:00
    • 古巴标准时间:Cuba Standard Time UT-4:00

以上换算关系:GMT + 8 = UTC + 8 = CST-8

UTC 是指当前使用的时间系统为世界标准时间,也称世界协调时间。英文名称为 Coordinated Universal Time,法文名称为 Temps Universel Coordonné。作为英文缩写 CUT 和法文缩写 TUC 的妥协方案,简称 UTC。中国所处时区为 UTC+8
CST:中国标准时间(China Standard Time),这个解释针对RedHat Linux和CentOS
CET(Central European Time欧洲中部时间)=UTC/GMT + 1

时区对比

通过前两个章节的对比可以发现

操作系统时区为CST-8 <=> JAVA组件进程时区为GMT

故相差8小时属于正常,符合预期

问题解决

重新设置操作系统时区

[root@engine ~]# tzselect
Please identify a location so that time zone rules can be set correctly.
Please select a continent or ocean.
 1) Africa
 2) Americas
 3) Antarctica
 4) Arctic Ocean
 5) Asia
 6) Atlantic Ocean
 7) Australia
 8) Europe
 9) Indian Ocean
10) Pacific Ocean
11) none - I want to specify the time zone using the Posix TZ format.
#? 5
Please select a country.
 1) Afghanistan       18) Israel            35) Palestine
 2) Armenia       19) Japan         36) Philippines
 3) Azerbaijan        20) Jordan            37) Qatar
 4) Bahrain       21) Kazakhstan        38) Russia
 5) Bangladesh        22) Korea (North)     39) Saudi Arabia
 6) Bhutan        23) Korea (South)     40) Singapore
 7) Brunei        24) Kuwait            41) Sri Lanka
 8) Cambodia          25) Kyrgyzstan        42) Syria
 9) China         26) Laos          43) Taiwan
10) Cyprus        27) Lebanon           44) Tajikistan
11) East Timor        28) Macau         45) Thailand
12) Georgia       29) Malaysia          46) Turkmenistan
13) Hong Kong         30) Mongolia          47) United Arab Emirates
14) India         31) Myanmar (Burma)       48) Uzbekistan
15) Indonesia         32) Nepal         49) Vietnam
16) Iran          33) Oman          50) Yemen
17) Iraq          34) Pakistan
#? 9
Please select one of the following time zone regions.
1) east China - Beijing, Guangdong, Shanghai, etc.
2) Heilongjiang (except Mohe), Jilin
3) central China - Sichuan, Yunnan, Guangxi, Shaanxi, Guizhou, etc.
4) most of Tibet & Xinjiang
5) west Tibet & Xinjiang
#? 1

The following information has been given:

    China
    east China - Beijing, Guangdong, Shanghai, etc.

Therefore TZ='Asia/Shanghai' will be used.
Local time is now:  Fri Jan 25 15:10:18 CST 2019.
Universal Time is now:  Fri Jan 25 07:10:18 UTC 2019.
Is the above information OK?
1) Yes
2) No
#? 1

You can make this change permanent for yourself by appending the line
    TZ='Asia/Shanghai'; export TZ
to the file '.profile' in your home directory; then log out and log in again.

Here is that TZ value again, this time on standard output so that you
can use the /usr/bin/tzselect command in shell scripts:
Asia/Shanghai
[root@engine ~]# 

调整时区文件到对应目录

备份当前的时区配置

[root@engine ~]# mv /etc/localtime /etc/localtime-old

替换系统时区文件

[root@engine ~]# cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime

或者创建链接文件

[root@engine ~]# ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime

修改clock系统配置文件/etc/sysconfig/clock为如下内容

[root@swk-204 ~]# cat /etc/sysconfig/clock
ZONE="Asia/Shanghai"
UTC=false                          #设置为false,硬件时钟不于utc时间一致
ARC=false
[root@swk-204 ~]# 

设置操作系统环境变量TZ

/etc/profile~/.bashrc文件中设置环境变量TZ

export TZ='Asia/Shanghai'

或者

TZ='Asia/Shanghai'; export TZ

通过source命令即可完成设置

JAVA进程调整时区

修改Java虚拟机时间

JAVA启动参数

-Duser.timezone=GMT+8

每个java程序启动的时候加参数

JAVA硬编码

import java.util.TimeZone;
import javax.annotation.PostConstruct;
import org.springframework.stereotype.Component;

@Component
public class TimeZoneStartup {  
    /**
     * 设置时区
     */
    @PostConstruct
    public void init(){
        TimeZone.setDefault(TimeZone.getTimeZone("Asia/Shanghai"));
    }
    
}

每个java程序都需要编码

JRE时区加载

JVM的时区默认的是操作系统的时区JVM是从/etc/sysconfig/clock这个文件中 获取时区信息的

获取当前操作系统时区

编辑JAVA文件MainClass.java

import java.util.TimeZone;

public class MainClass {
     public static void main(String[] args) {
        System.out.println(TimeZone.getDefault());
    }
}

执行程序

[root@swk-204 ~]# javac MainClass.java 
[root@swk-204 ~]# java MainClass
sun.util.calendar.ZoneInfo[id="Asia/Shanghai",offset=28800000,dstSavings=0,useDaylight=false,transitions=19,lastRule=null]
[root@swk-204 ~]# 

刨根问底

Sun上面有和我这种情况相关的bug - Default timezone is incorrectly set occasionally on Linux(http://bugs.sun.com/view_bug.do?bug_id=6456628), 里面描述了java vm取的默认timezone的算法

By examining the available JVM source code, I noticed that the logic that works out the default timezone on Linux is incorrect.
The way JVM works out the default timezone is as follows:

1) Looks to environment variable TZ
This is not set in our linux box

2) JVM looks for the file /etc/sysconfig/clock and tries to find the "ZONE" entry.
However, on these host the ZONE entry does not have a double quote around the actual variable, and the JVM code is unable to recongise the entry.

3) If the ZONE entry is not found, the JVM will compare contents fo /etc/localtime with the contents of every file in /usr/share/zoneinfo recursively. When the contents matches, it returns the path and filename, referenced from /usr/share/zoneinfo
On our machine, there are three files in /usr/share/zoneinfo that matches /etc/localtime (these files are standard on RHEL4 machines):
/usr/share/zoneinfo/America/New_York
/usr/share/zoneinfo/posixrules
/usr/share/zoneinfo/EST5EDT

What happens is that depends on the way OS transverse the filesystem, the name that JVM get can be different. On the box that fail the test, the jvm actually found posixrules file first, which means that the JVM thinks the timezone is "posixrules". The JVM will then attempt to look up timezone mapping in <java.home>/jre/lib/zi directory, and it will fail the find the timezone. So it will revert to the default GMT+offset timezone id as a fail safe, which does not take into the account for daylight saving.

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
We have three identical Linux server and ran the test case included below, the time reported by the machine is different.

1)如有环境变量 TZ设置,则用TZ中设置的时区

2)在 /etc/sysconfig/clock文件中找 "ZONE"的值

3)如何2)都没,就用/etc/localtime 和 /usr/share/zoneinfo 下的时区文件进行匹配,如找到匹配的,就返回对应的路径和文件名。

Java TimeZone 和 Linux TimeZone问题参考文档:

echo $TZ

附录

jinfo命令参考文档://www.greatytc.com/p/ece32dacce64

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,544评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,430评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,764评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,193评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,216评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,182评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,063评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,917评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,329评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,543评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,722评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,425评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,019评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,671评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,825评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,729评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,614评论 2 353

推荐阅读更多精彩内容