วิธีจัดการสัญญาณ SIGKILL ใน Java อย่างสง่างาม


113

คุณจะจัดการกับการล้างข้อมูลอย่างไรเมื่อโปรแกรมได้รับสัญญาณฆ่า

ตัวอย่างเช่นมีแอปพลิเคชันที่ฉันเชื่อมต่อซึ่งต้องการให้แอปของบุคคลที่สาม (แอปของฉัน) ส่งfinishคำสั่งเมื่อออกจากระบบ อะไรคือสิ่งที่ดีที่สุดในการส่งfinishคำสั่งนั้นเมื่อแอพของฉันถูกทำลายด้วย a kill -9?

แก้ไข 1: ไม่สามารถจับ kill -9 ได้ ขอบคุณพวกคุณที่แก้ไขฉัน

แก้ไข 2: ฉันเดาว่ากรณีนี้น่าจะเป็นตอนที่คนเรียก just kill ซึ่งเหมือนกับ ctrl-c


44
kill -9หมายถึงฉัน: "เริ่มต้นกระบวนการที่ไม่ดีออกไปกับเจ้า!" ซึ่งกระบวนการนี้จะหยุดลง ทันที
ZoogieZork

11
ส่วนใหญ่ * ที่ฉันรู้ว่า kill -9 ไม่สามารถดักจับและจัดการอย่างสง่างามโดยโปรแกรมใด ๆ ไม่ว่าจะเขียนด้วยภาษาใดก็ตาม
ประธาน James K. Polk

2
@Begui: นอกเหนือจากสิ่งที่คนอื่น ๆ แสดงความคิดเห็นและตอบหาก Un x OSของคุณไม่ได้ฆ่า * ในทันทีและรับทรัพยากรทั้งหมดที่โปรแกรมใช้ฆ่า -9'edดี ... ระบบปฏิบัติการเสีย
SyntaxT3rr0r

1
เกี่ยวกับคำสั่ง kill -9 manpage กล่าวอย่างแม่นยำยิ่งขึ้น: "9 KILL (ฆ่าไม่ได้และไม่สามารถเพิกเฉยได้)" SIGKILL สัญญาณที่ระบบปฏิบัติการจัดการไม่ใช่แอปพลิเคชัน
user1338062

4
เพียง แต่killไม่เหมือนกับ Ctrl-C เนื่องจากkillไม่มีการระบุว่าสัญญาณที่จะส่งจะส่ง SIGTERM ในขณะที่ Ctrl-C ส่ง SIGINT
alesguzik

คำตอบ:


136

เป็นไปไม่ได้ที่โปรแกรมใด ๆ ในภาษาใด ๆ จะจัดการกับ SIGKILL ได้ ดังนั้นจึงเป็นไปได้เสมอที่จะยุติโปรแกรมแม้ว่าโปรแกรมจะมีข้อบกพร่องหรือเป็นอันตรายก็ตาม แต่ SIGKILL ไม่ใช่วิธีเดียวในการยุติโปรแกรม อีกแบบคือการใช้ SIGTERM โปรแกรมสามารถจัดการกับสัญญาณนั้นได้ โปรแกรมควรจัดการสัญญาณโดยทำการปิดเครื่องที่มีการควบคุม แต่รวดเร็ว เมื่อคอมพิวเตอร์ปิดตัวลงขั้นตอนสุดท้ายของกระบวนการปิดระบบจะส่ง SIGTERM ทุกกระบวนการที่เหลือให้กระบวนการเหล่านั้นผ่อนผันไม่กี่วินาทีจากนั้นจึงส่ง SIGKILL ให้

วิธีจัดการสิ่งนี้สำหรับสิ่งอื่นนอกเหนือจากkill -9การลงทะเบียนhook การปิดระบบ หากคุณสามารถใช้ ( SIGTERM ) kill -15ตะขอปิดจะทำงาน ( SIGINT ) kill -2 DOESทำให้โปรแกรมออกอย่างสง่างามและเรียกใช้การปิดเครื่อง

ลงทะเบียน hook การปิดเครื่องเสมือนใหม่

เครื่องเสมือน Java ปิดตัวลงเพื่อตอบสนองต่อเหตุการณ์สองประเภท:

  • โปรแกรมจะออกตามปกติเมื่อเธรดที่ไม่ใช่ daemon ตัวสุดท้ายออกหรือเมื่อมีการเรียกใช้เมธอด exit (เทียบเท่า System.exit) หรือ
  • เครื่องเสมือนถูกยกเลิกเพื่อตอบสนองต่อการขัดจังหวะของผู้ใช้เช่นการพิมพ์ ^ C หรือเหตุการณ์ทั้งระบบเช่นผู้ใช้ออกจากระบบหรือปิดระบบ

