2010年9月15日
#
Throwable occurred: org.springframework.transaction.CannotCreateTransactionException: Could not open JDBC Connection for transaction; nested exception is com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: The last packet successfully received from the server was 50,123,505 milliseconds ago. The last packet sent successfully to the server was 50,123,505 milliseconds ago. is longer than the server configured value of 'wait_timeout'. You should consider either expiring and/or testing connection validity before use in your application, increasing the server configured values for client timeouts, or using the Connector/J connection property 'autoReconnect=true' to avoid this problem.
這主要是由兩個(gè)原因引起來的:
1.mysql 會(huì)自動(dòng)關(guān)閉長(zhǎng)時(shí)間不用的connection,一個(gè)連接如果處于sleep狀態(tài)達(dá)到mysql的參數(shù)wait_timeout指定的時(shí)間(默認(rèn)為8小時(shí)),就是自動(dòng)關(guān)閉這個(gè)連接
2.common pool中沒有指定相應(yīng)的連接檢查參數(shù)
解決辦法:從common pool的配置參數(shù)來解決:
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName">
<value>${db.driver}</value>
</property>
<property name="url">
<value>${db.url}</value>
</property>
<property name="username">
<value>${db.user}</value>
</property>
<property name="password">
<value>${db.password}</value>
</property>
<property name="maxActive">
<value>100</value>
</property>
<property name="maxIdle">
<value>50</value>
</property>
<property name="maxWait">
<value>10000</value>
</property>
<property name="timeBetweenEvictionRunsMillis">
<value>3600000</value><!--1 hours-->
</property>
<!--
<property name="minEvictableIdleTimeMillis">
<value>20000</value>
</property>
-->
<property name="testWhileIdle">
<value>true</value>
</property>
<property name="validationQuery">
<value>select 1 from dual</value>
</property>
</bean>
使用上述的三個(gè)紅色的參數(shù),就可以避免這個(gè)問題.這三個(gè)參數(shù)的意義:
timeBetweenEvictionRunsMillis:啟動(dòng)connection校驗(yàn)定時(shí)器,定時(shí)器運(yùn)行時(shí)間間隔就是timeBetweenEvictionRunsMillis的值.默認(rèn)為-1,表示不啟動(dòng)定時(shí)器,這里設(shè)定為1小時(shí),只要小于mysql的wait_timeout就可以了
testWhileIdle: true,表示檢查idle的connection,false為不檢查
validationQuery:用于檢查connection的sql語句.
這只是一種方法,另外的幾種方法:
timeBetweenEvictionRunsMillis+minEvictableIdleTimeMillis:這種方式不檢查Connection的有效性,而是檢查連接的空閑時(shí)間,大于minEvictableIdleTimeMillis就清除.
<property name="timeBetweenEvictionRunsMillis">
<value>3600000</value><!--1 hours-->
</property>
<property name="minEvictableIdleTimeMillis">
<value>120000</value><!--connection的空閑時(shí)間大于這個(gè)值,就直接被關(guān)閉,并從連接池中刪除-->
</property>
如果不喜歡用定時(shí)器,也可以配置testOnBorrow+validationQuery參數(shù):每次從連接池取參數(shù)都會(huì)校驗(yàn)連接的有效性.實(shí)際上這種方式性能會(huì)比定時(shí)器差些.
<property name="testOnBorrow">
<value>true</value>
</property>
<property name="validationQuery">
<value>select 1 from dual</value>
</property>
另外,也可以用testOnReturn+validationQuery,不過未必會(huì)解決問題:這表示每次使用完連接,歸還連接池的時(shí)候檢查連接的有效性,這有可能導(dǎo)致使用一次無效的連接,最好不要用.
上面的幾種方法可以合并使用,只是檢查的點(diǎn)多了,未必是好事
另外,也可以使用Abandoned的那幾個(gè)參數(shù),來刪除連接池中的連接.也能達(dá)到效果.我沒測(cè)試.
2010年8月12日
#

就是上面的樣子
做這個(gè)過程中我碰到兩個(gè)問題:
1:如何做帶尾巴的氣泡View
2:如何把這個(gè)View添加到MapView中.
1:如何做帶尾巴的氣泡View
我是采用背景圖的方式來實(shí)現(xiàn)的.當(dāng)然,普通的PNG在View 縮放的時(shí)候會(huì)失真,尤其是那個(gè)尖尖的尾巴.
后來采用9.png的格式,才完成了不變形的效果.9.png格式的Png可以用SDK\Tools\draw9patch.bat來處理,只要把普通的png的邊上標(biāo)志一下就可以了,具體draw9patch.bat如何使用這里就不說了,網(wǎng)上有很多文檔,自己查查就知道了.
我生成的9.png就是下面這個(gè)樣子,注意四周的黑線.就是9png拉伸時(shí)的標(biāo)識(shí)
有了這個(gè)png,直接放到你的工程下的res/drawable目錄就可以了,
然后在res/layout目錄下建立你的view的xml文件,比如叫overlay_pop.xml,我的是這樣的:
<?xml version="1.0" encoding="UTF-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:background="@drawable/bubble_background"
<!--這就是那個(gè)9.png-->
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="5px"
android:paddingTop="5px"
android:paddingRight="5px"
android:paddingBottom="20px"
<!--注意加上padding,否則view里面的東西就畫到邊框上了-->
>
<TextView android:id="@+id/map_bubbleTitle"
android:ellipsize="marquee"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:singleLine="true"
style="@style/map_BubblePrimary" />
<!--style可以沒有,我這里第一個(gè)TextView表示標(biāo)題,用的是大字體-->
<TextView android:id="@+id/map_bubbleText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="false"
style="@style/map_BubbleSecondary" />
<!--style可以沒有,我這里第二個(gè)TextView表示描述信息,用的是大字體-->
</LinearLayout>
這樣popView就建立好了
2:如何把這個(gè)View添加到MapView中.
通常是在mapView中點(diǎn)擊某個(gè)位置,彈出popView
或者點(diǎn)擊某個(gè)Overlay彈出popView,這里用點(diǎn)擊Overlay來說明,
overlay有onTap()方法,你可以實(shí)現(xiàn)自己的overlay,overideonTap()方法,彈出popView,
也可以使用setOnFocusChangeListener(),在listener中實(shí)現(xiàn)彈出popView,.
我是用的listener,因?yàn)閟etOnFocusChangeListener在失去焦點(diǎn)也會(huì)觸發(fā),我可以再失去焦點(diǎn)的時(shí)候隱藏popView.
MapView是
繼承自ViewGroup的,因此,MapView有addView()方法,同時(shí)還有
MapView.LayoutParams
MapView.LayoutParams 可以根據(jù)GeoPoint來定位,我就是利用這個(gè)特性來定位彈出的popView的.
PointItemizedOverlay overlay = new PointItemizedOverlay(drawable); <!--這是我繼承自ItemizedOverlay的overlay,主要就是畫一個(gè)圖片,寫一個(gè)名稱,很簡(jiǎn)單,這里不貼具體代碼了-->
public class BaseMapActivity extends MapActivity {
/**
* 地圖View
*/
protected MapView mapView;
/**
* 彈出的氣泡View
*/
private View popView;
/**
監(jiān)聽器
*/
private final ItemizedOverlay.OnFocusChangeListener onFocusChangeListener = new ItemizedOverlay.OnFocusChangeListener() {
@Override
public void onFocusChanged(ItemizedOverlay overlay, OverlayItem newFocus) {
//創(chuàng)建氣泡窗口
if (popView != null) {
popView.setVisibility(View.GONE);
}
if (newFocus != null) {
MapView.LayoutParams geoLP = (MapView.LayoutParams) popView.getLayoutParams();
geoLP.point = newFocus.getPoint();//這行用于popView的定位
TextView title = (TextView) popView.findViewById(R.id.map_bubbleTitle);
title.setText(newFocus.getTitle());
TextView desc = (TextView) popView.findViewById(R.id.map_bubbleText);
if (newFocus.getSnippet() == null || newFocus.getSnippet().length() == 0) {
desc.setVisibility(View.GONE);
} else {
desc.setVisibility(View.VISIBLE);
desc.setText(newFocus.getSnippet());
}
mapView.updateViewLayout(popView, geoLP);
popView.setVisibility(View.VISIBLE);
}
}
};
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
/**
省略其他代碼
**/
//初始化氣泡,并設(shè)置為不可見
popView = inflater.inflate(R.layout.overlay_popup, null);
mapView.addView( popView,
new MapView.LayoutParams(MapView.LayoutParams.WRAP_CONTENT, MapView.LayoutParams.WRAP_CONTENT,
null, MapView.LayoutParams.BOTTOM_CENTER));
//由于我的氣泡的尾巴是在下邊居中的,因此要設(shè)置成MapView.LayoutParams.BOTTOM_CENTER.
//這里沒有給GeoPoint,在onFocusChangeListener中設(shè)置
views.add(popView);
popView.setVisibility(View.GONE);
添加overlay
PointItemizedOverlay overlay = new PointItemizedOverlay(drawable);
//設(shè)置顯示/隱藏泡泡的監(jiān)聽器
overlay.setOnFocusChangeListener(onFocusChangeListener);
overlay.addOverlay(/*你自己的overlayItem*/);
overlay.addOverlay(/*你自己的overlayItem*/);
overlay.addOverlay(/*你自己的overlayItem*/);
}
}
這樣就基本完工了.
使用方法:
LineItemizedOverlay overlay = new LineItemizedOverlay();
overlay.addOverlay(/*起點(diǎn)的OverlayItem*/);
overlay.addOverlay(/*終點(diǎn)的OverlayItem*/);
overlay.addLinePoint(/*要畫的軌跡的GeoPoint的List*/);
mapView.getOverlays().add(overlay);
/**
*
*/
package com.xtyon.tuola.truck.map;
import java.util.ArrayList;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.drawable.Drawable;
import com.google.android.maps.GeoPoint;
import com.google.android.maps.ItemizedOverlay;
import com.google.android.maps.MapView;
import com.google.android.maps.OverlayItem;
import com.google.android.maps.Projection;
/**
* 地圖上的線型圖層:包括一個(gè)起點(diǎn),一個(gè)終點(diǎn),以及之間的曲線
* @author superwang
*/
public class LineItemizedOverlay extends ItemizedOverlay<OverlayItem> {
private static final int LAYER_FLAGS = Canvas.MATRIX_SAVE_FLAG | Canvas.CLIP_SAVE_FLAG
| Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.FULL_COLOR_LAYER_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG;
/**
* 用于保存起點(diǎn)/終點(diǎn)數(shù)據(jù)
*/
private final ArrayList<OverlayItem> mOverlays = new ArrayList<OverlayItem>();
/**
* 用于保存構(gòu)成曲線的點(diǎn)的數(shù)據(jù)
*/
private final ArrayList<GeoPoint> linePoints = new ArrayList<GeoPoint>();
/**
* @param defaultMarker
*/
public LineItemizedOverlay() {
super(null);
// TODO Auto-generated constructor stub
}
/* (non-Javadoc)
* @see com.google.android.maps.ItemizedOverlay#createItem(int)
*/
@Override
protected OverlayItem createItem(int i) {
return mOverlays.get(i);
}
/* (non-Javadoc)
* @see com.google.android.maps.ItemizedOverlay#size()
*/
@Override
public int size() {
// TODO Auto-generated method stub
return mOverlays.size();
}
/**
* 調(diào)價(jià)起點(diǎn)/終點(diǎn)
* description:
* @param overlay
*/
public void addOverlay(OverlayItem overlay) {
mOverlays.add(overlay);
populate();
}
/**
* 添加曲線中的點(diǎn)
* description:
* @param point
*/
public void addLinePoint(GeoPoint point) {
linePoints.add(point);
}
public ArrayList<GeoPoint> getLinePoints() {
return linePoints;
}
/**
* 畫起點(diǎn)/終點(diǎn)/軌跡
*/
@Override
public void draw(Canvas canvas, MapView mapView, boolean shadow) {
if (!shadow) {
//System.out.println("!!!!!!!!!!!!!!");
canvas.save(LAYER_FLAGS);
//canvas.save();
Projection projection = mapView.getProjection();
int size = mOverlays.size();
Point point = new Point();
Paint paint = new Paint();
paint.setAntiAlias(true);
OverlayItem overLayItem;
//畫起點(diǎn)/終點(diǎn)
for (int i = 0; i < size; i++) {
overLayItem = mOverlays.get(i);
Drawable marker = overLayItem.getMarker(0);
//marker.getBounds()
/* 象素點(diǎn)取得轉(zhuǎn)換 */
projection.toPixels(overLayItem.getPoint(), point);
if (marker != null) {
boundCenterBottom(marker);
}
/* 圓圈 */
//Paint paintCircle = new Paint();
//paintCircle.setColor(Color.RED);
paint.setColor(Color.RED);
canvas.drawCircle(point.x, point.y, 5, paint);
/* 文字設(shè)置 */
/* 標(biāo)題 */
String title = overLayItem.getTitle();
/* 簡(jiǎn)介 */
// String snippet = overLayItem.getSnippet();
//
// StringBuffer txt = new StringBuffer();
// if (title != null && !"".equals(title))
// txt.append(title);
//
// if (snippet != null && !"".equals(snippet)) {
// if (txt.length() > 0) {
// txt.append(":");
// }
// txt.append(snippet);
// }
//Paint paintText = new Paint();
if (title != null && title.length() > 0) {
paint.setColor(Color.BLACK);
paint.setTextSize(15);
canvas.drawText(title, point.x, point.y, paint);
}
}
//畫線
boolean prevInBound = false;//前一個(gè)點(diǎn)是否在可視區(qū)域
Point prev = null;
int mapWidth = mapView.getWidth();
int mapHeight = mapView.getHeight();
//Paint paintLine = new Paint();
paint.setColor(Color.RED);
//paint.setPathEffect(new CornerPathEffect(10));
paint.setStrokeWidth(2);
int count = linePoints.size();
//Path path = new Path();
//path.setFillType(Path.FillType.INVERSE_WINDING);
for (int i = 0; i < count; i++) {
GeoPoint geoPoint = linePoints.get(i);
//projection.toPixels(geoPoint, point); //這一行似乎有問題
point = projection.toPixels(geoPoint, null);
if (prev != null) {
if (point.x >= 0 && point.x <= mapWidth && point.y >= 0 && point.y <= mapHeight) {
if ((Math.abs(prev.x - point.x) > 2 || Math.abs(prev.y - point.y) > 2)) {
//這里判斷點(diǎn)是否重合,重合的不畫線,可能會(huì)導(dǎo)致畫線不在路上
canvas.drawLine(prev.x, prev.y, point.x, point.y, paint);
//path.lineTo(point.x, point.y);
prev = point;
prevInBound = true;
}
} else {
//在可視區(qū)與之外
if (prevInBound) {//前一個(gè)點(diǎn)在可視區(qū)域內(nèi),也需要?jiǎng)澗€
//path.lineTo(point.x, point.y);
canvas.drawLine(prev.x, prev.y, point.x, point.y, paint);
}
prev = point;
prevInBound = false;
}
} else {
//path.moveTo(point.x, point.y);
prev = point;
}
}
//canvas.drawPath(path, paint);
canvas.restore();
//DebugUtils.showMemory();
}
super.draw(canvas, mapView, shadow);
}
}
2010年6月30日
#
我做的應(yīng)用是以Spring為系統(tǒng)的基礎(chǔ)框架,mysql為后臺(tái)數(shù)據(jù)庫(kù).在tomcat上發(fā)布后,總是不能進(jìn)行熱部署(reload),多次reload后,就會(huì)出OutOfMemory PermGen,
為此煩惱了很久,總于下定決心找找根源.
經(jīng)過3天的不懈努力,小有成果,記錄下來
實(shí)際上下面的分析都已經(jīng)沒什么用了,如果你使用tomcat6.0.26及以后的版本,我所說的這些情況都已經(jīng)被處理了,并且比我處理的還要多很多.可以下載tomcat6.0.26的源代碼
看看WebappClassLoader類的處理就成了.
通過分析工具的分析(用了YourKit,以及JDK1.6/bin下的jps/jmap/jhat),發(fā)現(xiàn)有下面幾個(gè)方面會(huì)造成memory leak.
1.SystemClassLoader與WebappClassLoader加載的類相互引用,tomcat reload只是卸載WebappClassloader中的class,SystemClassLoader是不會(huì)卸載的(否則其他應(yīng)用也停止了).但是WebappClassloader加載的類被SystemClassLoader引用的化,WebappClassloader中的相關(guān)類就不會(huì)被JVM進(jìn)行垃圾收集
目前發(fā)現(xiàn)2種容易產(chǎn)生這種leak的現(xiàn)象.
a.在使用java.lang.ThreadLocal的時(shí)候很容易產(chǎn)生這種情況
b.使用jdbc驅(qū)動(dòng),而且不是在tomcat中配置的公共連接池.則java.sql.DriverManager一定會(huì)產(chǎn)生這種現(xiàn)象
ThreadLocal.set(Object),如果這個(gè)Object是WebappsClassLoader加載的,使用之后沒有做ThreadLocal.set(null)或者ThreadLocal.remove(),就會(huì)產(chǎn)生memory leak.
由于ThreadLocal實(shí)際上操作的是java.lang.Thread類中的ThreadLocalMap,Thread類是由SystemClassLoder加載的.而這個(gè)線程實(shí)例(main thread)在tomcat reload的時(shí)候不會(huì)銷毀重建,必然就產(chǎn)生了SystemClassLoder中的類引用WebappsClassLoader的類.
DriverManager也是由SystemClassLoder載入的,當(dāng)初始化某個(gè)JDBC驅(qū)動(dòng)的時(shí)候,會(huì)向DriverManager中注冊(cè)該驅(qū)動(dòng),通常是***.driver,例如com.mysql.jdbc.Driver
這個(gè)Driver是通過class.forName()加載的,通常也是加載到WebappClassLoader.這就出現(xiàn)了兩個(gè)classLoader中的類的交叉引用.導(dǎo)致memory leak.
解決辦法:
寫一個(gè)ServletContextListener,在contextDestroyed方法中統(tǒng)一刪除當(dāng)前Thread的ThreadLocalMap中的內(nèi)容.
public class ApplicationCleanListener implements ServletContextListener {
public void contextInitialized(ServletContextEvent event) {
}
public void contextDestroyed(ServletContextEvent event) {
//處理ThreadLocal
ThreadLocalCleanUtil.clearThreadLocals();
/*
* 如果數(shù)據(jù)故驅(qū)動(dòng)是通過應(yīng)用服務(wù)器(tomcat etc...)中配置的<公用>連接池,這里不需要 否則必須卸載Driver
*
* 原因: DriverManager是System classloader加載的, Driver是webappclassloader加載的,
* driver保存在DriverManager中,在reload過程中,由于system
* classloader不會(huì)銷毀,driverManager就一直保持著對(duì)driver的引用,
* driver無法卸載,與driver關(guān)聯(lián)的其他類
* ,例如DataSource,jdbcTemplate,dao,service....都無法卸載
*/
try {
System.out.println("clean jdbc Driver......");
for (Enumeration e = DriverManager.getDrivers(); e
.hasMoreElements();) {
Driver driver = (Driver) e.nextElement();
if (driver.getClass().getClassLoader() == getClass()
.getClassLoader()) {
DriverManager.deregisterDriver(driver);
}
}
} catch (Exception e) {
System.out
.println("Exception cleaning up java.sql.DriverManager's driver: "
+ e.getMessage());
}
}
}
/**
* 這個(gè)類根據(jù)
*/
public class ThreadLocalCleanUtil {
/**
* 得到當(dāng)前線程組中的所有線程 description:
*
* @return
*/
private static Thread[] getThreads() {
ThreadGroup tg = Thread.currentThread().getThreadGroup();
while (tg.getParent() != null) {
tg = tg.getParent();
}
int threadCountGuess = tg.activeCount() + 50;
Thread[] threads = new Thread[threadCountGuess];
int threadCountActual = tg.enumerate(threads);
while (threadCountActual == threadCountGuess) {
threadCountGuess *= 2;
threads = new Thread[threadCountGuess];
threadCountActual = tg.enumerate(threads);
}
return threads;
}
public static void clearThreadLocals() {
ClassLoader classloader = Thread
.currentThread()
.getContextClassLoader();
Thread[] threads = getThreads();
try {
Field threadLocalsField = Thread.class
.getDeclaredField("threadLocals");
threadLocalsField.setAccessible(true);
Field inheritableThreadLocalsField = Thread.class
.getDeclaredField("inheritableThreadLocals");
inheritableThreadLocalsField.setAccessible(true);
Class tlmClass = Class
.forName("java.lang.ThreadLocal$ThreadLocalMap");
Field tableField = tlmClass.getDeclaredField("table");
tableField.setAccessible(true);
for (int i = 0; i < threads.length; ++i) {
if (threads[i] == null)
continue;
Object threadLocalMap = threadLocalsField.get(threads[i]);
clearThreadLocalMap(threadLocalMap, tableField, classloader);
threadLocalMap = inheritableThreadLocalsField.get(threads[i]);
clearThreadLocalMap(threadLocalMap, tableField, classloader);
}
} catch (Exception e) {
e.printStackTrace();
}
}
private static void clearThreadLocalMap(Object map,
Field internalTableField, ClassLoader classloader)
throws NoSuchMethodException, IllegalAccessException,
NoSuchFieldException, InvocationTargetException {
if (map != null) {
Method mapRemove = map.getClass().getDeclaredMethod("remove",
new Class[] { ThreadLocal.class });
mapRemove.setAccessible(true);
Object[] table = (Object[]) internalTableField.get(map);
int staleEntriesCount = 0;
if (table != null) {
for (int j = 0; j < table.length; ++j) {
if (table[j] != null) {
boolean remove = false;
Object key = ((Reference) table[j]).get();
if ((key != null)
&& (key.getClass().getClassLoader() == classloader)) {
remove = true;
System.out.println("clean threadLocal key,class="
+ key.getClass().getCanonicalName()
+ ",value=" + key.toString());
}
Field valueField = table[j]
.getClass()
.getDeclaredField("value");
valueField.setAccessible(true);
Object value = valueField.get(table[j]);
if ((value != null)
&& (value.getClass().getClassLoader() == classloader)) {
remove = true;
System.out.println("clean threadLocal value,class="
+ value.getClass().getCanonicalName()
+ ",value=" + value.toString());
}
if (remove) {
if (key == null)
++staleEntriesCount;
else {
mapRemove.invoke(map, new Object[] { key });
}
}
}
}
}
if (staleEntriesCount > 0) {
Method mapRemoveStale = map
.getClass()
.getDeclaredMethod("expungeStaleEntries", new Class[0]);
mapRemoveStale.setAccessible(true);
mapRemoveStale.invoke(map, new Object[0]);
}
}
}
}
2.對(duì)于使用mysql JDBC驅(qū)動(dòng)的:mysql JDBC驅(qū)動(dòng)會(huì)啟動(dòng)一個(gè)Timer Thread,這個(gè)線程在reload的時(shí)候也是無法自動(dòng)銷毀.
因此,需要強(qiáng)制結(jié)束這個(gè)timer
可以在 上面的ApplicationCleanListener中加入如下代碼:
try {
Class ConnectionImplClass = Thread
.currentThread()
.getContextClassLoader()
.loadClass("com.mysql.jdbc.ConnectionImpl");
if (ConnectionImplClass != null
&& ConnectionImplClass.getClassLoader() == getClass()
.getClassLoader()) {
System.out.println("clean mysql timer......");
Field f = ConnectionImplClass.getDeclaredField("cancelTimer");
f.setAccessible(true);
Timer timer = (Timer) f.get(null);
timer.cancel();
}
} catch (java.lang.ClassNotFoundException e1) {
// do nothing
} catch (Exception e) {
System.out
.println("Exception cleaning up MySQL cancellation timer: "
+ e.getMessage());
}
3.common-logging+log4j似乎也會(huì)導(dǎo)致leak,看網(wǎng)上有人說在ApplicationCleanListene6中加入這行代碼就可以:
LogFactory.release(Thread.currentThread().getContextClassLoader());
我沒試成功,懶得再找原因,直接換成了slf4j+logback,沒有問題.據(jù)說slf4j+logback的性能還要更好.
后記:
tomcat-6.0.26之前的版本(我用的是tomcat-6.0.18),加入上述ApplicationCleanListener后,多次reload,不會(huì)出現(xiàn)outOfMemory.
但要注意,第一次啟動(dòng)后,reload一次,內(nèi)存會(huì)增加,也就是看著還是由memory Leak,但是重復(fù)reload,內(nèi)存始終保持在第一次reload的大小.似乎tomcat始終保留了雙WebappClassLoader.因此,配置內(nèi)存要小心些,至少要保證能夠load兩倍的你的所有jar包的大小(當(dāng)然,是指Perm的內(nèi)存大小).
測(cè)試過程中最好加上 JVM參數(shù) -verbosegc,這樣,在做GC的時(shí)候可以直觀的看到class被卸載.
2009年4月2日
#
keytool -genkey -alias tomcat -keyalg RSA -keysize 1024 -keypass changeit -storepass changeit -keystore tomcat.keystore -validity 3600
--這兩步可以不用
keytool -export -trustcacerts -alias tomcat -file tomcat.cer -keystore tomcat.keystore -storepass changeit
keytool -import -trustcacerts -alias tomcat -file tomcat.cer -keystore %JAVA_HOME%/jre/lib/security/cacerts -storepass changeit
Tomcat4.1.34配置:
<Connector className="org.apache.coyote.tomcat4.CoyoteConnector" port="8443" enableLookups="true" scheme="https" secure="true" acceptCount="100" useURIValidationHack="false" disableUploadTimeout="true" clientAuth="false" sslProtocol="TLS" keystoreFile="tomcat.keystore" keystorePass="changeit"/>
Tomcat5.5.9配置:
<Connector port="8443" maxHttpHeaderSize="8192"
maxThreads="150" minSpareThreads="25" maxSpareThreads="75"
enableLookups="false" disableUploadTimeout="true"
acceptCount="100" scheme="https" secure="true"
clientAuth="false" sslProtocol="TLS"
keystoreFile="tomcat.keystore"
keystorePass="changeit"/>
Tomcat5.5.20配置(此配置同樣可用于Tomcat6.0):
<Connector protocol="org.apache.coyote.http11.Http11Protocol"
port="8443" maxHttpHeaderSize="8192"
maxThreads="150" minSpareThreads="25" maxSpareThreads="75"
enableLookups="false" disableUploadTimeout="true"
acceptCount="100" scheme="https" secure="true"
clientAuth="false" sslProtocol="TLS"
keystoreFile="tomcat.keystore"
keystorePass="changeit"/>
Tomcat6.0.10配置:
<Connector protocol="org.apache.coyote.http11.Http11NioProtocol"
port="8443" minSpareThreads="5" maxSpareThreads="75"
enableLookups="true" disableUploadTimeout="true"
acceptCount="100" maxThreads="200"
scheme="https" secure="true" SSLEnabled="true"
clientAuth="false" sslProtocol="TLS"
keystoreFile="D:/tools/apache-tomcat-6.0.10/tomcat.keystore"
keystorePass="changeit"/>
其他有用keytool命令(列出信任證書庫(kù)中所有已有證書,刪除庫(kù)中某個(gè)證書):
keytool -list -v -keystore D:/sdks/jdk1.5.0_11/jre/lib/security/cacerts
keytool -delete -trustcacerts -alias tomcat -keystore D:/sdks/jdk1.5.0_11/jre/lib/security/cacerts -storepass changeit
2009年1月6日
#
1.在系統(tǒng)環(huán)境變量中(System.getProperties())中查找key=javax.xml.parsers.DocumentBuilderFactory
2.如果1沒有找到,則找java.home\lib\jaxp.properties 文件,如果文件存在,在文件中查找key=javax.xml.parsers.DocumentBuilderFactory
3.如果2沒有找到,則在classpath中的所有的jar包中查找META-INF/services/javax.xml.parsers.DocumentBuilderFactory 文件
全都沒找到,則返回null
2007年3月14日
#
2006年12月12日
#
對(duì)DOJO基本上算是文盲,只是項(xiàng)目中需要一些特效頁(yè)面,在網(wǎng)上找了找,感覺DOJO做的不錯(cuò),就拿過來用了,不過感覺性能很不好,頁(yè)面刷新明顯遲鈍
???我的頁(yè)面上大概有10幾個(gè)DOJO,刷一次頁(yè)面僅僅因?yàn)镈OJO的widget的初始化的問題就要5-6秒鐘,讀了一下DOJO的代碼,只要的時(shí)間都花費(fèi)在dojo.hostenv.makeWidgets這個(gè)方法中,本人的js水平比較低,基本上改不了DOJO的代碼,不過在這個(gè)方法中卻找到了一個(gè)稍微提高些性能的辦法,這就是 djConfig.searchIds的使用。
dojo.hostenv.makeWidgets = function(){
?// you can put searchIds in djConfig and dojo.hostenv at the moment
?// we should probably eventually move to one or the other
?var sids = [];
?if(djConfig.searchIds && djConfig.searchIds.length > 0) {
??sids = sids.concat(djConfig.searchIds);
?}
?if(dojo.hostenv.searchIds && dojo.hostenv.searchIds.length > 0) {
??sids = sids.concat(dojo.hostenv.searchIds);
?}
?if((djConfig.parseWidgets)||(sids.length > 0)){
??if(dojo.evalObjPath("dojo.widget.Parse")){
???// we must do this on a delay to avoid:
???//?http://www.shaftek.org/blog/archives/000212.html
???// IE is such a tremendous peice of shit.
????var parser = new dojo.xml.Parse();
????if(sids.length > 0){
?????for(var x=0; x<sids.length; x++){
??????var tmpNode = document.getElementById(sids[x]);
??????if(!tmpNode){ continue; }
??????var frag = parser.parseElement(tmpNode, null, true);
??????dojo.widget.getParser().createComponents(frag);
?????}
????}else if(djConfig.parseWidgets){
?????var frag? = parser.parseElement(document.getElementsByTagName("body")[0] || document.body, null, true);
?????dojo.widget.getParser().createComponents(frag);
????}
??}
?}
}
具體使用方法就是在自己的頁(yè)面上把所有的dojo的widget都要定義ID,類似這樣
<input id="queryStr_0" name="queryStr_0" dojoType="ComboBox" style="width:280px;" autocomplete="false" >
然后加上這樣一段js
<script language=javascript>
?djConfig.searchIds=['queryStr_0','queryStr_1','queryStr_2'];
</script>
這里的'queryStr_0'之類的就是你的widget的ID,這樣對(duì)于我的10幾個(gè)widget的頁(yè)面,速度基本上會(huì)快上1-2倍。
2006年11月27日
#
JSON-RPC 大家都知道了,我比較反感JSON-RPC的客戶端/服務(wù)器端的交互方式,個(gè)人認(rèn)為還是DWR的框架似乎好一些,不過單純比較后臺(tái)代碼的風(fēng)格,似乎JSON-RPC的代碼更好些---純粹個(gè)人喜好。
???雖然不喜歡JSON-RPC的框架,不過對(duì)于JSON的javaObject--javascriptObject的轉(zhuǎn)換代碼倒是很欣賞,因此直接把JSON-RPC的這部分代碼直接拿過來用,感覺也不錯(cuò)。當(dāng)然,這已經(jīng)跟AJAX關(guān)系不大了。
???1.將jsonrpc-1.0.jar包含在你的project的classpath中,
???2.寫個(gè)簡(jiǎn)單的Util類,將你的java Object 序列化成javascript的字符串。
public class JSONUtil {
?public static final JSONSerializer se = new JSONSerializer();
?private static Logger log = Logger.getLogger(JSONUtil.class);?
?static
?{
??try
??{
???se.registerDefaultSerializers();
??}
??catch (Exception e)
??{
???log.error(e);
??}
?}
?
?public static String toJSON(Object obj)
?{
??try
??{
???SerializerState state = new SerializerState();
???Object retuObj = se.marshall(state, obj);
???String retuStr = retuObj.toString();
???//retuStr.replaceAll("
\\\"", "
\\'");
???//log.debug("JSONStr:"+retuStr);
???return retuStr;
??}
??catch (Exception e){
???log.error(e);
???return obj.toString();
??}
?}
3.???客戶端的jsp中只要簡(jiǎn)單的加上這段js
<script language=javascript>
?eval('jsObject = <%=JSONUtil.toJSON(javaObject)%>'+';');
</script>
javaObject是你自己的java類的實(shí)例,這樣你就可以在js中直接操作jsObject 這個(gè)js對(duì)象了。
對(duì)于一個(gè)AJAX請(qǐng)求
如果返回的是標(biāo)準(zhǔn)的XML(有<?xml version="1.0" encoding="UTF-8"?>,并且ContentType = "text/xml"),則直接操作xmlhttp.responseXML應(yīng)該是可以的,比如:
var requestMsg=xmlhttp.responseXML;
alert(requestMsg.getElementsByTagName("book").length);
如果不是標(biāo)準(zhǔn)的XML.則返回的信息實(shí)際上是以文本的方式表示的,必須從xmlhttp.responseText中取得數(shù)據(jù),方式如下:
var requestMsg=getXMLDoc(originalRequest.responsetext);
alert(requestMsg.getElementsByTagName("book").length);
getXMLDoc方法如下:
?function getXMLDoc(xmlText){
??if(window.ActiveXObject){
???xmlDoc=new ActiveXObject("Microsoft.XMLDOM");
???xmlDoc.async=false;
???xmlDoc.onreadystatechange = function()
???{
????//if(xmlDoc.readyState == 4) doAction();
???}
???xmlDoc.loadXML(xmlText);
??}else if(document.implementation&&document.implementation.createDocument){
???xmlDoc=document.implementation.createDocument('','',null);
???//xmlDoc.onload=doAction();
???xmlDoc.loadXML(xmlText);
??}else return null;
??return xmlDoc;
?}