Quantcast
Channel: IT社区推荐资讯 - ITIndex.net
Viewing all 15843 articles
Browse latest View live

职业感悟:代码审计与编程在渗透中的重要性

$
0
0

    20130508074646266_副本

   配图:3D立体街头涂鸦

     博客连着断了一个月没更新,期间写过好几篇各个方向的文,都是写到一半就夭折,这篇文章强迫自己耐心写出来的。最近跟出版社沟通好了在写一本关于代码审计和安全编程的书,算是把我这几年的技术积累做一个总结,刚好昨天跟safekey team几个搞渗透的朋友说到编程的问题,就想写个文来好好讲讲。

   这个文原想命名为 《做个有创造力的海盗》,迎合一下刺(道哥)前段时间写的《 我要找的是海盗》,在他的文章讲的找海盗一点上我的看法和做法跟他是一模一样的。至今我也只有一个身份证,高一错学,初中、小学毕业证应该也是找不到了,18岁加入安全宝,所以我也没法对公司team成员有学历要求。另外在海盗这一点上,我的理解是 有激情、有胆量、有智谋、有创造力的人,点到为止,留一个题目《 创业路上的海盗军团》给海盗去思考吧,我要找的是有创造力的海盗。

   

   仔细想了一下我还是有能力驾驭这个题目,我在高一就开始接触编程,到现在快5年的编程经验,渗透连着做了4年,国内外大大小小目标。代码安全审计也有两年多不断的积累。像余弦在TED的演讲上说他是一个有邪气的人一样,非纯粹的白帽子,我们俩有点类似,这是一个我不想丢掉的特质,怕丢掉了会失去创造力。如果有心可以翻翻我的blog,可以看到我这两年写的blog和安全软件基本都是带攻击性质,就像代码审计系统也是用来发现漏洞,可以说我不是一个单纯的技术爱好者。

   首先抛出一个观点:代码审计和编程在渗透中有至关重要的作用。  
   这几年见过很多人,没有代码审计和编程能力做渗透都比较吃力,局限在用安全扫描做漏洞扫描以及利用网上公开的exp去攻击,也就是常说的脚本小子。即没有研究能力的攻击党,通常放出exp的人不可怕,可怕的是利用exp去批量攻击的人,这种人造成的危害最大。然而在攻击过程中,由于攻击对象的环境不一样,一个小小的问题也会导致攻击失败,这时候脚本小子的弱势就完美体现了,而有研究能力的攻击者(下面以 文化流氓代称)能够分析攻击失败的原因,对exp进行改造以使攻击成功。这类的case见过很多,举两个例子。

  情景一:
    一个SQL注入漏洞( http://www.cnseay.com/?m=news&c=list&id=188),因为目标网站存在waf (web application firewall),脚本小子使用自动化注入工具无法利用,通常会放弃这个漏洞。而文化流氓就不一样了,利用自己的SQL编程能力和对waf了解,通过提交不用的数据,根据server返回的数据信息、页面报错等来分析漏洞和waf的大致情况,最终会想出办法来绕过waf利用漏洞,成功入侵目标网站。类似的场景有代码、命令执行、XSS等等漏洞,扫描器在扫描出漏洞之后,都不会给出你能直接利用的exp,需要攻击者自己根据经验以及编程能力去分析构造利用脚本。

  情景二:
     这是一个在源码漏洞发现层次的场景,通过扫描器扫描到的一个源码包( http://www.cnseay.com/cnseay.rar),利用是该网站的程序源代码,通常攻击者都会利用源码包找一些配置文件一类,因为里面有数据库、api接口等等一类配置,如果环境有限制,如目标站数据库限制连接IP等,那么脚本小子可能在源码包漏洞利用也就到此为止。换在文化流氓的手上就不一样了,他通过对源码包进行安全审计,发现网站目录下一个文件存在代码执行漏洞,于是通过该漏洞直接向网站服务器写入一个web后门,通过web后门提权得到服务器权限。

  上面两个case可以很清楚的看到代码审计和编程在渗透中的重要性,多一种技能,也就多很多成功的机会。很简单的一个道理,给你一个漏洞,你也得要会用啊,不然给你有毛用。当然想要玩好代码审计也不是件简单的事,首页需要对编程语言足够的了解,起码要有代码阅读能力,另外还需要对各种漏洞进行深入理解,理解漏洞原理,即出现漏洞的原因以及利用和修复方法。

   备注:本人博客更新速度会下调,可能在写书过程中会发一些研究paper,如果不想错过可以点击【 邮箱订阅】订阅到邮箱,另外也可点击【 鲜果订阅】,或者 RSS,您随意 :D
博主猜你喜欢:

PHP代码审计学习总结 某牛的经验之作

帮助新手学习代码审计,分享WEB安全代码审计文章整理打包

PHP代码审计基础知识 对新手来说很精品哦

PHP编程函数安全篇

介绍公司php漏洞挖掘与代码审计方法
无觅

前端开发中使用”有限状态机“解决复杂的交互问题

$
0
0

  前端开发是有逻辑的,这点毋庸置疑。程序员的思维逻辑赋予了代码各种能力,但是前端开发中经常面对的是用户的操作。在一个比较复杂的页面中(貌似现在也很少有简单页面了),用户的操作是不可预见的,假如有很多按钮,每个按钮都会做一件自己独一无二的事,如果上帝保佑所有的这些操作,这些事件都彼此没有限制,而且结果互不影响,那没有问题。但在开发中好像没有这种好运气,所以经常需要协调和平衡这些函数之间的执行。

  如果你使用的是纯JS或者单独仅有jQuery的情况下,遇到这种让人焦头烂额的情形会尤为明显,前端MVC一定程度上隐藏了并处理了这些问题,但是也并不完全。这个时候你可能需要了解一下关于”有限状态机“的概念,前端开发中这应该是一个很有用的东西。

  描述一下”有限状态机“:

有限状态自动机,简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。

状态总数(state)是有限的。

任一时刻,只会处于一种状态中。

在某种条件下,会从一种状态转变到另一种状态中。

在维基百科中称:有限状态机FSM是设计和实现事件驱动程序内复杂行为组织原则的有力工具。

  对于前端来说,尤其是Javascript编程时,这个模型的意义就在于可以将其套用在很多对象上。具体个例子,比如一个按钮平时就是正常的按钮,当你点击后变成一个input。当然你可能会说这么简单的功能,我直接用jQuery甚至不用都可以,操作DOM显示和隐藏就好了。如果你还抱有这种天真的想法,我只能说你没遇到过让你痛不欲生的页面。。。当页面复杂到一定程度,单纯操作DOM去处理前端只会加快你疯掉的进程。

  这里对于Button套用有限状态机的模型,相当btn对象只有两个状态,显示状态和编辑状态。看看代码吧:

var btn = {
// 当前状态
currentState: 'btn',

// 绑定事件
initialize: function() {
var self = this;
self.on("click", self.transition);
},

// 状态转换
transition: function(event){
switch(this.currentState) {
case "btn":
this.currentState = 'input';
doSomething();
break;
case "input":
this.currentState = 'btn';
doSomething();
break;
default:
console.log('Invalid State!');
break;
}
} 
};

  上面就是有限状态机的写法,逻辑和层次上确实清晰了,对于状态越多的对象,就越适合这种写法。

  通过有限状态机的这种模式,我认为最重要的一点就是将用户的操作行为,也就是组件的事件响应(比如点击)与组件的行为表现分离开来.在确切的说,通过建立一个有限状态机的模型,我们完全不关心用户的点击行为具体做了什么,这时组件可能会有几种状态对应不同的表现形式,而用户触发的事件仅仅是切换了模型的状态.至于每个状态的具体表现和行为,我们完全可以单独定义,也就说这时一种行为和响应上的解耦.

  Github上有两个比较好的库,都是实现FSM的,有兴趣可以具体看看:

   https://github.com/fschaefer/Stately.js

   https://github.com/jakesgordon/javascript-state-machine

“今日头条”侵权的几种可能

$
0
0


1、新闻聚合增值,潜力还很大,“今日头条”的聚合仍属于初级阶段。
2、不改变原始网址,通过“标题+链接”或“标题+摘要+链接”做内容聚合,除非原来源明确表示禁止,我不认为构成了侵权。
但需注意以下几种情况:
3、如果链接到的网页已构成侵权,聚合也构成侵权。
4、内容来源明确拒绝第三方聚合后,未经单独许可,坚持聚合会构成侵权。
5、在移动端界面优化,如果未经授权就过滤了原网站的广告或其他内容,也构成侵权,不过不是侵犯版权,而是侵犯了商业利益。
6、在移动端推广时,“标题+图片+链接”或“标题+摘要+图片+链接”,图片很可能构成侵权,即使是缩略图也很可能侵权。

  青春就应该这样绽放   游戏测试:三国时期谁是你最好的兄弟!!   你不得不信的星座秘密

Android HttpURLConnection及HttpClient选择

$
0
0

介绍Android中Http请求方式的选择、区别及几个常用框架对API的选择

1. 两种请求方式对比
Android Http请求API主要分两种:
第一种是Java的HttpURLConnection,默认带gzip压缩
第二种Apache的HttpClient,默认不带gzip压缩
两种方式请求connection都是keep alive,默认User-Agent不同。

关于两种方式发出去的请求头对比图如下:
HttpURLConnection
HttpClient

上图是通过抓包抓包的,具体如何抓包可参考: Android利用Fiddler进行网络数据抓包
测试代码见: android-http-api-compare@github, 测试APK见: http-api-compare.apk

 

2. 常用Http框架对Http API的选择
Retrofit及Volley框架默认在Android API 9及以上都是用HttpURLConnection,9以下用HttpClient。

这样我们也能看出就算我们自己不使用框架对这两个API的选择了.

 

3. GZip压缩
一般对于API请求需带上GZip压缩,因为API返回数据大都是JSon串之类字符串,GZip压缩后内容大小大幅降低,下面是这两个网页GZip压缩前后对比,都是第一条表示GZip压缩后,第二条为压缩前
GZip
更多关于网络请求优化请参考: Android性能优化第四部分网络的介绍

相关文章:

影视剧 CG 特效后期制作的工作是怎样的?

$
0
0
以前在制作《2012》和《钢铁侠2》的特效公司 Pixomondo 做过 IT 实习,本身也是动画学院的学生,虽然本专业不是动画或影视特效相关,但本专业的另一个方向就是做影视特效的,所以还认识一些业内人士,说说我了解到的情况吧。

整个工作的方式和 @罗登描述的差不多,区别在于 @罗登描述的是整个创作过程,不仅仅是「后期」了,比如故事版、角色这些其实对于一部纯动画电影来讲是绝对的前期工作。但无论如何,整个 CG 行业(甚至是整个电影行业)都是一个高度模块化、流水线工作的模式。这样保证了整个行业的快速发展,可以把不同类型的工作外包给不同的公司。

比如我之前工作过的 Pixomondo,很多人可能会觉得「做过<2012>那一定是很厉害的公司了」,的确这个公司技术实例不凡,但是对于中国 office 和印度 office 来讲(我是 2010 年在那边,当时北京 office 刚成立不久,上海刚开始招人,PXO 全球当时有 11 个 office)拿到的工作是整个流水线中机械性最大、技术含量最低的内容,基本上是属于体力密集型劳动了,比如 rotoscope(听起来高大上吧?其实就是逐帧画 mask)、擦线、抠背景的等等,人家基本上是把中国当廉价劳动力来用的。因为创作团队在海外,所以每天晚上北京 office 睡觉的时间会做两件事,对方把我们明天要用的素材传送过来,我们把今天做好的传送回去,我当时就是做资产管理系统,基本上就是用来在各个 office 之间管理和共享这些素材。当时整个国内的影视外包公司都会承接这种类型的工作,但是还受到印度很大的威胁,人家擦线擦得又好又便宜...

现在国内的状况好多了,随着整个行业的发展,我们也能在更高技术难度的领域做出不比世界一流公司差的东西,但是为啥整个行业还这么挫啊?以下几个原因:
  1. 高端人才短缺。对于整个加工制作的流水线来讲,我们现在还集中在中下游的阶段,创作型人才短缺。无论是故事、角色形象都缺乏亮点。头几年有个《魔比斯环》还记得吧?上百个年轻人,花了几个亿,熬了两三年,做出来个四不像的东西,因为导演、编剧都是外国人,一个国产动画片能把对白写的跟译制片一样(哦,好了莱纳德,你再这样我就要踢你的屁股了,哦我发誓我会这样的。就是这种感觉你体会一下),真是让人哭笑不得。高等教育在影视方面基本上还都是应用型,比如我的母校中国传媒大学,大部分都是技术教学,对于创作能力的培养不是说没有,但是受制于体质、就业、生源质量和国家支持等因素,并不是很成功。
  2. 市场极度饥渴,但大多满足于赚快钱。可以看看国内近些年火爆的影片,要么都是粗制滥造(但成本可不低)但是商业运作成功票房令人咂舌的大动画电影,要么就是夫妻俩憋了几个月放在视频网站上引起轰动的小作坊出品。大家发现粗制滥造就能有高票房,影视投资人是什么人相信大家也都知道(煤老板都可以做),投资影视对他们来讲「玩」(动画角色没什么可玩的,真人电影嘛...呵呵)和「赚钱」一半一半,什么行业的良性发展呀,人才的培养和积累啊,洗洗睡吧人家不在乎。没有人想过,烂片也能挣大钱正说明了市场的饥渴和需求(都已经不是潜在需求了)的旺盛,没有人愿意为这个行业明天的发展抛头颅洒热血,愿意想愿意做这些的又都是从事基层工作的热血青年(比如夫妻小作坊),没有话语权没有影响力。
  3. 行业监管、政策跟不上。到现在连个分级制度都没有,审片制度不仅不透明结果更经常让人无语,可想而知整个行政监管多么落后了。光腚粽菊不是摆设,再加上文化部、教育部各种部委的多线干涉,影视行业是人家的玩物,做什么都是为了政绩服务,而不是以民为本的支持。在中国拍电影,最大的敌人和阻碍就是这些本应该最支持你的人。

P.S.@陈朕 完美的老板不是加入 SCC,是联合创立了 SCC...

======

更正一下 PXO 没有印度 office。

======

有人私信问关于动画师的情况,我说下我知道的。国内有动画师吗?当然有。有好的动画师吗?我没听人说起过。因为动画师是个严重依赖经验的职位,调过马了,就会调马,调过龙了,就会调龙,大家想想国产动画片里生物动作之生硬也大概知道我们是个什么水平了。Maya 早期骨骼系统的设计者(叫什么记不清了,一个大胡子加拿大人,07 年中国国际动漫节的时候有缘见过一面)是学医学出身的,对肌肉、骨骼、运动的认识和理解如果不是科班出身,且十数年的积累、磨练根本不可能达到这种高度。所以国外的动画师年龄普遍偏大,而国内...没挨到那岁数可能就被迫转行了...

— 完 —
本文作者: 赵望野

【知乎日报】 你都看到这啦,快来点我嘛 Σ(▼□▼メ)

此问题还有 5 个回答,查看全部。
延伸阅读:
电影后期制作都使用哪些专业设备?
电脑非编与特效技术成熟之前,影视后期是如何制作的?

国内访问google的几种快速解决办法

$
0
0
google 最近不能打开了。国内已经做了域名dns拦截。
解决方法:
1.设置自己的dns为:首选DNS服务器和备用DNS服务器分别设置为208.67.222.222和 208.67.220.220,完成后重新连接上网,就可以摆脱服务商对我们的DNS劫持。
2.通过代理方式访问
3.通过没有被域名劫持的地址访问
如:http://music.google.cn/search?newwindow=1&safe=off&hl=zh-CN&prmdo=1&sout=1&gbv=2&q=%E7%A8%8B%E5%BA%8F%E5%91%98%E7%99%BE%E5%91%B3&btnG=Google+%E6%90%9C%E7%B4%A2
 
 
from : http://www.bywei.cn


已有 1人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



ajax 上次附件(一个或者多个)

$
0
0

      最近自己的项目需要开发一个ajax上传和下载的功能集成在springmvc项目中,最近公司工作比较轻松,就研究了一下这个东西,希望对大家有所帮助。

  一。上传单个文件

       1.需要jar文件

        commons-io.jar

        commons-fileupload.jar

       2.配置applicationContext.xml配置

               
    <mvc:resources mapping="/upload/**" location="/upload/"/>
 
    <!-- SpringMVC上传文件时,需配置MultipartResolver处理器 -->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <!-- 指定所上传文件的总大小不能超过800KB......注意maxUploadSize属性的限制不是针对单个文件,而是所有文件的容量之和 -->
  
    <!-- set the max upload size1MB   1048576     -->
        <property name="maxUploadSize">
            <value>82428800</value>
        </property>
        <property name="maxInMemorySize">
            <value>2048</value>
        </property>
   
    </bean>
 
    <!-- SpringMVC在超出上传文件限制时,会抛出org.springframework.web.multipart.MaxUploadSizeExceededException -->
    <!-- 该异常是SpringMVC在检查上传的文件信息时抛出来的,而且此时还没有进入到Controller方法中 -->
    <bean id="exceptionResolver" class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
    <property name="exceptionMappings">
        <props>
        <!-- 遇到MaxUploadSizeExceededException异常时,自动跳转到/WEB-INF/jsp/error_fileupload.jsp页面 -->
        <prop key="org.springframework.web.multipart.MaxUploadSizeExceededException">error_fileupload</prop>
        </props>
    </property>
    </bean>
 
 <!-- SpringMVC在超出上传文件限制时,会抛出org.springframework.web.multipart.MaxUploadSizeExceededException -->
 <!-- 该异常是SpringMVC在检查上传的文件信息时抛出来的,而且此时还没有进入到Controller方法中 -->
 <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
  <property name="exceptionMappings">
   <props>
    <!-- 遇到MaxUploadSizeExceededException异常时,自动跳转到/WEB-INF/error_fileupload.jsp页面 -->
    <prop key="org.springframework.web.multipart.MaxUploadSizeExceededException">WEB-INF/error_fileupload</prop>
    <!-- 处理其它异常(包括Controller抛出的) -->
    <prop key="java.lang.Throwable">WEB-INF/500</prop>
   </props>
  </property>
 </bean>

3.新增一个上传页面

    <%@ page language="java" pageEncoding="UTF-8"%>
<%@ include file="/common/taglibs.jsp"%>
<%@ include file="/common/headcj.jsp"%>
<html>
  <head>
    <!-- 执行上传文件操作的函数 -->
      <script type="text/javascript">
          function ajaxFileUpload(){
            $.ajaxFileUpload({
       //处理文件上传操作的服务器端地址(可以传参数,已亲测可用) 
       url:'${ctx}/file/imgupload',
       secureuri:false,                       //是否启用安全提交,默认为false
       fileElementId:'myBlogImage',           //文件选择框的id属性
       dataType:'text',                       //服务器返回的格式,可以是json或xml等
       success:function(data, status){        //服务器响应成功时的处理函数
           alert(data);
           data = data.replace("<PRE>", '');  //ajaxFileUpload会对服务器响应回来的text内容加上<pre>text</pre>前后缀
           data = data.replace("</PRE>", '');
           data = data.replace("<pre>", '');
           data = data.replace("</pre>", ''); //本例中设定上传文件完毕后,服务端会返回给前台[0`filepath]
           if(data.substring(0, 1) == 0){     //0表示上传成功(后跟上传后的文件路径),1表示失败(后跟失败描述)
            $("img[id='uploadImage']").attr("src", data.substring(2));
            $('#result').html("图片上传成功<br/>");
           }else{
            $('#result').html('图片上传失败,请重试!!');
           }
       },
       error:function(data, status, e){ //服务器响应失败时的处理函数
           $('#result').html('图片上传失败,请重试!!');
       }
       });
                          
          }
      </script>
  </head>
 
  <body>
      <div>
        <input type="file" id="myBlogImage" name="imgFile"/>
        <input type="button" value="提交" onclick="ajaxFileUpload()"/>
    </div>
    <div id="result"></div>
   
  </body>
</html>

4.新增java接收方法

     /**
     * 这里这里用的是MultipartFile[] myfiles参数,所以前台就要用<input type="file" name="myfiles"/>
     * 上传文件完毕后返回给前台[0`filepath],0表示上传成功(后跟上传后的文件路径),1表示失败(后跟失败描述)
  * @throws IOException   @RequestParam("uname") String uname,
     */
 @RequestMapping(value="/fileUpload",method=RequestMethod.POST)
 @ResponseBody
 public String SaveFile(@RequestParam("uname") String uname,@RequestParam MultipartFile[] myfiles, HttpServletRequest request, HttpServletResponse response) throws IOException{
  //可以在上传文件的同时接收其它参数
 // System.out.println("收到用户[" + uname + "]的文件上传请求");
  //如果用的是Tomcat服务器,则文件会上传到 \\%TOMCAT_HOME%\\webapps\\YourWebProject\\upload\\文件夹中
  //这里实现文件上传操作用的是commons.io.FileUtils类,它会自动判断/upload是否存在,不存在会自动创建
  String realPath = request.getSession().getServletContext().getRealPath("/upload");
  //设置响应给前台内容的数据格式
  response.setContentType("text/plain; charset=UTF-8");
  //设置响应给前台内容的PrintWriter对象
  PrintWriter out = response.getWriter();
  //上传文件的原名(即上传前的文件名字)
  String originalFilename = null;
  //如果只是上传一个文件,则只需要MultipartFile类型接收文件即可,而且无需显式指定@RequestParam注解
  //如果想上传多个文件,那么这里就要用MultipartFile[]类型来接收文件,并且要指定@RequestParam注解
  //上传多个文件时,前台表单中的所有<input type="file"/>的name都应该是myfiles,否则参数里的myfiles无法获取到所有上传的文件
  for(MultipartFile myfile : myfiles){
   if(myfile.isEmpty()){
    out.print("1`请选择文件后上传");
    out.flush();
    return null;
   }else{
    originalFilename = myfile.getOriginalFilename();
    System.out.println("文件原名: " + originalFilename);
    System.out.println("文件名称: " + myfile.getName());
    System.out.println("文件长度: " + myfile.getSize());
    System.out.println("文件类型: " + myfile.getContentType());
    System.out.println("========================================");
    try {
     //这里不必处理IO流关闭的问题,因为FileUtils.copyInputStreamToFile()方法内部会自动把用到的IO流关掉
     //此处也可以使用Spring提供的MultipartFile.transferTo(File dest)方法实现文件的上传
      FileUtils.copyInputStreamToFile(myfile.getInputStream(), new File(realPath, originalFilename));

    } catch (IOException e) {
     System.out.println("文件["+ originalFilename + "]上传失败,堆栈轨迹如下");
     e.printStackTrace();
     out.print("1`文件上传失败,请重试!!");
     out.flush();
     return null;
    }
   }
  }
  //此时在Windows下输出的是[D:\Develop\apache-tomcat-6.0.36\webapps\AjaxFileUpload\\upload\愤怒的小鸟.jpg]
  //System.out.println(realPath + "\\" + originalFilename);
  //此时在Windows下输出的是[/AjaxFileUpload/upload/愤怒的小鸟.jpg]
  //System.out.println(request.getContextPath() + "/upload/" + originalFilename);
  //不推荐返回[realPath + "\\" + originalFilename]的值
  //因为在Windows下<img src=" file:///D:/aa.jpg">能被firefox显示,而<img src="D:/aa.jpg">firefox是不认的
  out.print("0`" + request.getContextPath() + "/upload/" + originalFilename);
  out.flush();
  return null;
 }

二 。上传多个文件

   1.修改fileupload.js文件,将form拼接部分进行修改成   

createUploadForm: function(id, fileElementId, data)
{
 //create form
    var formId = 'jUploadForm' + id[0];
    var fileId = 'jUploadFile' + id;
    var form = jQuery('<form  action="" method="POST" name="' + formId + '" id="' + formId + '" enctype="multipart/form-data"></form>');
    if (data) {
  for ( var i in data) {
   jQuery(
     '<input type="hidden" name="' + i + '" value="'
       + data[i] + '" />').appendTo(form);
  }
 }
    for (var i = 0; i < fileElementId.length; i++) {
  var oldElement = jQuery('#' + fileElementId[i]);
  var newElement = jQuery(oldElement).clone();
  jQuery(oldElement).attr('id', fileElementId[i]);
  jQuery(oldElement).attr('name', fileElementId[i]);
  jQuery(oldElement).before(newElement);
  jQuery(oldElement).appendTo(form);
 }
    //set attributes
    jQuery(form).css('position', 'absolute');
    jQuery(form).css('top', '-1200px');
    jQuery(form).css('left', '-1200px');
    jQuery(form).appendTo('body');
 return form;
   }

 2.html上传文件

<%@ page language="java" pageEncoding="UTF-8"%>
<%@ include file="/common/taglibs.jsp"%>
<%@ include file="/common/headcj.jsp"%>
<html>
  <head>
 <script type="text/javascript" src="${ctx }/js/common/dajaxfileupload.js"></script>
    <!-- 执行上传文件操作的函数 -->
      <script type="text/javascript">
       var count = 1;
  /**
  * 生成多附件上传框
  */
  function createInput(parentId){
      count++;
      var str = '<div name="div" ><font style="font-size:12px;">附件</font>'+
      '   '+ '<input type="file" contentEditable="false" id="uploads' + count + '' +
      '" name="uploads'+ count +'" value="" style="width: 220px"/><input type="button"  value="删除" onclick="removeInput(event)" />'+'</div>';
      document.getElementById(parentId).insertAdjacentHTML("beforeEnd",str);
  }
  /**
  * 删除多附件删除框
  */
  function removeInput(evt, parentId){
     var el = evt.target == null ? evt.srcElement : evt.target;
     var div = el.parentNode;
     var cont = document.getElementById(parentId);      
     if(cont.removeChild(div) == null){
      return false;
     }
     return true;
  }
  
  function addOldFile(data){
   var str = '<div name="div' + data.name + '" ><a href="#" style="text-decoration:none;font-size:12px;color:red;" onclick="removeOldFile(event,' + data.id + ')">删除</a>'+
      '   ' + data.name +
      '</div>';
      document.getElementById('oldImg').innerHTML += str;
  }
  
  function removeOldFile(evt, id){
      //前端隐藏域,用来确定哪些file被删除,这里需要前端有个隐藏域
      $("#imgIds").val($("#imgIds").val()=="" ? id :($("#imgIds").val() + "," + id));
      var el = evt.target == null ? evt.srcElement : evt.target;
      var div = el.parentNode;
      var cont = document.getElementById('oldImg');   
      if(cont.removeChild(div) == null){
          return false;
      }
      return true;
  }
  
  function ajaxFileUploadImg(id){
   //获取file的全部id
   var uplist = $("input[name^=uploads]");
   var arrId = [];
   for (var i=0; i< uplist.length; i++){
    if(uplist[i].value){
     arrId[i] = uplist[i].id;
     alert(uplist[i].id);
    }
   }
   $.ajaxFileUpload({
       url:'${ctx}/file/imguploads',
       secureuri:false,
       fileElementId: arrId,  //这里不在是以前的id了,要写成数组的形式哦!
       dataType: 'json',
       data: {          //需要传输的数据
          },
       success: function (data){
        alert("成功!");
       },
       error: function(data){
        alert("失败!");
       }
     });
   }
      </script>
 </head>
 
  <body>
      <input type="button"  value="添加附件" onclick="createInput('more');" />
  <div id="more"></div>
     <input type="button" onclick="ajaxFileUploadImg('3')" value="上传">
  </body>
</html>

3.新增java文件

/**
  * ajax 上传多个附件文件的后台接收处理
  * @param multipartRequest
  * @param response
  * @throws Exception
  */
 @RequestMapping(method=RequestMethod.POST, value="/imguploads")
 public void imgUpload(
      MultipartHttpServletRequest multipartRequest,
      HttpServletResponse response) throws Exception {
      response.setContentType("text/html;charset=UTF-8");
      Map<String, Object> result = new HashMap<String, Object>();
     
      //获取多个file
      for (Iterator it = multipartRequest.getFileNames(); it.hasNext();) {
        String key = (String) it.next();
        MultipartFile imgFile = multipartRequest.getFile(key);
        if (imgFile.getOriginalFilename().length() > 0) {
          String fileName = imgFile.getOriginalFilename();
                                  //改成自己的对象哦!
          Object obj = new Object();
          //Constant.UPLOAD_GOODIMG_URL 是一个配置文件路径
          try {
            String uploadFileUrl = multipartRequest.getSession().getServletContext().getRealPath(Constants.UPLOAD_GOODIMG_URL);
            File _apkFile = saveFileFromInputStream(imgFile.getInputStream(), uploadFileUrl, fileName);
            if (_apkFile.exists()) {
              FileInputStream fis = new FileInputStream(_apkFile);
            } else
              throw new FileNotFoundException("apk write failed:" + fileName);
            List list = new ArrayList();
            //将obj文件对象添加到list
            list.add(obj);
            result.put("success", true);
          } catch (Exception e) {
            result.put("success", false);
            e.printStackTrace();
          }
        }
      }
  //    String json = new Gson().toJson(result,new TypeToken<Map<String, Object>>() {}.getType());
      JSONObject json = JSONObject.fromObject(result);
      response.getWriter().print(json);
    }

   //保存文件
    private File saveFileFromInputStream(InputStream stream, String path,
        String filename) throws IOException {
      File file = new File(path + "/" + filename);
      FileOutputStream fs = new FileOutputStream(file);
      byte[] buffer = new byte[1024 * 1024];
      int bytesum = 0;
      int byteread = 0;
      while ((byteread = stream.read(buffer)) != -1) {
        bytesum += byteread;
        fs.write(buffer, 0, byteread);
        fs.flush();
      }
      fs.close();
      stream.close();
      return file;
    }

 

总结 :大致我看了一下它的fileajaxupload.js基本上就做了一个form拼接提交的原理。没有什么。



已有 0人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



Java 8中10个不易察觉的错误

$
0
0
  1. 不小心重用了流

我敢打赌,每人至少都会犯一次这样的错误。就像现有的这些“流”(比如说InputStream),你也只能对它们消费一次。下面的代码是无法工作的:

IntStream stream = IntStream.of(1, 2);
stream.forEach(System.out::println);

// That was fun! Let's do it again!
stream.forEach(System.out::println);

你会碰到一个这样的错误:

java.lang.IllegalStateException: 
  stream has already been operated upon or closed

因此使用流的时候应当格外注意。它只能消费一次。

  1. 不小心创建了一个”无限"流

你可能一不留神就创建了一个无限流。就拿下面这个例子来说:

IntStream.iterate(0, i -> i + 1)
         .forEach(System.out::println);

流的问题就在于它有可能是无限的,如果你的确是这样设计的话。唯一的问题就是,这并不是你真正想要的。因此,你得确保每次都给流提供一个适当的大小限制:

// That's better
IntStream.iterate(0, i -> i + 1)
         .limit(10)
         .forEach(System.out::println);
  1. 不小心创建了一个“隐藏的”无限流

这个话题是说不完的。你可能一不小心就真的创建了一个无限流。比如说下面的这个:

IntStream.iterate(0, i -> ( i + 1 ) % 2)
         .distinct()
         .limit(10)
         .forEach(System.out::println);

这样做的结果是:

  • 我们生成了0和1的交替数列
  • 然后只保留不同的数值,比如说,一个0和一个1
  • 然后再将流的大小限制为10
  • 最后对流进行消费

好吧,这个distinct()操作它并不知道iterate()所调用的这个函数生成的只有两个不同的值。它觉得可能还会有别的值。因此它会不停地从流中消费新的值,而这个limit(10)永远也不会被调用到。不幸的是,你的应用程序会崩掉。

  1. 不小心创建了一个”隐藏”的并行无限流

我还是想继续提醒下你,你可能真的一不小心就消费了一个无限流。假设你认为distinct()操作是会并行执行的。那你可能会这么写:

IntStream.iterate(0, i -> ( i + 1 ) % 2)
         .parallel()
         .distinct()
         .limit(10)
         .forEach(System.out::println);

现在我们可以知道的是,这段代码会一直执行下去。不过在前面那个例子中,你至少只消耗了机器上的一个CPU。而现在你可能会消耗四个,一个无限流的消费很可能就会消耗掉你整个系统的资源。这可相当不妙。这种情况下你可能得去重启服务器了。看下我的笔记本在最终崩溃前是什么样的:

  1. 操作的顺序

为什么我一直在强调你可能一不小心就创建了一个无限流?很简单。因为如果你把上面的这个流的limit()和distinct()操作的顺序掉换一下,一切就都OK了。

IntStream.iterate(0, i -> ( i + 1 ) % 2)
         .limit(10)
         .distinct()
         .forEach(System.out::println);

现在则会输出:

0
1

为什么会这样?因为我们先将无限流的大小限制为10个值,也就是(0 1 0 1 0 1 0 1 0 1),然后再在这个有限流上进行归约,求出它所包含的不同值,(0,1)。

当然了,这个在语义上就是错误的了。因为你实际上想要的是数据集的前10个不同值。没有人会真的要先取10个随机数,然后再求出它们的不同值的。

如果你是来自SQL背景的话,你可能不会想到还有这个区别。就拿SQL Server 2012举例来说,下面的两个SQL语句是一样的:

-- Using TOP
SELECT DISTINCT TOP 10 *
FROM i
ORDER BY ..

-- Using FETCH
SELECT *
FROM i
ORDER BY ..
OFFSET 0 ROWS
FETCH NEXT 10 ROWS ONLY

因此,作为一名SQL用户,你可能并不会注意到流操作顺序的重要性。

  1. 还是操作顺序

既然说到了SQL,如果你用的是MySQL或者PostgreSQL,你可能会经常用到LIMIT .. OFFSET子句。SQL里全是这种暗坑,这就是其中之一。正如SQL Server 2012中的语法所说明的那样,OFFSET子名会优先执行。

如果你将MySQL/PostgreSQL方言转化成流的话,得到的结果很可能是错的:

IntStream.iterate(0, i -> i + 1)
         .limit(10) // LIMIT
         .skip(5)   // OFFSET
         .forEach(System.out::println);

上面的代码会输出:

5
6
7
8
9

是的,它输出9后就结束了,因为首先生效的是limit(),这样会输出(0 1 2 3 4 5 6 7 8 9)。其次才是skip(),它将流缩减为(5 6 7 8 9)。而这并不是你所想要的。

警惕LIMIT .. OFFSET和OFFSET .. LIMIT的陷阱!

  1. 使用过滤器来遍历文件系统

这个问题我们之前已经讲过了。使用过滤器来遍历文件系统是个不错的方式:

Files.walk(Paths.get("."))
     .filter(p -> !p.toFile().getName().startsWith("."))
     .forEach(System.out::println);

看起来上面的这个流只是遍历了所有的非隐藏目录,也就是不以点号开始的那些目录。不幸的是,你又犯了错误五和错误六了。walk()方法已经生成一个当前目录下的所有子目录的流。虽然是一个惰性流,但是也包含了所有的子路径。现在的这个过滤器可以正确过滤掉所有名字以点号开始的那些目录,也就是说结果流中不会包含.git或者.idea。不过路径可能会是:..git\refs或者..idea\libraries。而这并不是你实际想要的。

你可别为了解决问题而这么写:

Files.walk(Paths.get("."))
     .filter(p -> !p.toString().contains(File.separator + "."))
     .forEach(System.out::println);

虽然这么写的结果是对的,但是它会去遍历整个子目录结构树,这会递归所有的隐藏目录的子目录。

我猜你又得求助于老的JDK1.0中所提供的File.list()了。不过好消息是, FilenameFilter和FileFilter现在都是函数式接口了。

  1. 修改流内部的集合

当遍历列表的时候,你不能在迭代的过程中同时去修改这个列表。这个在Java 8之前就是这样的,不过在Java 8的流中则更为棘手。看下下面这个0到9的列表:

// Of course, we create this list using streams:
List<Integer> list = 
IntStream.range(0, 10)
         .boxed()
         .collect(toCollection(ArrayList::new));

现在,假设下我们在消费流的时候同时去删除元素:

list.stream()
    // remove(Object), not remove(int)!
    .peek(list::remove)
    .forEach(System.out::println);

有趣的是,其中的一些元素中可以的删除的。你得到的输出将会是这样的:

0
2
4
6
8
null
null
null
null
null
java.util.ConcurrentModificationException

如果我们捕获异常后再查看下这个列表,会发现一个很有趣的事情。得到的结果是:

[1, 3, 5, 7, 9]

所有的奇数都这样。这是一个BUG吗?不,这更像是一个特性。如果你看一下JDK的源码,会发现在ArrayList.ArraListSpliterator里面有这么一段注释:

/* * If ArrayLists were immutable, or structurally immutable (no * adds, removes, etc), we could implement their spliterators * with Arrays.spliterator. Instead we detect as much * interference during traversal as practical without * sacrificing much performance. We rely primarily on * modCounts. These are not guaranteed to detect concurrency * violations, and are sometimes overly conservative about * within-thread interference, but detect enough problems to * be worthwhile in practice. To carry this out, we (1) lazily * initialize fence and expectedModCount until the latest * point that we need to commit to the state we are checking * against; thus improving precision. (This doesn't apply to * SubLists, that create spliterators with current non-lazy * values). (2) We perform only a single * ConcurrentModificationException check at the end of forEach * (the most performance-sensitive method). When using forEach * (as opposed to iterators), we can normally only detect * interference after actions, not before. Further * CME-triggering checks apply to all other possible * violations of assumptions for example null or too-small * elementData array given its size(), that could only have * occurred due to interference. This allows the inner loop * of forEach to run without any further checks, and * simplifies lambda-resolution. While this does entail a * number of checks, note that in the common case of * list.stream().forEach(a), no checks or other computation * occur anywhere other than inside forEach itself. The other * less-often-used methods cannot take advantage of most of * these streamlinings. */

现在来看下如果我们对这个流排序后会是什么结果:

list.stream()
    .sorted()
    .peek(list::remove)
    .forEach(System.out::println);

输出的结果看起来是我们想要的:

0
1
2
3
4
5
6
7
8
9

而流消费完后的列表是空的:

[]

也就是说所有的元素都正确地消费掉并删除了。sorted()操作是一个“带状态的中间操作”,这意味着后续的操作不会再操作内部的那个集合了,而是在一个内部的状态上进行操作。现在你可以安全地从列表里删除元素了!

不过,真的是吗这样?我们来试一下带有parallel(), sorted()的删除操作:

list.stream()
    .sorted()
    .parallel()
    .peek(list::remove)
    .forEach(System.out::println);

这个会输出 :

7
6
2
5
8
4
1
0
9
3

现在列表里包含:

[8]

唉呀。居然没有删完所有的元素?!谁能解决这个问题,我免费请他喝酒!

这些行为看起来都是不确定的,我只能建议你在使用流的时候不要去修改它内部的数据集合。这样做是没用的。

  1. 忘了去消费流

你觉得下面这个流在做什么?

IntStream.range(1, 5)
         .peek(System.out::println)
         .peek(i -> { 
              if (i == 5) 
                  throw new RuntimeException("bang");
          });

看完这段代码,你觉得应该会输出(1 2 3 4 5)然后抛出一个异常。不过并不是这样。它什么也不会做。这个流并没有被消费掉,它只是静静的待在那里。

正如别的流API或者DSL那样,你可能会忘了调用这个终止操作。当你使用peek()的时候也是这样的,因为peek有点类似于forEach()。

在jOOQ中也存在这样的情况,如果你忘了去调用 execute()或者fetch():

DSL.using(configuration)
   .update(TABLE)
   .set(TABLE.COL1, 1)
   .set(TABLE.COL2, "abc")

   .where(TABLE.ID.eq(3));

杯具。忘了调用execute方法了。

  1. 并行流死锁

终于快讲完了~

如果你没有正确地进行同步的话,所有的并发系统都可能碰到死锁。现实中的例子可能不那么明显,不过如果你想自己创造一个场景的话倒是很容易。下面这个parallel()流肯定会造成死锁:

Object[] locks = { new Object(), new Object() };

IntStream
    .range(1, 5)
    .parallel()
    .peek(Unchecked.intConsumer(i -> {
        synchronized (locks[i % locks.length]) {
            Thread.sleep(100);

            synchronized (locks[(i + 1) % locks.length]) {
                Thread.sleep(50);
            }
        }
    }))
    .forEach(System.out::println);

注意这里Unchecked.intConsumer()的使用,它把IntConsumer接口转化成了 org.jooq.lambda.fi.util.function.CheckedIntConsumer,这样你才可以抛出已检查异常。

好吧。这下你的机器倒霉了。这些线程会一直阻塞下去:-)

不过好消息就是,在Java里面要写出一个这种教科书上的死锁可不是那么容易。

想进一步了解的话,可以看下Brian Goetz在StackOverflow上的 一个回答

结论

引入了流和函数式编程之后,我们开始会碰到许多新的难以发现的BUG。这些BUG很难避免,除非你见过并且还时刻保持警惕。你必须去考虑操作的顺序,还得注意流是不是无限的。

流是一个非常强大的工具,但也是一个首先得去熟练掌握的工具。

原创文章转载请注明出处: Java 8中10个不易察觉的错误英文原文链接


通过btrace查找dbcp数据库连接池泄露的方法

$
0
0
    下面是一个通过btrace查找dbcp数据库连接池泄露的方法,如果使用的是其他的连接池,方法大同小异,只是更换一下打开和关闭数据库连接的类就好。
    虽然spring,mybatis等框架已经提供了各种管理数据库连接的模板,但是实际的环境中,我们还是会遇到直接向连接池申请连接的情况,如果遇到这种情况,由于开发人员的疏忽,很可能忘记关闭数据库连接,如出现数据库连接泄露的情况,使用btrace会是一个很好的解决方案。
   我在平安工作中,就遇到过一次数据库泄露,从发现问题到最后解决,花费了三个小时的时间。造成了很恶劣的影响,全国的平安车险系统直接停用三个小时。
   关于btrace相关的命令和使用方式(BTrace 是一款利用hotSpot虚拟机可以动态替换class的特点而完成的,可以对在线的程序动态的改变类的行为(一般为加些打印日志),进而进行线上调试的一个工具。),可去官网:https://kenai.com/projects/btrace/
下面是使用方法介绍:
1.查看日志,如果发现类似的日志,只有两种可能,一是可用连接数设置小了,二是连接池泄露了。
Could not open JDBC Connection for transaction; nested exception is org.apache.commons.dbcp.SQLNestedException: Cannot get a connection, pool error Timeout waiting for idle object
org.springframework.transaction.CannotCreateTransactionException: Could not open JDBC Connection for transaction; nested exception is org.apache.commons.dbcp.SQLNestedException: Cannot get a connection, pool error Timeout waiting for idle object
2.通过jps命令查询应用程序的进程id
3.btrace [进程id] BTraceDHCPConnection.java 命令
跟踪成产系统,分析打开和关闭连接是否成对出现,并打印当时的线程dump,可以看到哪些业务方法操作了数据库,从而直接定位问题所在。

已有 0人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



清空上传文本框中文件路径值

$
0
0

今天工作时,遇到了一个问题:需要将上传文件的input框中的值清空,在上传文件保存成功之后,(上传控件在IE浏览器下有input框)在IE浏览器下input框中依然还显示刚刚上传文件的路径,尝试着使用普通文本框清空的方法去做,但是起不到效果。后来在网上看到需要将原有的标签用新的标签替换就行了。代码如下:

var oldFile1 = document.getElementById("f1");
var newFile1 = oldFile1.cloneNode(true);
oldFile1.parentNode.replaceChild(newFile1, oldFile1);

 这样保存完信息后,同时刚刚input框中文件的路径值就消失了~~



已有 0人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



探秘Java虚拟机——内存管理与垃圾回收

$
0
0

探秘Java虚拟机——内存管理与垃圾回收

本文主要是基于Sun JDK 1.6 Garbage Collector(作者:毕玄)的整理与总结,原文请读者在网上搜索。

1、Java虚拟机运行时的数据区

image

2、常用的内存区域调节参数

-Xms:初始堆大小,默认为物理内存的1/64(<1GB);默认(MinHeapFreeRatio参数可以调整)空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制

-Xmx:最大堆大小,默认(MaxHeapFreeRatio参数可以调整)空余堆内存大于70%时,JVM会减少堆直到 -Xms的最小限制

-Xmn:新生代的内存空间大小, 注意:此处的大小是(eden+ 2 survivor space)。与jmap -heap中显示的New gen是不同的。整个堆大小=新生代大小 + 老生代大小 + 永久代大小。 
在保证堆大小不变的情况下,增大新生代后,将会减小老生代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。

-XX:SurvivorRatio:新生代中Eden区域与Survivor区域的容量比值,默认值为8。两个Survivor区与一个Eden区的比值为2:8,一个Survivor区占整个年轻代的1/10。

-Xss:每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。应根据应用的线程所需内存大小进行适当调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。一般小的应用, 如果栈不是很深, 应该是128k够用的,大的应用建议使用256k。这个选项对性能影响比较大,需要严格的测试。和threadstacksize选项解释很类似,官方文档似乎没有解释,在论坛中有这样一句话:"-Xss is translated in a VM flag named ThreadStackSize”一般设置这个值就可以了。

-XX:PermSize:设置永久代(perm gen)初始值。默认值为物理内存的1/64。

-XX:MaxPermSize:设置持久代最大值。物理内存的1/4。

3、内存分配方法

1)堆上分配   2)栈上分配  3)堆外分配(DirectByteBuffer或直接使用Unsafe.allocateMemory,但不推荐这种方式)

