java classloader的一个bug

最近遇到一个很诡异的问题,SAE的javaruntime有一部分用户反馈session老是丢失,致使至关一部分用户的代码部署后登陆功能没法正常使用。java

先说下sae session的托管方式:web

这里先说下SAE的session托管方式,采用分布式session,最终由mc实现session的综合存储,从而达到多jvm实例的状况下,session同步。分布式session的实现代码最终调用spymmecached的客户的对session的后端mc进行增删改。后端


经过分析用户运行时产生的日志分析发现一个以下的错误session

java.lang.ClassNotFoundException: com.xxx.xxx.xxxx   //这里通常都是mvc里的model代码,一些基本的用户信息处理类,序列化后存session是通常登陆的处理方式
     at java.net.URLClassLoader$1.run(URLClassLoader.java:217) 
     at java.security.AccessController.doPrivileged(Native Method) 
     at java.net.URLClassLoader.findClass(URLClassLoader.java:205) 
     at java.lang.ClassLoader.loadClass(ClassLoader.java:388) 
     at java.lang.ClassLoader.loadClass(ClassLoader.java:333) 
     at org.eclipse.jetty.webapp.WebAppClassLoader.loadClass(WebAppClassLoader.java:419) 
     at org.eclipse.jetty.webapp.WebAppClassLoader.loadClass(WebAppClassLoader.java:372) 
     at java.lang.Class.forName0(Native Method) 
     at java.lang.Class.forName(Class.java:279) 
     at inner.spy.memcached.transcoders.SerializingTranscoder$DeserializableObject.resolveClass(SerializingTranscoder.java:323)    //调用栈这里是调用到了spymemecached客户端改写的反序列化的代码
     at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1592) 
     at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1513) 
     at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1749) 
     at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1346) 
     at java.io.ObjectInputStream.readObject(ObjectInputStream.java:368) 
     at inner.spy.memcached.transcoders.SerializingTranscoder.deserialize(SerializingTranscoder.java:294) 
     at inner.spy.memcached.transcoders.SerializingTranscoder.decode(SerializingTranscoder.java:204) 
     at inner.spy.memcached.transcoders.TranscodeService$1.call(TranscodeService.java:63) 
     at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:334) 
     at java.util.concurrent.FutureTask.run(FutureTask.java:166) 
     at inner.spy.memcached.transcoders.TranscodeService$Task.run(TranscodeService.java:110) 
     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1110) 
     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:603) 
     at java.lang.Thread.run(Thread.java:714)

从以上的错误能够看出就是类找不到,照通常的想法应该是看看打包后的war包中是否包含这个类,不幸的是这个类通常都是在的,那么就是其余的问题来看看具体错误处的代码mvc

public Object deserialize(byte[] in) {  //这个方法是最终拿到session mc的数据后判断为object的时候,调用的反序列化
		Object rv = null;
		ByteArrayInputStream bis = null;
		ObjectInputStream is = null;
		try {
			if (in != null) {
				ClassLoader loader = Thread.currentThread().getContextClassLoader(); //这里是是拿到当前上下文的classloader
				DeserializableObject ois = new DeserializableObject( new ByteArrayInputStream(in),loader);
				return ois.readObject();
			}
		} catch (IOException e) {
			getLogger().warn("Caught IOException decoding %d bytes of data",
					in == null ? 0 : in.length, e);
		} catch (ClassNotFoundException e) {
			getLogger().warn("Caught CNFE decoding %d bytes of data",
					in == null ? 0 : in.length, e);
		} finally {
			CloseUtil.close(is);
			CloseUtil.close(bis);
		}
		return rv;
	}
	
	
	/**
	 * 反序列化字节流,使用一个指定的classloader序列化class流。
	 * @author Administrator
	 *
	 */
	private static class DeserializableObject extends ObjectInputStream { //具体处理逻辑
		private ClassLoader loader;
		DeserializableObject(InputStream in, ClassLoader loader) throws IOException {
			super(in);
			this.loader = loader;  //解析一些用户类的时候这里的classloader变成了StartJarClassloader
		}
		@Override
		protected Class<?> resolveClass(ObjectStreamClass desc)throws IOException, ClassNotFoundException {
			return Class.forName( desc.getName(),true,loader );
		}
	}

经过过debug发现,在解析一些用户的类的时候,当前上下文的classloader不知道怎么变成了StarJarClassloader对象实例,StartJarClassloader的对象实例加载的类对象是在jetty/lib下的相关jar包,固然没法找到用户本身写的类对象,查了半天也没有确认为何会致使这样的问题,那么只能换个思路解决问题了,能不能在jetty启动的时候将加载用户类的WebAppClassloader的实例保存起来,而后在反序列化的时候直接拿这个classloader来反射具体的类,因而考虑半天具体这样实现app

/**
	 * 自定义的反序列化
	 */
	public Object deserialize(byte[] in) {
		Object rv = null;
		ByteArrayInputStream bis = null;
		ObjectInputStream is = null;
		try {
			if (in != null) {
//				ClassLoader loader = Thread.currentThread().getContextClassLoader();
				ClassLoader loader = ServiceStatus.loader !=null?ServiceStatus.loader : Thread.currentThread().getContextClassLoader();  //用一个全局的类存下加载用户代码的classloader
				DeserializableObject ois = new DeserializableObject( new ByteArrayInputStream(in),loader);
				return ois.readObject();
			}
		} catch (IOException e) {
			getLogger().warn("Caught IOException decoding %d bytes of data",
					in == null ? 0 : in.length, e);
		} catch (ClassNotFoundException e) {
			getLogger().warn("Caught CNFE decoding %d bytes of data",
					in == null ? 0 : in.length, e);
		} finally {
			CloseUtil.close(is);
			CloseUtil.close(bis);
		}
		return rv;
	}
	
	
	/**
	 * 反序列化字节流,使用一个指定的classloader序列化class流。
	 * @author Administrator
	 *
	 */
	private static class DeserializableObject extends ObjectInputStream {
		private ClassLoader loader;
		DeserializableObject(InputStream in, ClassLoader loader) throws IOException {
			super(in);
			this.loader = loader;
		}
		@Override
		protected Class<?> resolveClass(ObjectStreamClass desc)throws IOException, ClassNotFoundException {
			return Class.forName( desc.getName(),true,loader );
		}

按照以上的处理方式处理后发现,果真解决了这个问题,真是曲线救国啊~~~eclipse

这里可能要问了怎么获取到到加载用户代码的WebAppClassloader呢 仍是贴代码webapp

public class SaeWebAppContext extends WebAppContext {  //这里的父类是构造用户代码的上下文,在这里会解析用户的war包,然后建立属于用户代码的WebAppClassloader 
    	@Override
	public void setClassLoader(ClassLoader classLoader) {  
		// TODO Auto-generated method stub
		super.setClassLoader(classLoader); //在父类建立完后 存下这个对象的引用
		ServiceStatus.loader = getClassLoader();
	}
}

自此 问题解决 目前运行良好,接下来要查查是什么缘由致使classloader变化,等查到了在继续分析缘由
jvm

本文完分布式

相关文章
相关标签/搜索