ฉันพยายามโปรแกรมการทดสอบต่อไปนี้บน OSX 10.6.3 และkill -9มันก็ไม่ได้เรียกใช้เบ็ดปิดเป็นไปตามคาด เมื่อkill -15มันไมวิ่งปิดเบ็ดทุกครั้ง

public class TestShutdownHook
{
    public static void main(String[] args) throws InterruptedException
    {
        Runtime.getRuntime().addShutdownHook(new Thread()
        {
            @Override
            public void run()
            {
                System.out.println("Shutdown hook ran!");
            }
        });

        while (true)
        {
            Thread.sleep(1000);
        }
    }
}

ไม่มีวิธีใดที่จะจัดการกับkill -9โปรแกรมใด ๆ ได้อย่างสง่างาม

ในสถานการณ์ที่เกิดขึ้นไม่บ่อยเครื่องเสมือนอาจยกเลิกนั่นคือหยุดทำงานโดยไม่ปิดเครื่องอย่างหมดจด สิ่งนี้เกิดขึ้นเมื่อเครื่องเสมือนถูกยกเลิกจากภายนอกตัวอย่างเช่นด้วยสัญญาณ SIGKILL บน Unix หรือการเรียก TerminateProcess บน Microsoft Windows

ตัวเลือกเดียวที่แท้จริงในการจัดการ a kill -9คือการมีโปรแกรมเฝ้าดูอื่นเพื่อให้โปรแกรมหลักของคุณหายไปหรือใช้สคริปต์ Wrapper คุณสามารถทำได้ด้วยเชลล์สคริปต์ที่สำรวจpsคำสั่งเพื่อค้นหาโปรแกรมของคุณในรายการและดำเนินการตามนั้นเมื่อมันหายไป

#!/usr/bin/env bash

java TestShutdownHook
wait
# notify your other app that you quit
echo "TestShutdownHook quit"

12

มีมีวิธีการที่จะจัดการกับสัญญาณของคุณเองใน JVMs บางอย่าง - ดูบทความนี้เกี่ยวกับ HotSpot JVMตัวอย่างเช่น

ด้วยการใช้sun.misc.Signal.handle(Signal, SignalHandler)วิธีการโทรภายในของ Sun คุณยังสามารถลงทะเบียนตัวจัดการสัญญาณได้ แต่อาจไม่ใช่สำหรับสัญญาณที่เหมือนINTหรือTERMตามที่ JVM ใช้

เพื่อให้สามารถจัดการกับสัญญาณใด ๆคุณจะต้องกระโดดออกจาก JVM และเข้าสู่ขอบเขตของระบบปฏิบัติการ

สิ่งที่ฉันทำโดยทั่วไป (ตัวอย่างเช่น) ตรวจพบการยุติที่ผิดปกติคือการเปิด JVM ของฉันภายในสคริปต์ Perl แต่ให้สคริปต์รอ JVM โดยใช้การwaitpidเรียกระบบ

จากนั้นฉันจะได้รับแจ้งเมื่อใดก็ตามที่ JVM ออกและเหตุใดจึงออกและสามารถดำเนินการที่จำเป็นได้


3
โปรดทราบว่าคุณสามารถจับภาพINTและTERMใช้sun.misc.Signalงานได้ แต่คุณไม่สามารถจัดการได้QUITเนื่องจาก JVM สงวนไว้สำหรับการดีบักหรือKILLเนื่องจากระบบปฏิบัติการจะยุติ JVM ทันที การพยายามจัดการอย่างใดอย่างหนึ่งจะทำให้IllegalArgumentExceptionไฟล์.
dimo414

12

ฉันคาดหวังว่า JVM จะขัดจังหวะ ( thread.interrupt()) เธรดที่กำลังทำงานอยู่ทั้งหมดที่สร้างโดยแอปพลิเคชันอย่างสง่างามอย่างน้อยสำหรับสัญญาณSIGINT (kill -2)และSIGTERM (kill -15).

วิธีนี้สัญญาณจะถูกส่งต่อไปพวกเขาช่วยให้มีการยกเลิกด้ายอย่างสง่างามและการสรุปทรัพยากรในรูปแบบมาตรฐาน

