如果您对java–basheval未检测到System.exit返回码和未检测到java环境感兴趣,那么这篇文章一定是您不可错过的。我们将详细讲解java–basheval未检测到System.exi
如果您对java – bash eval未检测到System.exit返回码和未检测到java环境感兴趣,那么这篇文章一定是您不可错过的。我们将详细讲解java – bash eval未检测到System.exit返回码的各种细节,并对未检测到java环境进行深入的分析,此外还有关于. System.exit(0)和System.exit(1) 和return 区别、c# – Reflection.emit System.InvalidProgramException:公共语言运行时检测到无效程序、domain-name-system – 未检测到我的域的SPF记录、Java System#exit无法退出程序的问题如何解决的实用技巧。
本文目录一览:- java – bash eval未检测到System.exit返回码(未检测到java环境)
- . System.exit(0)和System.exit(1) 和return 区别
- c# – Reflection.emit System.InvalidProgramException:公共语言运行时检测到无效程序
- domain-name-system – 未检测到我的域的SPF记录
- Java System#exit无法退出程序的问题如何解决
java – bash eval未检测到System.exit返回码(未检测到java环境)
挣扎了一个小时…… java代码:
ULogger.info("throwing out 666!");
System.exit(666);
bash包装:
eval ${COMMAND_TO_RUN}
ret_code=$?
printf "error code : [%d]" ${ret_code}
输出:
[2012-11-30 15:20:12,971][INFO ] throwing out 666!
error code : [0]
这是什么交易?谢谢…
[编辑]
${COMMAND_TO_RUN}是
((java -Xmx9000m -Dtoday_nix=20121128 -cp "/usr/lib/hadoop/conf" com.paypal.risk.ars.linking.task_fw.BaseRunnableProcess 3>&1 1>&2 2>&3) | tee /dev/tty) > batches_errors.log
((java -Xmx9000m -Dtoday_nix=20121128 -cp "/usr/lib/hadoop/conf" com.paypal.risk.ars.linking.task_fw.BaseRunnableProcess 3>&1 1>&2 2>&3) | tee /dev/tty) > batches_errors.log
调用的最后一个程序是tee,它将以状态0退出,覆盖退出
java的价值.
您可以使用$PIpestATUS,它是管道中的返回值数组.
例如:
$cat nonexistantFile | echo ; echo "e: $? p: ${PIpestATUS[@]}"
预期产量:
e: 0 p: 1 0
cat将失败(退出代码1),echo将成功(退出代码0). $?将是0.
${PIpestATUS [0]}将包含cat(1)的退出代码,${PIpestATUS [1]}将包含echo(0)的退出代码.
. System.exit(0)和System.exit(1) 和return 区别
1、System.exit(0)和System.exit(1)有什么区别?
2、在什么情况下执行System.exit()不会抛出异常?如果抛出了SecurityException异常,一般是因为什么原因?
3、如果我想中止当前运行的java程序,除了执行System.exit()还能用什么方法?
1。程序返回值不同。
2。抛出SecurityException是因为你调用了不允许的操作。比如在Applet中操作本地文件,或者在RMI程序中操作不允许的文件。Sun对于这个有专门的文章和解决方法。
3。对于java程序,运行System.exit()会终止JVM,所以Servlet和Applet中都不应该显示调用这个方法。
2個同為exit
public static void exit(int status)终止当前正在运行的 Java 虚拟机。参数用作状态码;根据惯例,非零的状态码表示异常终止。零状态是终止整个程序。
如果在main方法中System.exit(0)与return 没有区别,都是终止程序。
如果是别的方法,那System.exit(0)直接终止程序,就算后面有代码也不执行了
而return则返回至调用该方法的地方,如果后面还有代码则继续执行
2.解析
查看java.lang.System的源代码,我们可以找到System.exit(status)这个方法的说明,代码如下:
/** * Terminates the currently running Java Virtual Machine. The * argument serves as a status code; by convention, a nonzero status * code indicates abnormal termination. * <p> * This method calls the <code>exit</code> method in class * <code>Runtime</code>. This method never returns normally. * <p> * The call <code>System.exit(n)</code> is effectively equivalent to * the call: * <blockquote><pre> * Runtime.getRuntime().exit(n) * </pre></blockquote> * * @param status exit status. * @throws SecurityException * if a security manager exists and its <code>checkExit</code> * method doesn''t allow exit with the specified status. * @see java.lang.Runtime#exit(int) */ public static void exit(int status) { Runtime.getRuntime().exit(status); }注释中说的很清楚,这个方法是用来结束当前正在运行中的java虚拟机。如何status是非零参数,那么表示是非正常退出。
- System.exit(0)是将你的整个虚拟机里的内容都停掉了 ,而dispose()只是关闭这个窗口,但是并没有停止整个application exit() 。无论如何,内存都释放了!也就是说连JVM都关闭了,内存里根本不可能还有什么东西
- System.exit(0)是正常退出程序,而System.exit(1)或者说非0表示非正常退出程序
- System.exit(status)不管status为何值都会退出程序。和return 相比有以下不同点: return是回到上一层,而System.exit(status)是回到最上层
3.示例
在一个if-else判断中,如果我们程序是按照我们预想的执行,到最后我们需要停止程序,那么我们使用System.exit(0),而System.exit(1)一般放在catch块中,当捕获到异常,需要停止程序,我们使用System.exit(1)。这个status=1是用来表示这个程序是非正常退出。
c# – Reflection.emit System.InvalidProgramException:公共语言运行时检测到无效程序
public class RepositoryWrapper { public void CallRepositoryMethod(IAddressRepository repository,Address address) { repository.NODE_I_NodeExtendedDetails_Address3(address.NodeId); } }
这是它的代表:
IL_0000: nop IL_0001: ldarg.1 IL_0002: ldarg.2 IL_0003: callvirt instance int32 ReflectionServices.Node::get_NodeId() IL_0008: callvirt instance void ReflectionServices.IAddressRepository::NODE_I_NodeExtendedDetails_Address3(int32) IL_000d: nop IL_000e: ret
这是我用来创建它的代码:
internal static void Generate(this System.Reflection.Emit.ILGenerator @this,Type target,string method,Type instance) { var methodToCall = target.getmethod(method); var methodParams = methodToCall.GetParameters(); var instanceProperties = instance.GetProperties(BindingFlags.Public | BindingFlags.Instance); var orderedProperties = (from mp in methodParams join p in instanceProperties on mp.Name.ToLower() equals p.Name.ToLower() select p).ToArray(); //add properties to the string builder //load the object reference onto the stack sothat we can access its methods @this.Emit(OpCodes.nop); @this.Emit(OpCodes.Ldarg_1); @this.Emit(OpCodes.Ldarg_2); var property = orderedProperties.FirstOrDefault(x => x.Name == "NodeId"); if (property != null) { var getmethod = property.Getgetmethod(); @this.Emit(getmethod.IsVirtual ? OpCodes.Callvirt : OpCodes.Call,getmethod); } //call method @this.Emit(methodToCall.IsVirtual ? OpCodes.Callvirt : OpCodes.Call,methodToCall); @this.Emit(OpCodes.nop); //return from function @this.Emit(OpCodes.Ret); }
这是我得到的错误:
system.invalidProgramException: Common Language Runtime detected an invalid program. Result StackTrace: at ReflectionServices.Repository.NODE_I_NodeExtendedDetails3_Address40807399(Repository target,Address )
这是生成的il:
nop ldarg.1 ldarg.2 call instance int32 ReflectionServices.Node::get_NodeId() callvirt instance void ReflectionServices.Repository:: NODE_I_NodeExtendedDetails3_Address(int32) nop
RET
任何人都可以看到我遇到的问题是什么?
谢谢
这是我要求的dll和方法:
public sealed class ReflectionEmitWithDebuggingMethodGenerator { private AssemblyBuilder Assembly { get; set; } private ModuleBuilder Module { get; set; } private AssemblyName Name { get; set; } public ReflectionEmitWithDebuggingMethodGenerator() : base() { this.Name = new AssemblyName() { Name = Guid.NewGuid().ToString("N") }; this.Assembly = AppDomain.CurrentDomain.DefineDynamicAssembly( this.Name,AssemblyBuilderAccess.RunAndSave,@"C:\Users\darren\Documents\Visual Studio 2012\Projects\UnityInjection"); this.AddDebuggingAttribute(this.Assembly); this.Module = this.Assembly.DefineDynamicModule(this.Name.Name + ".dll",true); } public Action<TObject,TInstance> Generate<TObject,TInstance>(Type target,string methodN,Type instanceType) { var type = this.Module.DefineType(target.Namespace + "." + target.Name); var methodName = methodN + target.GetHashCode().ToString(); var method = type.DefineMethod(methodName,MethodAttributes.Static | MethodAttributes.Public,typeof(void),new Type[] { target,instanceType }); method.DefineParameter(1,Parameterattributes.In,"target"); method.DefineParameter(2,"instance"); ILGenerator.Generate(method.GetILGenerator(),target,methodN,instanceType); var createdType = type.CreateType(); var createdMethod = createdType.getmethod(methodName); return (Action<TObject,TInstance>)Delegate.CreateDelegate(typeof(Action<TObject,TInstance>),createdMethod); } }
解决方法
编译:
callvirt instance int32 ReflectionServices.Node::get_NodeId()
发出:
call instance int32 ReflectionServices.Node::get_NodeId()
您调用int32 get_NodeId()的类型是ReflectionServices.Node,但是您要传递给要尝试复制的方法的对象的类型是Address.这使我相信必须虚拟调用ReflectionServices.Node上定义的属性访问器,可能是因为它继承自另一个类(或实现一个接口),该类在ReflectionServices.Node实现它之前声明该属性.
当您发出该行代码时,只需虚拟调用它:
@this.Emit(OpCodes.Callvirt,getmethod);
编辑:根据进一步提供的代码,这是真正的解决方案.
因此,您在实现界面的基础知识方面存在问题:
var method = type.DefineMethod(methodName,MethodAttributes.Static | Method... // ^^^^^^
接口方法不是静态的;他们是实例成员.因此,首先需要在创建MethodBuilder时从attributes标志中删除MethodAttributes.Static.
其次,当您返回此函数时,您将不得不包含一个目标对象,该对象是调用该方法的实例.为此,您可以使用Activator.CreateInstance
调用默认生成的构造函数,并为您提供一个实例化对象以用作目标.用这些行替换Generate方法的最后一行来实现这一点.
var activatedobject = Activator.CreateInstance(type); return (Action<TObject,TInstance>)Delegate.CreateDelegate( typeof(Action<TObject,activatedobject,createdMethod);
domain-name-system – 未检测到我的域的SPF记录
这是我的DNS设置在NameCheap的控制面板中的样子(@表示无,或裸域“qplum.co”):
@ CNAME qplumLB-804948472.us-east-1.elb.amazonaws.com. @ TXT v=spf1 include:spf.mandrillapp.com mx ~all mandrill._domainkey TXT v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb--trimmedAB;
所有在线SPF工具(或dig或nslookup等命令行工具)都无法检测SPF记录(或任何其他TXT记录).此SPF validator tool给出以下错误:“结果 – 超出永久错误空缺查找限制2”.奇怪的是,Mandrill识别DKIM而不是SPF记录.
我究竟做错了什么?
解决方法
Java System#exit无法退出程序的问题如何解决
背景
有朋友碰到了一个情况:java.lang.system#exit无法退出应用程序。
我听到这种情况的时候是感觉很惊奇的,这函数还能不起作用?这就好奇不已了呀
接着,朋友继续给出了他的场景描述:在Dubbo应用连接注册中心的时候,如果连接(超时)失败,期望调用System#exit退出应用程序,但是程序并没有按期望退出,JVM进程还存在
与此同时,如果把执行System#exit的代码放到另一个线程,程序可以按期望退出,JVM进程结束
用伪代码描述如下:
立即学习“Java免费学习笔记(深入)”;
点击下载“修复打印机驱动工具”;
Future<Object> future = 连接注册中心的Future; try { Object o = future.get(3, TimeUnit.SECONDS); } catch (Exception e) { log.error("connect failed xxxx"); System.exit(1); // 程序无法退出 } ----------- Future<Object> future = 连接注册中心的Future; try { Object o = future.get(3, TimeUnit.SECONDS); } catch (Exception e) { log.error("connect failed xxxx"); new Thread(() -> System.exit(1)).start(); // 程序能按期望退出 }
朋友面临的场景比伪代码描述的情况复杂的多,但所面临的本质问题是一样的。
更一般化地问题,在Dubbo的org.apache.dubbo.registry.zookeeper.ZookeeperRegistry#ZookeeperRegistry构造函数中,直接执行System.exit(1);程序无法退出,放在异步线程中执行却可以按期望退出
即:
// org.apache.dubbo.registry.zookeeper.ZookeeperRegistry#ZookeeperRegistry public ZookeeperRegistry(URL url, ZookeeperTransporter zookeeperTransporter) { super(url); System.exit(1); //JVM进程无法退出 // ...(省略) } ----------- // org.apache.dubbo.registry.zookeeper.ZookeeperRegistry#ZookeeperRegistry public ZookeeperRegistry(URL url, ZookeeperTransporter zookeeperTransporter) { super(url); new Thread(() -> {System.exit(1);}).start(); //JVM进程正常退出 // ...(省略) }
这就更令人惊奇了!
问题排查
要找出问题产生的原因,首先得有一些预备知识,否则会茫然无措,感觉无从下手
java.lang.System#exit 方法是Java提供的能够停止JVM进程的方法
该方法被触发时,JVM会去调用Shutdown Hook(关闭勾子)方法,直到所有勾子方法执行完毕,才会关闭JVM进程
由上述第2点猜测:是否存在死循环的勾子函数无法退出,以致JVM没有去关闭进程?
举个例子:
public static void main(String[] args) { Runtime.getRuntime().addShutdownHook(new Thread(() -> { while (true) { try { System.out.println("closing..."); TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { } } })); System.out.println("before exit..."); System.exit(0); System.out.println("after exit..."); //代码不会执行 }
如上,在main方法里先注册了一个shutdown hook,该勾子函数是个死循环,永远也不会退出,每3秒打印一次"closing…"
接着执行System.exit(0);方法,期望退出JVM进程
before exit...closing...closing...closing...closing...closing......
结果是控制台不断打印"closing…",且JVM进程没有退出
原因正是上述第二点储备知识提到的:JVM会等待所有勾子执行完毕之后,才关闭进程。而示例中的shutdown hook 永远也不会执行完毕,因此JVM进程也不会被关闭
尽管有了储备知识,仍然很疑惑:如果存在死循环的shutdown hook,那么System.exit无论是在主线程中调用,还是在异步线程中调用,都应该不会关闭JVM进程;反之,如果不存在死循环的shutdown hook,无论是在哪个线程调用,都应该会关闭JVM进程。为什么在背景的伪代码中,却是因为不同的调用线程执行System.exit,导致不一样的结果呢?
这时候只好想办法,看看shutdown hook们都在偷摸干啥事,为什么未执行完毕,以致JVM进程不能退出
恰好对Dubbo的源码也略有研究,很容易就找到org.apache.dubbo.registry.zookeeper.ZookeeperRegistry#ZookeeperRegistry的构造函数,并在其中加上一行代码,如下所示,改完之后重新编译源码,并引入自己的工程中进行Debug
注:本次使用的Dubbo版本为2.7.6
// org.apache.dubbo.registry.zookeeper.ZookeeperRegistry#ZookeeperRegistry public ZookeeperRegistry(URL url, ZookeeperTransporter zookeeperTransporter) { super(url); System.exit(1); // 新增加的一行代码 // ...(省略) }
启动工程,熟悉Dubbo的朋友应该会知道,应用启动的过程中会去注册中心(这儿是Zookeeper)注册或者订阅,因为启动的是消费者,因此应用会尝试连接注册中心Zookeeper,会走到ZookeeperRegistry的构造函数,由于构造函数第二行是新增的代码System.exit(1);按照背景的说法,JVM不会退出,且会卡死,这时候,借助IDEA的"快照"功能,可以"拍"下Java线程栈的运行情况,功能上相当于执行jstack命令
从线程栈中看出一个可疑的线程:DubboShutdownHook
从名字上可以看出是一个Dubbo注册的一个shutdown hook,其主要目的是为了关闭连接、做一些资源的回收等工作
从图中也可以看出,线程阻塞在org.apache.dubbo.registry.support.AbstractRegistryFactory第83行
public static void destroyAll() { if (!destroyed.compareAndSet(false, true)) { return; } if (LOGGER.isInfoEnabled()) { LOGGER.info("Close all registries " + getRegistries()); } // Lock up the registry shutdown process LOCK.lock(); // 83行,DubboShutdownHook线程阻塞在此处 try { for (Registry registry : getRegistries()) { try { registry.destroy(); } catch (Throwable e) { LOGGER.error(e.getMessage(), e); } } REGISTRIES.clear(); } finally { // Release the lock LOCK.unlock(); } }
从代码中很显然可以看出,因为获取不到锁,因此线程阻塞在第83行,等待获取锁,也就是说,有别的线程持着这把锁,但还没释放,DubboShutdownHook不得不等待着
通过IDEA,查看有哪些地方获取了这把锁,如下,找到了
org.apache.dubbo.registry.support.AbstractRegistryFactory#getRegistry(org.apache.dubbo.common.URL)
会获取锁
// org.apache.dubbo.registry.support.AbstractRegistryFactory public Registry getRegistry(URL url) { // ...(省略) LOCK.lock(); // 获取锁 try { // ...(省略) // 创建Registry,由于我们选用的注册中心是Zookeeper,因此通过SPI选择了ZookeeperRegistryFactory对ZookeeperRegistry进行创建,最终会调用到我们添加过一行System.exit的ZookeeperRegistry构造函数中 registry = createRegistry(url); // ...(省略) } finally { // Release the lock LOCK.unlock(); // 创建完registry,与注册中心连上之后,才会释放锁 } }
// org.apache.dubbo.registry.zookeeper.ZookeeperRegistryFactory public Registry createRegistry(URL url) { // 调用修改过源码的ZookeeperRegistry构造函数 return new ZookeeperRegistry(url, zookeeperTransporter); }
如此,System.exit无法退出JVM进程的问题总算真相大白了:
1.Dubbo启动过程中会先获取锁,然后创建registry与注册中心进行连接,在ZookeeperRegistry中调用了java.lang.System#exit方法,程序转而执行"唤起shutdown hook"的代码并阻塞等待所有勾子函数执行完毕,而此时,之前持有的锁并没有释放
2.所有勾子函数(每个勾子函数都对应一个线程)被唤醒并执行,其中有一个Dubbo的勾子函数在执行的过程中,需要获取步骤1中的锁,由于获取锁失败,就阻塞等待着
3.由于1没有释放锁的情况下等待2执行完,而2的执行需要等待1释放锁,这样就形成了一个类似"死锁"的场景,因此也就导致了程序卡死,而JVM进程还存活的现象。之所以称为"类似"死锁,是因为1中执行System.exit的线程,也即持有锁的线程,永远不会走到释放锁的代码:一旦程序进入System.exit的世界里,就像进了一个单向虫洞,只能进不能出,如果勾子函数执行完毕,JVM进程接着就会被关闭,不会有机会再释放锁
那么,为什么在异步线程中执行System.exit,却能够正常退出JVM?
那是因为:"唤起shutdown hook"并阻塞等待所有勾子函数执行完毕的线程是其它线程(此处假设是线程A),该线程在阻塞时并未持有任何锁,而主线程会继续往下执行并接着释放锁。一旦锁释放,Shutdown hook就有机会持有该锁,并且执行其它资源的回收操作,等到所有的shutdown hook执行完毕,A线程就能从阻塞中返回并执行halt方法关闭JVM,因此能够正常退出JVM进程
深入学习
以上是对java.lang.System#exit 无法退出程序问题的分析,来龙去脉已经阐述清楚,受益于对Dubbo源码的了解以及正确的排查思路和排查手段,整个问题排查过程其实并没有花太多时间,但可以趁着这个机会,把java.lang.System#exit系统学习一下,或许会对以后问题排查、基础组件设计提供一些思路
System#exit
// java.lang.System public static void exit(int status) { Runtime.getRuntime().exit(status); }
Terminates the currently running Java Virtual Machine. The argument serves as a status code; by convention, a nonzero status code indicates abnormal termination.
This method calls the exit method in class Runtime. This method never returns normally.
The call System.exit(n) is effectively equivalent to the call:
Runtime.getRuntime().exit(n)
这个方法实现非常简单,是Runtime#exit的一个简便写法,其作用是用来关闭JVM进程,一旦调用该方法,永远也不会从该方法正常返回:执行完该方法后JVM进程就直接关闭了。
入参status取值分两类:0值与非0值,0值意味着正常关闭,非0值意味着异常关闭。
传入0值[有可能]会去执行所有的finalizer方法,非0值则一定不会执行(都不正常了,还执行啥finalizer呢?)。这儿提及[有可能]是因为,默认并不会执行finalizers,需要调用java.lang.Runtime#runFinalizersOnExit方法开启,而该方法早被JDK标识为Deprecated,因此通常情况下是不会开启的
// java.lang.Runtime @Deprecated public static void runFinalizersOnExit(boolean value) { SecurityManager security = System.getSecurityManager(); if (security != null) { try { security.checkExit(0); } catch (SecurityException e) { throw new SecurityException("runFinalizersOnExit"); } } Shutdown.setRunFinalizersOnExit(value); }
接着看java.lang.Runtime#exit,可以看到,最终调用的是Shutdown.exit(status);,该方法是个包级别可见的方法,外部不可见
// java.lang.Runtime public void exit(int status) { SecurityManager security = System.getSecurityManager(); if (security != null) { security.checkExit(status); } Shutdown.exit(status); }
// java.lang.Shutdown static void exit(int status) { // ...(省略) synchronized (Shutdown.class) { /* Synchronize on the class object, causing any other thread * that attempts to initiate shutdown to stall indefinitely */ // 执行shutdown序列 sequence(); // 关闭JVM halt(status); } }
// java.lang.Shutdown private static void sequence() { // ...(省略) runHooks(); // ...(省略) }
// java.lang.Shutdown private static void runHooks() { for (int i=0; i < MAX_SYSTEM_HOOKS; i++) { try { Runnable hook; synchronized (lock) { // 这个锁很重要,目的是通过Happens-Before保证内存的可见性 currentRunningHook = i; hook = hooks[i]; } if (hook != null) hook.run(); //执行勾子函数 } catch(Throwable t) { if (t instanceof ThreadDeath) { ThreadDeath td = (ThreadDeath)t; throw td; } } } }
java.lang.Shutdown#runHooks有两个点需要注意,第一点MAX_SYSTEM_HOOKS(hooks)这个并不是我们注册的shutdown hooks,而是按顺序预定义的系统关闭勾子,目前JDK源码(JDK8)预定义了三个:
Console restore hook
Application hooks
DeleteOnExit hook
其中,Application hooks才是我们应用程序中主动注册的shutdown hook。
在java.lang.ApplicationShutdownHooks类初始化时,会执行static代码块,并在其中注册了Application hooks
// java.lang.ApplicationShutdownHooks class ApplicationShutdownHooks { /* The set of registered hooks */ // 这个才是我们应用程序代码中注册的shutdown hook private static IdentityHashMap<Thread, Thread> hooks; static { try { Shutdown.add(1 /* shutdown hook invocation order */, false /* not registered if shutdown in progress */, new Runnable() { public void run() { runHooks(); } } ); hooks = new IdentityHashMap<>(); } catch (IllegalStateException e) { // application shutdown hooks cannot be added if // shutdown is in progress. hooks = null; } }
其次要注意的点是,给hook变量赋值的时候进行了加锁
Runnable hook; synchronized (lock) { currentRunningHook = i; hook = hooks[i]; }
一般而言,给局部变量赋值是不需要加锁的,因为局部变量是栈上变量,而线程栈之间数据是隔离的,不会出现线程安全的问题,因此不需要靠加锁来保证数据并发访问的安全性。
而此处加锁也并非为了解决线程安全问题,其真正的目的在于,通过Happens-Before规则来保证hooks的内存可见性:An unlock on a monitor happens-before every subsequent lock on that monitor。
如果不加锁,有可能导致从hooks数组中读取到的值并不是内存中最新的变量值,而是一个旧值
上面是读取hooks数组给hook变量赋值,为了满足HB(Happens-Before)原则,需要确保写操作中同样对hooks变量进行了加锁,因此我们看一下写hooks数组的地方,如下:
// java.lang.Shutdown static void add(int slot, boolean registerShutdownInProgress, Runnable hook) { synchronized (lock) { // ...(省略) hooks[slot] = hook; } }
写 操作确实加了锁,这样才能让接下来的 读 操作的加锁行为满足HB原则
由于篇幅原因,就不展开具体的HB介绍,相信了解过HB原则的朋友一下就能明白其中的原理
这个点个人感觉很有意思,因为锁的作用不单是为了保证线程安全,还可以用来做为内存通信、保证内存可见性的手段,因此可以当作面试的一个点,当下次面试官问到:你写的代码中用过锁(synchronized)吗?什么场景用到锁?都集群部署了,单机锁还有意义吗? 我们就可以回答:为了保证内存的可见性,balabalaba
所以你瞧,这个点其实也给我们设计基础组件带来很大的启发,synchronized在当今集群、分布式环境下并非一无是处,总有合适的地方在等待着它发挥光和热
注:JDK源码中真处处是宝藏,很多地方隐藏着巧妙而不可缺少的设计
在给hook变量赋值之后,就执行 if (hook != null) hook.run();,其中会执行到Application hooks,即上面提到的在ApplicationShutdownHooks类初始化时注册的勾子,勾子内部调用了java.lang.ApplicationShutdownHooks#runHooks方法
// java.lang.ApplicationShutdownHooks Shutdown.add(1 /* shutdown hook invocation order */, false /* not registered if shutdown in progress */, new Runnable() { public void run() { runHooks(); } } );
// java.lang.ApplicationShutdownHooks static void runHooks() { Collection<Thread> threads; synchronized(ApplicationShutdownHooks.class) { threads = hooks.keySet(); // hooks才是应用程序真正注册的shutdown hook hooks = null; } // 每一个shutdown hook都对应一个thread,由此可见是并发执行关闭勾子函数 for (Thread hook : threads) { hook.start(); } for (Thread hook : threads) { while (true) { try { hook.join(); // 死等到hook执行完毕 break; } catch (InterruptedException ignored) { // 即便被唤醒都不搭理,接着进行下一轮循环,继续死等 } } } }
上面的hooks才是应用程序真正注册的shutdown hook,由源码可以看出,每一个hook都对应着一个thread,且调用了它们的start方法,即开启thread,意味着shutdown hook是并发、无序地执行
接着,唤起shutdown hook的线程,会通过死循环和join死等到所有关闭勾子都执行完毕,且忽略任何 唤醒异常。也即是说,如果勾子们不执行完,唤醒线程是不会离开的
等所有的Application hooks执行完毕,接下来会执行DeleteOnExit hook(如果存在),等所有system hooks执行完毕,也基本意味着sequence方法执行完毕,接下来就执行halt方法关闭JVM虚拟机
synchronized (Shutdown.class) { sequence(); halt(status); }
这里额外还有一个知识点,上文只是提了一嘴,可能会容易忽略,此处拿出来解释一下:执行java.lang.System#exit永远也不会从该方法正常返回,也即是说,即便System#exit后边跟着的是finally,也不会执行 。一不注意就容易掉坑里
try { // ... System.exit(0); } finally { // 这里的代码永远执行不到 }
java.lang.Runtime#addShutdownHook
聊完System#exit方法,接着来聊聊注册shutdown hook的方法。该方法本身实现上很简单,如下示:
// java.lang.Runtime public void addShutdownHook(Thread hook) { // ...(省略) ApplicationShutdownHooks.add(hook); } // java.lang.ApplicationShutdownHooks static synchronized void add(Thread hook) { // ...(省略) hooks.put(hook, hook); }
需要注意的是,注册的关闭勾子会在以下几种时机被调用到
程序正常退出
最后一个非守护线程执行完毕退出时
System.exit方法被调用时
程序响应外部事件
程序响应用户输入事件,例如在控制台按ctrl+c(^+c)
程序响应系统事件,如用户注销、系统关机等
除此之外,shutdown hook是不会被执行的
Shutdown hook存在的意义之一,是能够帮助我们实现优雅停机,而优雅停机的意义是:应用的重启、停机等操作,不影响业务的连续性
以Dubbo Provider的视角为例,优雅停机需要满足两点基本诉求:
Consumer不应该请求到已经下线的Provider
在途请求需要处理完毕,不能被停机指令中断
Dubbo注册了Shutdown hook,JVM在收到操作系统发来的关闭指令时,会执行关闭勾子
在勾子中停止与注册中心的连接,注册中心会通知Consumer某个Provider已下线,后续不应该再调用该Provider进行服务。此行为是断掉上游流量,满足第一点诉求
接着,勾子执行Protocol(Dubbo相关概念)的注销逻辑,在其中判断server(Dubbo相关概念)是否还在处理请求,在超时时间内等待所有任务处理完毕,则关闭server。此行为是处理在途请求,满足第二点述求
因此,一种优雅停机的整体方案如下:
$pid = ps | grep xxx // 查找要关闭的应用 kill $pid // 发出关闭应用指令 sleep for a period of time // 等待一段时间,让应用程序执行shutdown hook进行现场的保留跟资源的清理工作 $pid = ps | grep xxx // 再次查找要关闭的应用,如果还存在,就需要强行关闭应用 if($pid){kill -9 $pid} // 等待一段时间之后,应用程序仍然没有正常停止,则需要强行关闭应用
以上就是Java System#exit无法退出程序的问题如何解决的详细内容,更多请关注php中文网其它相关文章!
关于java – bash eval未检测到System.exit返回码和未检测到java环境的介绍已经告一段落,感谢您的耐心阅读,如果想了解更多关于. System.exit(0)和System.exit(1) 和return 区别、c# – Reflection.emit System.InvalidProgramException:公共语言运行时检测到无效程序、domain-name-system – 未检测到我的域的SPF记录、Java System#exit无法退出程序的问题如何解决的相关信息,请在本站寻找。
本文标签: