[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ʕ˛Zp ~h!Iap"^J#)Cp$Gg%_&+(Уq侐LMTCDTCSTTZif2
ÿÿÿÿ°þÿÿÿÿǜÿÿÿÿɺ'pÿÿÿÿʕÿÿÿÿ˛Zp ~h!Iap"^J#)Cp$Gg%_&+(Уq侐LMTCDTCST
CST-8
时区为
CST-8
查看JAVA组件进程时区
方式一
通过jvisualvm
即可得到
方式二
通过组件名称得到进程号
[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
问题分析
知识储备
通过查询相关资料:
- GMT(Greenwich Mean Time,格林威治标准时间): 是指位于英国伦敦郊区的皇家格林尼治天文台的标准时间,因为本初子午线被定义在通过那里的经线。
- UTC(Universal Time/Temps Cordonné 世界标准时间)
- 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