一,如何正確的關閉遊戲服務器java
1,最簡單粗爆的方法linux
在Linux系統上,使用ps -aux|grep java 能夠查到全部運行的java程序的pid,即進程號,而後使用kill - 9 進程號,殺死一個進程。數據庫
這樣作雖然簡單快速,可是會有一個問題,若是咱們運行的服務器有緩存的數據,尚未來得及進行持久化存儲,那麼這樣操做,內存中的數據就會丟失。kill - 9是一個必殺命令,無論進程處於什麼狀態,都是殺無赦,它不會給進程留下任何善後的機會。那麼該如何正確的關閉遊戲服務器吧?緩存
2,優雅的關閉進程bash
優雅的關閉進程,就是在收到關閉進程的命令後,進程進行一些數據處理,好比:服務器
1,再也不接收鏈接併發
2,再也不接收數據ide
3,把未持久化的數據進行持久化函數
4,清理一些臨時文件等this
5,執行一些已經提交到線程池中但未執行的任務
3,Java進程如何接收進程中止命令
1,JVM 關閉鉤子
關閉鉤子本質上是一個線程(也稱爲Hook線程),用來監聽JVM的關閉。經過使用Runtime的addShutdownHook(Thread hook)能夠向JVM註冊一個關閉鉤子。Hook線程在JVM 正常關閉纔會執行,在強制關閉時不會執行。
這個鉤子能夠在一下幾種場景中被調用:
1. 程序正常退出
2. 使用System.exit()
3. 終端使用Ctrl+C觸發的中斷
4. 系統關閉
5. OutOfMemory宕機
6. 使用Kill pid命令幹掉進程(注:在使用kill -9 pid時,是不會被調用的)
對於一個JVM中註冊的多個關閉鉤子它們將會併發執行,因此JVM並不能保證它的執行順行。當全部的Hook線程執行完畢後,若是此時runFinalizersOnExit爲true,那麼JVM將先運行終結器,而後中止。Hook線程會延遲JVM的關閉時間,這就要求在編寫鉤子過程當中必需要儘量的減小Hook線程的執行時間。另外因爲多個鉤子是併發執行的,那麼極可能由於代碼不當致使出現競態條件或死鎖等問題,爲了不該問題,強烈建議在一個鉤子中執行一系列操做。
另外在使用關閉鉤子還要注意如下幾點:
1. 不能在鉤子調用System.exit(),不然卡住JVM的關閉過程,可是能夠調用Runtime.halt()。
2. 不能再鉤子中再進行鉤子的添加和刪掉操做,不然將會拋出IllegalStateException。
3. 在System.exit()以後添加的鉤子無效。
4. 當JVM收到SIGTERM命令(好比操做系統在關閉時)後,若是鉤子線程在必定時間沒有完成,那麼Hook線程可能在執行過程當中被終止。
5. Hool線程中一樣會拋出異常,若是拋出異常又不處理,那麼鉤子的執行序列就會被中止。
下面是一個簡單的示例:
public class T { @SuppressWarnings("deprecation") public static void main(String[] args) throws Exception { //啓用退出JVM時執行Finalizer Runtime.runFinalizersOnExit(true); MyHook hook1 = new MyHook("Hook1"); MyHook hook2 = new MyHook("Hook2"); MyHook hook3 = new MyHook("Hook3");
//註冊關閉鉤子 Runtime.getRuntime().addShutdownHook(hook1); Runtime.getRuntime().addShutdownHook(hook2); Runtime.getRuntime().addShutdownHook(hook3);
//移除關閉鉤子 Runtime.getRuntime().removeShutdownHook(hook3);
//Main線程將在執行這句以後退出 System.out.println("Main Thread Ends."); } }
class MyHook extends Thread { private String name; public MyHook (String name) { this.name = name; setName(name); } public void run() { System.out.println(name + " Ends."); } //重寫Finalizer,將在關閉鉤子後調用 protected void finalize() throws Throwable { System.out.println(name + " Finalize."); } } |
和(可能的)執行結果(由於JVM不保證關閉鉤子的調用順序,所以結果中的第2、三行可能出現相反的順序):
Main Thread Ends.
Hook2 Ends.
Hook1 Ends.
Hook3 Finalize.
Hook2 Finalize.
Hook1 Finalize.
能夠看到,main函數執行完成,首先輸出的是Main Thread Ends,接下來執行關閉鉤子,輸出Hook2 Ends和Hook1 Ends。這兩行也能夠證明:JVM確實不是以註冊的順序來調用關閉鉤子的。而因爲hook3在調用了addShutdownHook後,接着對其調用了removeShutdownHook將其移除,因而hook3在JVM退出時沒有執行,所以沒有輸出Hook3 Ends。
另外,因爲MyHook類實現了finalize方法,而main函數中第一行又經過Runtime.runFinalizersOnExit(true)打開了退出JVM時執行Finalizer的開關,因而3個hook對象的finalize方法被調用,輸出了3行Finalize。
注意,屢次調用addShutdownHook來註冊同一個關閉鉤子將會拋出IllegalArgumentException:
Exception in thread "main" java.lang.IllegalArgumentException: Hook previously registered
at java.lang.ApplicationShutdownHooks.add(ApplicationShutdownHooks.java:72)
at java.lang.Runtime.addShutdownHook(Runtime.java:211)
at T.main(T.java:12)
另外,從JavaDoc中得知:
Once the shutdown sequence has begun it can be stopped only by invoking the halt method, which forcibly terminates the virtual machine.
Once the shutdown sequence has begun it is impossible to register a new shutdown hook or de-register a previously-registered hook. Attempting either of these operations will cause an IllegalStateException to be thrown.
「一旦JVM關閉流程開始,就只能經過調用halt方法來中止該流程,也不可能再註冊或移除關閉鉤子了,這些操做將致使拋出IllegalStateException」。
若是在關閉鉤子中關閉應用程序的公共的組件,如日誌服務,或者數據庫鏈接等,像下面這樣:
Runtime.getRuntime().addShutdownHook(new Thread() {
public void run() {
try {
LogService.this.stop();
} catch (InterruptedException ignored){
//ignored
}
}
});
因爲關閉鉤子將併發執行,所以在關閉日誌時可能致使其餘須要日誌服務的關閉鉤子產生問題。爲了不這種狀況,可使關閉鉤子不依賴那些可能被應用程序或其餘關閉鉤子關閉的服務。實現這種功能的一種方式是對全部服務使用同一個關閉鉤子(而不是每一個服務使用一個不一樣的關閉鉤子),而且在該關閉鉤子中執行一系列的關閉操做。這確保了關閉操做在單個線程中串行執行,從而避免了在關閉操做以前出現競態條件或死鎖等問題。
二,在遊戲服務器中添加關閉鉤子
public class ShutDownService { private static CommonLog gameLogger = CommonLog.getInstance(); private static List<IShutDown> shutDownList = new ArrayList<>(); //註冊須要在關閉鉤子中執行的任務 public static void registShutDown(IShutDown shutDownServer) { shutDownList.add(shutDownServer); } public static void startShutDownHook() {
Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { gameLogger.warn(0, "開始關閉服務器,正在清理資源....."); for (IShutDown shutDown : shutDownList) { if (shutDown != null) { shutDown.shutDown(); while (!shutDown.isTerminated()) {
} gameLogger.warn(0, "----關閉" + shutDown.getClass().getName() + "成功----"); } } gameLogger.warn(0, "###---服務器關閉成功---###"); } }); } } |
三,Linux腳本根據端口殺死一個進程
#!/bin/bash echo "從新啓動服務" jar_name=GameLogicServer.jar PROCESS=`ps -ef|grep ${jar_name} |grep -v grep|grep -v PPID|awk '{ print $2}'` for i in $PROCESS do echo "######Kill the ${jar_name} process [ $i ] ########"
kill -15 $i done
while true do OLD_PROCESS=`ps -ef|grep ${jar_name} |grep -v grep|grep -v PPID|awk '{ print $2}'` if [ "${OLD_PROCESS}" = "" ] then echo "${PROCESS}進程已殺死成功,開始啓動新的進程" break else echo "正在等待${PROCESS}進程關閉....." sleep 1s fi
done
nohup java -server -agentpath:/usr/jprofiler9/jprofiler9/bin/linux-x64/libjprofilerti.so=port=8849,nowait -jar ${jar_name} > console.out 2>&1 & echo "服務器啓動完成" |
更多遊戲技術資料請參照:遊戲技術網:http://www.youxijishu.com/