4、监控方法

1)系统程序运行时可通过jstat –gcutil来查看堆中各个内存区域的变化以及GC的工作状态; 
2)启动时可添加-XX:+PrintGCDetails  –Xloggc:<file>输出到日志文件来查看GC的状况; 
3)jmap –heap可用于查看各个内存空间的大小;

5)断代法可用GC汇总

image

一、新生代可用GC

1)串行GC(Serial Copying):client模式下默认GC方式,也可通过-XX:+UseSerialGC来强制指定;默认情况下 eden、s0、s1的大小通过-XX:SurvivorRatio来控制,默认为8,含义 
为eden:s0的比例,启动后可通过jmap –heap [pid]来查看。

      默认情况下,仅在TLAB或eden上分配,只有两种情况下会在老生代分配: 
      1、需要分配的内存大小超过eden space大小; 
      2、在配置了PretenureSizeThreshold的情况下,对象大小大于此值。

      默认情况下,触发Minor GC时:
      之前Minor GC晋级到old的平均大小 < 老生代的剩余空间 < eden+from Survivor的使用空间。当HandlePromotionFailure为true,则仅触发minor gc;如为false,则触发full GC。

      默认情况下,新生代对象晋升到老生代的规则:

     1、经历多次minor gc仍存活的对象,可通过以下参数来控制:以MaxTenuringThreshold值为准,默认为15。
     2、to space放不下的,直接放入老生代;

