持续更新线上问题及解决方案
最近线上遇到几个小问题,排查代码发现基本都是些细节问题,做些总结提示大家不要掉到坑中。
一、fastjson的序列化SerializerFeature使用注意
我们都知道Integer、Double、Boolean等包装类型的字段默认值是null。如果不对这些字段设置值,那么在反序列化时得到的相应的值也应该是null。
1、本次服务接口升级后导致调用方业务逻辑判断失败,比如Integer type 在升级之前是返回的null,而升级后返回了数字0。究其原因发现同事在序列化时设置了SerializerFeature.WriteNullNumberAsZero,而导致Number类型(Boolean,Integer,Float,Double等)都转成了0。
2、null属性不显示
我们先来看一段代码
Map <String , Object > map = new HashMap<String , Object>(); map.put("a",1); map.put("b",""); map.put("c",null); String str = JSONObject.toJSONString(map); System.out.println(str);//输出结果:{"a":1,"b":""}
属性c,怎么就凭空消失了哪?这里不讲述原因,只说明解决方案。
解决方案:使用fastjson的SerializerFeature序列化属性,即JSONObject.toJSONString(Object object, SerializerFeature... features)
因此上面的代码就可以改写成
String str = JSONObject.toJSONString(map,SerializerFeature.WriteMapNullValue);
我们常用的SerializerFeature:
- SerializerFeature.WriteMapNullValue, //输出空置的字段
- SerializerFeature.WriteNonStringKeyAsString,//如果key不为String 则转换为String 比如Map的key为Integer
- SerializerFeature.WriteNullListAsEmpty,//list为null时输出[]
- SerializerFeature.WriteNullNumberAsZero,//number为null时输出0
- SerializerFeature.WriteNullStringAsEmpty,//String为null时输出""
- SerializerFeature.WriteNullBooleanAsFalse,//boolean为null时输出false
- SerializerFeature.QuoteFieldNames,//输出key时是否使用双引号,默认为true
- SerializerFeature.DisableCheckSpecialChar//一个对象的字符串属性中如果有特殊字符如双引号,将会在转成json时带有反斜杠转移符。如果不需要转义,可以使用这个属性。默认为false
总结:
1、服务接口的测试用例覆盖率还是太低
2、对于通用处理工具,在做修改时对工具类的前因后果要清楚
二、java.util.List.subList的陷阱
我们一般都会使用java.util.List中有一个subList方法,返回一个以fromIndex为起始索引(包含),以toIndex为终止索引(不包含)的一部分的视图(List)。
List<E> subList(int fromIndex, int toIndex);
之所以说是视图,是因为实际上,返回的list是靠原来的list支持的。原来的list和返回的list做的“非结构性修改”(non-structural changes),都会影响到彼此对方。
所谓的“非结构性修改”,是指不涉及到list的大小改变的修改。相反,结构性修改,指改变了list大小的修改。
如果发生结构性修改的是返回的子list,那么原来的list的大小也会发生变化;
如果发生结构性修改的是原来的list(不包括由于返回的子list导致的改变),那么会是抛出一个ConcurrentModificationException。
- 如何删除一个list中的某个值
list.remove(index)
- 如何删除一个list的某个区段
list.subList(int fromIndex, int toIndex).clear();
- 如何修改子list视图而不影响原来的list或修改原list而不影响子list视图
List<Integer> subList2 = new ArrayList<Integer>(list2.subList(2, list2.size()));
那么ArrayList的remove的底层是怎么做的?
AbstractList中有一个属性modCount,这个属性是跟踪list中数据被修改的次数,任何对list的add/remove操作,都将导致modCount++。
在AbstractList中还有一个内部类Itr implements Iterator,Itr是一个list遍历的工具类。当然list.iterator()方法也是返回Itr对象,在Itr中有一个校验位属性expectedModCount;对于一个itr对象,其初始时expectedModCount=modCount。
Iterator是list一个视图,其最终还是操作list的存储结构。在使用iterator遍历时,remove()操作,会导致modCount++(AbstractList.remove()),但是还有expectedModCount=modCount,即在iterator中remove数据,会带来expectedModCount与modCount值的同步。
在Iterator遍历时,next(),remove()方法会校验expectedModCount与modCount值是否一致,如果不一致,就意味着这list数据在iterator外部被修改,此时iterator遍历将会造成ConcurrentModificationException.
AbstractLlist不仅支持普通的iterator,还支持ListIterator(ArrayList,LinkedList均支持),ListIterator增加了遍历时双向游标能力(previous,next),增加了add方法。add方法和remove方法一样也做了expectedModCount和modCount一致性校验.
我们来看下面四个对list数据删除的代码的区别
1) for(int i=0;i<list.size();i++){ list.remove(i); } 2) for(int i=list.size()-1;i>=0;i--){ list.remove(i); } 3) int size = list.size(); for(int i=size-1;i>-1;i--){ list.remove(i); } 4) for(Object i : list){ //如果list中存在多个Object互相equals时,此方法仍然有效.注意list.remove(Object)内部使用了遍历操作,并使用equals来比较对象并删除. list.remove(i); } 5) Iterator it = list.iterator() while(it.hasNext()){ it.next(); it.remove(); }
1),2),3)是最普通的遍历方式,但是在遍历并有删除操作时,似乎它们执行的结果还有些差距,根据坐标删除,
那么1)实事上只会有一半被删掉,1)中每删除一次,计算一次list.size(),但是当前i++,且前端删除会造成数组结构copy。
2)后端删除,不会造成copy,每次都是删除最后一个位置,直至结束
3)因为size没有重新计算,在删除一半数据后,抛出IndexOutOfBoundsException
4)/5)正常
三、illegal character: \65279
问题产生的操作过程,同事使用SVN提交java文件,发现有冲突使用UltraEdit进行了修改,重新编译时却报了异常
java:[1,0] illegal character: \65279
解决方法
将文件重新保存成UTF-8 无BOM即可。
具体原因参看高人的解释
http://blog.csdn.net/shixing_11/article/details/6976900
四、Java线程池任务执行完毕后线程回收的问题
我们知道ThreadPoolExecutor解决了两个重要的问题:
1、由于减少了每个任务调用的开销,它们通常可以在执行大量异步任务时提供增强的性能
2、还可以提供绑定和管理资源(包括执行任务集时使用的线程)的方法。
当使用java中的ThreadPoolExecutor,给我们的工作带来方便的同时,如果不当使用同样也带来巨大潜在危险。
最近在review代码时,发现线程池中的所有任务执行完毕后,线程并没有被销毁。我们知道初始化ThreadPoolExecutor会有构造参数
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
我们看下TreadPoolExecutor是怎样工作的
注意
1、核心线程即core线程,只有当前线程数小于等于corePoolSize时,这时的线程才叫核心线程。
2、在新任务被提交时,如果运行的core线程少于corePoolSize,才创建新core线程。并不是一开始就创建corePoolSize个core线程。
3、如果运行的线程多于corePoolSize 而少于 maximumPoolSize,则仅当队列满时才创建新线程。
按需构造
核心线程最初只是在新任务到达时才被ThreadPoolExecutor创建和启动的,但是也可以手动调用方法 prestartCoreThread() 或 prestartAllCoreThreads()来的提前启动核心线程。
如果构造带有非空队列的池,这时则可能希望预先启动线程。
保持活动时间
如果线程池中当前线程数大于corePoolSize ,则这些多出的线程在空闲时间超过 keepAliveTime 时将会终止。
如果后来线程池中线程变得更为活动,则可以创建新的线程。也可以使用方法 setKeepAliveTime(long, java.util.concurrent.TimeUnit) 动态地更改此参数,如果把值设为Long.MAX_VALUE TimeUnit.NANOSECONDS 的话,空闲线程不会被回收直到ThreadPoolExecutor为Terminate。
默认情况下,此种活动策略只在有多于corePoolSize Threads的线程时才会应用。但是只要 keepAliveTime 值非 0,也可以通过allowCoreThreadTimeOut(boolean) 方法也可将此超时策略应用于核心线程。
注意1:setKeepAliveTime(long, java.util.concurrent.TimeUnit)用于设置空闲线程最长的活动时间,即如果空闲时间超过设定值,就停止该线程,对该线程进行回收。
该策略默认只对非内核线程有用(即当前线程数大于corePoolSize),可以调用allowCoreThreadTimeOut(boolean)方法将此超时策略扩大到核心线程
注意2:如果把值设为Long.MAX_VALUE TimeUnit.NANOSECONDS的话,空闲线程不会被回收直到ThreadPoolExecutor为Terminate。
线程终止
如果ThreadPoolExecutor在程序中没有任何引用且没有任何活动线程,线程池也不会自动 shutdown。
如果希望确保回收线程(即使用户忘记调用 shutdown()),则必须安排未使用的线程最终终止,设置适当保持活动时间,设置 allowCoreThreadTimeOut(boolean)。
关键函数
- public void shutdown()
按过去执行已提交任务的顺序发起一个有序的关闭,但是不接受新任务。如果已经关闭,则调用没有其他作用。
抛出:
SecurityException - 如果安全管理器存在并且关闭此 ExecutorService 可能操作某些不允许调用者修改的线程(因为它没有 RuntimePermission("modifyThread")),或者安全管理器的 checkAccess 方法拒绝访问。
- public List<Runnable> shutdownNow()
尝试停止所有的活动执行任务、暂停等待任务的处理,并返回等待执行的任务列表。在从此方法返回的任务队列中排空(移除)这些任务。
并不保证能够停止正在处理的活动执行任务,但是会尽力尝试。 此实现通过 Thread.interrupt() 取消任务,所以无法响应中断的任何任务可能永远无法终止。
返回:
从未开始执行的任务的列表。
抛出:
SecurityException - 如果安全管理器存在并且关闭此 ExecutorService
可能操作某些不允许调用者修改的线程(因为它没有 RuntimePermission("modifyThread")),
或者安全管理器的 checkAccess 方法拒绝访问。
- public int prestartAllCoreThreads()
启动所有核心线程,使其处于等待工作的空闲状态。仅当执行新任务时,此操作才重写默认的启动核心线程策略。
返回:
已启动的线程数
- public boolean allowsCoreThreadTimeOut()
如果此池允许核心线程超时和终止,如果在 keepAlive 时间内没有任务到达,新任务到达时正在替换(如果需要),则返回 true。当返回 true 时,适用于非核心线程的相同的保持活动策略也同样适用于核心线程。当返回 false(默认值)时,由于没有传入任务,核心线程不会终止。
返回:
如果允许核心线程超时,则返回 true;否则返回 false
- public void allowCoreThreadTimeOut(boolean value)
如果在保持活动时间内没有任务到达,新任务到达时正在替换(如果需要),则设置控制核心线程是超时还是终止的策略。当为 false(默认值)时,由于没有传入任务,核心线程将永远不会中止。当为 true 时,适用于非核心线程的相同的保持活动策略也同样适用于核心线程。为了避免连续线程替换,保持活动时间在设置为 true 时必须大于 0。通常应该在主动使用该池前调用此方法。
参数:
value - 如果应该超时,则为 true;否则为 false
抛出:
IllegalArgumentException - 如果 value 为 true 并且当前保持活动时间不大于 0。
我们了解了ThreadPoolExecutor后,确保线程的回收就可以通过以下方式
// 在allowCoreThreadTimeOut设置为true时,ThreadPoolExecutor的keepAliveTime参数必须大于0。 executor.allowCoreThreadTimeOut(true);
// 在任务执行完后,调用shutdown方法,将线程池中的空闲线程回收 executor.shutdown();
五、Eclipse下maven项目自动打war包丢失jar包问题解决方法
由于本地没有maven仓库和强大的“墙”,使用maven命令clean package却打包失败。没有办法使用了Eclipse最原始的Export命令,打好包后发现依赖的jar都没有打进去,真是一波三折呀。现说下完整的打包过程和解决方法:
1、选择相应的profile,没有配置略过
2、.project文件
检查<buildSpec>节点中是否包含如下节点
<buildCommand> <name> org.eclipse.m2e.core.maven2Builder </name> <arguments></arguments> </buildCommand>
检查<natures>节点中是否包含如下节点
<nature>org.eclipse.m2e.core.maven2Nature</nature>
3、.classpath文件
检查是否包含如下节点
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER"> <attributes> <attribute name="maven.pomderived" value="true"/> <attribute name="org.eclipse.jst.component.dependency" value="/WEB-INF/lib"/> </attributes> </classpathentry>
正因为没有这个属性<attribute name="org.eclipse.jst.component.dependency" value="/WEB-INF/lib"/>,才导致打的包中没有jar。
上述三步骤检查完毕后,重新clean工程,再打包就可以正常了。
六、Linux系统下忽略表明大小写
坑爹的DBA将线上的一个数据库重做后,导致了某些服务抛出了大量的异常
select name,type,updateTime from VIDEO_INFO where 1=1 ExecuteData Table 'MS.VIDEO_INFO' doesn't exist
信息提示找不到表明。
我们都知道Linux是区分表明大小写,windows不区分表明大小写的。DBA重做了新的数据库后却忽略设置表明大小写。
解决方法很简单:
编辑mysql配置文件:vi /etc/my.cnf
添加:lower_case_table_names=1 一句到文件中。
重启MySQL,服务一切正常。
小结:重要的事情说一遍,检查一遍。
七、swap file "*.swp" already exists!
Linux下我们使用vi或vim对文件编辑, 当打开文件或保存文件可能会出现:
swap file "*.swp" already exists!
[O]pen Read-Only, (E)dit anyway, (R)ecover, (D)elete it, (Q)uit, (A)bort:
原因:
使用vim编辑文件实际是先copy一份临时文件并映射到内存给你编辑, 编辑的是临时文件, 当执行:w后才保存临时文件到原文件,执行:q后才删除临时文件。
每次启动检索式否有临时文件, 有则询问如何处理,就会出现如上情景。
解决方法:
1、显示隐藏文件 ll -a或ls -a
2、删除隐藏文件 rm -f *.swp
八、20880端口被占用
问题描述:
在部署线上服务时,出现20880端口被占用而无法启动。
问题分析:
20880端口被该服务器上的客户端随机选取源端口给占用掉了。
解决方案:
1、使用net.ipv4.ip_local_port_range参数,规划出一段端口段预留作为服务的端口,这种方法是可以解决当前问题,但是会有个问题,端口使用量减少了,当服务器需要消耗大量的端口号的话,比如反代服务器,就存在瓶颈了。
2、将服务监听的端口以逗号分隔全部添加到ip_local_reserved_ports中,TCP/IP协议栈从ip_local_port_range中随机选取源端口时,会排除ip_local_reserved_ports中定义的端口,因此就不会出现端口被占用了服务无法启动。、
推荐使用第二种方法。
$ cat /proc/sys/net/ipv4/ip_local_port_range
32000 61000
$ cat /proc/sys/net/ipv4/ip_local_reserved_ports
8080,9148
由于本人经验有限,文章中难免会有错误,请浏览文章的您指正或有不同的观点共同探讨!
作者:三石雨
出处:http://www.cnblogs.com/exceptioneye
再烦,也别忘微笑;再急,也要注意语气。
再苦,也别忘坚持;再累,也要爱自己。
低调做人,你会一次比一次稳健;高调做事,你会一次比一次优秀。