[TOC]
問(wèn)題說(shuō)明
JAVA進(jìn)程在運(yùn)行過(guò)程中發(fā)現(xiàn)和當(dāng)前時(shí)間相差8小時(shí),檢查服務(wù)器時(shí)間和互聯(lián)網(wǎng)的北京時(shí)間一致匹厘,由此推測(cè)操作系統(tǒng)時(shí)區(qū)不對(duì)嘀趟,經(jīng)過(guò)查看操作系統(tǒng)時(shí)區(qū),發(fā)現(xiàn)時(shí)區(qū)正確愈诚,通過(guò)jinfo
命令查看Java進(jìn)程發(fā)現(xiàn)時(shí)區(qū)不是東八區(qū)她按,由此找到原因,在此把排查過(guò)程做簡(jiǎn)要記錄炕柔,便于后續(xù)遇到問(wèn)題快速解決酌泰。
中國(guó)跨越了東五區(qū)、東六區(qū)匕累、東七區(qū)陵刹、東八區(qū)、東九區(qū)五個(gè)時(shí)區(qū)欢嘿,一般都統(tǒng)一采用東八區(qū)計(jì)時(shí)時(shí)間衰琐。
查看操作系統(tǒng)當(dāng)前時(shí)間
[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 ~]#
查看操作系統(tǒng)當(dāng)前時(shí)區(qū)
方式一
[root@swk-204 ~]# date -R
Fri, 25 Jan 2019 19:04:13 +0800
-0800表示西八區(qū),是美國(guó)舊金山所在的時(shí)區(qū),+0800表示東八區(qū)炼蹦,是中國(guó)上海所在的時(shí)區(qū)
方式二
[root@swk-204 ~]# date "+%Z"
CST
方式三
[root@swk-204 ~]# date
Fri Jan 25 19:05:43 CST 2019
方式四
[root@engine ~]# cat /etc/localtime
TZif2
°tǜ?'p??Z??p ~h!Iap"^J#)Cp$Gg%_??&??+(У??q侐LMTCDTCSTTZif2
????°t????ǜ?????'p??????????Z??p ~h!Iap"^J#)Cp$Gg%_??&??+(У??q侐LMTCDTCST
CST-8
時(shí)區(qū)為
CST-8
查看JAVA組件進(jìn)程時(shí)區(qū)
方式一
通過(guò)jvisualvm
即可得到
方式二
通過(guò)組件名稱得到進(jìn)程號(hào)
[root@engine bin]# jps -lm |grep zkui
1728 ./zkui-2.0-SNAPSHOT-jar-with-dependencies.jar
通過(guò)jinfo
命令查看運(yùn)行中jvm的全部參數(shù)
[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:
通過(guò)grep
關(guān)鍵字進(jìn)行過(guò)濾
[root@engine bin]# jinfo 1728 |grep user.timezone
user.timezone = GMT
時(shí)區(qū)為
GMT
問(wèn)題分析
知識(shí)儲(chǔ)備
通過(guò)查詢相關(guān)資料:
- GMT(Greenwich Mean Time羡宙,格林威治標(biāo)準(zhǔn)時(shí)間): 是指位于英國(guó)倫敦郊區(qū)的皇家格林尼治天文臺(tái)的標(biāo)準(zhǔn)時(shí)間,因?yàn)楸境踝游缇€被定義在通過(guò)那里的經(jīng)線框弛。
- UTC(Universal Time/Temps Cordonné 世界標(biāo)準(zhǔn)時(shí)間)
- CST可以為如下4個(gè)不同的時(shí)區(qū)的縮寫:美國(guó)辛辨、澳大利亞、古巴或中國(guó)的標(biāo)準(zhǔn)時(shí)間
- 美國(guó)中部時(shí)間:Central Standard Time (USA) UT-6:00
- 澳大利亞中部時(shí)間:Central Standard Time (Australia) UT+9:30
- 中國(guó)標(biāo)準(zhǔn)時(shí)間:China Standard Time UT+8:00
- 古巴標(biāo)準(zhǔn)時(shí)間:Cuba Standard Time UT-4:00
以上換算關(guān)系:GMT + 8 = UTC + 8 = CST-8
UTC 是指當(dāng)前使用的時(shí)間系統(tǒng)為世界標(biāo)準(zhǔn)時(shí)間,也稱世界協(xié)調(diào)時(shí)間斗搞。英文名稱為 Coordinated Universal Time指攒,法文名稱為 Temps Universel Coordonné。作為英文縮寫 CUT 和法文縮寫 TUC 的妥協(xié)方案僻焚,簡(jiǎn)稱 UTC允悦。中國(guó)所處時(shí)區(qū)為 UTC+8
CST:中國(guó)標(biāo)準(zhǔn)時(shí)間(China Standard Time),這個(gè)解釋針對(duì)RedHat Linux和CentOS
CET(Central European Time歐洲中部時(shí)間)=UTC/GMT + 1
時(shí)區(qū)對(duì)比
通過(guò)前兩個(gè)章節(jié)的對(duì)比可以發(fā)現(xiàn)
操作系統(tǒng)時(shí)區(qū)為CST-8 <=> JAVA組件進(jìn)程時(shí)區(qū)為GMT
故相差8小時(shí)屬于正常虑啤,符合預(yù)期
問(wèn)題解決
重新設(shè)置操作系統(tǒng)時(shí)區(qū)
[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 ~]#
調(diào)整時(shí)區(qū)文件到對(duì)應(yīng)目錄
備份當(dāng)前的時(shí)區(qū)配置
[root@engine ~]# mv /etc/localtime /etc/localtime-old
替換系統(tǒng)時(shí)區(qū)文件
[root@engine ~]# cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
或者創(chuàng)建鏈接文件
[root@engine ~]# ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
修改clock系統(tǒng)配置文件/etc/sysconfig/clock
為如下內(nèi)容
[root@swk-204 ~]# cat /etc/sysconfig/clock
ZONE="Asia/Shanghai"
UTC=false #設(shè)置為false隙弛,硬件時(shí)鐘不于utc時(shí)間一致
ARC=false
[root@swk-204 ~]#
設(shè)置操作系統(tǒng)環(huán)境變量TZ
在/etc/profile
或~/.bashrc
文件中設(shè)置環(huán)境變量TZ
export TZ='Asia/Shanghai'
或者
TZ='Asia/Shanghai'; export TZ
通過(guò)source
命令即可完成設(shè)置
JAVA進(jìn)程調(diào)整時(shí)區(qū)
修改Java虛擬機(jī)時(shí)間
JAVA啟動(dòng)參數(shù)
-Duser.timezone=GMT+8
每個(gè)java程序啟動(dòng)的時(shí)候加參數(shù)
JAVA硬編碼
import java.util.TimeZone;
import javax.annotation.PostConstruct;
import org.springframework.stereotype.Component;
@Component
public class TimeZoneStartup {
/**
* 設(shè)置時(shí)區(qū)
*/
@PostConstruct
public void init(){
TimeZone.setDefault(TimeZone.getTimeZone("Asia/Shanghai"));
}
}
每個(gè)java程序都需要編碼
JRE時(shí)區(qū)加載
JVM的時(shí)區(qū)默認(rèn)的是操作系統(tǒng)的時(shí)區(qū)JVM
是從/etc/sysconfig/clock
這個(gè)文件中 獲取時(shí)區(qū)信息的
獲取當(dāng)前操作系統(tǒng)時(shí)區(qū)
編輯JAVA
文件MainClass.java
import java.util.TimeZone;
public class MainClass {
public static void main(String[] args) {
System.out.println(TimeZone.getDefault());
}
}
執(zhí)行程序
[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 ~]#
刨根問(wèn)底
Sun上面有和我這種情況相關(guān)的bug - Default timezone is incorrectly set occasionally on Linux(http://bugs.sun.com/view_bug.do?bug_id=6456628), 里面描述了java vm取的默認(rèn)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)如有環(huán)境變量 TZ設(shè)置,則用TZ中設(shè)置的時(shí)區(qū)
2)在 /etc/sysconfig/clock文件中找 "ZONE"的值
3)如何2)都沒(méi)狞山,就用/etc/localtime 和 /usr/share/zoneinfo 下的時(shí)區(qū)文件進(jìn)行匹配全闷,如找到匹配的,就返回對(duì)應(yīng)的路徑和文件名萍启。
Java TimeZone 和 Linux TimeZone問(wèn)題參考文檔:
echo $TZ
附錄
jinfo
命令參考文檔:http://www.reibang.com/p/ece32dacce64