2)并行GC(ParNew):CMS GC时默认采用,也可采用-XX:+UseParNewGC强制指定;垃圾回收的时候采用多线程的方式。

3)并行回收GC(Parallel Scavenge):server模式下默认的GC方式,也可采用-XX:+UseParallelGC强制指定;eden、s0、s1的大小可通过-XX:SurvivorRatio来控制,但默认情况下
以-XX:InitialSurivivorRatio为准,此值默认为8, 代表的为新生代大小 : s0,这点要特别注意。

      默认情况下,当TLAB、eden上分配都失败时,判断需要分配的内存大小是否 >= eden space的一半大小,如是就直接在老生代上分配;

      默认情况下的垃圾回收规则:

      1、在回收前PS GC会先检测之前每次PS GC时,晋升到老生代的平均大小是否大于老生代的剩余空间,如大于则直接触发full GC;
      2、在回收后,也会按照上面的规则进行检测。

      默认情况下的新生代对象晋升到老生代的规则:
     1、经历多次minor gc仍存活的对象,可通过以下参数来控制:AlwaysTenure,默认false,表示只要minor GC时存活,就晋升到老生代;NeverTenure,默认false,表示永不晋升到老生代;上面两个都没设置的情冴下,如UseAdaptiveSizePolicy,启动时以InitialTenuringThreshold值作为存活次数的阈值,在每次ps gc后会动态调整,如不使用UseAdaptiveSizePolicy,则以MaxTenuringThreshold为准。
     2、to space放不下的,直接放入老生代。

     在回收后,如UseAdaptiveSizePolicy,PS GC会根据运行状态动态调整eden、to以及TenuringThreshold的大小。如果不希望动态调整可设置-XX:-UseAdaptiveSizePolicy。如希望跟踪每次的变化情况,可在启劢参数上增加: PrintAdaptiveSizePolicy。

二、老生代可用GC

1、串行GC(Serial Copying):client方式下默认GC方式,可通过-XX:+UseSerialGC强制指定。

    触发机制汇总:
   1)old gen空间不足;
   2)perm gen空间不足;
   3)minor gc时的悲观策略;
   4)minor GC后在eden上分配内存仍然失败;
   5)执行heap dump时;
   6)外部调用System.gc,可通过-XX:+DisableExplicitGC来禁止。

2、并行回收GC(Parallel Scavenge): server模式下默认GC方式,可通过-XX:+UseParallelGC强制指定; 并行的线程数为当cpu core<=8 ? cpu core : 3+(cpu core*5)/8或通过-XX:ParallelGCThreads=x来强制指定。如ScavengeBeforeFullGC为true(默认值),则先执行minor GC。

3、并行Compacting:可通过-XX:+UseParallelOldGC强制指定。

4、并发CMS:可通过-XX:+UseConcMarkSweepGC来强制指定。并发的线程数默认为:( 并行GC线程数+3)/4,也可通过ParallelCMSThreads指定。

    触发机制:
    1、当老生代空间的使用到达一定比率时触发;

     Hotspot V 1.6中默认为65%,可通过PrintCMSInitiationStatistics(此参数在V 1.5中不能用)来查看这个值到底是多少;可通过CMSInitiatingOccupancyFraction来强制指定,默认值并不是赋值在了这个值上,是根据如下公式计算出来的: ((100 - MinHeapFreeRatio) +(double)(CMSTriggerRatio * MinHeapFreeRatio) / 100.0)/ 100.0; 其中,MinHeapFreeRatio默认值: 40   CMSTriggerRatio默认值: 80。

     2、当perm gen采用CMS收集且空间使用到一定比率时触发;

     perm gen采用CMS收集需设置:-XX:+CMSClassUnloadingEnabled   Hotspot V 1.6中默认为65%;可通过CMSInitiatingPermOccupancyFraction来强制指定,同样,它是根据如下公式计算出来的:((100 - MinHeapFreeRatio) +(double)(CMSTriggerPermRatio* MinHeapFreeRatio) / 100.0)/ 100.0; 其中,MinHeapFreeRatio默认值: 40    CMSTriggerPermRatio默认值: 80。

      3、Hotspot根据成本计算决定是否需要执行CMS GC;可通过-XX:+UseCMSInitiatingOccupancyOnly来去掉这个动态执行的策略。
      4、外部调用了System.gc,且设置了ExplicitGCInvokesConcurrent;需要注意,在hotspot 6中,在这种情况下如应用同时使用了NIO,可能会出现bug。

6、GC组合

1)默认GC组合

image

2)可选的GC组合

image

7、GC监测

1)jstat –gcutil [pid] [intervel] [count]
2)-verbose:gc // 可以辅助输出一些详细的GC信息;-XX:+PrintGCDetails // 输出GC详细信息;-XX:+PrintGCApplicationStoppedTime // 输出GC造成应用暂停的时间
-XX:+PrintGCDateStamps // GC发生的时间信息;-XX:+PrintHeapAtGC // 在GC前后输出堆中各个区域的大小;-Xloggc:[file] // 将GC信息输出到单独的文件中,建议都加上,这个消耗不大,而且对查问题和调优有很大的帮助。gc的日志拿下来后可使用GCLogViewer或gchisto进行分析。
3)图形化的情况下可直接用jvisualvm进行分析。

