java signal 说明_Java Signal实例



TimYang在博文《降低应用latency方法谈》 中提到对Java方法进行Profiling,在某些场景更倾向于采用纯Java工具类的方法,比如通过给进程发Signals来实现,以求动态地打开或关闭Profiler。

感觉这个思路不错,以下是一个大致实例:

SignalTest.java

Java代码

0818b9ca8b590ca3270a3433284dd417.png 

0818b9ca8b590ca3270a3433284dd417.png

0818b9ca8b590ca3270a3433284dd417.png

/**

*

*/

packagesignal;

importjava.util.Observable;

importjava.util.Observer;

importsun.misc.Signal;

importsun.misc.SignalHandler;

/**

* @author xuanyin

*

*/

publicclassSignalTestimplementsObserver {

/**

* @param args

*/

publicstaticvoidmain(String[] args) {

newSignalTest().go();

}

privatevoidgo() {

try{

HandlerTest sh = newHandlerTest();

sh.addObserver(this);

sh.handleSignal("HUP");

sh.handleSignal("BUS");

System.out.println("Sleeping for 60 seconds: hit me with signals!");

Thread.sleep(60000);

} catch(Throwable x) {

x.printStackTrace();

}

}

/**

*

*/

@Override

publicvoidupdate(Observable arg0, Object arg1) {

System.out.println("Received signal: "+ arg1);

}

/**

* HandlerTest Class

*/

classHandlerTestextendsObservableimplementsSignalHandler {

@Override

publicvoidhandle(Signal signal) {

setChanged();

notifyObservers(signal);

}

/**

*

* @param signalName

* @throws IllegalArgumentException

*/

publicvoidhandleSignal(String signalName)throwsIllegalArgumentException {

try{

Signal.handle(newSignal(signalName),this);

} catch(IllegalArgumentException x) {

throwx;

} catch(Throwable x) {

thrownewIllegalArgumentException("Signal unsupported: "+signalName, x);

}

}

}

}

/**

*

*/

package signal;

import java.util.Observable;

import java.util.Observer;

import sun.misc.Signal;

import sun.misc.SignalHandler;

/**

* @author xuanyin

*

*/

public class SignalTest implements Observer {

/**

* @param args

*/

public static void main(String[] args) {

new SignalTest().go();

}

private void go() {

try {

HandlerTest sh = new HandlerTest();

sh.addObserver(this);

sh.handleSignal("HUP");

sh.handleSignal("BUS");

System.out.println("Sleeping for 60 seconds: hit me with signals!");

Thread.sleep(60000);

} catch (Throwable x) {

x.printStackTrace();

}

}

/**

*

*/

@Override

public void update(Observable arg0, Object arg1) {

System.out.println("Received signal: " + arg1);

}

/**

* HandlerTest Class

*/

class HandlerTest extends Observable implements SignalHandler {

@Override

public void handle(Signal signal) {

setChanged();

notifyObservers(signal);

}

/**

*

* @param signalName

* @throws IllegalArgumentException

*/

public void handleSignal(String signalName) throws IllegalArgumentException {

try {

Signal.handle(new Signal(signalName), this);

} catch (IllegalArgumentException x) {

throw x;

} catch (Throwable x) {

throw new IllegalArgumentException("Signal unsupported: "+signalName, x);

}

}

}

}

首先运行执行上述程序,然后查看其系统进程号。

如,若是4089,则在终端中执行kill -s BUS 4089

Java程序输出:Received signal: SIGBUS

信号具有平台相关性,不同平台下能使用的信号种类是有差异的。

Linux下支持的信号:

SEGV, ILL, FPE, BUS, SYS, CPU, FSZ, ABRT, INT, TERM, HUP, USR1, USR2, QUIT, BREAK, TRAP, PIPE

Windows下支持的信号:

SEGV, ILL, FPE, ABRT, INT, TERM, BREAK

不足之处欢迎大家留言指正:)

======================

在java 中调用Signal的方法handle可以去注册一个信号的处理函数,方法的如下:

publicstaticsynchronizedSignalHandler handle(Signal sig,

SignalHandler handler) {

....

}

public static synchronized SignalHandler handle(Signal sig,

SignalHandler handler) {

....

}

