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

本文完分佈式

相關文章
相關標籤/搜索