4)查看内存的消耗状况

      (1)长期消耗,可以直接dump,然后MAT(内存分析工具)查看即可

      (2)短期消耗,图形界面情况下,可使用jvisualvm的memory profiler或jprofiler。

8、系统调优方法

步骤:1、评估现状 2、设定目标 3、尝试调优 4、衡量调优 5、细微调整

设定目标:

1)降低Full GC的执行频率?
2)降低Full GC的消耗时间?
3)降低Full GC所造成的应用停顿时间?
4)降低Minor GC执行频率?
5)降低Minor GC消耗时间?
例如某系统的GC调优目标:降低Full GC执行频率的同时,尽可能降低minor GC的执行频率、消耗时间以及GC对应用造成的停顿时间。

衡量调优:

1、衡量工具
1)打印GC日志信息:-XX:+PrintGCDetails –XX:+PrintGCApplicationStoppedTime -Xloggc: {文件名}  -XX:+PrintGCTimeStamps
2)jmap:(由于每个版本jvm的默认值可能会有改变,建议还是用jmap首先观察下目前每个代的内存大小、GC方式) 
3)运行状况监测工具:jstat、jvisualvm、sar 、gclogviewer

2、应收集的信息
1)minor gc的执行频率;full gc的执行频率,每次GC耗时多少?
2)高峰期什么状况?
3)minor gc回收的效果如何?survivor的消耗状况如何,每次有多少对象会进入老生代?
4)full gc回收的效果如何?(简单的 memory leak判断方法)
5)系统的load、cpu消耗、qps or tps、响应时间

QPS每秒查询率:是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准。在因特网上,作为域名服务器的机器性能经常用每秒查询率来衡量。对应fetches/sec,即每秒的响应请求数,也即是最大吞吐能力。
TPS(Transaction Per Second):每秒钟系统能够处理的交易或事务的数量。

尝试调优:

注意Java RMI的定时GC触发机制,可通过:-XX:+DisableExplicitGC来禁止或通过 -Dsun.rmi.dgc.server.gcInterval=3600000来控制触发的时间。

1)降低Full GC执行频率 – 通常瓶颈
老生代本身占用的内存空间就一直偏高,所以只要稍微放点对象到老生代,就full GC了;
通常原因:系统缓存的东西太多;
例如:使用oracle 10g驱动时preparedstatement cache太大;
查找办法:现执行Dump然后再进行MAT分析;

(1)Minor GC后总是有对象不断的进入老生代,导致老生代不断的满
通常原因:Survivor太小了
系统表现:系统响应太慢、请求量太大、每次请求分配的内存太多、分配的对象太大...
查找办法:分析两次minor GC之间到底哪些地方分配了内存;
利用jstat观察Survivor的消耗状况,-XX:PrintHeapAtGC,输出GC前后的详细信息;
对于系统响应慢可以采用系统优化,不是GC优化的内容;

(2)老生代的内存占用一直偏高
调优方法:① 扩大老生代的大小(减少新生代的大小或调大heap的 大小);
减少new注意对minor gc的影响并且同时有可能造成full gc还是严重;
调大heap注意full gc的时间的延长,cpu够强悍嘛,os是32 bit的吗?
② 程序优化(去掉一些不必要的缓存)

(3)Minor GC后总是有对象不断的进入老生代
前提:这些进入老生代的对象在full GC时大部分都会被回收
调优方法:
① 降低Minor GC的执行频率;
② 让对象尽量在Minor GC中就被回收掉:增大Eden区、增大survivor、增大TenuringThreshold;注意这些可能会造成minor gc执行频繁;
③ 切换成CMS GC:老生代还没有满就回收掉,从而降低Full GC触发的可能性;
④ 程序优化:提升响应速度、降低每次请求分配的内存、

(4)降低单次Full GC的执行时间
通常原因:老生代太大了...
调优方法:1)是并行GC吗?   2)升级CPU  3)减小Heap或老生代

(5)降低Minor GC执行频率
通常原因:每次请求分配的内存多、请求量大
通常办法:1)扩大heap、扩大新生代、扩大eden。注意点:降低每次请求分配的内存;横向增加机器的数量分担请求的数量。

(6)降低Minor GC执行时间
通常原因:新生代太大了,响应速度太慢了,导致每次Minor GC时存活的对象多
通常办法:1)减小点新生代吧;2)增加CPU的数量、升级CPU的配置;加快系统的响应速度

细微调整:

首先需要了解以下情况:

① 当响应速度下降到多少或请求量上涨到多少时,系统会宕掉?

② 参数调整后系统多久会执行一次Minor GC,多久会执行一次Full GC,高峰期会如何?

需要计算的量:

①每次请求平均需要分配多少内存?系统的平均响应时间是多少呢?请求量是多少、多常时间执行一次Minor GC、Full GC?

②现有参数下,应该是多久一次Minor GC、Full GC,对比真实状况,做一定的调整;

必杀技:提升响应速度、降低每次请求分配的内存?

9、系统调优举例

     现象:1、系统响应速度大概为100ms;2、当系统QPS增长到40时,机器每隔5秒就执行一次minor gc,每隔3分钟就执行一次full gc,并且很快就一直full GC了;4、每次Full gc后旧生代大概会消耗400M,有点多了。

     解决方案:解决Full GC次数过多的问题

    (1)降低响应时间或请求次数,这个需要重构,比较麻烦;——这个是终极方法,往往能够顺利的解决问题,因为大部分的问题均是由程序自身造成的。

    (2)减少老生代内存的消耗,比较靠谱;——可以通过分析Dump文件(jmap dump),并利用MAT查找内存消耗的原因,从而发现程序中造成老生代内存消耗的原因。

    (3)减少每次请求的内存的消耗,貌似比较靠谱;——这个是海市蜃楼,没有太好的办法。

    (4)降低GC造成的应用暂停的时间——可以采用CMS GS垃圾回收器。参数设置如下:

     -Xms1536m -Xmx1536m -Xmn700m -XX:SurvivorRatio=7 -XX:+UseConcMarkSweepGC -XX:+UseCMSCompactAtFullCollection

     -XX:CMSMaxAbortablePrecleanTime=1000 -XX:+CMSClassUnloadingEnabled -XX:+UseCMSInitiatingOccupancyOnly -XX:+DisableExplicitGC

    (5)减少每次minor gc晋升到old的对象。可选方法:1) 调大新生代。2)调大Survivor。3)调大TenuringThreshold。

      调大Survivor:当前采用PS GC,Survivor space会被动态调整。由于调整幅度很小,导致了经常有对象直接转移到了老生代;于是禁止Survivor区的动态调整了,-XX:-UseAdaptiveSizePolicy,并计算Survivor Space需要的大小,于是继续观察,并做微调…。最终将Full GC推迟到2小时1次。

10、垃圾回收的实现原理

      内存回收的实现方法:1)引用计数:不适合复杂对象的引用关系,尤其是循环依赖的场景。2)有向图Tracing:适合于复杂对象的引用关系场景,Hotspot采用这种。常用算法:Copying、Mark-Sweep、Mark-Compact。

      Hotspot从root set开始扫描有引用的对象并对Reference类型的对象进行特殊处理。
      以下是Root Set的列表:1)当前正在执行的线程;2)全局/静态变量;3)JVM Handles;4)JNI 【 Java Native Interface 】Handles;

      另外:minor GC只扫描新生代,当老生代的对象引用了新生代的对象时,会采用如下的处理方式:在给对象赋引用时,会经过一个write barrier的过程,以便检查是否有老生代引用新生代对象的情况,如有则记录到remember set中。并在minor gc时,remember set指向的新生代对象也作为root set。

      新生代串行GC(Serial Copying):

     新生代串行GC(Serial Copying)完整内存的分配策略:

     1)首先在TLAB(本地线程分配缓冲区)上尝试分配;
     2)检查是否需要在新生代上分配,如需要分配的大小小于PretenureSizeThreshold,则在eden区上进行分配,分配成功则返回;分配失败则继续;
     3)检查是否需要尝试在老生代上分配,如需要,则遍历所有代并检查是否可在该代上分配,如可以则进行分配;如不需要在老生代上尝试分配,则继续;
     4)根据策略决定执行新生代GC或Full GC,执行full gc时不清除soft Ref;
     5)如需要分配的大小大于PretenureSizeThreshold,尝试在老生代上分配,否则尝试在新生代上分配;
     6)尝试扩大堆并分配;
     7)执行full gc,并清除所有soft Ref,按步骤5继续尝试分配。  

     新生代串行GC(Serial Copying)完整内存回收策略
     1)检查to是否为空,不为空返回false;
     2)检查老生代剩余空间是否大于当前eden+from已用的大小,如大于则返回true,如小于且HandlePromotionFailure为true,则检查剩余空间是否大于之前每次minor gc晋级到老生代的平均大小,如大于返回true,如小于返回false。
     3)如上面的结果为false,则执行full gc;如上面的结果为true,执行下面的步骤;
     4)扫描引用关系,将活的对象copy到to space,如对象在minor gc中的存活次数超过tenuring_threshold或分配失败,则往老生代复制,如仍然复制失败,则取决于HandlePromotionFailure,如不需要处理,直接抛出OOM,并退出vm,如需处理,则保持这些新生代对象不动;

     新生代可用GC-PS

    完整内存分配策略
    1)先在TLAB上分配,分配失败则直接在eden上分配;
    2)当eden上分配失败时,检查需要分配的大小是否 >= eden space的一半,如是,则直接在老生代分配;
    3)如分配仍然失败,且gc已超过频率,则抛出OOM;
    4)进入基本分配策略失败的模式;
    5)执行PS GC,在eden上分配;
    6)执行非最大压缩的full gc,在eden上分配;
    7)在旧生代上分配;
    8)执行最大压缩full gc,在eden上分配;
    9)在旧生代上分配;
    10)如还失败,回到2。

   最悲惨的情况,分配触发多次PS GC和多次Full GC,直到OOM。

   完整内存回收策略
   1)如gc所执行的时间超过,直接结束;
   2)先调用invoke_nopolicy
       2.1 先检查是不是要尝试scavenge;
       2.1.1 to space必须为空,如不为空,则返回false;
       2.1.2 获取之前所有minor gc晋级到old的平均大小,并对比目前eden+from已使用的大小,取更小的一个值,如老生代剩余空间小于此值,则返回false,如大于则返回true;
       2.2 如不需要尝试scavenge,则返回false,否则继续;
       2.3 多线程扫描活的对象,并基亍copying算法回收,回收时相应的晋升对象到旧生代;
       2.4 如UseAdaptiveSizePolicy,那么重新计算to space和tenuringThreshold的值,并调整。
   3)如invoke_nopolicy返回的是false,或之前所有minor gc晋级到老生代的平均大小 > 旧生代的剩余空间,那么继续下面的步骤,否则结束;
   4)如UseParallelOldGC,则执行PSParallelCompact,如不是UseParallelOldGC,则执行PSMarkSweep。

     老生代并行CMS GC:

    优缺点:

    1) 大部分时候和应用并发进行,因此只会造成很短的暂停时间;
    2)浮动垃圾,没办法,所以内存空间要稍微大一点;
    3)内存碎片,-XX:+UseCMSCompactAtFullCollection 来解决;
    4) 争抢CPU,这GC方式就这样;
    5)多次remark,所以总的gc时间会比并行的长;
    6)内存分配,free list方式,so性能稍差,对minor GC会有一点影响;
    7)和应用并发,有可能分配和回收同时,产生竞争,引入了锁,JVM分配优先。

11、TLAB的解释

     堆内的对象数据是各个线程所共享的,所以当在堆内创建新的对象时,就需要进行锁操作。锁操作是比较耗时,因此JVM为每个线在堆上分配了一块“自留地”——TLAB(全称是Thread Local Allocation Buffer),位于堆内存的新生代,也就是Eden区。每个线程在创建新的对象时,会首先尝试在自己的TLAB里进行分配,如果成功就返回,失败了再到共享的Eden区里去申请空间。在线程自己的TLAB区域创建对象失败一般有两个原因:一是对象太大,二是自己的TLAB区剩余空间不够。通常默认的TLAB区域大小是Eden区域的1%,当然也可以手工进行调整,对应的JVM参数是-XX:TLABWasteTargetPercent。

参考文献:

1、Sun JDK 1.6 GC(Garbage Collector)  作者:毕玄



已有 0人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



你真的离不开新闻吗?

$
0
0

  《你真的离不开新闻吗》,这是一篇来自罗尔夫•多贝里的文章,也是来自《明智行动的艺术》一书最后的部分。在这篇文章看完之后,我决定断掉一些经常刷新的东西,比如朋友圈,来尝试一下不看新闻是否能带来价值。如果刷朋友圈是寻找存在感,那么我们的一生有很多可以追求的东西,以下为全文,我直接帖了出来。这种转载其实是并不负责的,但如果你喜欢这个文章,我还是功你去购买《 明智行动的艺术》原书。

  苏门答腊的地震、俄罗斯的坠机事件、一个男人将自己的女人在地下室关了30年、海蒂·克鲁姆和席尔分手、德意志银行破纪录的工资报酬、在巴基斯坦发生的暗杀、马里总统的辞职、掷铅球的最新世界纪录,人们真的必须要知道这些事情?

  我们的信息很灵通,但知道的很少,为什么呢?因为我们在200年前发明了一种有毒的知识类型:新闻,即世界各地的消息报道。新闻对于我们的精神的影响,就像糖对于身体的影响一样:新闻是可口的,容易消化的—但长期下来却是有害的。

  3年前我开始进行一个实验。我决定不再阅读新闻,取消了所有报纸和杂志的订阅,把电视机和收音机从家中搬走,将苹果手机上的新闻应用程序删除,我不再碰任何一份免费的报纸,而且当飞机上有人在我面前阅读报纸时,我会有意识地看向别处。在最开始的几个星期这么做是不容易的,很煎熬,我总害怕会错过些什么。但在一段时间之后,我有了一种新的生活感觉。3年之后,我的思维变得更清晰,见识更深远,可以做出更好的决定,有了更多的时间,而且最好的一点是:我从未错过什么重要的新闻。我的社交圈子—不是指脸谱网,而是现实中真正的朋友和熟人—起着新闻过滤器的作用。

  回避新闻有很多的理由,我这里只说最重要的三个。第一: 我们的大脑对于骇人听闻的、与人物相关的、喧闹的和变换快的刺激会有极为强烈的反应—而对于抽象的、复杂的、需要解释的信息则有着极为微弱的反应。新闻制作者便利用了这一点。吸引人的故事、显眼的图片和耸人听闻的“事实”会牢牢吸引着我们的注意力,结果就是所有构思缜密的、复杂的、抽象的和不易看透的内容都会自动地被隐去,尽管这些内容与我们的生活和与对世界的理解更加相关。所以说为新闻消费的结果就是凭借一张错误的危机入场券在大脑中闲逛。花钱去看新闻的人会把大多数话题的重要性完全估计错误,他们在报纸上读到的那些危机不是真正的危机。

  第二: 新闻的意义是不大的。人们可以在过去的1年内看完大约一万则短新闻—每天约30则。请你很诚实地说出一则新闻,它使你能比以前做出更好的决定—对你的生活、事业或生意。没有人在我提了这个问题之后能说出2则以上的新闻—从一万则新闻中。多么可悲的比率。新闻机构要的是使你相信,他们给你提供了更具竞争力的优势,很多人就这样受骗了。实际上关注新闻不是什么竞争力的优势,而是种劣势。如果关注新闻真的可以使人们获得更多成功,那么记者早就应该站在收入金字塔的顶端了,但他们并不是这样。

  第三: 浪费时间。普通人因为新闻平均每周浪费半个工作日。从全球范围看来,这给生产力带来的损失是很大的。比如2008年在孟买发生的恐怖袭击,恐怖分子在残酷的自我满足欲望中杀害了200人。请你想象一下:平均一个小时有10亿人在关注孟买的悲剧,他们关注着这一新闻,听着电视上某些“专家”和“评论家”的喋喋不休。这是个很符合实际的推算,因为在印度有超过10亿的人口,但我们还是按照10亿保守地计算,10亿人每关注一个小时加起来就是10亿个小时,换算过来就是:有2000个人的寿命被新闻消耗了—比恐怖袭击造成的死亡人数还多9倍,这个视角虽然很讽刺但也很现实。

  消除掉这100多个思维错误和行动错误,与放弃新闻一样,可以给你带来的益处是显而易见的。你害怕没有新闻的生活会使你被朋友们排斥?你也许不知道在西伯利亚某处有飞机坠毁,但是你可以理解这世上深层次的而且往往不是一目了然的关联,你可以将这些与其他人分享。谈到你不看新闻的事情时你不要有顾虑,人们会很感兴趣地听你讲述。长话短说,请你放弃对新闻的消费,而且是全部。你可以阅读长篇的背景文章和书籍,因为如果想要理解这个世界的话,没有比读书更好的方法了。

为什么移动搜索产品都那么糟糕?

$
0
0

shutterstock_159315359

即使放到现在来看,我们依然可以说,移动搜索还不算是太成功的那一类产品。

