少年阿宾

那些青春的岁月

  BlogJava :: 首页 :: 联系 :: 聚合  :: 管理
  500 Posts :: 0 Stories :: 135 Comments :: 0 Trackbacks

#

//spring-mvc.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
 xmlns:cache="http://www.springframework.org/schema/cache"
 xmlns:context="http://www.springframework.org/schema/context"
 xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:oxm="http://www.springframework.org/schema/oxm"
 xmlns:p="http://www.springframework.org/schema/p" xmlns:util="http://www.springframework.org/schema/util"
 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd 
 http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd 
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd 
    http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
    http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.1.xsd">
 <!-- 指定系统寻找controller路径 -->
 <!-- json 数据格式转换
 <mvc:annotation-driven>
  <mvc:message-converters>
   <bean id="jsonConverter" class="com.abin.lee.ssh.util.json.fastjson.FastjsonHttpMessageConverter">
    <property name="supportedMediaTypes" value="application/json" />
    <property name="serializerFeature">
     <list>
      <value>WriteMapNullValue</value>
      <value>QuoteFieldNames</value>
     </list>
    </property>
   </bean>
  </mvc:message-converters>

 </mvc:annotation-driven>
 -->
 <!-- 搜索的包路径 -->
 <context:component-scan base-package="com.abin.lee.ssh"
  use-default-filters="false">
  <context:include-filter type="annotation"
   expression="org.springframework.stereotype.Controller" />
 </context:component-scan>
 
 <!-- jsp视图解释器 -->
 <bean id="jspViewResolver"
  class="org.springframework.web.servlet.view.InternalResourceViewResolver">
  <property name="viewClass"
   value="org.springframework.web.servlet.view.JstlView" />
  <property name="prefix" value="/page/" />
  <property name="suffix" value=".jsp" />
 </bean>
 
</beans>