แต่นี่ไม่ใช่กรณี (อย่างน้อยในการใช้งาน JVM ของฉัน: Java(TM) SE Runtime Environment (build 1.8.0_25-b17), Java HotSpot(TM) 64-Bit Server VM (build 25.25-b02, mixed mode).

ตามที่ผู้ใช้รายอื่นแสดงความคิดเห็นการใช้ตะขอปิดระบบดูเหมือนจะบังคับ

แล้วฉันจะจัดการยังไงดี?

ก่อนอื่นฉันไม่สนใจมันในทุกโปรแกรมเฉพาะในโปรแกรมที่ฉันต้องการติดตามการยกเลิกของผู้ใช้และการสิ้นสุดที่ไม่คาดคิด ตัวอย่างเช่นสมมติว่าโปรแกรม java ของคุณเป็นกระบวนการที่จัดการโดยโปรแกรมอื่น ๆ คุณอาจต้องการแยกความแตกต่างว่ามีการยกเลิกอย่างไม่เป็นธรรม ( SIGTERMจากกระบวนการจัดการ) หรือเกิดการปิดระบบ (เพื่อเปิดใช้งานใหม่โดยอัตโนมัติเมื่อเริ่มต้น)

โดยพื้นฐานแล้วฉันมักจะทำให้เธรดที่ใช้งานยาวนานของฉันรับรู้ถึงสถานะที่ถูกขัดจังหวะเป็นระยะ ๆ และInterruptedExceptionหากพวกเขาถูกขัดจังหวะ สิ่งนี้เปิดใช้งานการสรุปผลการดำเนินการในลักษณะที่ควบคุมโดยนักพัฒนา (ให้ผลลัพธ์เช่นเดียวกับการดำเนินการบล็อกมาตรฐาน) จากนั้นที่ระดับบนสุดของสแต็กเธรดInterruptedExceptionจะถูกจับและดำเนินการล้างข้อมูลที่เหมาะสม เธรดเหล่านี้ได้รับการเข้ารหัสเพื่อให้ทราบว่าจะตอบสนองต่อคำร้องขอหยุดชะงักได้อย่างไร การออกแบบการทำงานร่วมกันสูง

ดังนั้นในกรณีเหล่านี้ฉันเพิ่ม hook การปิดระบบซึ่งจะทำสิ่งที่ฉันคิดว่า JVM ควรทำตามค่าเริ่มต้น: ขัดจังหวะเธรดที่ไม่ใช่ daemon ทั้งหมดที่สร้างโดยแอปพลิเคชันของฉันที่ยังทำงานอยู่:

Runtime.getRuntime().addShutdownHook(new Thread() {
    @Override
    public void run() {
        System.out.println("Interrupting threads");
        Set<Thread> runningThreads = Thread.getAllStackTraces().keySet();
        for (Thread th : runningThreads) {
            if (th != Thread.currentThread() 
                && !th.isDaemon() 
                && th.getClass().getName().startsWith("org.brutusin")) {
                System.out.println("Interrupting '" + th.getClass() + "' termination");
                th.interrupt();
            }
        }
        for (Thread th : runningThreads) {
            try {
                if (th != Thread.currentThread() 
                && !th.isDaemon() 
                && th.isInterrupted()) {
                    System.out.println("Waiting '" + th.getName() + "' termination");
                    th.join();
                }
            } catch (InterruptedException ex) {
                System.out.println("Shutdown interrupted");
            }
        }
        System.out.println("Shutdown finished");
    }
});

กรอกใบสมัครทดสอบที่ github: https://github.com/idelvall/kill-test


6

คุณสามารถใช้Runtime.getRuntime().addShutdownHook(...)แต่คุณไม่สามารถรับประกันได้ว่ามันจะถูกเรียกว่าในกรณีใด


12
แต่ในกรณีฆ่า -9 มันแทบจะไม่วิ่ง
ประธาน James K. Polk

1

มีวิธีหนึ่งในการตอบสนองต่อการฆ่า -9 นั่นคือการมีกระบวนการแยกต่างหากที่ตรวจสอบกระบวนการที่ถูกฆ่าและทำความสะอาดหลังจากนั้นหากจำเป็น สิ่งนี้อาจเกี่ยวข้องกับ IPC และจะใช้งานได้ไม่น้อยและคุณยังสามารถลบล้างได้โดยการฆ่าทั้งสองกระบวนการในเวลาเดียวกัน ฉันคิดว่ามันจะไม่คุ้มกับปัญหาในกรณีส่วนใหญ่

ใครก็ตามที่ฆ่ากระบวนการที่มี -9 ในทางทฤษฎีควรรู้ว่าเขา / เธอกำลังทำอะไรอยู่และอาจทำให้สิ่งต่างๆอยู่ในสถานะที่ไม่สอดคล้องกัน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.