Cách xử lý thông minh tín hiệu SIGKILL trong Java


113

Làm thế nào để bạn xử lý dọn dẹp khi chương trình nhận được tín hiệu kết thúc?

Ví dụ: có một ứng dụng tôi kết nối muốn bất kỳ ứng dụng bên thứ ba nào (ứng dụng của tôi) gửi finishlệnh khi đăng xuất. Câu nói hay nhất để gửi finishlệnh đó khi ứng dụng của tôi đã bị hủy bằng dấu kill -9?

sửa 1: giết -9 không thể bị bắt. Cảm ơn các bạn đã sửa cho tôi.

chỉnh sửa 2: Tôi đoán trường hợp này sẽ là khi một lệnh gọi giết giống như ctrl-c


44
kill -9có nghĩa là với tôi: "Hãy bỏ qua, quá trình xấu xa, hãy bỏ đi với bạn!", theo đó quá trình sẽ chấm dứt. Ngay.
ZoogieZork

11
Trên hầu hết các * nixes mà tôi biết, kill -9 không thể bị chặn và duyên dáng xử lý bởi bất kỳ chương trình không có vấn đề gì ngôn ngữ nó được viết bằng.
Tổng thống James K. Polk

2
@Begui: ngoài những gì những người khác đã nhận xét và trả lời, NẾU Hệ điều hành Un x của bạn không bị giết ngay lập tức * VÀ TÁI TẠO TẤT CẢ CÁC NGUỒN LỰC mà chương trình sử dụng bị giết -9 , thì ... Hệ điều hành bị hỏng.
Cú phápT3rr0r Ngày

1
Về lệnh kill -9, manpage nói chính xác hơn là: "9 KILL (giết không bắt được, không thể bỏ qua)". SIGKILL một tín hiệu được xử lý bởi hệ điều hành, không phải ứng dụng.
user1338062

4
Chỉ killkhông giống với Ctrl-C, vì killkhông chỉ định tín hiệu nào để gửi sẽ gửi SIGTERM, trong khi Ctrl-C gửi SIGINT.
alesguzik

Câu trả lời:


136

Không thể cho bất kỳ chương trình nào, bằng bất kỳ ngôn ngữ nào, có thể xử lý một SIGKILL. Điều này giúp bạn luôn có thể chấm dứt một chương trình, ngay cả khi chương trình bị lỗi hoặc độc hại. Nhưng SIGKILL không phải là phương tiện duy nhất để chấm dứt một chương trình. Cách khác là sử dụng SIGTERM. Các chương trình có thể xử lý tín hiệu đó. Chương trình sẽ xử lý tín hiệu bằng cách tắt máy có kiểm soát nhưng nhanh chóng. Khi một máy tính tắt, giai đoạn cuối cùng của quá trình tắt máy sẽ gửi cho mọi quá trình còn lại một SIGTERM, cho các quá trình đó gia hạn một vài giây, sau đó gửi cho chúng một SIGKILL.

Cách để xử lý điều này cho bất kỳ điều gì khác hơn kill -9là đăng ký một hook tắt máy . Nếu bạn có thể sử dụng ( SIGTERM ) kill -15, móc tắt máy sẽ hoạt động. ( SIGINT ) kill -2 KHÔNG làm cho chương trình thoát một cách duyên dáng và chạy các móc tắt máy.

Đăng ký một hook tắt máy ảo mới.

Máy ảo Java tắt để đáp ứng với hai loại sự kiện:

  • Chương trình thoát bình thường, khi luồng không phải daemon cuối cùng thoát ra hoặc khi phương thức thoát (tương đương, System.exit) được gọi, hoặc
  • Máy ảo bị ngắt do người dùng làm gián đoạn, chẳng hạn như gõ ^ C hoặc một sự kiện trên toàn hệ thống, chẳng hạn như đăng xuất của người dùng hoặc tắt hệ thống.

Tôi đã thử chương trình thử nghiệm sau trên OSX 10.6.3 và trên kill -9đó KHÔNG chạy móc tắt máy như mong đợi. Trên kill -15KHÔNG chạy móc tắt máy mọi lúc.

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);
        }
    }
}

Không có bất kỳ cách nào để thực sự xử lý một cách duyên dáng kill -9trong bất kỳ chương trình nào.

Trong một số trường hợp hiếm hoi, máy ảo có thể ngừng hoạt động, tức là ngừng chạy mà không tắt hoàn toàn. Điều này xảy ra khi máy ảo bị kết thúc bên ngoài, ví dụ với tín hiệu SIGKILL trên Unix hoặc lệnh gọi TerminaProcess trên Microsoft Windows.

Lựa chọn thực sự duy nhất để xử lý a kill -9là yêu cầu một chương trình người xem khác theo dõi chương trình chính của bạn biến mất hoặc sử dụng tập lệnh trình bao bọc. Bạn có thể làm điều này với một tập lệnh shell thăm dò pslệnh tìm kiếm chương trình của bạn trong danh sách và hành động tương ứng khi nó biến mất.

