saturn java 热加载(一)
背景:
每次启用都要重启executor,在此之前还要确保该executor下没有任务在执行,很麻烦
方案:
将不常更换的公共包放在executor classpath下,仍然由saturn加载
将常变动的包放在executor类加载器可见范围外,使其不能加载
public class ProxyJdsJavaJob extends AbstractSaturnJavaJob { private static final Logger LOGGER = LoggerFactory.getLogger(ProxyJdsJavaJob.class); @Override public SaturnJobReturn handleJavaJob(final String jobName, final Integer shardItem, final String shardParam, final SaturnJobExecutionContext context) { LOGGER.info("saturn 传入 java job 文件路径及参数:{}", shardParam); AbstractJdsJavaJob abstractJdsJavaJob = null; try { String [] temp = shardParam.split(";"); String dir = "file:"+temp[0]; LOGGER.info("saturn 传入 java job 文件路径:{}", temp[0]); URL url = new URL(dir); URL[] urls2 = {url}; MyUrlClassLoader myUrlClassLoader = new MyUrlClassLoader(urls2); LOGGER.info("saturn 传入 java job 类名:{}", temp[1]); // Class CA = myUrlClassLoader.loadClass(temp[1]); Class CA = myUrlClassLoader.findClass(temp[1]); abstractJdsJavaJob = (AbstractJdsJavaJob)CA.newInstance(); abstractJdsJavaJob.runJdsJob(jobName, shardItem, shardParam); LOGGER.info("success:{},{},{}", jobName, shardItem, shardParam); return new SaturnJobReturn(Common.createJobResString("", jobName, shardItem, shardParam, true)); } catch (Exception e) { LOGGER.info("error:{},{},{}", jobName, shardItem, shardParam); LOGGER.error(ExceptionUtils.getStackTrace(e)); new SendMailThread(new Date(), e, jobName, shardItem, shardParam).execute(); throw new JdsFastJobException(e); } catch (Throwable throwable) { LOGGER.error(throwable.getMessage(), throwable); throw new JdsFastJobException("类加载器失败"); } finally { if(abstractJdsJavaJob != null) abstractJdsJavaJob.release(); } } }
public class MyUrlClassLoader extends URLClassLoader { private static final Logger LOGGER = LoggerFactory.getLogger(MyUrlClassLoader.class); public MyUrlClassLoader(URL[] urls) { super(urls, Thread.currentThread().getContextClassLoader()); // 父加载器不一定是系统类加载器,可能是saturn自定义加载器 ClassLoader parent = Thread.currentThread().getContextClassLoader(); LOGGER.info("parent class loader {}", parent); } @Override protected Class<?> findClass(final String name) throws ClassNotFoundException { return super.findClass(name); } }
1. 如果待热加载jar包(后称为A)在executor类加载器可见范围内,必须使用findclass避开缓存
如果在可见范围外,loadclass与findclass都可以,但请注意:
findclass(A),A中引用B,那么jvm使用loadclass加载B,这就意味着,如果B在父加载器缓存中,则不会热加载
所以如果B也要热加载,则1)将B也放到主程序类加载可见范围外,2)在A中使用findclass避开主类加载器缓存,当然同时也打破双亲委派
2. 这里有个坑,executor的类加载器并不是AppClassLoader,日志显示
(MyUrlClassLoader.java:21) -> parent class loader com.vip.saturn.job.executor.JobClassLoader@5fd764ee
所以,自定义类加载器必须指定parent class loader为saturn的类加载器,而不是使用默认null作为参数,这将导致AppClassLoader作为父加载器,否则
AbstractJdsJavaJob是由saturn类加载器加载
而具体的被热加载的类继承AbstractJdsJavaJob,由自定义类加载器加载,缺省情况下,其parent为系统类加载器,导致自定义加载器和saturn类加载器变为平行关系,saturn加载的类,对我们要热加载的类不可见,从而抛出error
3. 永远要确保finally中的语句是安全的,因为如果它抛异常,将覆盖catch中所有代码
详细见:https://www.cnblogs.com/silyvin/p/9993458.html
4. 由第2点抛出的异常,catch Exception是catch不到的,详细见:https://www.cnblogs.com/silyvin/p/10274898.html
故使用了catch Throwable,成功抓取
[INFO] 2019-01-15 17:26:43 985 [jds.fast.job.ProxyJdsJavaJob] [Saturn-test_ProxyJdsJavaJob-24-thread-1] (ProxyJdsJavaJob.java:30) -> saturn 传入 java job 类名:saturn.DemoJob [ERROR] 2019-01-15 17:26:43 987 [jds.fast.job.ProxyJdsJavaJob] [Saturn-test_ProxyJdsJavaJob-24-thread-1] (ProxyJdsJavaJob.java:45) -> jds/fast/job/AbstractJdsJavaJob java.lang.NoClassDefFoundError: jds/fast/job/AbstractJdsJavaJob at java.lang.ClassLoader.defineClass1(Native Method) at java.lang.ClassLoader.defineClass(ClassLoader.java:763) at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142) at java.net.URLClassLoader.defineClass(URLClassLoader.java:468) at java.net.URLClassLoader.access$100(URLClassLoader.java:74) at java.net.URLClassLoader$1.run(URLClassLoader.java:369) at java.net.URLClassLoader$1.run(URLClassLoader.java:363) at java.security.AccessController.doPrivileged(Native Method) at java.net.URLClassLoader.findClass(URLClassLoader.java:362) at jds.fast.job.MyUrlClassLoader.findClass(MyUrlClassLoader.java:17) at jds.fast.job.ProxyJdsJavaJob.handleJavaJob(ProxyJdsJavaJob.java:32) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at com.vip.saturn.job.java.SaturnJavaJob$1.internalCall(SaturnJavaJob.java:218) at com.vip.saturn.job.basic.AbstractSaturnJob$JobBusinessClassMethodCaller.call(AbstractSaturnJob.java:205) at com.vip.saturn.job.java.SaturnJavaJob.handleJavaJob(SaturnJavaJob.java:214) at com.vip.saturn.job.java.SaturnJavaJob.doExecution(SaturnJavaJob.java:204) at com.vip.saturn.job.basic.JavaShardingItemCallable.call(JavaShardingItemCallable.java:158) at com.vip.saturn.job.basic.ShardingItemFutureTask.call(ShardingItemFutureTask.java:88) at com.vip.saturn.job.basic.ShardingItemFutureTask.call(ShardingItemFutureTask.java:17) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:748) Caused by: java.lang.ClassNotFoundException: jds.fast.job.AbstractJdsJavaJob at java.net.URLClassLoader.findClass(URLClassLoader.java:382) at jds.fast.job.MyUrlClassLoader.findClass(MyUrlClassLoader.java:17) at java.lang.ClassLoader.loadClass(ClassLoader.java:424) at java.lang.ClassLoader.loadClass(ClassLoader.java:357) ... 26 common frames omitted
5 7.13补充:
public class MyUrlClassLoader extends URLClassLoader { private static final Logger LOGGER = LoggerFactory.getLogger(MyUrlClassLoader.class); public MyUrlClassLoader(URL[] urls) { super(urls, Thread.currentThread().getContextClassLoader()); // 父加载器不一定是系统类加载器,可能是saturn自定义加载器 ClassLoader parent = Thread.currentThread().getContextClassLoader(); LOGGER.info("parent class loader {}", parent); ClassLoader grandParent = parent.getParent(); LOGGER.info("grand class loader {}", grandParent); } @Override protected Class<?> findClass(final String name) throws ClassNotFoundException { return super.findClass(name); } @Override protected void finalize() throws Throwable { LOGGER.info("回收MyUrlClassLoader"); super.finalize(); } }
grandParent 居然是null,这也导致了与以premain方式注入监控的jmx_exporter所在的系统类加载器相互不可见:https://www.cnblogs.com/silyvin/articles/10600530.html
2019.12.26
这篇文章能干啥
1)任务隔离
2)热加载
3)加解密
4)任务状态统一记录