比如常用的addShutdownHook钩子函数里,就是在 Terminator.setup();的时候将Shutdown.exit 的函数注册到了信号SHUTDOWN1_SIGNAL(SIGHUP),SHUTDOWN2_SIGNAL(SIGINT),SHUTDOWN3_SIGNAL(SIGTERM)中,当线程接受到信号时,通过调用函数Shutdown.exit的调用hook中的钩子函数。

在笔者的文章(java 中关于信号的处理在linux下的实现)也提到jdk如何处理信号的,那么调用handle里是不是直接就把这个方法注册进了信号处理呢?

请注意,handle是一个java的方法,而注册信号函数是c的代码,显然不能简单的将java的方法直接提供给c调用,其次信号处理函数是在内核态中处理,安全性和执行时间的长短将影响到内核的信号调度。

先看java源码,如下面所示

publicstaticsynchronizedSignalHandler handle(Signal sig,

SignalHandler handler)

throwsIllegalArgumentException {

longnewH = (handlerinstanceofNativeSignalHandler) ?

((NativeSignalHandler)handler).getHandler() : 2;

longoldH = handle0(sig.number, newH);

if(oldH == -1) {

thrownewIllegalArgumentException

("Signal already used by VM: "+ sig);

}

signals.put(newInteger(sig.number), sig);

synchronized(handlers) {

SignalHandler oldHandler = (SignalHandler)handlers.get(sig);

handlers.remove(sig);

if(newH ==2) {

handlers.put(sig, handler);

}

if(oldH ==0) {

returnSignalHandler.SIG_DFL;

} elseif(oldH ==1) {

returnSignalHandler.SIG_IGN;

} elseif(oldH ==2) {

returnoldHandler;

} else{

returnnewNativeSignalHandler(oldH);

}

}

}

public static synchronized SignalHandler handle(Signal sig,

SignalHandler handler)

throws IllegalArgumentException {

long newH = (handler instanceof NativeSignalHandler) ?

((NativeSignalHandler)handler).getHandler() : 2;

long oldH = handle0(sig.number, newH);

if (oldH == -1) {

throw new IllegalArgumentException

("Signal already used by VM: " + sig);

}

signals.put(new Integer(sig.number), sig);

synchronized (handlers) {

SignalHandler oldHandler = (SignalHandler)handlers.get(sig);

handlers.remove(sig);

if (newH == 2) {

handlers.put(sig, handler);

}

if (oldH == 0) {

return SignalHandler.SIG_DFL;

} else if (oldH == 1) {

return SignalHandler.SIG_IGN;

} else if (oldH == 2) {

return oldHandler;

} else {

return new NativeSignalHandler(oldH);

}

}

}

在native code hand0里并没有将handle的方法传进去,只是传了一个整型值。

在c++代码中hand0里调用了函数 JVM_RegisterSignal,具体来看一下这个函数的实现

JVM_ENTRY_NO_ENV(void*, JVM_RegisterSignal(jint sig,void* handler))

// Copied from classic vm

// signals_md.c       1.4 98/08/23

void* newHandler = handler == (void*)2

? os::user_handler()

: handler;

switch(sig) {

/* The following are already used by the VM. */

caseINTERRUPT_SIGNAL:

caseSIGFPE:

caseSIGILL:

caseSIGSEGV:

/* The following signal is used by the VM to dump thread stacks unless

ReduceSignalUsage is set, in which case the user is allowed to set

his own _native_ handler for this signal; thus, in either case,

we do not allow JVM_RegisterSignal to change the handler. */

caseBREAK_SIGNAL:

return(void*)-1;

/* The following signals are used for Shutdown Hooks support. However, if

ReduceSignalUsage (-Xrs) is set, Shutdown Hooks must be invoked via

System.exit(), Java is not allowed to use these signals, and the the

user is allowed to set his own _native_ handler for these signals and

invoke System.exit() as needed. Terminator.setup() is avoiding

registration of these signals when -Xrs is present.

- If the HUP signal is ignored (from the nohup) command, then Java

is not allowed to use this signal.

*/

caseSHUTDOWN1_SIGNAL:

caseSHUTDOWN2_SIGNAL:

caseSHUTDOWN3_SIGNAL:

if(ReduceSignalUsage)return(void*)-1;

if(os::Linux::is_sig_ignored(sig))return(void*)1;

}

void* oldHandler = os::signal(sig, newHandler);

if(oldHandler == os::user_handler()) {

return(void*)2;

} else{

returnoldHandler;

}

JVM_END

JVM_ENTRY_NO_ENV(void*, JVM_RegisterSignal(jint sig, void* handler))

// Copied from classic vm

// signals_md.c 1.4 98/08/23

void* newHandler = handler == (void *)2

? os::user_handler()

: handler;

switch (sig) {

/* The following are already used by the VM. */

case INTERRUPT_SIGNAL:

case SIGFPE:

case SIGILL:

case SIGSEGV:

/* The following signal is used by the VM to dump thread stacks unless

ReduceSignalUsage is set, in which case the user is allowed to set

his own _native_ handler for this signal; thus, in either case,

we do not allow JVM_RegisterSignal to change the handler. */

case BREAK_SIGNAL:

return (void *)-1;

/* The following signals are used for Shutdown Hooks support. However, if

ReduceSignalUsage (-Xrs) is set, Shutdown Hooks must be invoked via

System.exit(), Java is not allowed to use these signals, and the the

user is allowed to set his own _native_ handler for these signals and

invoke System.exit() as needed. Terminator.setup() is avoiding

registration of these signals when -Xrs is present.

- If the HUP signal is ignored (from the nohup) command, then Java

is not allowed to use this signal.

*/

case SHUTDOWN1_SIGNAL:

case SHUTDOWN2_SIGNAL:

case SHUTDOWN3_SIGNAL:

if (ReduceSignalUsage) return (void*)-1;

if (os::Linux::is_sig_ignored(sig)) return (void*)1;

}

void* oldHandler = os::signal(sig, newHandler);

if (oldHandler == os::user_handler()) {

return (void *)2;

} else {

return oldHandler;

}

JVM_END

void* newHandler = handler == (void*)2

? os::user_handler()

: handler;

void* newHandler = handler == (void *)2

? os::user_handler()

: handler;

因为传进的值是2,那么真正在c++里的信号处理函数实际上os::user_handler(),同时jvm也保护了几个信号,不允许外部改变信号的处理函数。

一切豁然开朗,笔者的博客(java 中关于信号的处理在linux下的实现)已经提到过这个函数,通过os:signal_notify 去通知signal dispatcher 线程的os::signal_wait,也就是接受到信号的线程通过信号函数notify到处理信号的线程(signal dispatcher ),最后由该线程做后续的事情。

具体来看signal dispatcher 的thread entry

staticvoidsignal_thread_entry(JavaThread*thread, TRAPS) {

....

default: {

// Dispatch the signal to java

HandleMark hm(THREAD);

klassOop k = SystemDictionary::resolve_or_null(vmSymbolHandles::sun_misc_Signal(), THREAD);

KlassHandle klass (THREAD, k);

if(klass.not_null()) {

JavaValue result(T_VOID);

JavaCallArguments args;

args.push_int(sig);

JavaCalls::call_static(

&result,

klass,

vmSymbolHandles::dispatch_name(),

vmSymbolHandles::int_void_signature(),

&args,

THREAD

);

}

....

}

static void signal_thread_entry(JavaThread* thread, TRAPS) {

....

default: {

// Dispatch the signal to java

HandleMark hm(THREAD);

klassOop k = SystemDictionary::resolve_or_null(vmSymbolHandles::sun_misc_Signal(), THREAD);

KlassHandle klass (THREAD, k);

if (klass.not_null()) {

JavaValue result(T_VOID);

JavaCallArguments args;

args.push_int(sig);

JavaCalls::call_static(

&result,

klass,

vmSymbolHandles::dispatch_name(),

vmSymbolHandles::int_void_signature(),

&args,

THREAD

);

}

....

}

也就是在jvm的c++源码中,反调用了java的方法,也就是signal.java中的dispatch(int number),方法dispatch中才是真正的调用了在文章开头提到的注册到Signal的方法handle。

dispatch方法中仍然起了一个新的线程去处理handle,这样就不会block signal dispatcher 线程。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值