//web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
  <display-name>front</display-name>
 
   <!-- spring MVC -->
 <servlet>
  <servlet-name>spring-mvc</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  <init-param>
   <param-name>contextConfigLocation</param-name>
   <param-value>classpath*:spring-mvc.xml</param-value>
  </init-param>
  <load-on-startup>2</load-on-startup>
 </servlet>
 <servlet-mapping>
  <servlet-name>spring-mvc</servlet-name>
  <url-pattern>/mvc/*</url-pattern>
 </servlet-mapping>
 
 
 
 
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>default.html</welcome-file>
    <welcome-file>default.htm</welcome-file>
    <welcome-file>default.jsp</welcome-file>
  </welcome-file-list>
</web-app>





//UnivernalController.java

package com.abin.lee.ssh.controller;

import java.util.ArrayList;
import java.util.List;

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;

import com.abin.lee.ssh.entity.UniversalBean;
@Scope("prototype")
@Controller
@RequestMapping("/sky/")
public class UniversalController {
 
 @RequestMapping("/activity")
 public String activity(ModelMap map){
  List<UniversalBean> list=new ArrayList<UniversalBean>();
  UniversalBean bean=null;
  for(int i=0;i<=5;i++){
   bean=new UniversalBean();
   bean.setId(i);
   bean.setName("abin"+i);
   bean.setImageUrl("http://localhost:7700/front/"+i+".jpg");
   list.add(bean);
  }
  map.put("list", list);
  return "mobile/show";
 }

}




//show.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn"%>
</head>

<script language="javascript" type="text/javascript" src="../script/js/share.js" ></script>
<script type="text/javascript">

</script>
<body>
 <div id="content">
  <c:forEach items="${list}" var="obj">
   <div id="" class="lb">
    <c:choose>
     <c:when test="${empty obj.name}">
      I am Empty
     </c:when>
     <c:when test="${obj.name=='abin1'}">
      ${obj.name} is a boy
     </c:when>
     <c:otherwise>
      ${obj.name } is normally
     </c:otherwise>
    </c:choose>
    <img alt="显示不出来" src="${obj.imageUrl }"></img>
   </div>
  
  </c:forEach>
 </div>
</body>
</html>



















posted @ 2013-07-26 15:38 abin 阅读(818) | 评论 (0)编辑 收藏

http://blog.csdn.net/kelly859/article/details/5827365
posted @ 2013-07-26 10:48 abin 阅读(367) | 评论 (0)编辑 收藏

window的load事件会在页面中的一切都加载完毕时触发,但这个过程可能会因为要加载外部资源过多而颇费周折。而DOMContentLoaded事件则在形成完整的DOM树之后就会触发,不理会图像、JavaScript文件、CSS文件或其他资源是否已经下载完毕。与load事件不同,DOMContentLoaded支持在页面下载的早期添加事件处理程序,这也就意味着用户能够尽早地与页面进行交互。

要处理DOMContentLoaded事件,可以为document或window添加相应的事件处理程序(尽管这个事件会冒泡到window,但它的目标实际上是document)。来看下面的例子:


var EventUtil = {
    addHandler
: function (element, type, handler) {
       
if (element.addEventListener) {
            element
.addEventListener(type, handler, false);
       
} else if (element.attachEvent) {
            element
.attachEvent("on" + type, handler);
       
} else {
            element
["on" + type] = handler;
       
}
   
}
};
EventUtil.addHandler(document, "DOMContentLoaded", function (event) {
    alert
("Content loaded.");
});



DOMContentLoaded事件对象不会提供任何额外的信息(其target属性是document)。
Firefox、Chrome、Safari 3.1及更高的版本和Opera 9及更高版本都支持DOMContentLoaded事件,通常这个事件既可以添加事件处理程序,也可以执行其它DOM操作。这个事件始终都会在load事件之前触发。

对于不支持DOMContLoaded的浏览器,我们建议在页面加载期间设置一个事件为0毫秒的超时调用,如下面的例子所示:

sentTimeout(function () {
   
//在此添加事件处理程序
}, 0);

这段代码实际意思是:“在当前JavaScript处理完成后立即运行这个函数。”在页面下载和构建期间,只有一个JavaScript处理过程,因此超时调用会在该过程结束时立即触发。至于这个事件与DOMContentLoaded被触发的时间是否同步,主要还是取决与用户使用的浏览器页面中的其它代码。为了确保这个方法有效,必须将其作为页面中的第一个超时调用;即便如此,也还是无法保证在所有浏览器中该超时调用一定会遭遇load事件被触发。



posted @ 2013-07-26 09:39 abin 阅读(354) | 评论 (0)编辑 收藏

package com.abin.lee.wechat;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.Map;
import javax.imageio.ImageIO;
import javax.imageio.stream.FileImageOutputStream;
public class WeChatPhoto {
public String createzPicture(Map<String,String> request){
String timeStamp=new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new java.util.Date());
File fileOne = new File("E:\\pament\\wechat\\photo\\"+timeStamp+".png");
try {
BufferedImage d = ImageIO.read(new File("E:\\pament\\wechat\\photo\\flight-model.png"));
Graphics2D graohics =d.createGraphics();
//写字往图片上面
String startPlace=request.get("startPlace");
String arrivalPlace=request.get("arrivalPlace");
String flightNo=request.get("flightNo");
String startTime=request.get("startTime");
String arrivalTime=request.get("arrivalTime");
Font font = new Font("粗体", Font.PLAIN, 18);// 添加字体的属性设置
graohics.setColor(Color.BLACK);
graohics.setFont(font);
graohics.drawString(flightNo, 300, 20);
graohics.drawString(startPlace, 30, 100);
graohics.drawString(arrivalPlace, 555, 100);
graohics.drawString(startTime, 50, 145);
graohics.drawString(arrivalTime, 600, 145);
ImageIO.write(d, "png", new FileImageOutputStream(fileOne));
} catch (Exception e) {
e.printStackTrace();
}
return timeStamp;
}
public static void main(String[] args) {
WeChatPhoto we=new WeChatPhoto();
Map<String,String> request=new HashMap<String, String>();
request.put("startPlace", "北京首都T2");
request.put("arrivalPlace", "上海虹桥T1");
request.put("startTime", "08:58");
request.put("arrivalTime", "10:23");
request.put("flightNo", "MU5183");
String timeStamp=we.createzPicture(request);
System.out.println("timeStamp="+timeStamp);
}
}
posted @ 2013-06-07 17:32 abin 阅读(371) | 评论 (0)编辑 收藏

最近因项目存在内存泄漏,故进行大规模的JVM性能调优 现把经验做一记录。

一、JVM内存模型及垃圾收集算法

 1.根据Java虚拟机规范,JVM将内存划分为:

  • New(年轻代)
  • Tenured(年老代)
  • 永久代(Perm)

  其中New和Tenured属于堆内存,堆内存会从JVM启动参数(-Xmx:3G)指定的内存中分配,Perm不属于堆内存,有虚拟机直接分配,但可以通过-XX:PermSize -XX:MaxPermSize 等参数调整其大小。

  • 年轻代(New):年轻代用来存放JVM刚分配的Java对象
  • 年老代(Tenured):年轻代中经过垃圾回收没有回收掉的对象将被Copy到年老代
  • 永久代(Perm):永久代存放Class、Method元信息,其大小跟项目的规模、类、方法的量有关,一般设置为128M就足够,设置原则是预留30%的空间。

New又分为几个部分:

  • Eden:Eden用来存放JVM刚分配的对象
  • Survivor1
  • Survivro2:两个Survivor空间一样大,当Eden中的对象经过垃圾回收没有被回收掉时,会在两个Survivor之间来回Copy,当满足某个条件,比如Copy次数,就会被Copy到Tenured。显然,Survivor只是增加了对象在年轻代中的逗留时间,增加了被垃圾回收的可能性。

 2.垃圾回收算法

  垃圾回收算法可以分为三类,都基于标记-清除(复制)算法:

  • Serial算法(单线程)
  • 并行算法
  • 并发算法

  JVM会根据机器的硬件配置对每个内存代选择适合的回收算法,比如,如果机器多于1个核,会对年轻代选择并行算法,关于选择细节请参考JVM调优文档。

  稍微解释下的是,并行算法是用多线程进行垃圾回收,回收期间会暂停程序的执行,而并发算法,也是多线程回收,但期间不停止应用执行。所以,并发算法适用于交互性高的一些程序。经过观察,并发算法会减少年轻代的大小,其实就是使用了一个大的年老代,这反过来跟并行算法相比吞吐量相对较低。

  还有一个问题是,垃圾回收动作何时执行?

  • 当年轻代内存满时,会引发一次普通GC,该GC仅回收年轻代。需要强调的时,年轻代满是指Eden代满,Survivor满不会引发GC
  • 当年老代满时会引发Full GC,Full GC将会同时回收年轻代、年老代
  • 当永久代满时也会引发Full GC,会导致Class、Method元信息的卸载

  另一个问题是,何时会抛出OutOfMemoryException,并不是内存被耗空的时候才抛出

  • JVM98%的时间都花费在内存回收
  • 每次回收的内存小于2%

  满足这两个条件将触发OutOfMemoryException,这将会留给系统一个微小的间隙以做一些Down之前的操作,比如手动打印Heap Dump。

二、内存泄漏及解决方法

 1.系统崩溃前的一些现象:

    • 每次垃圾回收的时间越来越长,由之前的10ms延长到50ms左右,FullGC的时间也有之前的0.5s延长到4、5s
    • FullGC的次数越来越多,最频繁时隔不到1分钟就进行一次FullGC
    • 年老代的内存越来越大并且每次FullGC后年老代没有内存被释放

       之后系统会无法响应新的请求,逐渐到达OutOfMemoryError的临界值。

       2.生成堆的dump文件

       通过JMX的MBean生成当前的Heap信息,大小为一个3G(整个堆的大小)的hprof文件,如果没有启动JMX可以通过Java的jmap命令来生成该文件。

       3.分析dump文件

       下面要考虑的是如何打开这个3G的堆信息文件,显然一般的Window系统没有这么大的内存,必须借助高配置的Linux。当然我们可以借助X-Window把Linux上的图形导入到Window。我们考虑用下面几种工具打开该文件:

      1. Visual VM
      2. IBM HeapAnalyzer
      3. JDK 自带的Hprof工具

       使用这些工具时为了确保加载速度,建议设置最大内存为6G。使用后发现,这些工具都无法直观地观察到内存泄漏,Visual VM虽能观察到对象大小,但看不到调用堆栈;HeapAnalyzer虽然能看到调用堆栈,却无法正确打开一个3G的文件。因此,我们又选用了Eclipse专门的静态内存分析工具:Mat。

       4.分析内存泄漏

       通过Mat我们能清楚地看到,哪些对象被怀疑为内存泄漏,哪些对象占的空间最大及对象的调用关系。针对本案,在ThreadLocal中有很多的JbpmContext实例,经过调查是JBPM的Context没有关闭所致。

       另,通过Mat或JMX我们还可以分析线程状态,可以观察到线程被阻塞在哪个对象上,从而判断系统的瓶颈。

       5.回归问题

         Q:为什么崩溃前垃圾回收的时间越来越长?

         A:根据内存模型和垃圾回收算法,垃圾回收分两部分:内存标记、清除(复制),标记部分只要内存大小固定时间是不变的,变的是复制部分,因为每次垃圾回收都有一些回收不掉的内存,所以增加了复制量,导致时间延长。所以,垃圾回收的时间也可以作为判断内存泄漏的依据

         Q:为什么Full GC的次数越来越多?

         A:因此内存的积累,逐渐耗尽了年老代的内存,导致新对象分配没有更多的空间,从而导致频繁的垃圾回收

         Q:为什么年老代占用的内存越来越大?

         A:因为年轻代的内存无法被回收,越来越多地被Copy到年老代

      三、性能调优

       除了上述内存泄漏外,我们还发现CPU长期不足3%,系统吞吐量不够,针对8core×16G、64bit的Linux服务器来说,是严重的资源浪费。

       在CPU负载不足的同时,偶尔会有用户反映请求的时间过长,我们意识到必须对程序及JVM进行调优。从以下几个方面进行:

      • 线程池:解决用户响应时间长的问题
      • 连接池
      • JVM启动参数:调整各代的内存比例和垃圾回收算法,提高吞吐量
      • 程序算法:改进程序逻辑算法提高性能

        1.Java线程池(java.util.concurrent.ThreadPoolExecutor)

          大多数JVM6上的应用采用的线程池都是JDK自带的线程池,之所以把成熟的Java线程池进行罗嗦说明,是因为该线程池的行为与我们想象的有点出入。Java线程池有几个重要的配置参数:

      • corePoolSize:核心线程数(最新线程数)
      • maximumPoolSize:最大线程数,超过这个数量的任务会被拒绝,用户可以通过RejectedExecutionHandler接口自定义处理方式
      • keepAliveTime:线程保持活动的时间
      • workQueue:工作队列,存放执行的任务

          Java线程池需要传入一个Queue参数(workQueue)用来存放执行的任务,而对Queue的不同选择,线程池有完全不同的行为:

      • SynchronousQueue: 一个无容量的等待队列,一个线程的insert操作必须等待另一线程的remove操作,采用这个Queue线程池将会为每个任务分配一个新线程
      • LinkedBlockingQueue 无界队列,采用该Queue,线程池将忽略 maximumPoolSize参数,仅用corePoolSize的线程处理所有的任务,未处理的任务便在LinkedBlockingQueue中排队
      • ArrayBlockingQueue: 有界队列,在有界队列和 maximumPoolSize的作用下,程序将很难被调优:更大的Queue和小的maximumPoolSize将导致CPU的低负载;小的Queue和大的池,Queue就没起动应有的作用。

          其实我们的要求很简单,希望线程池能跟连接池一样,能设置最小线程数、最大线程数,当最小数<任务<最大数时,应该分配新的线程处理;当任务>最大数时,应该等待有空闲线程再处理该任务。

          但线程池的设计思路是,任务应该放到Queue中,当Queue放不下时再考虑用新线程处理,如果Queue满且无法派生新线程,就拒绝该任务。设计导致“先放等执行”、“放不下再执行”、“拒绝不等待”。所以,根据不同的Queue参数,要提高吞吐量不能一味地增大maximumPoolSize。

          当然,要达到我们的目标,必须对线程池进行一定的封装,幸运的是ThreadPoolExecutor中留了足够的自定义接口以帮助我们达到目标。我们封装的方式是:

      • 以SynchronousQueue作为参数,使maximumPoolSize发挥作用,以防止线程被无限制的分配,同时可以通过提高maximumPoolSize来提高系统吞吐量
      • 自定义一个RejectedExecutionHandler,当线程数超过maximumPoolSize时进行处理,处理方式为隔一段时间检查线程池是否可以执行新Task,如果可以把拒绝的Task重新放入到线程池,检查的时间依赖keepAliveTime的大小。

        2.连接池(org.apache.commons.dbcp.BasicDataSource)

          在使用org.apache.commons.dbcp.BasicDataSource的时候,因为之前采用了默认配置,所以当访问量大时,通过JMX观察到很多Tomcat线程都阻塞在BasicDataSource使用的Apache ObjectPool的锁上,直接原因当时是因为BasicDataSource连接池的最大连接数设置的太小,默认的BasicDataSource配置,仅使用8个最大连接。

          我还观察到一个问题,当较长的时间不访问系统,比如2天,DB上的Mysql会断掉所以的连接,导致连接池中缓存的连接不能用。为了解决这些问题,我们充分研究了BasicDataSource,发现了一些优化的点:

      • Mysql默认支持100个链接,所以每个连接池的配置要根据集群中的机器数进行,如有2台服务器,可每个设置为60
      • initialSize:参数是一直打开的连接数
      • minEvictableIdleTimeMillis:该参数设置每个连接的空闲时间,超过这个时间连接将被关闭
      • timeBetweenEvictionRunsMillis:后台线程的运行周期,用来检测过期连接
      • maxActive:最大能分配的连接数
      • maxIdle:最大空闲数,当连接使用完毕后发现连接数大于maxIdle,连接将被直接关闭。只有initialSize < x < maxIdle的连接将被定期检测是否超期。这个参数主要用来在峰值访问时提高吞吐量。
      • initialSize是如何保持的?经过研究代码发现,BasicDataSource会关闭所有超期的连接,然后再打开initialSize数量的连接,这个特性与minEvictableIdleTimeMillis、timeBetweenEvictionRunsMillis一起保证了所有超期的initialSize连接都会被重新连接,从而避免了Mysql长时间无动作会断掉连接的问题。

        3.JVM参数

          在JVM启动参数中,可以设置跟内存、垃圾回收相关的一些参数设置,默认情况不做任何设置JVM会工作的很好,但对一些配置很好的Server和具体的应用必须仔细调优才能获得最佳性能。通过设置我们希望达到一些目标:

      • GC的时间足够的小
      • GC的次数足够的少
      • 发生Full GC的周期足够的长

        前两个目前是相悖的,要想GC时间小必须要一个更小的堆,要保证GC次数足够少,必须保证一个更大的堆,我们只能取其平衡。

         (1)针对JVM堆的设置一般,可以通过-Xms -Xmx限定其最小、最大值,为了防止垃圾收集器在最小、最大之间收缩堆而产生额外的时间,我们通常把最大、最小设置为相同的值
         (2)年轻代和年老代将根据默认的比例(1:2)分配堆内存,可以通过调整二者之间的比率NewRadio来调整二者之间的大小,也可以针对回收代,比如年轻代,通过 -XX:newSize -XX:MaxNewSize来设置其绝对大小。同样,为了防止年轻代的堆收缩,我们通常会把-XX:newSize -XX:MaxNewSize设置为同样大小

         (3)年轻代和年老代设置多大才算合理?这个我问题毫无疑问是没有答案的,否则也就不会有调优。我们观察一下二者大小变化有哪些影响

      • 更大的年轻代必然导致更小的年老代,大的年轻代会延长普通GC的周期,但会增加每次GC的时间;小的年老代会导致更频繁的Full GC
      • 更小的年轻代必然导致更大年老代,小的年轻代会导致普通GC很频繁,但每次的GC时间会更短;大的年老代会减少Full GC的频率
      • 如何选择应该依赖应用程序对象生命周期的分布情况:如果应用存在大量的临时对象,应该选择更大的年轻代;如果存在相对较多的持久对象,年老代应该适当增大。但很多应用都没有这样明显的特性,在抉择时应该根据以下两点:(A)本着Full GC尽量少的原则,让年老代尽量缓存常用对象,JVM的默认比例1:2也是这个道理 (B)通过观察应用一段时间,看其他在峰值时年老代会占多少内存,在不影响Full GC的前提下,根据实际情况加大年轻代,比如可以把比例控制在1:1。但应该给年老代至少预留1/3的增长空间

        (4)在配置较好的机器上(比如多核、大内存),可以为年老代选择并行收集算法: -XX:+UseParallelOldGC ,默认为Serial收集

        (5)线程堆栈的设置:每个线程默认会开启1M的堆栈,用于存放栈帧、调用参数、局部变量等,对大多数应用而言这个默认值太了,一般256K就足用。理论上,在内存不变的情况下,减少每个线程的堆栈,可以产生更多的线程,但这实际上还受限于操作系统。

        (4)可以通过下面的参数打Heap Dump信息

      • -XX:HeapDumpPath
      • -XX:+PrintGCDetails
      • -XX:+PrintGCTimeStamps
      • -Xloggc:/usr/aaa/dump/heap_trace.txt

          通过下面参数可以控制OutOfMemoryError时打印堆的信息

      • -XX:+HeapDumpOnOutOfMemoryError

       请看一下一个时间的Java参数配置:(服务器:Linux 64Bit,8Core×16G)

       JAVA_OPTS="$JAVA_OPTS -server -Xms3G -Xmx3G -Xss256k -XX:PermSize=128m -XX:MaxPermSize=128m -XX:+UseParallelOldGC -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/aaa/dump -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:/usr/aaa/dump/heap_trace.txt -XX:NewSize=1G -XX:MaxNewSize=1G"

      经过观察该配置非常稳定,每次普通GC的时间在10ms左右,Full GC基本不发生,或隔很长很长的时间才发生一次

      通过分析dump文件可以发现,每个1小时都会发生一次Full GC,经过多方求证,只要在JVM中开启了JMX服务,JMX将会1小时执行一次Full GC以清除引用,关于这点请参考附件文档。

       4.程序算法调优:本次不作为重点

      参考资料:

      http://java.sun.com/javase/technologies/hotspot/gc/gc_tuning_6.html

      posted @ 2013-05-31 15:49 abin 阅读(526) | 评论 (0)编辑 收藏

      AOP有三种植入切面的方法:其一是编译期织入,这要求使用特殊的Java编译器,AspectJ是其中的代表者;其二是类装载期织入,而这要求使用特殊的类装载器,AspectJ和AspectWerkz是其中的代表者;其三为动态代理织入,在运行期为目标类添加增强生成子类的方式,Spring AOP采用动态代理织入切面。

      Spring AOP使用了两种代理机制,一种是基于JDK的动态代理,另一种是基于CGLib的动态代理,之所以需要两种代理机制,很大程度上是因为JDK本身只提供基于接口的代理,不支持类的代理。

      Spring的三种注入方式:
      接口注入(不推荐)
      getter,setter方式注入(比较常用)
      构造器注入(死的应用)

      什么是AOP?
      面向切面编程(AOP)完善spring的依赖注入(DI),面向切面编程在spring中主要表现为两个方面
      1.面向切面编程提供声明式事务管理
      2.spring支持用户自定义的切面

      面向切面编程(aop)是对面向对象编程(oop)的补充,
      面向对象编程将程序分解成各个层次的对象,面向切面编程将程序运行过程分解成各个切面。
      AOP从程序运行角度考虑程序的结构,提取业务处理过程的切面,oop是静态的抽象,aop是动态的抽象,
      是对应用执行过程中的步骤进行抽象,,从而获得步骤之间的逻辑划分。

      aop框架具有的两个特征:
      1.各个步骤之间的良好隔离性
      2.源代码无关性

      什么是DI机制?
      依赖注入(Dependecy Injection)和控制反转(Inversion of Control)是同一个概念,具体的讲:当某个角色
      需要另外一个角色协助的时候,在传统的程序设计过程中,通常由调用者来创建被调用者的实例。但在spring中
      创建被调用者的工作不再由调用者来完成,因此称为控制反转。创建被调用者的工作由spring来完成,然后注入调用者
      因此也称为依赖注入。
      spring以动态灵活的方式来管理对象 , 注入的两种方式,设置注入和构造注入。
      设置注入的优点:直观,自然
      构造注入的优点:可以在构造器中决定依赖关系的顺序。

      spring 的优点都有哪些?
      1.降低了组件之间的耦合性 ,实现了软件各层之间的解耦
      2.可以使用容易提供的众多服务,如事务管理,消息服务等
      3.容器提供单例模式支持
      4.容器提供了AOP技术,利用它很容易实现如权限拦截,运行期监控等功能
      5.容器提供了众多的辅助类,能加快应用的开发
      6.spring对于主流的应用框架提供了集成支持,如hibernate,JPA,Struts等
      7.spring属于低侵入式设计,代码的污染极低
      8.独立于各种应用服务器
      9.spring的DI机制降低了业务对象替换的复杂性
      10.Spring的高度开放性,并不强制应用完全依赖于Spring,开发者可以自由选择spring的部分或全部


      一、spring工作原理:

      1.spring mvc请所有的请求都提交给DispatcherServlet,它会委托应用系统的其他模块负责负责对请求进行真正的处理工作。
      2.DispatcherServlet查询一个或多个HandlerMapping,找到处理请求的Controller.
      3.DispatcherServlet请请求提交到目标Controller
      4.Controller进行业务逻辑处理后,会返回一个ModelAndView
      5.Dispathcher查询一个或多个ViewResolver视图解析器,找到ModelAndView对象指定的视图对象
      6.视图对象负责渲染返回给客户端。


      描述一下Spring中实现DI(Dependency Injection)的几种方式

      方式一:接口注入,在实际中得到了普遍应用,即使在IOC的概念尚未确立时,这样的方法也已经频繁出现在我们的代码中。
      方式二:Type2 IoC: Setter injection对象创建之后,将被依赖对象通过set方法设置进去
      方式三:Type3 IoC: Constructor injection对象创建时,被依赖对象以构造方法参数的方式注入

       简单描述下IOC(inversion of control)的理解
      一个类需要用到某个接口的方法,我们需要将类A和接口B的实现关联起来,最简单的方法是类A中创建一个对于接口B的实现C的实例,但这种方法显然两者的依赖(Dependency)太大了。而IoC的方法是只在类A中定义好用于关联接口B的实现的方法,将类A,接口B和接口B的实现C放入IoC的 容器(Container)中,通过一定的配置由容器(Container)来实现类A与接口B的实现C的关联。

      spring提供的事务管理可以分为两类:编程式的和声明式的。编程式的,比较灵活,但是代码量大,存在重复的代码比较多;声明式的比编程式的更灵活。
      编程式主要使用transactionTemplate。省略了部分的提交,回滚,一系列的事务对象定义,需注入事务管理对象.
      void add()
      {
      transactionTemplate.execute( new TransactionCallback(){
      pulic Object doInTransaction(TransactionStatus ts)
      { //do sth}
      }
      }
      声明式:
      使用TransactionProxyFactoryBean:

      PROPAGATION_REQUIRED PROPAGATION_REQUIRED PROPAGATION_REQUIRED,readOnly
      围绕Poxy的动态代理 能够自动的提交和回滚事务
      org.springframework.transaction.interceptor.TransactionProxyFactoryBean
      PROPAGATION_REQUIRED–支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
      PROPAGATION_SUPPORTS–支持当前事务,如果当前没有事务,就以非事务方式执行。
      PROPAGATION_MANDATORY–支持当前事务,如果当前没有事务,就抛出异常。
      PROPAGATION_REQUIRES_NEW–新建事务,如果当前存在事务,把当前事务挂起。
      PROPAGATION_NOT_SUPPORTED–以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
      PROPAGATION_NEVER–以非事务方式执行,如果当前存在事务,则抛出异常。
      PROPAGATION_NESTED–如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。

      spring中的核心类有那些,各有什么作用?
      BeanFactory:产生一个新的实例,可以实现单例模式
      BeanWrapper:提供统一的get及set方法
      ApplicationContext:提供框架的实现,包括BeanFactory的所有功能
      spring的ioc及di代表什么意思?
      控制权由代码转向容器,通过容器动态将某些对象加入。
      如何在spring中实现国际化?
      在applicationContext.xml加载一个bean
      message.properties是一个键名加键值的文件,

















































       

      posted @ 2013-05-29 14:37 abin 阅读(621) | 评论 (0)编辑 收藏

      Java堆,分配对象实例所在空间,是GC的主要对象。分为
       新生代(Young Generation/New)
       老年代(Tenured Generation/Old)
      新生代又划分成
       Eden Space
       From Survivor/Survivor 0
       To Survivor/Survivor 1
      新生代要如此划分是因为新生代使用的GC算法是复制收集算法。这种算法效率较高,而GC主要是发生在对象经常消亡的新生代,因此新生代适合使用这种复制收集算法。由于有一个假设:在一次新生代的GC(Minor GC)后大部分的对象占用的内存都会被回收,因此留存的放置GC后仍然活的对象的空间就比较小了。这个留存的空间就是Survivor space:From Survivor或To Survivor。这两个Survivor空间是一样大小的。例如,新生代大小是10M(Xmn10M),那么缺省情况下(-XX:SurvivorRatio=8),Eden Space 是8M,From和To都是1M。
      在new一个对象时,先在Eden Space上分配,如果Eden Space空间不够就要做一次Minor GC。Minor GC后,要把Eden和From中仍然活着的对象们复制到To空间中去。如果To空间不能容纳Minor GC后活着的某个对象,那么该对象就被promote到老年代空间。从Eden空间被复制到To空间的对象就有了age=1。此age=1的对象如果在下一次的Minor GC后仍然存活,它还会被复制到另一个Survivor空间(如果认为From和To是固定的,就是又从To回到了From空间),而它的age=2。如此反复,如果age大于某个阈值(-XX:MaxTenuringThreshold=n),那个该对象就也可以promote到老年代了。
      如果Survivor空间中相同age(例如,age=5)对象的总和大于等于Survivor空间的一半,那么age>=5的对象在下一次Minor GC后就可以直接promote到老年代,而不用等到age增长到阈值。
      在做Minor GC时,只对新生代做回收,不会回收老年代。即使老年代的对象无人索引也将仍然存活,直到下一次Full GC。
      posted @ 2013-05-28 14:31 abin 阅读(448) | 评论 (0)编辑 收藏

      切分(Sharding)并不是特定数据库产品所附属的功能,而是在具体技术细节之上的抽象处理。是水平扩展(Scale Out)的解决方案,主要目的是解决单节点数据库服务器的能力限制,以及整个应用其架构的可扩展性(Scalability)。

      切分主要有两种方式:水平切分(Horizental Sharding)和垂直切分(Vertical Sharding)。

      水平切分所指的是通过一系列的切分规则将数据水平分布到不同的DB或table中,在通过相应的DB路由 或者table路由规则找到需要查询的具体的DB或者table以进行Query操作,比如根据用户ID将用户表切分到多台数据库上。

      垂直切分指的是按业务、产品切分,将不同类型的数据且分到不同的服务器上,通过数据库代理疏通程序与多个数据库的通讯、降低应用的复杂度。

      读写分离简单的说是把对数据库读和写的操作分开对应不同的数据库服务器,这样能有效地减轻数据库压力,也能减轻io压力。主数据库提供写操作,从数据库提供读操作,这样既避免了主数据库服务器(Master)的过载,也有效地利用了从数据库服务器(Slave)的资源。

      这里ebay工程师的文章:《可伸缩性最佳实践:来自eBay的经验》更详细地介绍了一些概念及业务场景。

      --End--

      posted @ 2013-05-27 10:14 abin 阅读(845) | 评论 (0)编辑 收藏

      Java内存分配:

      1. 寄存器:我们在程序中无法控制
      2. 栈:存放基本类型的数据和对象的引用,但对象本身不存放在栈中,而是存放在堆中
      3. 堆:存放用new产生的数据
      4. 静态域:存放在对象中用static定义的静态成员
      5. 常量池:存放常量
      6. 非RAM(随机存取存储器)存储:硬盘等永久存储空间
      ----------------------------------------------------------------------------------------------------------------------

      a.在函数中定义的一些基本类型的变量数据和对象的引用变量都在函数的栈内存中分配。  
      当在一段代码块定义一个变量时,Java就在栈中为这个变量分配内存空间,当该变量退出该作用域后,Java会自动释放掉为该变量所分配的内存空间,该内存空间可以立即被另作他用。  
      b.堆内存用来存放由new创建的对象和数组。 在堆中分配的内存,由Java虚拟机的自动垃圾回收器来管理。  
      在堆中产生了一个数组或对象后,还可以在栈中定义一个特殊的变量,让栈中这个变量的取值等于数组或对象在堆内存中的首地址,栈中的这个变量就成了数组或对象的引用变量。  引用变量就相当于是为数组或对象起的一个名称,以后就可以在程序中使用栈中的引用变量来访问堆中的数组或对象。引用变量就相当于是为 数组或者对象起的一个名称。引用变量是普通的变量,定义时在栈中分配,引用变量在程序运行到其作用域之外后被释放。而数组和对象本身在堆中分配,即使程序运行到使用 new 产生数组或者对象的语句所在的代码块之外,数组和对象本身占据的内存不会被释放,数组和对象在没有引用变量指向它的时候,才变为垃圾,不能在被使用,但仍然占据内存空间不放,在随后的一个不确定的时间被垃圾回收器收走(释放掉)。这也是 Java 比较占内存的原因。 
      实际上,栈中的变量指向堆内存中的变量,这就是 Java 中的指针! 
      c.常量池(constant pool)指的是在编译期被确定,并被保存在已编译的.class文件中的一些数据。除了包含代码中所定义的各种基本类型(如int、long等等)和对象型(如String及数组)的常量值(final)还包含一些以文本形式出现的符号引用,比如: 类和接口的全限定名; 字段的名称和描述符; 方法和名称和描述符。 虚拟机必须为每个被装载的类型维护一个常量池。常量池就是该类型所用到常量的一个有序集和,包括直接常量(string,integer和floating point常量)和对其他类型,字段和方法的符号引用。对于String常量,它的值是在常量池中的。而JVM中的常量池在内存当中是以表的形式存在的,对于String类型,有一张固定长度的CONSTANT_String_info表用来存储文字字符串值,注意:该表只存储文字字符串值,不存储符号引用。说到这里,对常量池中的字符串值的存储位置应该有一个比较明了的理解了。

      在程序执行的时候,常量池会储存在Method Area,而不是堆中.
      d.Java的堆是一个运行时数据区,类的(对象从中分配空间。这些对象通过new、newarray、anewarray和multianewarray等指令建立,它们不需要程序代码来显式的释放。堆是由垃圾回收来负责的,堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,因为它是在运行时动态分配内存的,Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态分配内存,存取速度较慢。 

      栈的优势是,存取速度比堆要快,仅次于寄存器,栈数据可以共享。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。栈中主要存放一些基本类型的变量数据(int, short, long, byte, float, double, boolean, char)和对象句柄(引用)。 

      栈有一个很重要的特殊性,就是存在栈中的数据可以共享。假设我们同时定义: 
      int a = 3; 
      int b = 3; 
      编译器先处理int a = 3;首先它会在栈中创建一个变量为a的引用,然后查找栈中是否有3这个值,如果没找到,就将3存放进来,然后将a指向3。接着处理int b = 3;在创建完b的引用变量后,因为在栈中已经有3这个值,便将b直接指向3。这样,就出现了a与b同时均指向3的情况。 

      这时,如果再令a=4;那么编译器会重新搜索栈中是否有4值,如果没有,则将4存放进来,并令a指向4;如果已经有了,则直接将a指向这个地址。因此a值的改变不会影响到b的值。 

      要注意这种数据的共享与两个对象的引用同时指向一个对象的这种共享是不同的,因为这种情况a的修改并不会影响到b, 它是由编译器完成的,它有利于节省空间。而一个对象引用变量修改了这个对象的内部状态,会影响到另一个对象引用变量。

      /*****************************************************************************/

       

      String是一个特殊的包装类数据。可以用: 
      String str = new String("abc"); 
      String str = "abc"; 
      两种的形式来创建,第一种是用new()来新建对象的,它会在存放于堆中。每调用一次就会创建一个新的对象。 
      而第二种是先在栈中创建一个对String类的对象引用变量str,然后通过符号引用去字符串常量池里找有没有"abc",如果没有,则将"abc"存放进字符串常量池,并令str指向”abc”,如果已经有”abc” 则直接令str指向“abc”。 

      比较类里面的数值是否相等时,用equals()方法;当测试两个包装类的引用是否指向同一个对象时,用==,下面用例子说明上面的理论。 
      String str1 = "abc"; 
      String str2 = "abc"; 
      System.out.println(str1==str2); //true 
      可以看出str1和str2是指向同一个对象的。 

      String str1 =new String ("abc"); 
      String str2 =new String ("abc"); 
      System.out.println(str1==str2); // false 
      用new的方式是生成不同的对象。每一次生成一个。 

      因此用第二种方式创建多个”abc”字符串,在内存中其实只存在一个对象而已. 这种写法有利与节省内存空间. 同时它可以在一定程度上提高程序的运行速度,因为JVM会自动根据栈中数据的实际情况来决定是否有必要创建新对象。而对于String str = new String("abc");的代码,则一概在堆中创建新对象,而不管其字符串值是否相等,是否有必要创建新对象,从而加重了程序的负担。 

      另一方面, 要注意: 我们在使用诸如String str = "abc";的格式定义类时,总是想当然地认为,创建了String类的对象str。担心陷阱!对象可能并没有被创建!而可能只是指向一个先前已经创建的对象。只有通过new()方法才能保证每次都创建一个新的对象。 
      由于String类的immutable性质,当String变量需要经常变换其值时,应该考虑使用StringBuffer类,以提高程序效率。 
      1. 首先String不属于8种基本数据类型,String是一个对象。 
      因为对象的默认值是null,所以String的默认值也是null;但它又是一种特殊的对象,有其它对象没有的一些特性。 

      2. new String()和new String(”")都是申明一个新的空字符串,是空串不是null; 

      3. String str=”kvill”;String str=new String (”kvill”)的区别

      看例1: 

      String s0="kvill"; 
      String s1="kvill"; 
      String s2="kv" + "ill"; 
      System.out.println( s0==s1 ); 
      System.out.println( s0==s2 ); 
      结果为: 
      true 
      true 

      首先,我们要知结果为道Java会确保一个字符串常量只有一个拷贝。 
      因为例子中的s0和s1中的”kvill”都是字符串常量,它们在编译期就被确定了,所以s0==s1为true;而”kv”和”ill”也都是字符串常量,当一个字符串由多个字符串常量连接而成时,它自己肯定也是字符串常量,所以s2也同样在编译期就被解析为一个字符串常量,所以s2也是常量池中” kvill”的一个引用。所以我们得出s0==s1==s2;用new String() 创建的字符串不是常量,不能在编译期就确定,所以new String() 创建的字符串不放入常量池中,它们有自己的地址空间。 

      看例2: 
      String s0="kvill"; 
      String s1=new String("kvill"); 
      String s2="kv" + new String("ill"); 
      System.out.println( s0==s1 ); 
      System.out.println( s0==s2 ); 
      System.out.println( s1==s2 ); 
      结果为: 
      false 
      false 
      false 

      例2中s0还是常量池中"kvill”的应用,s1因为无法在编译期确定,所以是运行时创建的新对象”kvill”的引用,s2因为有后半部分 new String(”ill”)所以也无法在编译期确定,所以也是一个新创建对象”kvill”的应用;明白了这些也就知道为何得出此结果了。 

      4. String.intern(): 
      再补充介绍一点:存在于.class文件中的常量池,在运行期被JVM装载,并且可以扩充。String的intern()方法就是扩充常量池的 一个方法;当一个String实例str调用intern()方法时,Java查找常量池中是否有相同Unicode的字符串常量,如果有,则返回其的引用,如果没有,则在常量池中增加一个Unicode等于str的字符串并返回它的引用;看例3就清楚了 

      例3: 
      String s0= "kvill"; 
      String s1=new String("kvill"); 
      String s2=new String("kvill"); 
      System.out.println( s0==s1 ); 
      System.out.println( "**********" ); 
      s1.intern(); 
      s2=s2.intern(); //把常量池中"kvill"的引用赋给s2 
      System.out.println( s0==s1); 
      System.out.println( s0==s1.intern() ); 
      System.out.println( s0==s2 ); 
      结果为: 
      false 
      ********** 
      false //虽然执行了s1.intern(),但它的返回值没有赋给s1 
      true //说明s1.intern()返回的是常量池中"kvill"的引用 
      true 

      最后我再破除一个错误的理解:有人说,“使用 String.intern() 方法则可以将一个 String 类的保存到一个全局 String 表中 ,如果具有相同值的 Unicode 字符串已经在这个表中,那么该方法返回表中已有字符串的地址,如果在表中没有相同值的字符串,则将自己的地址注册到表中”如果我把他说的这个全局的 String 表理解为常量池的话,他的最后一句话,”如果在表中没有相同值的字符串,则将自己的地址注册到表中”是错的: 

      看例4: 
      String s1=new String("kvill"); 
      String s2=s1.intern(); 
      System.out.println( s1==s1.intern() ); 
      System.out.println( s1+" "+s2 ); 
      System.out.println( s2==s1.intern() ); 
      结果: 
      false 
      kvill kvill 
      true 

      在这个类中我们没有声名一个”kvill”常量,所以常量池中一开始是没有”kvill”的,当我们调用s1.intern()后就在常量池中新添加了一个”kvill”常量,原来的不在常量池中的”kvill”仍然存在,也就不是“将自己的地址注册到常量池中”了。 
      s1==s1.intern()为false说明原来的”kvill”仍然存在;s2现在为常量池中”kvill”的地址,所以有s2==s1.intern()为true。 

      5. 关于equals()和==: 
      这个对于String简单来说就是比较两字符串的Unicode序列是否相当,如果相等返回true;而==是比较两字符串的地址是否相同,也就是是否是同一个字符串的引用。 

      6. 关于String是不可变的 
      这一说又要说很多,大家只要知道String的实例一旦生成就不会再改变了,比如说:String str=”kv”+”ill”+” “+”ans”; 就是有4个字符串常量,首先”kv”和”ill”生成了”kvill”存在内存中,然后”kvill”又和” ” 生成 “kvill “存在内存中,最后又和生成了”kvill ans”;并把这个字符串的地址赋给了str,就是因为String的”不可变”产生了很多临时变量,这也就是为什么建议用StringBuffer的原因了,因为StringBuffer是可改变的。

      /*******************************************************************************/

      下面是一些String相关的常见问题:

      String中的final用法和理解
      final StringBuffer a = new StringBuffer("111");
      final StringBuffer b = new StringBuffer("222");
      a=b;//此句编译不通过

      final StringBuffer a = new StringBuffer("111");
      a.append("222");//编译通过

      可见,final只对引用的"值"(即内存地址)有效,它迫使引用只能指向初始指向的那个对象,改变它的指向会导致编译期错误。至于它所指向的对象的变化,final是不负责的。

      String 常量池问题的几个例子

      下面是几个常见例子的比较分析和理解:
      [1]
      String a = "a1"; 
      String b = "a" + 1; 
      System.out.println((a == b)); //result = true
      String a = "atrue"; 
      String b = "a" + "true"; 
      System.out.println((a == b)); //result = true
      String a = "a3.4"; 
      String b = "a" + 3.4; 
      System.out.println((a == b)); //result = true

      分析:JVM对于字符串常量的"+"号连接,将程序编译期,JVM就将常量字符串的"+"连接优化为连接后的值,拿"a" + 1来说,经编译器优化后在class中就已经是a1。在编译期其字符串常量的值就确定下来,故上面程序最终的结果都为true。

      [2]
      String a = "ab"; 
      String bb = "b"; 
      String b = "a" + bb; 
      System.out.println((a == b)); //result = false

      分析:JVM对于字符串引用,由于在字符串的"+"连接中,有字符串引用存在,而引用的值在程序编译期是无法确定的,即"a" + bb无法被编译器优化,只有在程序运行期来动态分配并将连接后的新地址赋给b。所以上面程序的结果也就为false。

      [3]
      String a = "ab"; 
      final String bb = "b"; 
      String b = "a" + bb; 
      System.out.println((a == b)); //result = true

      分析:和[3]中唯一不同的是bb字符串加了final修饰,对于final修饰的变量,它在编译时被解析为常量值的一个本地拷贝存储到自己的常量池中或嵌入到它的字节码流中。所以此时的"a" + bb和"a" + "b"效果是一样的。故上面程序的结果为true。

      [4]
      String a = "ab"; 
      final String bb = getBB(); 
      String b = "a" + bb; 
      System.out.println((a == b)); //result = false 
      private static String getBB() {
      return "b"; 
      }

      分析:JVM对于字符串引用bb,它的值在编译期无法确定,只有在程序运行期调用方法后,将方法的返回值和"a"来动态连接并分配地址为b,故上面程序的结果为false。

      通过上面4个例子可以得出得知:
      String  s  =  "a" + "b" + "c";   
      就等价于String s = "abc";   

      String  a  =  "a";   
      String  b  =  "b";   
      String  c  =  "c";   
      String  s  =   a  +  b  +  c;  

      这个就不一样了,最终结果等于:   
      StringBuffer temp = new StringBuffer();   
      temp.append(a).append(b).append(c);   
      String s = temp.toString();

      由上面的分析结果,可就不难推断出String 采用连接运算符(+)效率低下原因分析,形如这样的代码:

      public class Test {
      public static void main(String args[]) {
      String s = null;
      for(int i = 0; i < 100; i++) {
      s += "a";
      }
      }
      }

      每做一次 + 就产生个StringBuilder对象,然后append后就扔掉。下次循环再到达时重新产生个StringBuilder对象,然后 append 字符串,如此循环直至结束。 如果我们直接采用 StringBuilder 对象进行 append 的话,我们可以节省 N - 1 次创建和销毁对象的时间。所以对于在循环中要进行字符串连接的应用,一般都是用StringBuffer或StringBulider对象来进行append操作。

      String对象的intern方法理解和分析:

      public class Test4 {
      private static String a = "ab"; 
      public static void main(String[] args){
      String s1 = "a";
      String s2 = "b";
      String s = s1 + s2;
      System.out.println(s == a);//false
      System.out.println(s.intern() == a);//true  
      }
      }

      这里用到Java里面是一个常量池的问题。对于s1+s2操作,其实是在堆里面重新创建了一个新的对象,s保存的是这个新对象在堆空间的的内容,所以s与a的值是不相等的。而当调用s.intern()方法,却可以返回s在常量池中的地址值,因为a的值存储在常量池中,故s.intern和a的值相等

       

      总结:

      a.栈中用来存放一些原始数据类型的局部变量数据和对象的引用(String,数组.对象等等)但不存放对象内容

      b.堆中存放使用new关键字创建的对象.

      c.字符串是一个特殊包装类,其引用是存放在栈里的,而对象内容必须根据创建方式不同定(常量池和堆).有的是编译期就已经创建好,存放在字符串常量池中,而有的是运行时才被创建.使用new关键字,存放在堆中。

       

      本文转自:http://zy19880423.javaeye.com/blog/434179

      posted @ 2013-05-23 22:39 abin 阅读(508) | 评论 (0)编辑 收藏

      1. Snapshot版本代表不稳定、尚处于开发中的版本 

      2. Release版本则代表稳定的版本 

      3. 什么情况下该用SNAPSHOT? 
           协同开发时,如果A依赖构件B,由于B会更新,B应该使用SNAPSHOT来标识自己。这种做法的必要性可以反证如下: 

            a.如果B不用SNAPSHOT,而是每次更新后都使用一个稳定的版本,那版本号就会升得太快,每天一升甚至每个小时一升,这就是对版本号的滥用。 

            b.如果B不用SNAPSHOT, 但一直使用一个单一的Release版本号,那当B更新后,A可能并不会接受到更新。因为A所使用的repository一般不会频繁更新release版本的缓存(即本地repository),所以B以不换版本号的方式更新后,A在拿B时发现本地已有这个版本,就不会去远程Repository下载最新的B 

      4. 不用Release版本,在所有地方都用SNAPSHOT版本行不行?      
           不行。正式环境中不得使用snapshot版本的库。 比如说,今天你依赖某个snapshot版本的第三方库成功构建了自己的应用,明天再构建时可能就会失败,因为今晚第三方可能已经更新了它的snapshot库。你再次构建时,Maven会去远程repository下载snapshot的最新版本,你构建时用的库就是新的jar文件了,这时正确性就很难保证了。
      posted @ 2013-05-23 15:21 abin 阅读(525) | 评论 (0)编辑 收藏

      仅列出标题
      共50页: First 上一页 11 12 13 14 15 16 17 18 19 下一页 Last