#!/usr/bin/env bash

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

12

nhiều cách để xử lý tín hiệu của riêng bạn trong một số JVM nhất định - hãy xem bài viết này về HotSpot JVM chẳng hạn.

Bằng cách sử dụng sun.misc.Signal.handle(Signal, SignalHandler)lệnh gọi phương thức nội bộ Sun, bạn cũng có thể đăng ký một trình xử lý tín hiệu, nhưng có lẽ không phải đối với các tín hiệu giống INThoặc TERMnhư chúng được sử dụng bởi JVM.

Để có thể xử lý bất kỳ tín hiệu nào, bạn sẽ phải nhảy ra khỏi JVM và vào lãnh thổ Hệ điều hành.

Những gì tôi thường làm để (ví dụ) phát hiện kết thúc bất thường là khởi chạy JVM của tôi bên trong tập lệnh Perl, nhưng để tập lệnh chờ JVM bằng lệnh waitpidgọi hệ thống.

Sau đó, tôi được thông báo bất cứ khi nào JVM thoát ra, và lý do tại sao nó thoát và có thể thực hiện hành động cần thiết.


3
Lưu ý rằng bạn có thể nắm bắt INTTERMvới sun.misc.Signal, nhưng bạn không thể xử lý QUITvì JVM dự trữ nó để gỡ lỗi, cũng như KILLvì Hệ điều hành sẽ chấm dứt JVM ngay lập tức. Cố gắng xử lý một trong hai sẽ tăng một IllegalArgumentException.
dimo414,

12

Tôi mong đợi rằng JVM ngắt ( thread.interrupt()) tất cả các luồng đang chạy được tạo bởi ứng dụng, ít nhất là đối với các tín hiệu SIGINT (kill -2)SIGTERM (kill -15).

Bằng cách này, tín hiệu sẽ được chuyển tiếp đến chúng, cho phép hủy luồng một cách duyên dáng và hoàn thiện tài nguyên theo những cách tiêu chuẩn .

Nhưng đây không phải là trường hợp (ít nhất là trong việc thực hiện JVM của tôi: Java(TM) SE Runtime Environment (build 1.8.0_25-b17), Java HotSpot(TM) 64-Bit Server VM (build 25.25-b02, mixed mode).

Như những người dùng khác nhận xét, việc sử dụng móc tắt máy dường như là bắt buộc.

Vì vậy, tôi sẽ xử lý nó như thế nào?

Đầu tiên, tôi không quan tâm đến nó trong tất cả các chương trình, chỉ ở những chương trình mà tôi muốn theo dõi các lần hủy của người dùng và kết thúc không mong muốn. Ví dụ: hãy tưởng tượng rằng chương trình java của bạn là một quá trình được quản lý bởi khác. Bạn có thể muốn phân biệt xem nó đã được kết thúc một cách duyên dáng ( SIGTERMtừ quy trình của người quản lý) hay đã xảy ra tắt máy (để tự động khởi chạy lại công việc khi khởi động).

Về cơ bản, tôi luôn định kỳ để các luồng chạy dài của mình nhận biết được trạng thái bị gián đoạn và ném InterruptedExceptionnếu chúng bị gián đoạn. Điều này cho phép hoàn tất quá trình thực thi theo cách do nhà phát triển kiểm soát (cũng tạo ra kết quả tương tự như các hoạt động chặn tiêu chuẩn). Sau đó, ở cấp cao nhất của ngăn xếp luồng, InterruptedExceptionđược ghi lại và thực hiện dọn dẹp thích hợp. Các luồng này được mã hóa để biết cách đáp ứng yêu cầu gián đoạn. Thiết kế có tính gắn kết cao .

Vì vậy, trong những trường hợp này, tôi thêm một móc tắt máy, đó là điều tôi nghĩ JVM nên làm theo mặc định: ngắt tất cả các luồng không phải daemon được tạo bởi ứng dụng của tôi vẫn đang chạy:

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");
    }
});

Hoàn thành đơn đăng ký thử nghiệm tại github: https://github.com/idelvall/kill-test


6

Bạn có thể sử dụng Runtime.getRuntime().addShutdownHook(...), nhưng bạn không thể đảm bảo rằng nó sẽ được gọi trong mọi trường hợp .


12
Nhưng trong trường hợp giết -9, nó gần như chắc chắn sẽ không chạy.
Tổng thống James K. Polk

1

Có một cách để phản ứng với một lần giết -9: đó là có một quy trình riêng theo dõi quá trình bị giết và dọn dẹp sau đó nếu cần thiết. Điều này có thể liên quan đến IPC và sẽ khá nhiều công việc và bạn vẫn có thể ghi đè nó bằng cách giết cả hai quy trình cùng một lúc. Tôi cho rằng nó sẽ không đáng gặp rắc rối trong hầu hết các trường hợp.

Về mặt lý thuyết, bất kỳ ai giết một tiến trình với -9 phải biết họ đang làm gì và nó có thể khiến mọi thứ ở trạng thái không nhất quán.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.