一方面,用户的搜索习惯正在从PC向移动设备延伸,高达90.4%的用户会在手机上使用搜索功能;另一方面,用户并不喜欢移动搜索产品,高达61.4%的用户觉得在手机上搜索比在PC上困难得多。(数据出自: Serch Engine Land

于是,你可以看到这样一个怪象—— 搭了“传统互联网没落”便车的移动搜索产品在获得大量用户的同时,却仍然没有找到一个真正被移动互联网用户所接纳的产品形态。用户并不怎么喜欢移动搜索产品,他们之所以还在用是因为不得不用。而以Siri为代表的语音搜索也没能真正改善移动搜索体验,只有区区18.5%的用户认为语音搜索提高了移动搜索体验。

除了通过引入新的交互模式来改善移动搜索的产品形态,搜索服务商们将搜索结果内容结构化,而非提供一堆链接。另一方面,随着互联网内容逐渐向移动应用内转移,应用内内容的搜索也开始变得越来越重要。

Google在2013年12月开始支持 应用内的内容索引(App Indexing),他们希望借此将应用内容与各个网站进行连接,使得Google搜索结果能够提供应用的Deep Link(深层链接)。在搜索结果内展示应用内的内容,需要开发者做两件事:更新应用,增加对Deep Link的支持;将应用的Deep Link提供给Google。

在中国,豌豆荚则是另一家较早涉足应用内搜索的公司,他们从2013年8月开始做应用内搜索的第一个产品——视频搜索。今年3月,豌豆荚上线了“ 应用内搜索技术协议”,在推出自己标准的同时兼容Google的App Indexing标准和Quixey的AppURL标准。他们希望合作伙伴将自己的应用内容接入到豌豆荚里,让用户能够搜索到应用内容。

Deep Link是支持应用内搜索的一个必要环节。“我们应用内搜索这个事需要有几个环节,第一件事是我们需要知道内容在哪里,因为内容都是在一个一个应用信息孤岛里面,我们需要想办法把内容从信息孤岛里面拉出来。第二步我们需要知道怎么消费这个内容,怎么消费内容就跟Deep Link有关。”豌豆荚搜索技术负责人李大海对PingWest说,“比较通俗地说, Deep Link就是描述我们怎么展现,怎么打开,怎么处理应用里面的内容。

但Deep Link仍然不够普及。根据李大海向PingWest透露的数据, 在最热门的1000款应用里,真正友好支持“调起”的应用不会超过20家。这意味着,绝大多数开发者需要对自己的应用进行修改来支持对Deep Link的支持。

“早期的时候,所有开发者更关注自己的业务,‘调起’只要能工作就好了,不太关注这个事情是不是满足规范,因为这完全是内部行为。现在的现实是, 很多开发者虽然自己的产品体验比较好,但是‘调起’这块情况非常糟糕,甚至完全不支持对外部的”调起”,有一些还会刻意的说我就不允许外部‘调起’。这会给外部‘调起’带来很大的困难,甚至产生很多无形当中的障碍。”李大海对PingWest说。

这些对“调起”并不那么在意的开发者,在制造了一大批不合乎标准的应用的同时,也给应用内搜索的实现制造了障碍。

于是,你会发现 在App Store里的应用越来越多的同时,移动搜索不管是在体验还是对应用内搜索的支持度上都没有相应地跟上。最明显的例子是,Google的App Indexing上线半年以来接入的第三方应用不足百家,豌豆荚上线应用内搜索SDK三个月以来接入的应用为30家——要知道他们收录的应用数量已经超过了150万个。

(Facebook的App Link介绍)

不仅如此, 各家的应用内搜索技术标准还处于各自为战的混乱状态。除了Google的App Indexing、Quixey的AppURL外,Facebook最近也上线了自己的标准App Links。虽然方案很多,但是应用内搜索还没有形成统一的标准,也缺少一个像W3C这样的中间机构来出面统一标准。

“传统互联网发明了hyperlink(超链接)以后才开始变得宽广起来, 但是移动互联网最早基于的是应用,信息的流动不是一开始考虑的重点。随着内容也转移到手机上,内容的流动和容易被发现变得越来越重要,信息孤岛限制了信息的自然流动。“李大海对PingWest说,“每个厂商都希望从自己的角度出发解决问题。例如,Google把网页和应用结合在一起,是因为他们更熟悉网页内容。“

为什么移动搜索产品都那么糟糕?你可以拿“标准的不统一”“开发者的不在意”和“交互模式的落后”来解释。从互联网的发明到基于超链接的万维网的出现经历了约二十年时间,但愿在移动互联网时代,我们不必经历那么长久的等待。

题图出处: shutterstock

相关阅读:

     对话豌豆荚搜索技术负责人李大海:“应用内搜索”到底有多难?

     追踪你的健康数据这件事,Google的方案也要来了

     Facebook的“去Facebook化”

     【PW晨报】易到用车出海,将入驻旧金山和纽约市

正确理解ThreadLocal

$
0
0

转自:http://www.iteye.com/topic/103804

首先,ThreadLocal 不是用来解决共享对象的多线程访问问题的,一般情况下,通过ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的。各个线程中访问的是不同的对象。 

另外,说ThreadLocal使得各线程能够保持各自独立的一个对象,并不是通过ThreadLocal.set()来实现的,而是通过每个线程中的new 对象 的操作来创建的对象,每个线程创建一个,不是什么对象的拷贝或副本。通过ThreadLocal.set()将这个新创建的对象的引用保存到各线程的自己的一个map中,每个线程都有这样一个map,执行ThreadLocal.get()时,各线程从自己的map中取出放进去的对象,因此取出来的是各自自己线程中的对象,ThreadLocal实例是作为map的key来使用的。 

如果ThreadLocal.set()进去的东西本来就是多个线程共享的同一个对象,那么多个线程的ThreadLocal.get()取得的还是这个共享对象本身,还是有并发访问问题。 

下面来看一个hibernate中典型的ThreadLocal的应用: 

Java代码   收藏代码
  1. private static final ThreadLocal threadSession = new ThreadLocal();  
  2.   
  3. public static Session getSession() throws InfrastructureException {  
  4.     Session s = (Session) threadSession.get();  
  5.     try {  
  6.         if (s == null) {  
  7.             s = getSessionFactory().openSession();  
  8.             threadSession.set(s);  
  9.         }  
  10.     } catch (HibernateException ex) {  
  11.         throw new InfrastructureException(ex);  
  12.     }  
  13.     return s;  
  14. }  


可以看到在getSession()方法中,首先判断当前线程中有没有放进去session,如果还没有,那么通过sessionFactory().openSession()来创建一个session,再将session set到线程中,实际是放到当前线程的ThreadLocalMap这个map中,这时,对于这个session的唯一引用就是当前线程中的那个ThreadLocalMap(下面会讲到),而threadSession作为这个值的key,要取得这个session可以通过threadSession.get()来得到,里面执行的操作实际是先取得当前线程中的ThreadLocalMap,然后将threadSession作为key将对应的值取出。这个session相当于线程的私有变量,而不是public的。 
显然,其他线程中是取不到这个session的,他们也只能取到自己的ThreadLocalMap中的东西。要是session是多个线程共享使用的,那还不乱套了。 
试想如果不用ThreadLocal怎么来实现呢?可能就要在action中创建session,然后把session一个个传到service和dao中,这可够麻烦的。或者可以自己定义一个静态的map,将当前thread作为key,创建的session作为值,put到map中,应该也行,这也是一般人的想法,但事实上,ThreadLocal的实现刚好相反,它是在每个线程中有一个map,而将ThreadLocal实例作为key,这样每个map中的项数很少,而且当线程销毁时相应的东西也一起销毁了,不知道除了这些还有什么其他的好处。 

总之,ThreadLocal不是用来解决对象共享访问问题的,而主要是提供了保持对象的方法和避免参数传递的方便的对象访问方式。归纳了两点: 
1。每个线程中都有一个自己的ThreadLocalMap类对象,可以将线程自己的对象保持到其中,各管各的,线程可以正确的访问到自己的对象。 
2。将一个共用的ThreadLocal静态实例作为key,将不同对象的引用保存到不同线程的ThreadLocalMap中,然后在线程执行的各处通过这个静态ThreadLocal实例的get()方法取得自己线程保存的那个对象,避免了将这个对象作为参数传递的麻烦。 

当然如果要把本来线程共享的对象通过ThreadLocal.set()放到线程中也可以,可以实现避免参数传递的访问方式,但是要注意get()到的是那同一个共享对象,并发访问问题要靠其他手段来解决。但一般来说线程共享的对象通过设置为某类的静态变量就可以实现方便的访问了,似乎没必要放到线程中。 

ThreadLocal的应用场合,我觉得最适合的是按线程多实例(每个线程对应一个实例)的对象的访问,并且这个对象很多地方都要用到。 

下面来看看ThreadLocal的实现原理(jdk1.5源码) 

Java代码   收藏代码
  1. public class ThreadLocal<T> {  
  2.     /** 
  3.      * ThreadLocals rely on per-thread hash maps attached to each thread 
  4.      * (Thread.threadLocals and inheritableThreadLocals).  The ThreadLocal 
  5.      * objects act as keys, searched via threadLocalHashCode.  This is a 
  6.      * custom hash code (useful only within ThreadLocalMaps) that eliminates 
  7.      * collisions in the common case where consecutively constructed 
  8.      * ThreadLocals are used by the same threads, while remaining well-behaved 
  9.      * in less common cases. 
  10.      */  
  11.     private final int threadLocalHashCode = nextHashCode();  
  12.   
  13.     /** 
  14.      * The next hash code to be given out. Accessed only by like-named method. 
  15.      */  
  16.     private static int nextHashCode = 0;  
  17.   
  18.     /** 
  19.      * The difference between successively generated hash codes - turns 
  20.      * implicit sequential thread-local IDs into near-optimally spread 
  21.      * multiplicative hash values for power-of-two-sized tables. 
  22.      */  
  23.     private static final int HASH_INCREMENT = 0x61c88647;  
  24.   
  25.     /** 
  26.      * Compute the next hash code. The static synchronization used here 
  27.      * should not be a performance bottleneck. When ThreadLocals are 
  28.      * generated in different threads at a fast enough rate to regularly 
  29.      * contend on this lock, memory contention is by far a more serious 
  30.      * problem than lock contention. 
  31.      */  
  32.     private static synchronized int nextHashCode() {  
  33.         int h = nextHashCode;  
  34.         nextHashCode = h + HASH_INCREMENT;  
  35.         return h;  
  36.     }  
  37.   
  38.     /** 
  39.      * Creates a thread local variable. 
  40.      */  
  41.     public ThreadLocal() {  
  42.     }  
  43.   
  44.     /** 
  45.      * Returns the value in the current thread's copy of this thread-local 
  46.      * variable.  Creates and initializes the copy if this is the first time 
  47.      * the thread has called this method. 
  48.      * 
  49.      * @return the current thread's value of this thread-local 
  50.      */  
  51.     public T get() {  
  52.         Thread t = Thread.currentThread();  
  53.         ThreadLocalMap map = getMap(t);  
  54.         if (map != null)  
  55.             return (T)map.get(this);  
  56.   
  57.         // Maps are constructed lazily.  if the map for this thread  
  58.         // doesn't exist, create it, with this ThreadLocal and its  
  59.         // initial value as its only entry.  
  60.         T value = initialValue();  
  61.         createMap(t, value);  
  62.         return value;  
  63.     }  
  64.   
  65.     /** 
  66.      * Sets the current thread's copy of this thread-local variable 
  67.      * to the specified value.  Many applications will have no need for 
  68.      * this functionality, relying solely on the {@link #initialValue} 
  69.      * method to set the values of thread-locals. 
  70.      * 
  71.      * @param value the value to be stored in the current threads' copy of 
  72.      *        this thread-local. 
  73.      */  
  74.     public void set(T value) {  
  75.         Thread t = Thread.currentThread();  
  76.         ThreadLocalMap map = getMap(t);  
  77.         if (map != null)  
  78.             map.set(this, value);  
  79.         else  
  80.             createMap(t, value);  
  81.     }  
  82.   
  83.     /** 
  84.      * Get the map associated with a ThreadLocal. Overridden in 
  85.      * InheritableThreadLocal. 
  86.      * 
  87.      * @param  t the current thread 
  88.      * @return the map 
  89.      */  
  90.     ThreadLocalMap getMap(Thread t) {  
  91.         return t.threadLocals;  
  92.     }  
  93.   
  94.     /** 
  95.      * Create the map associated with a ThreadLocal. Overridden in 
  96.      * InheritableThreadLocal. 
  97.      * 
  98.      * @param t the current thread 
  99.      * @param firstValue value for the initial entry of the map 
  100.      * @param map the map to store. 
  101.      */  
  102.     void createMap(Thread t, T firstValue) {  
  103.         t.threadLocals = new ThreadLocalMap(this, firstValue);  
  104.     }  
  105.   
  106.     .......  
  107.   
  108.     /** 
  109.      * ThreadLocalMap is a customized hash map suitable only for 
  110.      * maintaining thread local values. No operations are exported 
  111.      * outside of the ThreadLocal class. The class is package private to 
  112.      * allow declaration of fields in class Thread.  To help deal with 
  113.      * very large and long-lived usages, the hash table entries use 
  114.      * WeakReferences for keys. However, since reference queues are not 
  115.      * used, stale entries are guaranteed to be removed only when 
  116.      * the table starts running out of space. 
  117.      */  
  118.     static class ThreadLocalMap {  
  119.   
  120.     ........  
  121.   
  122.     }  
  123.   
  124. }  



可以看到ThreadLocal类中的变量只有这3个int型: 

Java代码   收藏代码
  1. private final int threadLocalHashCode = nextHashCode();  
  2. private static int nextHashCode = 0;  
  3. private static final int HASH_INCREMENT = 0x61c88647;  


而作为ThreadLocal实例的变量只有 threadLocalHashCode 这一个,nextHashCode 和HASH_INCREMENT 是ThreadLocal类的静态变量,实际上HASH_INCREMENT是一个常量,表示了连续分配的两个ThreadLocal实例的threadLocalHashCode值的增量,而nextHashCode 的表示了即将分配的下一个ThreadLocal实例的threadLocalHashCode 的值。 

可以来看一下创建一个ThreadLocal实例即new ThreadLocal()时做了哪些操作,从上面看到构造函数ThreadLocal()里什么操作都没有,唯一的操作是这句: 

Java代码   收藏代码
  1. private final int threadLocalHashCode = nextHashCode();  


那么nextHashCode()做了什么呢: 

Java代码   收藏代码
  1. private static synchronized int nextHashCode() {  
  2.     int h = nextHashCode;  
  3.     nextHashCode = h + HASH_INCREMENT;  
  4.     return h;  
  5. }  

就是将ThreadLocal类的下一个hashCode值即nextHashCode的值赋给实例的threadLocalHashCode,然后nextHashCode的值增加HASH_INCREMENT这个值。 

因此ThreadLocal实例的变量只有这个threadLocalHashCode,而且是final的,用来区分不同的ThreadLocal实例,ThreadLocal类主要是作为工具类来使用,那么ThreadLocal.set()进去的对象是放在哪儿的呢? 

看一下上面的set()方法,两句合并一下成为 

Java代码   收藏代码
  1. ThreadLocalMap map = Thread.currentThread().threadLocals;  


这个ThreadLocalMap 类是ThreadLocal中定义的内部类,但是它的实例却用在Thread类中: 

Java代码   收藏代码
  1. public class Thread implements Runnable {  
  2.     ......  
  3.   
  4.     /* ThreadLocal values pertaining to this thread. This map is maintained 
  5.      * by the ThreadLocal class. */  
  6.     ThreadLocal.ThreadLocalMap threadLocals = null;    
  7.     ......  
  8. }  



再看这句: 

Java代码   收藏代码
  1. if (map != null)  
  2.     map.set(this, value);  


也就是将该ThreadLocal实例作为key,要保持的对象作为值,设置到当前线程的ThreadLocalMap 中,get()方法同样大家看了代码也就明白了,ThreadLocalMap 类的代码太多了,我就不帖了,自己去看源码吧。 

写了这么多,也不知讲明白了没有,有什么不当的地方还请大家指出来。



已有 0人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



Redis Sentinel的配置和使用

$
0
0

使用Jedis的ShardedJedis做Redis集群

Redis主从系统,除了做数据冗余,开可以做高可用性灾备。Reids提供了Sentinel工具来监控各Master的状态,如果Master异常,则会做主从切换,将slave作为master,将master作为slave。主从切换之后,master_redis.conf、slave_redis.conf和sentinel.conf的内容都会发生改变。master_redis.conf中会多了一句slaveof的配置,sentinel.conf的监控目标也随之调换,这一点要注意。

如果要监控两个redis实例,可以进行如下配置 sentinel.conf:


port 26379
daemonize yes
logfile "/var/log/redis/sentinel.log"

#master 7000
sentinel monitor master-7000 127.0.0.1 7000 2
sentinel down-after-milliseconds master-7000 30000
sentinel parallel-syncs master-7000 1
sentinel failover-timeout master-7000 180000

#master 7001
sentinel monitor master-7001 127.0.0.1 7001 1
sentinel down-after-milliseconds master-7001 30000
sentinel parallel-syncs master-7001 1
sentinel failover-timeout master-7001 180000

启动:

redis-sentinel sentinel.conf

[43023] 17 Jun 14:49:55.189 # Sentinel runid is 7b2f26a9633b9ec07f76ab6efba7508c235bf75d
[43023] 17 Jun 14:49:55.189 # +monitor master master-7000 1.0.0.1 7000 quorum 1
[43023] 17 Jun 14:49:55.189 # +monitor master master-7001 1.0.0.1 7001 quorum 1
[43023] 17 Jun 14:49:55.190 * +slave slave 1.0.0.2:7000 1.0.0.2 7000 @ master-7000 1.0.0.1 7000
[43023] 17 Jun 14:49:55.191 * +slave slave 1.0.0.2:7001 1.0.0.2 7001 @ master-7001 1.0.0.1 7001
[43023] 17 Jun 14:52:55.815 * +fix-slave-config slave 1.0.0.2:7000 1.0.0.2 7000 @ master-7000 1.0.0.1 7000
[43023] 17 Jun 14:52:55.816 * +fix-slave-config slave 1.0.0.2:7001 1.0.0.2 7001 @ master-7001 1.0.0.1 7001

 

需要注意的几个地方:

1、注意第一行最后一个2,意思是当有两个sentinel实例同时检测到redis异常时,才会有反应。

2、主从切换后,redis.conf、sentinel.conf内容都会改变,主要还想要原来的主从架构,要再修改配置文件;

3、master挂掉,sentinel已经选择了新的master,但是还没有将其改成master,但是已经将old master改成了slave。那么这时候如果重启old master,就会处于无主状态。所以一方面要等sentinel稳定后再启动old master,或者重新人工修改配置文件,重新启动集群。

4、sentinel只是在server端做主从切换,app端要自己开发,例如Jedis库的SentinelJedis,能够监控sentinel的状态。这样才能完整的实现高可用性的主从切换。

您可能也喜欢:

Redis Cluster的安装和配置(beta-6)

Redis集群增删节点的数据迁移问题

使用Jedis的ShardedJedis做Redis集群

Redis在新浪微博中的应用

Jedis sharding
无觅

mysql数据库的安装以及常见优化设置

$
0
0

原文请详见: http://www.ucai.cn/blogdetail/7036?mid=1&f=5

可以在线运行查看效果哦!


        本文根据优才网课程整理,面向web开发者,内容以实用为主,专业DBA可以绕行。

        如果你在大公司,可能有专门的DBA来做这些事情,如果你在一个小公司当架构师或者技术总监,或者你自己创业,那DBA的活你也得干了。咱们来讲一下基本的mysql安装和优化。

 

一: MYSQL安装和基本配置

在linux上安装,可以用包管理工具来安装,比较简单:

RedHat 系列:yum -yinstall  mysql mysql-server 

Debian系列:sudo apt-getinstall  mysql mysql-server

 

安装之后不知道mysql装到哪了怎么办,用whereis mysql 命令来找一下。先找到mysql的默认配置文件。一般来说,安装后有这么几个备选的配置:my-huge.cnf my-innodb-heavy-4G.cnf my-large.cnf my-medium.cnf my-small.cnf

 

就2014年的机器配置来说,咱们直接用my-huge.cnf。把my-huge.cnf 复制到/etc/下,改名my.cnf。配置文件就有了,然后启动mysql: /etc/init.d/mysqld start 。

 

安装之后默认的帐号是root, 密码为空。咱们要做的第一件事是改root密码。

进入mysql:mysql -uroot -p

选择数据库: use mysql

改密码: UPDATE user SET Password = PASSWORD('xxxx') WHERE user = 'root';

刷新权限: FLUSH PRIVILEGES;

 

现在数据库装好了,帐号和权限也设置了,是不是就可以使用了呢,还要检查几个配置。打开配置文件 vim /etc/my.cnf

skip-networking 要关闭。

bind-address = 127.0.0.1 这一行要关闭或者修改成允许的IP

skip-name-resolve 禁止dns解析,只能用IP连,这个可以打开。

       如果你的mysql经过一段时间运行,挺过了访问高峰,咱们再来检查一下配置是否合适。下面说的配置,都必须是在运行一段时间后检查才有意义。如果刚启动没多久,mysql的运行状态没有代表性,不能作为参考。检查配置参数也没意义。

 

二:连接数(connection)配置


       max_connections 可以设置最大并发连接数。当MySql的并发连接达到这个设定值时,新的连接将会被拒绝(“Can not connect to MySQL server. Too many connections”-mysql 1040错误,)。当发现MySql有能力处理更多的并发的时候, 建议调大这个值,相应给服务器带来更高的负载(CPU/IO/内存)。

 

查看设置的最大连接是多少:

mysql> show variables like 'max_connections';

+-----------------+-------+

| Variable_name   | Value |

+-----------------+-------+

| max_connections | 151   | 允许的最大连接数

+-----------------+-------+

 

 看当前连接数:showstatus like 'threads_connected';

最大连接数: show status like "max_used_connections";

如果max_used_connections已经接近 max_connections了,就说明max_connections太小。不合适了。

 

还有一些跟连接数相关的配置:

back_log=50

MySQL能暂存的连接数量。当主要MySQL线程在一个很短时间内得到非常多的连接请求,这就起作用。如果MySQL的连接数据达到max_connections时,新来的请求将会被存在堆栈中,以等待某一连接释放资源,该堆栈的数量即back_log,如果等待连接的数量超过back_log,将不被授予连接资源。

        back_log值指出在MySQL暂时停止回答新请求之前的短时间内有多少个请求可以被存在堆栈中。只有如果期望在一个短时间内有很多连接,你需要增加它,换句话说,这值对到来的TCP/IP连接的侦听队列的大小。

        不同的操作系统在这个队列大小上有它自己的限制。试图设定back_log高于你的操作系统的限制将是无效的。默认值为50。对于Linux系统推荐设置为小于512的整数。

如果链接数超过max_connections+back_log ,才会出错。

max_connect_errors=10

        当客户端连接服务端超时(超过connect_timeout), 服务端就会给这个客户端记录一次error,当出错的次数达到max_connect_errors的时候,这个客户端就会被锁定。除非执行FLUSH HOSTS命令。

connect_timeout=5

连接超时的秒数

 

三:查询缓存(query_cache)配置

 

       查询缓存就是内存中的一块存储区域,其存储了用户的SQL文本以及相关的查询结果。通常情况下,用户下次查询时,如果所使用的SQL文本是相同的,并且自从上次查询后,相关的纪录没有被更新过,此时数据库就直接采用缓存中的内容。从内存中读取要比从硬盘上速度要快好几百倍。MYSQL的查询缓存用于缓存select查询结果,并在下次接收到同样的查询请求时,不再执行实际查询处理而直接返回结果,有这样的查询缓存能提高查询的速度,使查询性能得到优化.

       要使用缓存,有几个条件:一是所采用的SQL语句是相同的。每次查询的语句不一样,肯定不能用到缓存。比如语句里带当前秒数 where ctime > xxx;二是表数据没有改过。没有改过结构,没有update,insert;三、客户端与服务器的默认字符集得一样。所以可以看出,要利用好缓存,有大量的相同的查询,而很少改变表里的数据,否则没有必要使用此功能。

 

查看查询缓存的设置:

SHOW VARIABLES LIKE '%query_cache%';
+------------------------------+----------+
| Variable_name                | Value    |
+------------------------------+----------+
| have_query_cache             | YES      |
| query_cache_limit            | 1048576  |  如果单个查询结果大于这个值,则不Cache
| query_cache_min_res_unit     | 4096    |  每次给QC结果分配内存的大小
| query_cache_size             | 33554432 |
| query_cache_type             | ON       |
| query_cache_wlock_invalidate | OFF      |
+------------------------------+----------+

 

query_cache_type=1

        如果设置为1,将会缓存所有的结果,除非你的select语句使用SQL_NO_CACHE禁用了查询缓存。如果设置为2,则只缓存在select语句中通过SQL_CACHE指定需要缓存的查询。query_cache_size 默认是32M,太小了,可调到128M或者256M。 可以通过Qcache_lowmem_prunes变量的值来检查是否当前的值满足你目前系统的负载。

        query_cache_size的工作原理:一个SELECT查询在DB中工作后,DB会把该语句缓存下来,当同样的一个SQL再次来到DB里调用时,DB在该表没发生变化的情况下把结果从缓存中返回给Client。这里有一个关建点,就是DB在利用Query_cache工作时,要求该语句涉及的表在这段时间内没有发生变更。那如果该表在发生变更时,Query_cache里的数据又怎么处理呢?

        首先要把Query_cache和该表相关的语句全部置为失效,然后在写入更新。那么如果Query_cache非常大,该表的查询结构又比较多,查询语句失效也慢,一个更新或是Insert就会很慢,这样看到的就是Update或是Insert怎么这么慢了。所以在数据库写入量或是更新量也比较大的系统,该参数不适合分配过大。而且在高并发,写入量大的系统,建系把该功能禁掉。 

       Qcache_lowmem_prunes可以检查是否设置的太小。query_cache_limit 默认是1M,根据你的常用查询的数据结果大小来定。如果返回的数据小,可以设置小一点。设置值大对大数据查询有好处,但如果你的查询都是小数据查询,就容易造成内存碎片和浪费。

 

查看缓存使用效果如何:

 showstatus like '%Qcache%';

 +-------------------------+----------+

| Variable_name           | Value    |

+-------------------------+----------+

| Qcache_free_blocks      | 160     | 目前还处于空闲状态的 Query Cache中内存 Block 数目,数目大说明可能有碎片。FLUSH QUERY CACHE会对缓存中的碎片进行整理,从而得到一个空闲块。

| Qcache_free_memory      | 23147296 | 缓存中的空闲内存总量。

| Qcache_hits             | 52349    | 缓存命中次数。

| Qcache_inserts          | 8827     | 缓存失效次数。

| Qcache_lowmem_prunes    | 0       | 缓存出现内存不足并且必须要进行清理以便为更多查询提供空间的次数。这个数字最好长时间来看;如果这个数字在不断增长,就表示可能碎片非常严重,或者内存很少。

| Qcache_not_cached       | 2446     | 没有被cache和不适合进行缓存的查询的数量,通常是由于这些查询不是SELECT语句以及由于query_cache_type设置的不会被Cache的查询。show,use,desc

| Qcache_queries_in_cache | 5234     | 当前被cache的SQL数量。

| Qcache_total_blocks     | 10796   | 缓存中块的数量。

+-------------------------+----------+

 

show global status like 'Com_select'; 

+---------------+-------+

| Variable_name | Value |

+---------------+-------+

| Com_select    | 12592 | com_select 变量记录的是无缓存的查询次数+错误查询+权限检查查询。

+---------------+-------+

 

Mysql的查询缓存命中率没有官方算法,只有前人的经验总结

命中率≈ qcache_hits / (qcache_hits + com_select)

 

缓存碎片率 = Qcache_free_blocks / Qcache_total_blocks * 100% ,如果碎片率太高,20% ,可以FLUSHQUERY CACHE整理缓存碎片,或者试试减小query_cache_min_res_unit,如果你的查询都是小数据量的话。

 

查询缓存利用率 = (query_cache_size – Qcache_free_memory) / query_cache_size * 100%

#查询缓存利用率在25%以下的话说明query_cache_size设置的过大,可适当减小;查询缓存利用率在80%以上而且Qcache_lowmem_prunes> 50的话说明query_cache_size可能有点小,要不就是碎片太多。

如何提高命中率:

1、字符集相同;2、SQL语句尽量固定(SQL语句避免随机数,秒数等);3、 加大缓存空间; 4、适当分表,动静分离。


四、临时表缓存(tmp_table_size)配置

 

       mysql进行复杂查询或者 做高级GROUP BY操作的时候,系统为了优化查询,生成一些临时表。通过设置tmp_table_size选项来设置临时表占用空间的大小。我们使用explain分析SQL,如果在Extra列看到Using temporary就意味着使用了临时表。

        MySQL临时表分为“内存临时表”和“磁盘临时表”,其中内存临时表使用MySQL的MEMORY存储引擎,磁盘临时表使用MySQL的MyISAM存储引擎。一般情况下,MySQL会先创建内存临时表,但内存临时表超过配置指定的值后,MySQL会将内存临时表导出到磁盘临时表。临时表将在你连接MySQL期间存在。当你断开时,MySQL将自动删除表并释放所用的空间。

 

mysql> SHOW VARIABLES LIKE'%tmp_table_size%';
+----------------+----------+
| Variable_name  | Value   |
+----------------+----------+
| tmp_table_size | 33554432 | 
+----------------+----------+
1 row in set (0.00 sec)
mysql> show global status like'created_tmp%';
+-------------------------+----------+
| Variable_name           | Value    |
+-------------------------+----------+

| Created_tmp_disk_tables | 690421   |  服务器执行语句时在硬盘上自动创建的临时表的数量;

| Created_tmp_files       | 755473   | mysqld已经创建的临时文件的数量;

| Created_tmp_tables      | 14372959 | 服务器执行语句时自动创建的内存中的临时表的数量。如果Created_tmp_disk_tables较大,你可能要增加tmp_table_size值使临时 表基于内存而不基于硬盘;

+-------------------------+----------+

 

       每次创建临时表,Created_tmp_tables增加,如果临时表大小超过tmp_table_size,则是在磁盘上创建临时表,Created_tmp_disk_tables也增加,Created_tmp_files表示MySQL服务创建的临时文件文件数,比较理想的配置是:Created_tmp_disk_tables /Created_tmp_tables * 100% <= 25%

比如上面的服务器Created_tmp_disk_tables / Created_tmp_tables * 100% =1.20%,应该相当好了。默认大小是 32M,可调到64-256最佳,线程独占,太大可能内存不够I/O堵塞。

        跟临时表相关的另一配置是max_heap_table_size ,用户可以创建的独立的内存表所允许的最大容量.这个变量不适用与用户创建的内存表(memory table).SHOW VARIABLES LIKE'%max_heap_table_size%'; 咱们希望临时表是放到内存的。所以这个值设置的临时表缓存的空间一样就行。

(实际起限制作用的是tmp_table_size和max_heap_table_size的最小值。)如果临时表大于这两个的任何一个,都会存硬盘缓存:自动地把它转化为基于磁盘的MyISAM表,存储在指定的tmpdir目录下。

 

五、索引缓冲区(key_buffer_size)配置

 

        key_buffer_size是对MyISAM表性能影响最大的一个参数.key_buffer_size指定索引缓冲区的大小,它决定索引处理的速度,尤其是索引读的速度。通过检查状态值Key_read_requests和Key_reads,可以知道key_buffer_size设置是否合理。比例key_reads /key_read_requests应该尽可能的低,至少是1:100,1:1000更好。

       key_buffer_size只对MyISAM表起作用。即使你不使用MyISAM表,但是内部的临时磁盘表是MyISAM表,也要使用该值。可以使用检查状态值created_tmp_disk_tables得知详情。


mysql> show variables like'key_buffer_size';
+-----------------+----------+
| Variable_name   | Value   |
+-----------------+----------+
| key_buffer_size | 67108864 | 索引缓冲区的大小
+-----------------+----------+
show variables like 'key_cache_block_size';
mysql> show global status like 'key%';
+------------------------+------------+
| Variable_name          | Value      |
+------------------------+------------+

| Key_blocks_not_flushed | 0          |索引缓存内已经更改,但还没有清空到硬盘上的索引的数据块数量。

| Key_blocks_unused      | 0          | 索引缓存内未使用的块数量。你可以使用该值来确定使用了多少键缓存

| Key_blocks_used        | 53585      | 索引缓存内使用的块数量。该值为高水平线标记,说明已经同时最多使用了多少块。

| Key_read_requests      | 4952122733 | 一共有XXX个索引读取请求,

| Key_reads              | 11879      | 索引读取请求在内存中没有找到,直接从硬盘读取索引

| Key_write_requests     | 10508455   | 将索引的数据块写入缓存的请求数。

| Key_writes             | 6042774    |  将索引向硬盘写入数据块的物理写操作的次数。

+------------------------+------------+


比例key_reads /key_read_requests应该尽可能的低,至少是1:100,1:1000更好;

如果Key_reads太大,则应该把my.cnf中key_buffer_size变大.可以用Key_reads/Key_read_requests计算出cache失败率;

Key_writes/Key_write_requests:比例接近1较好。

       别人的经验是内存在4GB左右的服务器该参数可设置为384M或512M。可以自己算一下自己数据库的索引文件大小。注意:该参数值设置的过大反而会是服务器整体效率降低!

 

Cache命中比率:

1 - Key_reads / Key_read_requests 

Key buffer的使用率

100 – ( (Key_blocks_unused *key_cache_block_size) * 100 / key_buffer_size )


作者:bianbiancheng 发表于2014-6-17 17:05:54 原文链接
阅读:56 评论:0 查看评论

Java编码规范(Google版本)

$
0
0
【引用说明】
本文摘自:http://hawstein.com/posts/google-java-style.html

【前言】
这份文档是Google Java编程风格规范的完整定义。当且仅当一个Java源文件符合此文档中的规则, 我们才认为它符合Google的Java编程风格。
与其它的编程风格指南一样,这里所讨论的不仅仅是编码格式美不美观的问题,同时也讨论一些约定及编码标准。然而,这份文档主要侧重于我们所普遍遵循的规则,对于那些不是明确强制要求的,我们尽量避免提供意见。

1.1 术语说明
在本文档中,除非另有说明:
术语class可表示一个普通类,枚举类,接口或是annotation类型(@interface)
术语comment只用来指代实现的注释(implementation comments),我们不使用“documentation comments”一词,而是用Javadoc。
其他的术语说明会偶尔在后面的文档出现。

1.2 指南说明
本文档中的示例代码并不作为规范。也就是说,虽然示例代码是遵循Google编程风格,但并不意味着这是展现这些代码的唯一方式。示例中的格式选择不应该被强制定为规则。

【源文件基础】
2.1 文件名
源文件以其最顶层的类名来命名,大小写敏感,文件扩展名为.java。

2.2 文件编码:UTF-8
源文件编码格式为UTF-8。

2.3 特殊字符
2.3.1 空白字符
除了行结束符序列,ASCII水平空格字符(0x20,即空格)是源文件中唯一允许出现的空白字符,这意味着:
所有其它字符串中的空白字符都要进行转义。
制表符不用于缩进。

2.3.2 特殊转义序列
对于具有特殊转义序列的任何字符(\b, \t, \n, \f, \r, \“, \‘及\),我们使用它的转义序列,而不是相应的八进制(比如\012)或Unicode(比如\u000a)转义。

2.3.3 非ASCII字符
对于剩余的非ASCII字符,是使用实际的Unicode字符(比如∞),还是使用等价的Unicode转义符(比如\u221e),取决于哪个能让代码更易于阅读和理解。
Tip: 在使用Unicode转义符或是一些实际的Unicode字符时,建议做些注释给出解释,这有助于别人阅读和理解。

例如:
String unitAbbrev = "μs";                                 | 赞,即使没有注释也非常清晰
String unitAbbrev = "\u03bcs"; // "μs"                    | 允许,但没有理由要这样做
String unitAbbrev = "\u03bcs"; // Greek letter mu, "s"    | 允许,但这样做显得笨拙还容易出错
String unitAbbrev = "\u03bcs";                            | 很糟,读者根本看不出这是什么
return '\ufeff' + content; // byte order mark             | Good,对于非打印字符,使用转义,并在必要时写上注释
Tip: 永远不要由于害怕某些程序可能无法正确处理非ASCII字符而让你的代码可读性变差。当程序无法正确处理非ASCII字符时,它自然无法正确运行, 你就会去fix这些问题的了。(言下之意就是大胆去用非ASCII字符,如果真的有需要的话)

【源文件结构】
一个源文件包含(按顺序地):

许可证或版权信息(如有需要)
package语句
import语句
一个顶级类(只有一个)
以上每个部分之间用一个空行隔开。

3.1 许可证或版权信息
如果一个文件包含许可证或版权信息,那么它应当被放在文件最前面。

3.2 package语句
package语句不换行,列限制(4.4节)并不适用于package语句。(即package语句写在一行里)

3.3 import语句
3.3.1 import不要使用通配符
即,不要出现类似这样的import语句:import java.util.*;

3.3.2 不要换行
import语句不换行,列限制(4.4节)并不适用于import语句。(每个import语句独立成行)

3.3.3 顺序和间距
import语句可分为以下几组,按照这个顺序,每组由一个空行分隔:

所有的静态导入独立成组
com.google imports(仅当这个源文件是在com.google包下)
第三方的包。每个顶级包为一组,字典序。例如:android, com, junit, org, sun
java imports
javax imports
组内不空行,按字典序排列。

3.4 类声明
3.4.1 只有一个顶级类声明
每个顶级类都在一个与它同名的源文件中(当然,还包含.java后缀)。
例外:package-info.java,该文件中可没有package-info类。

3.4.2 类成员顺序
类的成员顺序对易学性有很大的影响,但这也不存在唯一的通用法则。不同的类对成员的排序可能是不同的。 最重要的一点,每个类应该以某种逻辑去排序它的成员,维护者应该要能解释这种排序逻辑。比如, 新的方法不能总是习惯性地添加到类的结尾,因为这样就是按时间顺序而非某种逻辑来排序的。

3.4.2.1 重载:永不分离
当一个类有多个构造函数,或是多个同名方法,这些函数/方法应该按顺序出现在一起,中间不要放进其它函数/方法。

【格式】
术语说明:块状结构(block-like construct)指的是一个类,方法或构造函数的主体。需要注意的是,数组初始化中的初始值可被选择性地视为块状结构(4.8.3.1节)。

4.1 大括号
4.1.1 使用大括号(即使是可选的)
大括号与if, else, for, do, while语句一起使用,即使只有一条语句(或是空),也应该把大括号写上。

4.1.2 非空块:K & R 风格
对于非空块和块状结构,大括号遵循Kernighan和Ritchie风格 (Egyptian brackets):

左大括号前不换行
左大括号后换行
右大括号前换行
如果右大括号是一个语句、函数体或类的终止,则右大括号后换行; 否则不换行。例如,如果右大括号后面是else或逗号,则不换行。

示例:
return new MyClass() {
  @Override public void method() {
    if (condition()) {
      try {
        something();
      } catch (ProblemException e) {
        recover();
      }
    }
  }
};
4.8.1节给出了enum类的一些例外。

4.1.3 空块:可以用简洁版本
一个空的块状结构里什么也不包含,大括号可以简洁地写成{},不需要换行。例外:如果它是一个多块语句的一部分(if/else 或 try/catch/finally) ,即使大括号内没内容,右大括号也要换行。

示例:
void doNothing() {}

4.2 块缩进:2个空格
每当开始一个新的块,缩进增加2个空格,当块结束时,缩进返回先前的缩进级别。缩进级别适用于代码和注释。(见4.1.2节中的代码示例)

4.3 一行一个语句
每个语句后要换行。

4.4 列限制:80或100
一个项目可以选择一行80个字符或100个字符的列限制,除了下述例外,任何一行如果超过这个字符数限制,必须自动换行。

例外:
不可能满足列限制的行(例如,Javadoc中的一个长URL,或是一个长的JSNI方法参考)。
package和import语句(见3.2节和3.3节)。
注释中那些可能被剪切并粘贴到shell中的命令行。

4.5 自动换行
术语说明:一般情况下,一行长代码为了避免超出列限制(80或100个字符)而被分为多行,我们称之为自动换行(line-wrapping)。
我们并没有全面,确定性的准则来决定在每一种情况下如何自动换行。很多时候,对于同一段代码会有好几种有效的自动换行方式。
Tip: 提取方法或局部变量可以在不换行的情况下解决代码过长的问题(是合理缩短命名长度吧)

4.5.1 从哪里断开
自动换行的基本准则是:更倾向于在更高的语法级别处断开。
如果在非赋值运算符处断开,那么在该符号前断开(比如+,它将位于下一行)。注意:这一点与Google其它语言的编程风格不同(如C++和JavaScript)。 这条规则也适用于以下“类运算符”符号:点分隔符(.),类型界限中的&(<T extends Foo & Bar>),catch块中的管道符号(catch (FooException | BarException e)
如果在赋值运算符处断开,通常的做法是在该符号后断开(比如=,它与前面的内容留在同一行)。这条规则也适用于foreach语句中的分号。
方法名或构造函数名与左括号留在同一行。
逗号(,)与其前面的内容留在同一行。

4.5.2 自动换行时缩进至少+4个空格
自动换行时,第一行后的每一行至少比第一行多缩进4个空格(注意:制表符不用于缩进。见2.3.1节)。
当存在连续自动换行时,缩进可能会多缩进不只4个空格(语法元素存在多级时)。一般而言,两个连续行使用相同的缩进当且仅当它们开始于同级语法元素。
第4.6.3水平对齐一节中指出,不鼓励使用可变数目的空格来对齐前面行的符号。

4.6 空白
4.6.1 垂直空白
以下情况需要使用一个空行:

类内连续的成员之间:字段,构造函数,方法,嵌套类,静态初始化块,实例初始化块。
例外:两个连续字段之间的空行是可选的,用于字段的空行主要用来对字段进行逻辑分组。
在函数体内,语句的逻辑分组间使用空行。
类内的第一个成员前或最后一个成员后的空行是可选的(既不鼓励也不反对这样做,视个人喜好而定)。
要满足本文档中其他节的空行要求(比如3.3节:import语句)
多个连续的空行是允许的,但没有必要这样做(我们也不鼓励这样做)。

4.6.2 水平空白
除了语言需求和其它规则,并且除了文字,注释和Javadoc用到单个空格,单个ASCII空格也出现在以下几个地方:

分隔任何保留字与紧随其后的左括号(()(如if, for catch等)。
分隔任何保留字与其前面的右大括号(})(如else, catch)。
在任何左大括号前({),两个例外:
@SomeAnnotation({a, b})(不使用空格)。
String[][] x = foo;(大括号间没有空格,见下面的Note)。
在任何二元或三元运算符的两侧。这也适用于以下“类运算符”符号:
类型界限中的&(<T extends Foo & Bar>)。
catch块中的管道符号(catch (FooException | BarException e)。
foreach语句中的分号。
在, : ;及右括号())后
如果在一条语句后做注释,则双斜杠(//)两边都要空格。这里可以允许多个空格,但没有必要。
类型和变量之间:List list。
数组初始化中,大括号内的空格是可选的,即new int[] {5, 6}和new int[] { 5, 6 }都是可以的。
Note:这个规则并不要求或禁止一行的开关或结尾需要额外的空格,只对内部空格做要求。

4.6.3 水平对齐:不做要求
术语说明:水平对齐指的是通过增加可变数量的空格来使某一行的字符与上一行的相应字符对齐。
这是允许的(而且在不少地方可以看到这样的代码),但Google编程风格对此不做要求。即使对于已经使用水平对齐的代码,我们也不需要去保持这种风格。

以下示例先展示未对齐的代码,然后是对齐的代码:
private int x; // this is fine
private Color color; // this too

private int   x;      // permitted, but future edits
private Color color;  // may leave it unaligned

Tip:对齐可增加代码可读性,但它为日后的维护带来问题。考虑未来某个时候,我们需要修改一堆对齐的代码中的一行。 这可能导致原本很漂亮的对齐代码变得错位。很可能它会提示你调整周围代码的空白来使这一堆代码重新水平对齐(比如程序员想保持这种水平对齐的风格), 这就会让你做许多的无用功,增加了reviewer的工作并且可能导致更多的合并冲突。

4.7 用小括号来限定组:推荐
除非作者和reviewer都认为去掉小括号也不会使代码被误解,或是去掉小括号能让代码更易于阅读,否则我们不应该去掉小括号。 我们没有理由假设读者能记住整个Java运算符优先级表。

4.8 具体结构
4.8.1 枚举类
枚举常量间用逗号隔开,换行可选。
没有方法和文档的枚举类可写成数组初始化的格式:
private enum Suit { CLUBS, HEARTS, SPADES, DIAMONDS }
由于枚举类也是一个类,因此所有适用于其它类的格式规则也适用于枚举类。

4.8.2 变量声明
4.8.2.1 每次只声明一个变量
不要使用组合声明,比如int a, b;。

4.8.2.2 需要时才声明,并尽快进行初始化
不要在一个代码块的开头把局部变量一次性都声明了(这是c语言的做法),而是在第一次需要使用它时才声明。 局部变量在声明时最好就进行初始化,或者声明后尽快进行初始化。

4.8.3 数组
4.8.3.1 数组初始化:可写成块状结构
数组初始化可以写成块状结构,比如,下面的写法都是OK的:

new int[] {
  0, 1, 2, 3
}

new int[] {
  0,
  1,
  2,
  3
}

new int[] {
  0, 1,
  2, 3
}

new int[]
    {0, 1, 2, 3}

4.8.3.2 非C风格的数组声明
中括号是类型的一部分:String[] args, 而非String args[]。

4.8.4 switch语句
术语说明:switch块的大括号内是一个或多个语句组。每个语句组包含一个或多个switch标签(case FOO:或default:),后面跟着一条或多条语句。

4.8.4.1 缩进
与其它块状结构一致,switch块中的内容缩进为2个空格。
每个switch标签后新起一行,再缩进2个空格,写下一条或多条语句。

4.8.4.2 Fall-through:注释
在一个switch块内,每个语句组要么通过break, continue, return或抛出异常来终止,要么通过一条注释来说明程序将继续执行到下一个语句组, 任何能表达这个意思的注释都是OK的(典型的是用// fall through)。这个特殊的注释并不需要在最后一个语句组(一般是default)中出现。

示例:
switch (input) {
  case 1:
  case 2:
    prepareOneOrTwo();
    // fall through
  case 3:
    handleOneTwoOrThree();
    break;
  default:
    handleLargeNumber(input);
}

4.8.4.3 default的情况要写出来
每个switch语句都包含一个default语句组,即使它什么代码也不包含。

4.8.5 注解(Annotations)
注解紧跟在文档块后面,应用于类、方法和构造函数,一个注解独占一行。这些换行不属于自动换行(第4.5节,自动换行),因此缩进级别不变。

例如:
@Override
@Nullable
public String getNameIfPresent() { ... }
例外:单个的注解可以和签名的第一行出现在同一行。

例如:
@Override public int hashCode() { ... }
应用于字段的注解紧随文档块出现,应用于字段的多个注解允许与字段出现在同一行。

例如:
@Partial @Mock DataLoader loader;
参数和局部变量注解没有特定规则。

4.8.6 注释
4.8.6.1 块注释风格
块注释与其周围的代码在同一缩进级别。它们可以是/* ... */风格,也可以是// ...风格。对于多行的/* ... */注释,后续行必须从*开始, 并且与前一行的*对齐。以下示例注释都是OK的。

/*
* This is          // And so           /* Or you can
* okay.            // is this.          * even do this. */
*/
注释不要封闭在由星号或其它字符绘制的框架里。
Tip:在写多行注释时,如果你希望在必要时能重新换行(即注释像段落风格一样),那么使用/* ... */。

4.8.7 Modifiers
类和成员的modifiers如果存在,则按Java语言规范中推荐的顺序出现。
public protected private abstract static final transient volatile synchronized native strictfp

【命名约定】
5.1 对所有标识符都通用的规则
标识符只能使用ASCII字母和数字,因此每个有效的标识符名称都能匹配正则表达式\w+。
在Google其它编程语言风格中使用的特殊前缀或后缀,如name_, mName, s_name和kName,在Java编程风格中都不再使用。

5.2 标识符类型的规则
5.2.1 包名
包名全部小写,连续的单词只是简单地连接起来,不使用下划线。

5.2.2 类名
类名都以UpperCamelCase风格编写。
类名通常是名词或名词短语,接口名称有时可能是形容词或形容词短语。现在还没有特定的规则或行之有效的约定来命名注解类型。
测试类的命名以它要测试的类的名称开始,以Test结束。例如,HashTest或HashIntegrationTest。

5.2.3 方法名
方法名都以lowerCamelCase风格编写。
方法名通常是动词或动词短语。
下划线可能出现在JUnit测试方法名称中用以分隔名称的逻辑组件。一个典型的模式是:test<MethodUnderTest>_<state>,例如testPop_emptyStack。 并不存在唯一正确的方式来命名测试方法。

5.2.4 常量名
常量名命名模式为CONSTANT_CASE,全部字母大写,用下划线分隔单词。那,到底什么算是一个常量?
每个常量都是一个静态final字段,但不是所有静态final字段都是常量。在决定一个字段是否是一个常量时, 考虑它是否真的感觉像是一个常量。例如,如果任何一个该实例的观测状态是可变的,则它几乎肯定不会是一个常量。 只是永远不打算改变对象一般是不够的,它要真的一直不变才能将它示为常量。

// Constants
static final int NUMBER = 5;
static final ImmutableList<String> NAMES = ImmutableList.of("Ed", "Ann");
static final Joiner COMMA_JOINER = Joiner.on(',');  // because Joiner is immutable
static final SomeMutableType[] EMPTY_ARRAY = {};
enum SomeEnum { ENUM_CONSTANT }

// Not constants
static String nonFinal = "non-final";
final String nonStatic = "non-static";
static final Set<String> mutableCollection = new HashSet<String>();
static final ImmutableSet<SomeMutableType> mutableElements = ImmutableSet.of(mutable);
static final Logger logger = Logger.getLogger(MyClass.getName());
static final String[] nonEmptyArray = {"these", "can", "change"};
这些名字通常是名词或名词短语。

5.2.5 非常量字段名
非常量字段名以lowerCamelCase风格编写。
这些名字通常是名词或名词短语。

5.2.6 参数名
参数名以lowerCamelCase风格编写。
参数应该避免用单个字符命名。

5.2.7 局部变量名
局部变量名以lowerCamelCase风格编写,比起其它类型的名称,局部变量名可以有更为宽松的缩写。
虽然缩写更宽松,但还是要避免用单字符进行命名,除了临时变量和循环变量。
即使局部变量是final和不可改变的,也不应该把它示为常量,自然也不能用常量的规则去命名它。

5.2.8 类型变量名
类型变量可用以下两种风格之一进行命名:
单个的大写字母,后面可以跟一个数字(如:E, T, X, T2)。
以类命名方式(5.2.2节),后面加个大写的T(如:RequestT, FooBarT)。

5.3 驼峰式命名法(CamelCase)
驼峰式命名法分大驼峰式命名法(UpperCamelCase)和小驼峰式命名法(lowerCamelCase)。 有时,我们有不只一种合理的方式将一个英语词组转换成驼峰形式,如缩略语或不寻常的结构(例如"IPv6"或"iOS")。Google指定了以下的转换方案。
名字从散文形式(prose form)开始:
把短语转换为纯ASCII码,并且移除任何单引号。例如:"Müller’s algorithm"将变成"Muellers algorithm"。
把这个结果切分成单词,在空格或其它标点符号(通常是连字符)处分割开。
推荐:如果某个单词已经有了常用的驼峰表示形式,按它的组成将它分割开(如"AdWords"将分割成"ad words")。 需要注意的是"iOS"并不是一个真正的驼峰表示形式,因此该推荐对它并不适用。
现在将所有字母都小写(包括缩写),然后将单词的第一个字母大写:
每个单词的第一个字母都大写,来得到大驼峰式命名。
除了第一个单词,每个单词的第一个字母都大写,来得到小驼峰式命名。
最后将所有的单词连接起来得到一个标识符。

示例:
Prose form                Correct               Incorrect
------------------------------------------------------------------
"XML HTTP request"        XmlHttpRequest        XMLHTTPRequest
"new customer ID"         newCustomerId         newCustomerID
"inner stopwatch"         innerStopwatch        innerStopWatch
"supports IPv6 on iOS?"   supportsIpv6OnIos     supportsIPv6OnIOS
"YouTube importer"        YouTubeImporter
                          YoutubeImporter*
加星号处表示可以,但不推荐。

Note:在英语中,某些带有连字符的单词形式不唯一。例如:"nonempty"和"non-empty"都是正确的,因此方法名checkNonempty和checkNonEmpty也都是正确的。

【编程实践】
6.1 @Override:能用则用
只要是合法的,就把@Override注解给用上。

6.2 捕获的异常:不能忽视
除了下面的例子,对捕获的异常不做响应是极少正确的。(典型的响应方式是打印日志,或者如果它被认为是不可能的,则把它当作一个AssertionError重新抛出。)
如果它确实是不需要在catch块中做任何响应,需要做注释加以说明(如下面的例子)。

try {
  int i = Integer.parseInt(response);
  return handleNumericResponse(i);
} catch (NumberFormatException ok) {
  // it's not numeric; that's fine, just continue
}
return handleTextResponse(response);
例外:在测试中,如果一个捕获的异常被命名为expected,则它可以被不加注释地忽略。下面是一种非常常见的情形,用以确保所测试的方法会抛出一个期望中的异常, 因此在这里就没有必要加注释。

try {
  emptyStack.pop();
  fail();
} catch (NoSuchElementException expected) {
}

6.3 静态成员:使用类进行调用
使用类名调用静态的类成员,而不是具体某个对象或表达式。

Foo aFoo = ...;
Foo.aStaticMethod(); // good
aFoo.aStaticMethod(); // bad
somethingThatYieldsAFoo().aStaticMethod(); // very bad

6.4 Finalizers: 禁用
极少会去重载Object.finalize。
Tip:不要使用finalize。如果你非要使用它,请先仔细阅读和理解Effective Java 第7条款:“Avoid Finalizers”,然后不要使用它。

【Javadoc】
7.1 格式
7.1.1 一般形式
Javadoc块的基本格式如下所示:

/**
* Multiple lines of Javadoc text are written here,
* wrapped normally...
*/
public int method(String p1) { ... }
或者是以下单行形式:

/** An especially short bit of Javadoc. */
基本格式总是OK的。当整个Javadoc块能容纳于一行时(且没有Javadoc标记@XXX),可以使用单行形式。

7.1.2 段落
空行(即,只包含最左侧星号的行)会出现在段落之间和Javadoc标记(@XXX)之前(如果有的话)。 除了第一个段落,每个段落第一个单词前都有标签<p>,并且它和第一个单词间没有空格。

7.1.3 Javadoc标记
标准的Javadoc标记按以下顺序出现:@param, @return, @throws, @deprecated, 前面这4种标记如果出现,描述都不能为空。 当描述无法在一行中容纳,连续行需要至少再缩进4个空格。

7.2 摘要片段
每个类或成员的Javadoc以一个简短的摘要片段开始。这个片段是非常重要的,在某些情况下,它是唯一出现的文本,比如在类和方法索引中。
这只是一个小片段,可以是一个名词短语或动词短语,但不是一个完整的句子。它不会以A {@code Foo} is a...或This method returns...开头, 它也不会是一个完整的祈使句,如Save the record...。然而,由于开头大写及被加了标点,它看起来就像是个完整的句子。
Tip:一个常见的错误是把简单的Javadoc写成/** @return the customer ID */,这是不正确的。它应该写成/** Returns the customer ID. */。

7.3 哪里需要使用Javadoc
至少在每个public类及它的每个public和protected成员处使用Javadoc,以下是一些例外:

7.3.1 例外:不言自明的方法
对于简单明显的方法如getFoo,Javadoc是可选的(即,是可以不写的)。这种情况下除了写“Returns the foo”,确实也没有什么值得写了。
单元测试类中的测试方法可能是不言自明的最常见例子了,我们通常可以从这些方法的描述性命名中知道它是干什么的,因此不需要额外的文档说明。
Tip:如果有一些相关信息是需要读者了解的,那么以上的例外不应作为忽视这些信息的理由。例如,对于方法名getCanonicalName, 就不应该忽视文档说明,因为读者很可能不知道词语canonical name指的是什么。

7.3.2 例外:重载
如果一个方法重载了超类中的方法,那么Javadoc并非必需的。

7.3.3 可选的Javadoc
对于包外不可见的类和方法,如有需要,也是要使用Javadoc的。如果一个注释是用来定义一个类,方法,字段的整体目的或行为, 那么这个注释应该写成Javadoc,这样更统一更友好。


已有 0人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



Java JSON(一)Jackson区别于竞争对手的7个杀手锏--转

$
0
0
转自:http://www.blogjava.net/wangxinsh55/archive/2012/09/06/387179.html


http://simpleframework.net/blog/v/20053.html


Jackson JSON 处理器 为一行之有效的Java JSON处理器,诸如它具有如下特点:

    简但且方便的JSON解析,以及与Java对象的相互转换。
    通过注释(annotations)和配置(settings)可扩展的配置性。
    超快的基于流的解析性能,以及完整的数据绑定。

但许多其他Java的JSON库仅考虑方便性和配置性,性能反倒不是面向用户的最重要层面。

那么,为何Java开发人员不选择Jackson而放弃其竞争产品呢?

以上功能的简短列表其实不过是Jackson功能的冰山一角。 的确,这三个常见功能是重要的,但某种程度上也仅是最基本的东西,起码,JSON处理器应被作为值得考虑的工具使用。除此之外,还有很多很多功能应该提供,而这也正是Jackson具备的能力。

因此,让我们来看看7个样例 -- 吉祥的数字 -- "杀手锏" --Jackson近年来领先竞争对手的几点,依据先后顺序介绍(始于1.0版,最新的一个特性是1.6版加入的)。

1.多处理模式,且可很好协作

从基本开始,有多种方法来使用和生产JSON数据,尽管多数JSON包仅提供单一的处理方式,但却有三种互补的JSON处理方式(详细解释见:There are Tree ways...):

    增量解析及生成(流模式)。高性能,低开销的顺序访问。这是最低级的处理方法,相当于用于处理XML的SAX和StAX API。所有包装内部必须有这样的分析器,但并非所有都公开。
    基于树的数据模式(JSON DOM)。 树是一种描述JSON内容的自然的概念模型,因此许多软件包提供将JSON作为逻辑树处理的功能。这是一个灵活的模式,可很好适用某类任务,原型处理或即席访问也相当杰出。
    数据绑定(JSON 与POJO相互转换。极为方便,通常较树状模式更高的存取效率,也是适于Java开发人员通用的自然的数据绑定方式。常用于大多数Java REST框架,诸如JAX-RS。

尽管多角度的好处显而易见,且各自提供最佳的用例,很少有(如果有的话?)其他的Java JSON包提供这些规范的处理模式。
大多数只提供一个模式(org.json以树组织数据; Gson实现了数据绑定)。 Jackson提供所有模式,所有模式完全支持,且最棒的是,它很容易在两种模式之间转换,混合和适配。 例如,处理非常大的JSON流时,通常始于流解析,而使用数据绑定器将子数据段绑定到Java对象:这允许处理巨大的文件而没有过多的内存占用,但却提供 完整便利的数据绑定能力。

2. 可使用任何构造及工厂方法(不只是默的零参方法)

大多数数据(对JSON和XML)绑定工具需要一个定义和无参数的构造函数,实例化Java对象,并通过setter设置属性或直接访问字段。 不幸的是它使人们难以利用用“不可变对象”模式,也不同于正常代码中的访问模式。

Jackson thinks that developers deserve ability to specify whatever constructor or factory methods they want for instantiation; just annotate thing you want like so:Jacson认为,开发者应该能够指定他们想为实例化的任何工厂或构造方法,只要你喜欢,注释即可:

    public class MyBean {
      private final int value;
      @JsonCreator
      public MyBean(@JsonProperty("value") int v) {
        this.value = v;
      }
      public int getValue() { return value; }
    }

而你可以这样定义POJO,JSON处理的情况下也无妨。(Jackson处理不可变对象的信息,可见: 博客条目 )

3. 不仅是注解,可以混合式注解!

虽然有很多好处,利用Java注解定义元数据(如类型安全、编译时检查,消除单独的XML配置,DTY原则等),但也有缺点:如明显的是,添加注解须能修改类。 而你通常不能(也不应该)修改第三方库的代码,至少不只是JSON序列化配置方面。

但是,如果你只可以松散动态关联注释,而不是嵌入在代码中?我认为这是个了不起的想法,不管你对Jackson的混合式注解了解多少:您可以将注释与目标类关联(声明为代理接口或类的一部分)目标类的处理方式如同目标类本身声明的注解一样。

要了解更多信息,请阅读“ 使用混合式注解实现重用、解耦 ”。

4. 完全支持泛型类型

现在,泛型是Java开发的完整组成部分,然而,并非所有的JSON库支持泛型,甚至在处理非常复杂的数据类型时会出错。

以下列泛型为例:

  public class Wrapper<T> {     public T value;   }      public class ListWrapper<E> extends Wrapper<List<E>> { }

若需反序列化这些类型的数据,代码如下:

  ListWrapper<Integer> w = objectMapper.readValue("[{\"value\":13},{\"value\":7}]",     new TypeReference<ListWrapper<Integer>>() { } );

Jackson在弄清必要的东西及生成期望的值方面有点小麻烦,但却是支持泛型(或更多)的仅有的Java包。

5. 多态类型

下面是另一个factoid:继承和多态类型可用于面向对象开发的很好方法,但也是任意实现数据绑定功能的系统的PITA。

ORM(如Hibernate)大部分的复杂性是由于沿继承层次结构扁平化和非扁平化数据功能的需要,同样适用于像JAXB的数据序列化包。这也就难怪,当时,只有极少数的Java包支持多态类型的JSON反序列化,大多数需要用户建立应用代码显式进行类型解析。


杰克逊怎么样? Jackson 不仅支持自动序列化和反序列化动态和多态类型,它试图尽力把它做好。具体来说,没有必要公开Java类名(这是其它JSON包支持多态的唯一机制)作为类 型信息 - 尽管,人们可以,它是可配置 - 但可以使用逻辑类型名称(通过注解,或注册实现配置)。不管采用什么类型的标识符,包含的方法也可以配置(这很好,因为极大地简化了JSON格式)。所有 这一切都与合理的缺省功能,具有上下文适用性(这意味着你也可以定义不同类型的设置!)。

关于Jackson如何处理多态,详见: "Jackson 1.5: 多态类型处理"。

6. 物化接口 (even less monkey code to write!)

尽管支持多态类型是强大的功能 -- 但却存在固有的充足的复杂性 -- 这里是简化事情的方式:物化接口(或抽象类)。.

给定接口如下:

      public interface Bean {     public int getX();     public void setX(int value);   }

你可能想跳过这一步“Bean接口实现,包含两倍代码的类”,而直接处理:

      Bean bean = objectMapper.readValue(json, Bean.class);

(不收你200元...呃,书写10行代码的猴子 - 注意,那就是,我们可以省略接口中的'setX();Bean先生很聪明,知道一些方法需要注入值)。

只有一行配置,便可实现这神奇的功能(又称“Bean先生”)。更多关于物化接口的信息参见“ 物化接口信息“。

.我还没有找到一个愿写这些接口实现的编码人员,因此,如果寻找Jackson的单一功能亮点,那就是它了。

7. 支持父/子引用(一对多,ORM)

经过前面的通用功能集,我们总结的东西更为具体:能够干净地处理循环类型的某些子集,成为父/子链接。 这些都是互相紧密耦合的引用,其中两个对象以层次结构的方式交叉引用,如父/子树节点的关联, 或更常见的,ORM采用的表间连接(join)的描述。

对引用问题(或更普遍的,循环引用),JSON没有处理它们的自然方法。,不像Java对象,没有标识信息可用。
常用的解决方法是只标记一个被忽略的引用(Jackson可通过使用@ JsonIgnore注解实现),但其缺点是反序列化时会丢失实际的耦合目标。

Jackson 有简单的基于注解的解决该问题的方案:两个引用需要一个注解(对“子”连接作@JsonManagedReference注解,对“父”或“返回”连接作 @JsonBackReference注解),并在此基础,Jackson知道要省略反向引用的序列化,但反序列化对象时要恢复它。此方式适用于典型的 ORM用例。

8. 这是全部吗,有何异议?

实际上,Jackson的一些特性并未包含在本文中,若如此,请指正!


已有 0人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



Java异常处理策略

$
0
0
【异常的由来】
任务与预先设定的规则不相符的情况都可以称之为异常。但凡业务逻辑操作,都会划定一些边界或规则,但是往往事与愿违,总会有调皮鬼来挑战系统的健壮性。这些调皮鬼包括:

1、系统用户。用户并不都是知道潜规则的,比如用户的银行账户中只有100块钱,但是用户不查询就直接取200块。
2、开发人员。码农有时候太过自信了,比如你在编写文件下载功能时忽略了文件有可能不存在这个分支流程。
3、运行环境。软件系统也是要靠天吃饭的,谁都保不准网络一直畅通,硬盘一直稳定。

【异常处理框架】
有问题我们就要解决问题,如果问题解决不了,那么就把问题的影响面降到最低。对于异常也是如此,为了提高健壮性,需要对于异常进行兼容。处理异常一个很重要的原则是不逃避,不歪曲。吞异常与提供错误的异常信息一样罪恶。对于异常的处理框架可以大致分为以下几点:

1、异常类封装。根据异常产生的原因,我们把异常封装为两类,分别是业务逻辑异常(BusinessException)和系统编程异常(ProgramException),分类的目的是进行分类处理。
2、异常检测类。这是产生异常的地方,包括了业务逻辑检测(对于不符合业务逻辑的情况封装异常抛出),还包括了对于底层异常的转化封装(比如将数据库操作产生的异常捕获并封装为ProgramException)。
3、异常处理类。这是异常的终点,在此异常会以一种比较合适的方式对系统运维人员和系统的用户进行优雅的展示(比如记录日志或者页面提示)。

同时,为了统一的编码风格,我们对于异常的处理做如下约定:

1、只有最靠近用户的层面才会最终处理异常。这个用户包括了系统真实的自然人用户,也包括了一些系统,比如定时器或者命令行交互等。其他各层只负责捕获底层的异常并向上传递即可。
2、对于系统编程异常,对用户进行统一的提示语,并记录异常日志,通过监控系统用户系统运维人员进行监控告警。
3、对于业务逻辑异常,需要根据不同的异常类型以及上下文情况,对用户进行友好性的提示。同时考虑到对于用户的提示属于产品文案的一部分,可能会经常变化的特点,需要把提示语通过“错误码--提示语”的形式进行配置。
4、为了便于对错误码进行统一管理,约定一下错误码的编码规则:[错误消息类型(一位)] + [系统组编号(两位数字)]  + [子系统编号(两位数字)] + [顺序编号(两位数字)]。
5、其中错误消息类型取值:E——普通错误消息;W——警告类消息;Q——询问类消息;F——致命错误消息

【WEB工程异常处理】
对于WEB系统,由于我们使用的是Spring MVC框架,因为针对此种框架进行说明。

1、封装两个实体类:BisinessException和SystemException。
2、增加HandlerExceptionResolver 接口的实现类MyExceptionHandler,代码如下:
public class MyExceptionHandler implements HandlerExceptionResolver {  
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,  
            Exception ex) {  
        Map<String, Object> model = new HashMap<String, Object>();  
        model.put("ex", ex);  
        // 根据不同错误转向不同页面  
        if(ex instanceof BusinessException) {  
            return new ModelAndView("error-business", model);  
        }else if(ex instanceof ParameterException) {  
            return new ModelAndView("error-parameter", model);  
        } else {  
            return new ModelAndView("error", model);  
        }  
    }  
}  

3、在Spring的配置文件applicationContext.xml中增加以下内容:
<bean id="exceptionHandler" class="cn.basttg.core.exception.MyExceptionHandler"/>
 
4、对于Unchecked Exception而言,增加对应的errorpage:
<error-page><exception-type>java.lang.Throwable</exception-type><location>/500.jsp</location></error-page><error-page><error-code>500</error-code><location>/500.jsp</location></error-page><error-page><error-code>404</error-code><location>/404.jsp</location></error-page>

5、系统采用与系统风格统一的的error page页面。

【参考资料】
1、http://cgs1999.iteye.com/blog/1547197

已有 0人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



Linode 推出 USD$10/month 方案

$
0
0

Linode推出了比 Linode 2G 更低階的方案 Linode 1G:「 11th Linode Birthday / $10 Linode plan」。

先前的 2G 太大台了,大多數人根本用不到 (我的 blog 也才租 DigitalOcean的 512MB,然後開 swap file)。現在總算出了 1G 的版本,也許會有一些使用者回流吧,不過我覺得 Linode 的動作太慢了…

該來把東京的機器 downgrade 了…

Related Posts:

Viewing all 15843 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>