Tag Archive for CTO

JVM王者宝座谁来坐?

The next big JVM language?

There’s an interesting thread of comments related to a blog post by Stephen Colebourne, who is giving a talk at this year’s JavaOne entitled “Next Big JVM language.” In particular, he and others note that the Fantom language could be the answer (I find this interesting as Fantom really wasn’t even on my radar. Until now.). Moreover, many of the threads claim Scala to be the next big language. It seems people still prefer static typing over dynamic-ness. Either way, I got the distinct impression, based upon those individuals that left comments, which, by no means reflects the community at large, that Groovy isn’t it.

Principally, the arguments against Groovy can be summarized as its lack of performance (compared to Scala, for instance). Not to be outdone, a few folks brought up Groovy++ (which attempts to add a bit of static-ness to Groovy ostensibly to increase performance). Nevertheless, the comments are quite interesting to read if for anything that Fantom is gaining mind share perhaps at the cost of other more mainstream alternatives like Groovy.

这几年,关于谁是下一个JVM上的王者语言之争已经不是新鲜事情了。看了上面的文章,您是不是又所感慨呢?

笔者对部分上述语言接触过,下面发表一下意见:

Read more

JVM探险之Class Loader类的查找

转载自:http://www.blogjava.net/zhvfeng/archive/2010/08/17/329078.html

众所周知,所有的Java class文件都是由JVM(虚拟机)加载并执行的。深入理解JVM对于我们提高Java技术和解决Java问题都有非常大的帮助。

JVM内部主要包括内存管理和Class Loader(类加载器)两个部分。熟悉了内存管理,我们就会清楚程序在内存中是怎么分配和执行的,就能解决所有和对象相关的问题(比如Memory Leak)。理解了Class Loader,就能解决所有类找不到(比如遇到NoClassDefFoundError或ClassNotFoundException)或配置文件找不到问题。

这次我们只讨论JVM的Class Loader,下次再讨论JVM的内存管理。

Class Loader的主要作用就是负责查找类并将其加载到内存中。有趣的是,Java中的Class Loader也是由Java所写,就和普通的class一样。这就产生了一个是鸡生蛋还是蛋生鸡的问题,到底第一个class由谁来加载呢?我们稍后会来讨论这个问题。

先来看一下Class Loader所具有的特点。

1.       继承关系

虽然Class Loader也是一个Java class,但这里的继承不是指定义class时使用的extends关键字来实现的继承,而是指由属性来维持的继承关系。即通过Class Loader的构造方法或其它方法显式的设置一个父Class Loader。

Read more

高性能JAVA开发之内存管理

一、JVM中的对象生命周期

对象的生命周期一般分为7个阶段:创建阶段,应用阶段,不可视阶段,不可到达阶段,可收集阶段,终结阶段,释放阶段。

创建阶段,首先大家看一下,如下两段代码:

test1:

for( int i=0; i<10000; i++)

Object obj=new Object();

test2:

Object obj=null;

for( int i=0; i<10000; i++)

obj=new Object();

这两段代码都是相同的功能,但是显然test2的性能要比test1性能要好,内存使用率要高, 这是为什么呢?原因很简单,test1每次执行for循环都要创建一个Object的临时对象,但是这些临时对象由于JVM的GC不能马上销毁,所以他们 还要存在很长时间,而test2则只是在内存中保存一份对象的引用,而不必创建大量新临时变量.从而降低了内存的使用.

另外不要对同一个对象初始化多次.例如:

public class A{

private Hashtable table = new Hashtable();

public A(){

table = new Hashtable(); // 这里应该去掉,因为table已经被初始化.

}

}

这样就new了两个Hashtable,但是却只使用了一个.另外一个则没有被引用.而被忽略掉.浪费了内存.并且由于进行了两次new操作.也影响了代码的执行速度。

应用阶段:即该对象至少有一个引用在维护他。

不可视阶段:即超出该变量的作用域.这里有一个很好的做法,因为JVM在GC的时候并不是马上进行回收,而是要判断对象是否被其他引用在维护.所 以,这个时候如果我们在使用完一个对象以后对其obj=null或者obj.doSomething()操作,将其标记为空,可以帮助JVM及时发现这个 垃圾对象。

不可到达阶段:就是在JVM中找不到对该对象的直接或者间接的引用。

可收集阶段,终结阶段,释放阶段:此为回收器发现该对象不可到达,finalize方法已经被执行,或者对象空间已被重用的时候。

二、java内存管理特点

Java一个最大的优点就是取消了指针,由垃圾收集器来自动管理内存的回收。程序员不需要通过调用函数来释放内存。

(1)Java的内存管理就是对象的分配和释放问题。

在Java中,内存的分配是由程序完成的,而内存的释放是有GC完成的,这种收支两条线的方法简化了程序员的工作。但也加重了JVM的工作。这也是Java程序运行速度较慢的原因之一。【还有几种其他原因呢?】

在Java中,程序员需要通过关键字new为每个对象申请内存空间 (基本类型除外),所有的对象都在堆 (Heap)中分配空间,对象的释放是由GC决定和执行的。

GC释放空间方法:监控每一个对象的运行状态,包括对象的申请、引用、被引用、赋值等。当该对象不再被引用时,释放对象,但程序中对GC的操作并不一定能 达到管理内存的效果,GC对于程序员来说基本是透明的,不可见的。我们只有几个函数可以访问GC,例如运行GC的函数System.gc()。但是根据 Java语言规范定义,System.gc()函数不保证JVM的垃圾收集器一定会执行。因为,不同的JVM实现者可能使用不同的算法管理GC。通 常,GC的线程的优先级别较低【是否可以调节】,而且强制内存回收对于系统自动的内存回收机制会产生负面影响,会加大系统自动回收的处理时间,所以应该尽量避免显式使用 System.gc(),

JVM调用GC的策略有很多种,有的是内存使用到达一定程度时,GC才开始工作,也有定时执行的,有的是平缓执行GC,有的是中断式执行GC。但通常来 说,我们不需要关心这些。除非在一些特定的场合,GC的执行影响应用程序的性能,例如对于基于Web的实时系统,如网络游戏等,用户不希望GC突然中断应 用程序执行而进行垃圾回收,那么我们需要调整GC的参数,让GC能够通过平缓的方式释放内存,例如将垃圾回收分解为一系列的小步骤执行,Sun提供的 HotSpot JVM就支持这一特性。

(2)内存管理结构

Java使用有向图的方式进行内存管理,对于程序运行的每一个时刻,我们都有一个有向图表示JVM的内存分配情况。

将对象考虑为有向图的顶点,将引用关系考虑为图的有向边,有向边从引用者指向被引对象。另外,每个线程对象可以作为一个图的起始顶点,例如大多程序从 main进程开始执行,那么该图就是以main进程顶点开始的一棵根树。在这个有向图中,根顶点可达的对象都是有效对象,GC将不回收这些对象。如果某个 对象 (连通子图)与这个根顶点不可达(注意,该图为有向图),那么我们认为这个(这些)对象不再被引用,可以被GC回收。

(3)使用有向图方式管理内存的优缺点

Java使用有向图的方式进行内存管理,可以消除引用循环的问题,例如有三个对象,相互引用,只要它们和根进程不可达的,那么GC也是可以回收它们的。这 种方式的优点是管理内存的精度很高,但是效率较低。另外一种常用的内存管理技术是使用计数器,例如COM模型采用计数器方式管理构件,它与有向图相比,精 度行低(很难处理循环引用的问题),但执行效率很高。

三、Java的内存泄露

Java虽然由GC来回收内存,但也是存在泄露问题的,只是比C++小一点。

(1)与C++的比较

C++所有对象的分配和回收都需要由用户来管理。即需要管理点,也需要管理边。若存在不可达的点,无法在回收分配给那个点的内存,导致内存泄露。存在无用的对象引用,自然也会导致内存泄露。

Java由GC来管理内存回收,GC将回收不可达的对象占用的内存空间。所以,Java需要考虑 的内存泄露问题主要是那些被引用但无用的对象——即指要管理边就可以。被引用但无用的对象,程序引用了该对象,但后续不会再使用它。它占用的内存空间就浪 费了,如果存在对象的引用,这个对象就被定义为“活动的”,同时不会被释放。

(2)Java内存泄露处理

处理Java的内存泄露问题:确认该对象不再会被使用,接着典型的做法——把对象数据成员设为null

注意,当局部变量不需要时,不需明显的设为null,因为一个方法执行完毕时,这些引用会自动被清理。

例子:

List myList=new ArrayList();

for (int i=1;i<100; i++)

{

Object o=new Object();

myList.add(o);

o=null;

}

此时,所有的Object对象都没有被释放,因为变量myList引用这些对象。当myList后来不再用到,将之设为null,释放所有它引用的对象。之后GC便会回收这些对象占用的内存。

(3)内存泄露检测

市场上已有几种专业检查Java内存泄漏的工具,它们的基本工作原理大同小异,都是通过监测Java程序运行时,所有对象的申请、释放等动作,将内存管理 的所有信息进行统计、分析、可视化。开发人员将根据这些信息判断程序是否有内存泄漏问题。这些工具包括Optimizeit Profiler,JProbe Profiler,JinSight, Rational公司的Purify等。

在运行过程中,我们可以随时观察内存的使用情况,通过这种方式,我们可以很快找到那些长期不被释放,并且不再使用的对象。我们通过检查这些对象的生存周期,确认其是否为内存泄露。

四、java程序设计中有关内存管理的经验

1. 最基本的建议是尽早释放无用对象的引用。如:…

A a = new A();

//应用a对象

a = null; //当使用对象a之后主动将其设置为空

….

注:如果a 是方法的返回值,不要做这样的处理,否则你从该方法中得到的返回值永远为空,而且这种错误不易被发现、排除

2. 尽量少用finalize函数。它会加大GC的工作量。

3. 注意集合数据类型,包括数组、树、图、链表等数据结构,这些数据结构对GC来说,回收更为复杂。

4. 尽量避免在类的默认构造器中创建、初始化大量的对象,防止在调用其自类的构造器时造成不必要的内

存资源浪费。由于对象的创建是递归式的,也就是先调用超级类的构造,然后依次向下递归调用构造函数,

所以应该避免在类的构造函数中初始化变量,这样可以避免不必要的创建对象造成不必要的内存消耗.当

然这里也就看出来接口的优势。

5. 尽量避免强制系统做垃圾内存的回收,增长系统做垃圾回收的最终时间

6. 尽量避免显式申请数组空间

7. 别用new Boolean()

在很多场景中Boolean类型是必须的,比如JDBC中boolean类型的set与get都是通过Boolean封装传递的,大部分ORM也是用Boolean来封装boolean类型的,比如:

ps.setBoolean(“isClosed”,new Boolean(true));

ps.setBoolean(“isClosed”,new Boolean(isClosed));

ps.setBoolean(“isClosed”,new Boolean(i==3));

通常这些系统中构造的Boolean实例的个数是相当多的,所以系统中充满了大量Boolean实例小对象,这是相当消耗内存的。Boolean类实际上只要两个实例就够了,一个true的实例,一个false的实例。

Boolean类提供两了个静态变量:

public static final Boolean TRUE = new Boolean(true);

public static final Boolean FALSE = new Boolean(false);

需要的时候只要取这两个变量就可以了,

比如:ps.setBoolean(“isClosed”,Boolean.TRUE);

那么象2、3句那样要根据一个boolean变量来创建一个Boolean怎么办呢?可以使用Boolean提供的静态方法:Boolean.valueOf()

比如:

ps.setBoolean(“isClosed”,Boolean.valueOf(isClosed));

ps.setBoolean(“isClosed”,Boolean.valueOf(i==3));

因为valueOf的内部实现是:return (b ? TRUE : FALSE);

所以可以节省大量内存。相信如果Java规范直接把Boolean的构造函数规定成private,就再也不会出现这种情况了。

8. 别用new Integer

和Boolean类似,java开发中使用Integer封装int的场合也非常多,并且通常用int表示的数值通常都非常小。SUN SDK中对Integer的实例化进行了优化,Integer类缓存了-128到127这256个状态的Integer,如果使用 Integer.valueOf(int i),传入的int范围正好在此内,就返回静态实例。这样如果我们使用Integer.valueOf代替new Integer的话也将大大降低内存的占用。如果您的系统要在不同的SDK(比如IBM SDK)中使用的话,那么可以自己做了工具类封装一下,比如IntegerUtils.valueOf(),这样就可以在任何SDK中都可以使用这种特 性。

9. 用StringBuffer代替字符串相加

这个我就不多讲了,因为已经被人讲过N次了。我只想将一个不是笑话的笑话,我在看国内某“著名”java开发的WEB系统的源码中,竟然发现其中大量的使用字符串相加,一个拼装SQL语句的方法中竟然最多构造了将近100个string实例。无语中!

10. 不要过滥使用哈希表

有一定开发经验的开发人员经常会使用hash表(hash表在JDK中的一个实现就是HashMap)来缓存一些数据,从而提高系统的运行速度。比如使用HashMap缓 存一些物料信息、人员信息等基础资料,这在提高系统速度的同时也加大了系统的内存占用,特别是当缓存的资料比较多的时候。其实我们可以使用操作系统中的缓 存的概念来解决这个问题,也就是给被缓存的分配一个一定大小的缓存容器,按照一定的算法淘汰不需要继续缓存的对象,这样一方面会因为进行了对象缓存而提高 了系统的运行效率,同时由于缓存容器不是无限制扩大,从而也减少了系统的内存占用。现在有很多开源的缓存实现项目,比如ehcache、oscache等,这些项目都实现了FIFO、MRU等常见的缓存算法。

11. 避免过深的类层次结构和过深的方法调用。因为这两者都是非常占用内存的(特别是方法调用更是堆栈空间的消耗大户)。

12. 变量只有在用到它的时候才定义和实例化。

13. 共享静态存储空间

我们都知道静态变量在程序运行期间其内存是共享的,因此有时候为了节约内存工件,将一些变量声明为静态变量确实可以起到节约内存空间的作用。但是由于静态变量生命周期很长,不易被系统回收,所以使用静态变量要合理,不能盲目的使用.以免适得其反。

因此建议在下面情况下使用:变量所包含的对象体积较大,占用内存过多;变量所包含对象生命周期较长;变量所包含数据稳定;该类的对象实例有对该变量所包含的对象的共享需求.(也就是说是否需要作为全局变量)。

面试前的JAVA基础复习

【】里面是我加的~
问题一:我声明了什么!

String s = “Hello world!”;

许多人都做过这样的事情,但是,我们到底声明了什么?回答通常是:一个String,内容是“Hello world!”。这样模糊的回答通常是概念不清的根源。如果要准确的回答,一半的人大概会回答错误。
这个语句声明的是一个指向对象的引用,名为“s”,可以指向类型为String的任何对象,目前指向”Hello world!”这个String类型的对象。这就是真正发生的事情。我们并没有声明一个String对象,我们只是声明了一个只能指向String对象的 引用变量。所以,如果在刚才那句语句后面,如果再运行一句:

String string = s;

我们是声明了另外一个只能指向String对象的引用,名为string,并没有第二个对象产生,string还是指向原来那个对象,也就是,和s指向同一个对象。

【这里需要避免的一个问题就是 String str = new String(“asd”);  多出了无用对象  在effective java 里面有说到】
问题二:”==”和equals方法究竟有什么区别?

==操作符专门用来比较变量的值是否相等。比较好理解的一点是:
int a=10;
int b=10;
则a==b将是true。
但不好理解的地方是:
String a=new String(“foo”);
String b=new String(“foo”);
则a==b将返回false。

根据前一帖说过,对象变量其实是一个引用,它们的值是指向对象所在的内存地址,而不是对象本身。a和b都使用了new操作符,意味着将在内存中产生两个内 容为”foo”的字符串,既然是“两个”,它们自然位于不同的内存地址。a和b的值其实是两个不同的内存地址的值,所以使用”==”操作符,结果会是 false。诚然,a和b所指的对象,它们的内容都是”foo”,应该是“相等”,但是==操作符并不涉及到对象内容的比较。
对象内容的比较,正是equals方法做的事。

看一下Object对象的equals方法是如何实现的:
boolean equals(Object o){

return this==o;

}
Object 对象默认使用了==操作符。所以如果你自创的类没有覆盖equals方法,那你的类使用equals和使用==会得到同样的结果。同样也可以看出, Object的equals方法没有达到equals方法应该达到的目标:比较两个对象内容是否相等。因为答案应该由类的创建者决定,所以Object把 这个任务留给了类的创建者。

看一下一个极端的类:
Class Monster{
private String content;

boolean equals(Object another){ return true;}

}
我覆盖了equals方法。这个实现会导致无论Monster实例内容如何,它们之间的比较永远返回true。

所以当你是用equals方法判断对象的内容是否相等,请不要想当然。因为可能你认为相等,而这个类的作者不这样认为,而类的equals方法的实现是由 他掌握的。如果你需要使用equals方法,或者使用任何基于散列码的集合(HashSet,HashMap,HashTable),请察看一下java doc以确认这个类的equals逻辑是如何实现的。
【关于 ==  还有个很著名的pitfall :就是Integer 的 == 问题

Integer a = new Integer(6);  Integer b = new Integer(6);

System..out.print(a==b);

按照对象的== 操作 是对比引用来说 答案应该是 false 但是 Integer 很特殊 -127~ 128之间的数他都有缓存~

所以很令人失望的是:System..out.print(a==b);  出来的是 true


问题三:String到底变了没有?

没有。因为String被设计成不可变(immutable)类,所以它的所有对象都是不可变对象。请看下列代码:

String s = “Hello”;
s = s + ” world!”;

s 所指向的对象是否改变了呢?从本系列第一篇的结论很容易导出这个结论。我们来看看发生了什么事情。在这段代码中,s原先指向一个String对象,内容是 “Hello”,然后我们对s进行了+操作,那么s所指向的那个对象是否发生了改变呢?答案是没有。这时,s不指向原来那个对象了,而指向了另一个 String对象,内容为”Hello world!”,原来那个对象还存在于内存之中,只是s这个引用变量不再指向它了。
通过上面的说明,我们很容易导出另一个结论,如果经常对字符串进行各种各样的修改,或者说,不可预见的修改,那么使用String来代表字符串的话会引起 很大的内存开销。因为 String对象建立之后不能再改变,所以对于每一个不同的字符串,都需要一个String对象来表示。这时,应该考虑使用StringBuffer类, 它允许修改,而不是每个不同的字符串都要生成一个新的对象。并且,这两种类的对象转换十分容易。
同时,我们还可以知道,如果要使用内容相同的字符串,不必每次都new一个String。例如我们要在构造器中对一个名叫s的String引用变量进行初始化,把它设置为初始值,应当这样做:
public class Demo {
private String s;

public Demo {
s = “Initial Value”;
}

}
而非
s = new String(“Initial Value”);
后者每次都会调用构造器,生成新对象,性能低下且内存开销大【跟正原作者的错误:不是调用构造器 生成一个对象会导致性能低下 是多出一个String多对象了 】,并且没有意义,因为String对象不可改变,所以对于内容相同的字符串,只要一个 String对象来表示就可以了。也就说,多次调用上面的构造器创建多个对象,他们的String类型属性s都指向同一个对象。
上面的结论还基于这样一个事实:对于字符串常量,如果内容相同,Java认为它们代表同一个String对象。而用关键字new调用构造器,总是会创建一个新的对象,无论内容是否相同。
至于为什么要把String类设计成不可变类,是它的用途决定的。其实不只String,很多Java标准类库中的类都是不可变的。在开发一个系统的时 候,我们有时候也需要设计不可变类,来传递一组相关的值,这也是面向对象思想的体现。不可变类有一些优点,比如因为它的对象是只读的,所以多线程并发访问 也不会有任何问题。当然也有一些缺点,比如每个不同的状态都要一个对象来代表,可能会造成性能上的问题。所以Java标准类库还提供了一个可变版本,即 StringBuffer。

问题四:final关键字到底修饰了什么? 【这个是我一直有点迷茫的呢】

final使得被修饰的变量”不变”,但是由于对象型变量的本质是“引用”,使得“不变”也有了两种含义:引用本身的不变,和引用指向的对象不变。

引用本身的不变:
final StringBuffer a=new StringBuffer(“immutable”);
final StringBuffer b=new StringBuffer(“not immutable”);
a=b;//编译期错误

引用指向的对象不变:
final StringBuffer a=new StringBuffer(“immutable”);
a.append(” broken!”); //编译通过

可见,final对引用的“值”(也即它所指向的那个对象的内存地址)有效,它迫使引用只能指向初始指向的那个对象,改变它的指向会导致编译期错误。至 于它所指向的对象的变化,final是不负责的。这很类似==操作符:==操作符只负责引用的“值”相等,至于这个地址所指向的对象内容是否相等,==操 作符是不管的。

理解final问题有很重要的含义。许多程序漏洞都基于此—-final只能保证引用永远指向固定对象,不能保证那个对象的状态不变。在多线程的操作 中,一个对象会被多个线程共享或修改,一个线程对对象无意识的修改可能会导致另一个使用此对象的线程崩溃。一个错误的解决方法就是在此对象新建的时候把它 声明为final,意图使得它“永远不变”。其实那是徒劳的。

问题五:到底要怎么样初始化

本问题讨论变量的初始化,所以先来看一下Java中有哪些种类的变量。
1. 类的属性,或者叫值域
2. 方法里的局部变量
3. 方法的参数

对于第一种变量,Java虚拟机会自动进行初始化。如果给出了初始值,则初始化为该初始值。如果没有给出,则把它初始化为该类型变量的默认初始值。

int类型变量默认初始值为0
float类型变量默认初始值为0.0f
double类型变量默认初始值为0.0
boolean类型变量默认初始值为false
char类型变量默认初始值为0(ASCII码)
long类型变量默认初始值为0
所有对象引用类型变量默认初始值为null,即不指向任何对象。注意数组本身也是对象,所以没有初始化的数组引用在自动初始化后其值也是null。

对于两种不同的类属性,static属性与instance属性,初始化的时机是不同的。instance属性在创建实例的时候初始化,static属性 在类加载,也就是第一次用到这个类的时候初始化,对于后来的实例的创建,不再次进行初始化。这个问题会在以后的系列中进行详细讨论。

对于第二种变量,必须明确地进行初始化。如果再没有初始化之前就试图使用它,编译器会抗议。如果初始化的语句在try块中或if块中,也必须要让它在第一 次使用前一定能够得到赋值。也就是说,把初始化语句放在只有if块的条件判断语句中编译器也会抗议,因为执行的时候可能不符合if后面的判断条件,如此一 来初始化语句就不会被执行了,这就违反了局部变量使用前必须初始化的规定。但如果在else块中也有初始化语句,就可以通过编译,因为无论如何,总有至少 一条初始化语句会被执行,不会发生使用前未被初始化的事情。对于try-catch也是一样,如果只有在try块里才有初始化语句,编译部通过。如果在 catch或finally里也有,则可以通过编译。总之,要保证局部变量在使用之前一定被初始化了。所以,一个好的做法是在声明他们的时候就初始化他 们,如果不知道要出事化成什么值好,就用上面的默认值吧!

其实第三种变量和第二种本质上是一样的,都是方法中的局部变量。只不过作为参数,肯定是被初始化过的,传入的值就是初始值,所以不需要初始化。

问题六:instanceof是什么东东?

instanceof是Java的一个二元操作符,和==,>, <是同一类东东。由于它是由字母组成的,所以也是Java的保留关键字。它的作用是测试它左边的对象是否是它右边的类的实例,返回boolean类型的数据。举个例子:

String s = “I AM an Object!”;
boolean isObject = s instanceof Object;

我们声明了一个String对象引用,指向一个String对象,然后用instancof来测试它所指向的对象是否是Object类的一个实例,显然,这是真的,所以返回true,也就是isObject的值为True。
instanceof有一些用处。比如我们写了一个处理账单的系统,其中有这样三个类:

public class Bill {//省略细节}
public class PhoneBill extends Bill {//省略细节}
public class GasBill extends Bill {//省略细节}

在处理程序里有一个方法,接受一个Bill类型的对象,计算金额。假设两种账单计算方法不同,而传入的Bill对象可能是两种中的任何一种,所以要用instanceof来判断:

public double calculate(Bill bill) {
if (bill instanceof PhoneBill) {
//计算电话账单
}
if (bill instanceof GasBill) {
//计算燃气账单
}

}
这样就可以用一个方法处理两种子类。

然而,这种做法通常被认为是没有好好利用面向对象中的多态性。其实上面的功能要求用方法重载完全可以实现,这是面向对象变成应有的做法,避免回到结构化编程模式。只要提供两个名字和返回值都相同,接受参数类型不同的方法就可以了:

public double calculate(PhoneBill bill) {
//计算电话账单
}

public double calculate(GasBill bill) {
//计算燃气账单
}

所以,使用instanceof在绝大多数情况下并不是推荐的做法,应当好好利用多态。

百度收录wordpress页面过少的原因完全剖析

本人之前发表过一篇文章,阐述wordpress收录页面过少的原因。我将wordpress 由博客主题换成CMS主题后,发现文章收录数量明显增加,并由此得出一个结论:百度不爱搜录具有博客特征的网页。由于当时考虑不周到,发表之后,受到个别 网友批评指正。事实上,博客特征的网页容易产生重复内容,高度重复才是引起百度不收录wordpress的真正原因。经过深思熟虑,以及对蜘蛛访问日志的 观察和分析,我对百度收录wordpress博客异常的原因得出新的结论。

1.安装SEO插件后,百度不收录页面。

很多采用wordpress建站的朋友都接触过All in One SEO这个插件。这个插件可以让新手一键搞定wordpress的站内优化。事实证明,这款插件对google优化非常的完美,但百度却总容易出现不收录 的情况。我曾经一度怀疑百度会认为All in One SEO 插件存在优化过度的问题,从而导致网站降权,不收录。实际上,All in One SEO 的网页并没有被百度降权,导致百度不收录的罪魁祸首是它的noindex设置。

noindex标签的作用是向搜索引擎申明该网页禁止被搜录和索引,谷歌蜘蛛爬行到这样标签的页面,会自动丢弃掉该页并继续爬行其他页面,从而 减小文章重复度,利于站内目标文章页权重的集中。百度蜘蛛遇到noindex标签的页面后,同样会丢弃掉该页。与谷歌不同的是,百度蜘蛛一般不再爬行该页 面包含的文章链接,直接返回上一级目录。由于包含在该页中的文章无法被百度蜘蛛爬行到,所以页目录之下的文章将不会被索引。

解决办法:

取消All in One SEO 中 noindex设置。(这个是最根本的原因~~就是百度算法不健壮导致的!ca~)

2.wordpress模板导致的原因。

wordpress之所以流行,与其强大的插件扩展能力和数量丰富的主题模板密不可分。我曾经说过,使用wordpress建站的人,百分之 99%都直接采用现成的主题来建站。同样的模板必然导致网络中出现大量高度重复网站结构和内容。百度不喜欢重复的内容,这些重复内容当然也包含网站模板的 HTML代码。

SEOer通常会选择一些使用人数较少的模板来做SEO博客,同时还会对代码部分做一些更改,如ID部分名称,title 描述,CSS名称等等。这些操作的目的,是使得网页模板代码尽量避免与其他网站重复,从而更有利于文章的搜录。当博客正文内容字数越少时,模板重复对收录 的影响就会变得越大。

解决办法:

1).增加文章字数。

2).选择非热门wordpress主题。

3).对现有主题进行修改。

3.wordpress摘要设置不当造成站内文章重复。

百度也好,谷歌也罢,没有哪一家搜索引擎喜欢重复的文章。当一篇文章出现在同一网站内多个不同的页面中,搜索引擎将花费更多的处理时间来判断哪 一篇文章才是正文。当搜索引擎从程序上难以无法判断主次时,会降权收录文章第一次被索引的页面,或者直接K掉这些重复的页面。于是很多wordpress 站长在百度里site自己的网址,然后惊讶的发现:出现在结果前面的,竟然全都是日历页,归档页或分类目录,而文章正文内容却被百度隐藏而作为补充内容而 存在,甚至压根就不被索引。

导致这样的原因是因为你在录入文章数据的时候,既没有采用more标签对文章进行截断,也没有手动添加摘要。wordpress默认情况下,会 将more标签之前的内容作为摘要输出到首页,标签目录,分类目录,归档目录,日历目录下。如果没有用more标签进行截断,也没有手动输入摘要,那么文 章正文会同时出现在首页,几个目录页,以及文章页中。由于首页比目录页权重高,目录页比文章页权重高,百度以为文章页权重最低,便优先舍弃掉了。 google的真正从技术上实现了文章的筛选机制,能保文章页被正常的收录,而百度这方面的技术尚需进一步完善。

解决办法:

1).每个文章前一两段后添加more标签。

2).手动添加文章摘要,不能与more标签之前相同。

3).精简标签数量。

4.URL设置不合理,不利于收录。

在很多SEO教程里,都提出过目录不宜过深。受到这一思想影响,许多站长就将目录页,文章页都控制在二级目录以内,以为这样可以让网页更好的被 收录。殊不知,这样的URL方式其实并不利于SEO。搜索引擎蜘蛛爬行算法不光包含URL深度,同时还包括URL的重复度。当蜘蛛需要爬行一个网站时,首 先需要根据网站的权重算出索引的深度和重复数,当网站权重越高时,蜘蛛爬行的深度就越深,允许的重复数就越大。蜘蛛在索引某一网站的时候,当深度操过一定 层次后就会终止爬行其子目录。在爬行某个层次链接的过程中,URL重复数度超过一定数量,就会结束对该层目录的爬行。

如果你的站内除了根目录就是2级页面,文章数量少时还好,如果文章数量一多,便会加大搜索引擎引擎服务器的负荷,从而引起蜘蛛反感。

解决办法:

一般而言,搜索引擎对于新站爬行层次都在三层左右。最佳的目录设置方案应当是目录/时段/正文。虽然这样的分类方式在建站初期收录处在劣势,但 对网站(尤其是对靠长尾关键词取胜的站点)的长期发展百利而无一害。我个人认为,这种目录结构是对搜索引擎最友好的,最容易被收录的结构。

权衡的艺术-产品经理如何把产品做成功?

产品经理如何把产品做成功?

那其实就是一门权衡的艺术~并且我觉得产品经理和架构师的处境类似~产品经理需要和架构师多沟通才行~(当然这么说的话就针对比较“大”的产品了~对,本文不针对“小”产品)

产品经理如何把产品做成功呢?这个是大多数产品经理每时每刻在思考的问题。
“做正确的事”和“把事情做正确”这两个要素对于一位优秀的产品经理而言,因该是双向的,“做正确的事”更多时候是把握产品的方向和蓝图;在一些产品体系健全的公司,这项工 作主要是由公司的产品线经理或者产品总监去完成。而在现实的中国IT企业中,这项工作往往是由产品经理去完成。
那对于产品经理而言“做正确的事”,究竟应该怎么做呢?
如果想把一个产品做正确,就必须把握清楚这个产品的需求,这个需求主要从N个方面进行一个描述:
客户的需求对 于一些有甲方的企业来说,客户需求是放在第一位的,因为他们很清楚一件事,如果客户不满意,那还有何用户可言,客户对你的产品不肯定、产品不能满足客户的 需求;那这 个产品问世的可能性不大。所以说客户的需求尤其重要。所以产品经理在做产品蓝图规划时,必须要弄清楚客户空间想要什么,并想清楚客户的这个要求,如何在我 的这个产品里 做体现,更优秀的产品会考虑的更长远一些,及如何把客户的需求与产品完美结合。如果你的产品方向或思路得到客户的认可,对于后台的产品推广方面,将会得到 客户的更多关 注和支持。
公司的内部需求公司的内部产品需求,要从N个方面去阐述。
公司高层对产品的期望产品经理必 须要弄清楚,公司的高层对你所规划的产品,持有怎么样的期许,在公司的战略规划里,你的产品的方向和里程碑式的目标又是怎么?你所设计的产品如何给公司带 来 最大化的利益?你所设计的产品是否与高层的期望是一致的?这些疑问都是产品经理要去解决的,通过与高层的不断沟通,必须要弄清楚高层对于产品的深层次需求 是什么,当你 的产品规划案得到公司高层的认同时,你的产品工作就又上了一层台阶,在产品的天平上又增加了一块生要的法码。
公司的运营部门的需求有些公司的产品建设完成后,要交由公司的运营部门的,运营部门要去完成产品上市后的一系列运营事务。因为你的产品做出来后,是要给运营部门来运营的,你的产品设计是否合理,有一项重要指标就是运营人员在使用了你设计的产品之后,相关的转化率有没有提高?有没有有效的降低了运营的成本。产品经理必须要弄清楚运营部门需要解决什么样的问题?要实现什么样的目标?你所设计的产品有没有有效的解决运营部门的问题,就显得尤为重要。
公司技术支撑部门的需求有些人不禁会想这个产品做出来后是给运营部门去运营的,技术支撑部门的需求真的需要考虑吗?这里的答案是肯定的。技术支撑部门对于产品的设计更多时候有自己的想法,有 时是降低产品的开发成本,实现重用性。
用户需求说到这儿,估计很多UED团队的朋友要跳出来,可能会大声疾呼:“这哥们到底懂不懂啥叫以用户为中心的产品设计啊?”当然“以用户为中心的产品设计”,是产品人员所追求的一个 目标。对于一些没有甲方的客户,UED团队当然是设计以用户为中心的设计。
我曾经遇到过一些情况,在充分以用户为中心的产品设计后,用户在体验上确实得到了一些改善,用户是用得爽了,但是运营部门却无法完成他的指标。也没有 达到高层的满意。 这种情况时常发生,所以对于产品经理而言,“做正确的事”这个方向上是相当有挑战的,有经验的产品经理可以很好的把这些需求点进行梳理和统一规划,很清晰 的完成操作。
当产品经理把产品牵涉相关的需求都考虑在内的话?那么成功的方向就离我们不远了?
话虽这么说~但是这样做很难~先来看一张图:

image

涉众利益相关人的影响

上图其实是架构师的处境(图片来自 SoftWare Architecture in Practice 一书)~非常生动展示了架构师因为不同涉众利益人提出的各种要求而崩溃了~
The Architecture Business Cycle 对一个软件持续发展的影响就体现在了图中~一个软件一个产品一种软件架构不仅涉及上面所提及客户,高管,技术人员的影响~~并且在产品的后期 持续性的受到运营人员 维护人员 市场推广人员的 影响~我所谓的影响就是需求的抛出~

image

在公司里产品经理会时不时的对技术人员提出某个需求~而产品经理的上头会有一堆人对他提出需求~~这么看来技术人员是最辛苦也是最核心的人员了~呵呵~高级技术人员可以抑或说架构师,他们又凌驾在一般程序员之上~对产品进行整体蓝图规划。然后产品(软件或者说软件的架构)在完成开发之后~再运营维护阶段必然对运营人员~维护人员等产生influence~然后有返回来影响到这个产品~~

这个就是所谓的 The Architecture Business Cycle!简称ABC

image

The Architecture Business Cycle!

最后我们回到本文的主题:产品经理如何把产品做成功?

根据ABC原理 一个成功的产品是具备 可维护性 易用性 可测试性 等重要质量属性的!所以不能在产品的初期只是更具业务需求来规划产品。不然后期维护可能会很吃力~就是说需要在产品规划的初期对客户的需求进行考虑之外 还需要对后期维护可能会发生的问题进行科学的搜寻规划~(其实这些学科在国外都已经很发达~SEI早就有相关的Golden Practice-CMM CMMI等 后面的文章我会介绍~)

并且在自己的公司环境下~在权衡所有环节的情况下着重突出最需要的产品设计~这个绝对值一门艺术~而不是科学了~因为这个没有可重复性实践的可能~每个公司都不一样的环境就会导致不同的环境~即使客户需求一样~不同的工作环境就有不一样的总体需求!就会有不一样的最终产品~

我引用一句话:If it is true that, given the same technical requirements for a system, two different architects in different organizations will produce different architectures, how can we determine if either one of them is the right one?

这句话就是我上面所说的:即使客户需求一样~不同的工作环境就有不一样的总体需求!就会有不一样的最终产品~

所以产品经理要想把产品做成功不仅需要充分理解显性需求,还需要充分挖掘自己所处的环境所带来的隐性需求!并且做出在不同隐性需求之间的权衡~很清楚~这已经不是科学~就是艺术~

把产品做成功是一门权衡的艺术

本文参考:

http://www.xiuze.net

http://etutorials.org/Programming/Software+architecture+in+practice,+second+edition/

关于java.util.concurrent必须知道的5件事(一)

本文原作者:Ted Neward 地址:http://www.ibm.com/developerworks/java/library/j-5things4.html

本文指导并发编程初学者(像我这样的) = =通过并发 Collections 进行多线程编程~

简介: 编写能够良好执行,防止应用程序受损的多线程代码是很艰巨的任务 — 这也是为什么我们需要 java.util.concurrent 的原因。Ted Neward 会向您说明并发 Collections 类,比如 CopyOnWriteArrayListBlockingQueue,还有 ConcurrentMap,如何针对您的并发编程需求改进标准 Collections 类。

Concurrent Collections 是 Java™ 5 的巨大附加产品,但是在关于注释和泛型的争执中很多 Java 开发人员忽视了它们。此外(或者更老实地说),许多开发人员避免使用这个数据包,因为他们认为它一定很复杂,就像它所要解决的问题一样。

事实上,java.util.concurrent 包含许多类,能够有效解决普通的并发问题,无需复杂工序。阅读本文,了解 java.util.concurrent 类,比如    CopyOnWriteArrayListBlockingQueue 如何帮助您解决多线程编程的棘手问题。

1. TimeUnit

尽管本质上 不是 Collections 类,但 java.util.concurrent.TimeUnit 枚举让代码更易读懂。使用 TimeUnit 将使用您的方法或 API 的开发人员从毫秒的 “暴政” 中解放出来。

TimeUnit 包括所有时间单位,从 MILLISECONDSMICROSECONDSDAYSHOURS,这就意味着它能够处理一个开发人员所需的几乎所有的时间范围类型。同时,因为在列举上声明了转换方法,在时间加快时,将 HOURS 转换回 MILLISECONDS 甚至变得更容易。

2. CopyOnWriteArrayList

创建数组的全新副本是过于昂贵的操作,无论是从时间上,还是从内存开销上,因此在通常使用中很少考虑;开发人员往往求助于使用同步的 ArrayList。然而,这也是一个成本较高的选择,因为每当您跨集合内容进行迭代时,您就不得不同步所有操作,包括读和写,以此保证一致性。

这又让成本结构回到这样一个场景:需多读者都在读取 ArrayList,但是几乎没人会去修改它。

CopyOnWriteArrayList 是个巧妙的小宝贝 :roll: ,能解决这一问题。它的 Javadoc 将 CopyOnWriteArrayList 定义为一个 “ArrayList 的线程安全变体,在这个变体中所有易变操作(添加,设置等)可以通过复制全新的数组来实现”。

集合从内部将它的内容复制到一个没有修改的新数组,这样读者访问数组内容时就不会产生同步成本(因为他们从来不是在易变数据上操作)。

本质上讲,CopyOnWriteArrayList 很适合处理 ArrayList 经常让我们失败的这种场景:读取频繁,但很少有写操作的集合,例如 JavaBean 事件的 Listeners。

(Copy on write 也是操作系统内存模型中一个非常常用和经典的策略,  类似的策略如Load on calling :为了防止程序启动时的加载开销,典型的 懒汉型的单例模式就是这样的~不过有时候需要Load on Startup ,比如启动的类依赖于很多其他的类时,或者为了确保资源的可用性,以免当调用的时候才报错(=  = 阿里的webx的启动就先要加载一堆Service对象的~))。

3. BlockingQueue

BlockingQueue 接口表示它是一个 Queue,意思是它的项以先入先出(FIFO)顺序存储。在特定顺序插入的项以相同的顺序检索 — 但是需要附加保证,从空队列检索一个项的任何尝试都会阻塞调用线程,直到这个项准备好被检索。同理,想要将一个项插入到满队列的尝试也会导致阻塞调用线程,直到队列的存储空间可用。

BlockingQueue 干净利落地解决了如何将一个线程收集的项“传递”给另一线程用于处理的问题,无需考虑同步问题。Java Tutorial 的 Guarded Blocks 试用版就是一个很好的例子。它构建一个单插槽绑定的缓存,当新的项可用,而且插槽也准备好接受新的项时,使用手动同步和 wait()/notifyAll() 在线程之间发信。

尽管 Guarded Blocks 教程中的代码有效,但是它耗时久,混乱,而且也并非完全直观。退回到 Java 平台较早的时候,没错,Java 开发人员不得不纠缠于这种代码;但现在是 2010 年 — 情况难道没有改善?

清单 1 显示了 Guarded Blocks 代码的重写版,其中我使用了一个 ArrayBlockingQueue,而不是手写的 Drop

import java.util.*;
import java.util.concurrent.*;

class Producer
    implements Runnable
{
    private BlockingQueue drop;
    List messages = Arrays.asList(
        "Mares eat oats",
        "Does eat oats",
        "Little lambs eat ivy",
        "Wouldn't you eat ivy too?");

    public Producer(BlockingQueue d) { this.drop = d; }

    public void run()
    {
        try
        {
            for (String s : messages)
                drop.put(s);
            drop.put("DONE");
        }
        catch (InterruptedException intEx)
        {
            System.out.println("Interrupted! " +
                "Last one out, turn out the lights!");
        }
    }
}

class Consumer
    implements Runnable
{
    private BlockingQueue drop;
    public Consumer(BlockingQueue d) { this.drop = d; }

    public void run()
    {
        try
        {
            String msg = null;
            while (!((msg = drop.take()).equals("DONE")))
                System.out.println(msg);
        }
        catch (InterruptedException intEx)
        {
            System.out.println("Interrupted! " +
                "Last one out, turn out the lights!");
        }
    }
}

public class ABQApp
{
    public static void main(String[] args)
    {
        BlockingQueue drop = new ArrayBlockingQueue(1, true);
        (new Thread(new Producer(drop))).start();
        (new Thread(new Consumer(drop))).start();
    }
}

ArrayBlockingQueue 还体现了“公平” — 意思是它为读取器和编写器提供线程先入先出访问。这种替代方法是一个更有效,但又冒穷尽部分线程风险的政策。(即,允许一些读取器在其他读取器锁定时运行效率更高,但是您可能会有读取器线程的流持续不断的风险,导致编写器无法进行工作。)

注意 Bug!

顺便说一句,如果您注意到 Guarded Blocks 包含一个重大 bug,那么您是对的 — 如果开发人员在 main() 中的 Drop 实例上同步,会出现什么情况呢?

BlockingQueue 还支持接收时间参数的方法,时间参数表明线程在返回信号故障以插入或者检索有关项之前需要阻塞的时间。这么做会避免非绑定的等待,这对一个生产系统是致命的,因为一个非绑定的等待会很容易导致需要重启的系统挂起。

4. ConcurrentMap

Map 有一个微妙的并发 bug,这个 bug 将许多不知情的 Java 开发人员引入歧途。ConcurrentMap 是最容易的解决方案。

当一个 Map 被从多个线程访问时,通常使用 containsKey() 或者 get() 来查看给定键是否在存储键/值对之前出现。但是即使有一个同步的 Map,线程还是可以在这个过程中潜入,然后夺取对 Map 的控制权。问题是,在对 put() 的调用中,锁在 get() 开始时获取,然后在可以再次获取锁之前释放。它的结果是个竞争条件:这是两个线程之间的竞争,结果也会因谁先运行而不同。

如果两个线程几乎同时调用一个方法,两者都会进行测试,调用 put,在处理中丢失第一线程的值。幸运的是,ConcurrentMap 接口支持许多附加方法,它们设计用于在一个锁下进行两个任务:putIfAbsent(),例如,首先进行测试,然后仅当键没有存储在 Map 中时进行 put。

5. SynchronousQueues

根据 Javadoc,SynchronousQueue 是个有趣的东西:

这是一个阻塞队列,其中,每个插入操作必须等待另一个线程的对应移除操作,反之亦然。一个同步队列不具有任何内部容量,甚至不具有 1 的容量。

本质上讲,SynchronousQueue 是之前提过的 BlockingQueue 的又一实现。它给我们提供了在线程之间交换单一元素的极轻量级方法,使用 ArrayBlockingQueue 使用的阻塞语义。在清单 2 中,我重写了1的代码,使用 SynchronousQueue 替代 ArrayBlockingQueue

清单 2. SynchronousQueue

import java.util.*;
import java.util.concurrent.*;

class Producer
    implements Runnable
{
    private BlockingQueue drop;
    List messages = Arrays.asList(
        "Mares eat oats",
        "Does eat oats",
        "Little lambs eat ivy",
        "Wouldn't you eat ivy too?");

    public Producer(BlockingQueue d) { this.drop = d; }

    public void run()
    {
        try
        {
            for (String s : messages)
                drop.put(s);
            drop.put("DONE");
        }
        catch (InterruptedException intEx)
        {
            System.out.println("Interrupted! " +
                "Last one out, turn out the lights!");
        }
    }
}

class Consumer
    implements Runnable
{
    private BlockingQueue drop;
    public Consumer(BlockingQueue d) { this.drop = d; }

    public void run()
    {
        try
        {
            String msg = null;
            while (!((msg = drop.take()).equals("DONE")))
                System.out.println(msg);
        }
        catch (InterruptedException intEx)
        {
            System.out.println("Interrupted! " +
                "Last one out, turn out the lights!");
        }
    }
}

public class SynQApp
{
    public static void main(String[] args)
    {
        BlockingQueue drop = new SynchronousQueue();
        (new Thread(new Producer(drop))).start();
        (new Thread(new Consumer(drop))).start();
    }
}

实现代码看起来几乎相同,但是应用程序有额外获益:SynchronousQueue 允许在队列进行一个插入,只要有一个线程等着使用它。

在实践中,SynchronousQueue 类似于 Ada 和 CSP 等语言中可用的 “会合通道”。这些通道有时在其他环境中也称为 “连接”,这样的环境包括 .NET .

结束语 :roll:

当 Java 运行时知识库提供便利、预置的并发性时,为什么还要苦苦挣扎,试图将并发性导入到您的 Collections 类?本系列的下一篇文章将会进一步探讨 java.util.concurrent 名称空间的内容。

本文参考资料

      URL标准优化最佳实战

      URL 设计是 Web 设计中常被忽视的东西,事实上 URL 非常重要,这不仅是一个网页唯一的路径,还涉及到你的站点是否干净,友好。本文讲述 URL 这个司空见惯的 Web 元素中包含的大量不应为忽视的知识,准则与最佳实践。需要注意的是 W3C 建议使用 URI 取代 URL 一说

      关于 URL 的一些准则

      首先是与 URL 有关的一些准则。

      一个 URL 必须唯一地,永久地代表一个在线对象

      URL 的最基本的使命是唯一地代表 Internet 上的一个对象,URL 必须和 Internet 上的对象一对一匹配。然而现实中,这很难实现,我们经常可以通过多个 URL 到达同一个页面,比如, http://mysite.com/product/tv 和 http://mysite.com/product?name=tv,这种情形在现代 CMS 中更是比比皆是,针对这个问题,SEO moz 有一篇很好的文章,讲到了如何使用 Canonical URL 机制解决站点中的重复 URL 问题

      URL 应该是永久的,这就要求你在站点上线前就非常严谨地规划 URL。如果有一天,你不得不更改 URL,一定使用 HTTP 301 机制(我对产品经理新视线 做了301转向,在cpanel里面设置了 把 http://gstarwd.com 永久重定向到 http://www.gstarwd.com 之前没做重定向~被百度降权了~~悲剧 ),告诉浏览器和搜索引擎,你的那个 URL 所代表的对象,已经搬迁到新地址,这个机制可以保证你旧地址所获得 PR 不会被清零。

      关于这点~REST的理念就发源于URI的唯一定位的概念~

      尽可能用户友好

      这是 URL 设计的根本,你的 URL 应该为最终用户而设计。保持 URL 友好的一个好办法是在保证可读性的同时让它尽可能短。

      比如 /about 就好过 /about-acme-corp-page,当然,保持简短不能牺牲可读性, /13d2 一类的地址短则短矣,但并不友好。如果要在 人人 Twitter, Facebook 一类的社会媒体网络分享你的 URL,可以使用 Bit.ly 一类的网址缩短工具,但这种工具产生的缩短 URL 并不友好,在 WordPress 一类的 CMS 中,可以使用 PrettyLink ProShort URL plugin 一类的可控制的地址缩短插件。(产品经理新视线用的是permanent link 插件~)

      image

      wordpres shortlink插件

      URL 的设计切忌使用一些对用户来说没有意义的内容,比如数据库的 ID 号, /products/23 这样的 URL 地址对用户是极不友好的,应当使用 /products/ballpoint-pen 一类的地址。

      保持一致性

      站点内的所有 URL 必须保持一致的格式和结构,这样可以为用户带来信任感,如果你必须更改 URL 格式和结构,需要使用 HTTP 301 机制。

      可预测的 URL

      这也是 URL 一致性的一个表现,如果你的 URL 拥有很好的一致性,用户可以根据 URL 猜测别的内容的 URL,假如 /events/2010/01 指向 2010 年 1 月份的日程内容,那

      • /events/2009/01 应当指向 2009 年 1 月的日程。
      • /events/2010 应当指向 2010 年全年的日程。
      • /events/2010/01/21 应当指向2010年1月21日的日程。

      URL中的关键词

      URL 中应该包含本页重点内容的关键词,比如 /posts/2010/07/02/trip-best-buy-memory-cards 一类的 URL 本身就是对页面内容的反应。在 URL 包含重点内容关键词,也可以提高 SEO 性能。SEO 的一个很重要的原则就是,在 URL 地址中包含内容关键词。

      关于 URL 的技术细节

      下面说的是有关 URL 的一些技术细节。

      URL 不应包含 .html, aspx, cfm 一类的后缀

      这类信息对最终用户是没有意义的,却占了额外的空间,一个例外是 .atom, .rss, .json 一类的特殊地址,这类地址是有特别的意义的。译者注:在某些虚拟主机式 Web 服务器,这种做法未必现实。

      URL 不应包含 WWW 部分

      WWW 部分并不包含任何意义,是一个额外的负担,不友好。可以使用 HTTP 301 机制,将 www.domain.com 定向到 domain.com 。(这个我会在做新站的时候考虑~我之前一直以为带www会比较好~~但是事实证明 不带更好~因为不带www的收录更快! 搜索引擎更加喜欢~)

      URL 的格式

      URL 的格式如下:

      domain.com/[key information]/[name]/?[modifiers]

      Key information 部分一般代表信息的类型或类别。Modifiers 部分则属于查询字符串范畴,它不应当代表数据结构,应当代表数据的修饰。Key information 部分应当尽可能简短,同时应当表现出一种层级关系,比如 http://domain.com/posts/servers/nginx-ubuntu-10.04,或 http://domain.com/news/tech/2007/11/05/google-announces-android。

      Google News 对新闻源有一个有趣的要求Google 要求新闻源页面的 URL 中必须包含至少 3 位唯一的数字,因为他们会忽略年份数字,因此,应该使用一个5位或5位以上的数字。另外,也应该提供 Google News 站点地图 。如果你想向 Google 提供新闻,必须按这样的结构提供 URL,当然保持一致性,可以预测性也是必需的。

      使用小写字符

      URL 中所有字符都应使用小写,这更容易阅读。

      URL 中包含的行为元素

      URL 查询字符串中可能包含一些表示行为的元素,比如 show, delete, edit 等。非破坏性的行为可以体现在 URL 中,破坏性的行为应该使用 POST 。

      使用 URL 友好字符

      在 URL 中体现网页标题的时候,往往会用到一些特殊字符,应当把它们转换为 URL 友好字符:

      • 全部大写字符换成小写
      • 诸如 é 一类的字符应转换成对应的 e
      • 空格使用短划线代替
      • 诸如 !, @, #, $, %, ^, &, * 一类的字符应该使用短划线代替
      • 双短划线应该使用单短划线代替

      另外,没有必要的话,避免使用 %20 一类的 URL 逃逸符。

      更多观点

      Chris Shiflett 建议,可以使用一些类似句子的 URL,如:

      chriscoyier.net/authored/digging-into-wordpress/
      chriscoyier.net/has-worked-for/chatman-design/
      chriscoyier.net/likes/trailer-park-boys
      jacobwg.com/thinks/this-post/is/basically-done

      译者补充:URL 的长度上限

      URL 的最大长度是多少?W3C 的 HTTP 协议 并没有限定,然而,在实际应用中,经过试验,不同浏览器和 Web 服务器有不同的约定:

      • IE 的 URL 长度上限是 2083 字节,其中纯路径部分不能超过 2048 字节。
      • Firefox 浏览器的地址栏中超过 65536 字符后就不再显示。
      • Safari 浏览器一致测试到 80000 字符还工作得好好的。
      • Opera 浏览器测试到 190000 字符的时候,还正常工作。

      Web 服务器:

      • Apache Web 服务器在接收到大约 4000 字符长的 URL 时候产生 413 Entity Too Large” 错误。
      • IIS 默认接收的最大 URL 是 16384 字符。

      Reference:css-tricks.com Guidelines for URI Design

      Servlet相关

      这个东西是现在J2EE下的基础component了~
      作为一个开发者~其实非常容易的使用~但是只是停留在开发的层面上还是远远不够的~
      之前开发过很多servlet的程序。~我甚至不知道:
      他的架构里主要类是啥?他和WEB容器的调优解决方案都有哪些?SessionId是怎么生成的?
      其实这些都是基础 呜呜~我之前不重视~~不过还来得及~我慢慢加强基础。。。。。 :|
      现在有空稍微整理下~

      老生常谈之servlet生命周期~

      其实servlet本质是多线程~为了解决内存资源不够的问题。对比于CGI可以说明问题。

      Servlet的生命周期分为5个阶段:
      实例化:Servlet容器创建Servlet类的实例。
      初始化:该容器调用init()方法,通常会申请资源。
      服务:由容器调用service()方法,(也就是doGet()和doPost())。
      破坏:在释放Servlet实例之前调用destroy()方法,通常会释放资源。
      不可用:释放内存的实例。
      
      CGI(Common Gateway Interface通用网关接口)程序来实现数据在Web上的传输,使用的是如Perl这样的语言编写的,它对于客户端作出的每个请求,必须创建CGI程序的一个新实例,这样占用大量的内存资源。由此才引入了Servlet技术。
      
      Servlet是一个用java编写的应用程序,在服务器上运行,处理请求信息并将其发送到客户端。对于客户端的请求,只需要创建Servlet的实例一次,因此节省了大量的内存资源。Servlet在初始化后就保留在内存中,因此每次作出请求时无需加载。
      JSP 的执行过程
      (1) 客户端发出Request (请求);
      (2) JSP Container 将JSP转译成Servlet的源代码;
      (3) 将产生的Servlet 的源代码经过编译后,并加载到内存执行;
      (4) 把结果Response (响应)至客户端。
      在执行 JSP 网页时,通常可分为两个时期:转译时期(Translation Time)和请求时期(Request Time)
      转译时期:JSP网页转译成Servlet类。
      请求时期:Servlet类执行后,响应结果至客户端。
      注:
      转译期间主要做了两件事情:将JSP网页转译为 Servlet 源代码(.java),此段称为转译时期(Translation time);将Servlet源代码(.java)编译成 Servlet 类(.class),此段称为编译时期(Compilation time)。

      Servlet 的生命周期
      1) 产生 Servlet,加载到Servlet Engine中,然后调用 init()这个方法来进行初始化工作。
      2) 以多线程的方式处理来自Client 的请求。 service()
      3) 调用 destroy()来销毁Servlet,进行垃圾收集 (garbage collection)。
      Servlet 从产生到结束的流程
      1. 加载和实例化
      当Container一开始启动, 或是客户端发出请求服务时, Container会负责加载和实例化一个Servlet。
      2. 初始化
      Servlet 加载并实例化后,再来Container必须初始化 Servlet。初始化的过程主要是读取配置 信息(例如JDBC连接)或其他须执行的任务。我们可以借助 ServletConfig 对象取得 Container的 配置信息,例如:
      <servlet>
      <servlet-name>HelloServlet</servlet-name>
      <servlet-class>tw.com.javaworld.CH2.HelloServlet</servlet-class>
      <init-param>
      <param-name>user</param-name>
      <param-value>browser</param-value>
      </init-param>
      </servlet>
      其中user为初始化的参数名称;browser 为初始化的值。因此,可以在 HelloServlet程序中使用ServletConfig 对象的getInitParameter(“user”)方法来取得 browser。
      3. 处理请求
      Servlet被初始化后,就可以开始处理请求。每一个请求由 ServletRequest 对象来接收请求;而ServletResponse对象来响应该请求。
      4. 服务结束
      当 Container 没有限定一个加载的 Servlet 能保存多长时间,因此,一个 Servlet 实例可能只在Container中存活几毫秒,或是其他更长的任意时间。一旦 destroy( )方法被调用时,Container将移除该 Servlet,那么它必须释放所有使用中的任何资源,若 Container 需要再使用该 Servlet时,它必须重新建立新的实例。
      1. Servlet的生命周期?
      Servlet是一种可以在Servlet容器中运行的组件,那么理所当然就应该有一个从创建到销毁的过程,这个过程我们可以称之为 Servlet生命周期Servlet的生命周期可以分为加载、实例化、初始化、处理客户请求和卸载五个阶段,体现在方法上主要是init()、 service()和destroy()三个方法。生命周期的具体说明如下:

      Servlet容器完成加载Servlet类和实例化一个Servlet对象
      init()方法完成初始化工作,该方法由Servlet容器调用完成
      service()方法处理客户端请求,并返回响应结果
      destroy()方法在Servlet容器卸载Servlet之前被调用,释放一些资源
      2. Servlet的实例是在生命周期什么时候创建的? 配置servlet最重要的是什么?
      Servlet实例是在servlet第一次在容器中被加载的是时候创建的, Init()方法是用来配置这个servlet实例的,这个方法在servlet的生命周期中只被调用一次,所以应该把所有servlet生命周期中的配置操作都写在这个方法法里面。
      3. 为什么不在Servlet中写一个构造(Contructor)方法?
      容器会自动为Servlet写一个无参的构造方法
      4.  我们没有写servlet的构造方法,那么容器是怎么创建servlet的实例呢?
      容器会自动为Servlet写一个无参的构造方法,容器是用Class.forName(className).newInstance()来创建servlet的实例的。
      5. 当容器调用servlet的destory()方法的时候,servlet会马上销毁么? 如果当时这个servlet正在执行其他任务或者线程呢?
      是的, 当容器调用servlet的destory()方法的时候,servlet会马上销毁,但是容器在调用destory()方法之前,会等servlet的service()方法结束剩余的任务。
      6. 用ServletRequest和ServletContext调用ReqestDispatcher有什么区别?
      在用ServletRequest调用RequestDispatcher的时候可以用相对URL, 但是ServletContext不行。
      7. 为什么在用ServletRequest.getRequestDispatcher()的时候可以用相对URL而用ServletContext.getRequestDispatcher()的时候不可以?
      因为ServletRequest包含当前的request path,可以用当前的request path去计算URL,但是ServletContext不包含当前的request path。
      例如:

      Java代码
      1. RequestDispatcher rd = request.getRequestDispatcher(“\error.jsp”);
      2. rd.forward(request, response);
    • 关于Session的持久化
    • 作用:
      提高服务器内存利用率,保持会话
      集群系统中session对象的复制
      Web应用程序关闭后重启,会话继续
      背景:如果内存中大量HttpSession对象堆积,会造成大量的内存消耗~毕竟session不是cookie那样,大小限制的很死。
      解决方案:Web服务器将暂时不活动但未超时的HttpSession对象转移到文件系统或DB中保存,一旦需要他们是,再从File system活DB中装载进内存;
      应用:Tomcat的Session持久化管理
      Tomcat使用Session Manager类来管理Session的持久化
      StandardManager:
      默认的方法。当Tomcat服务器重启或重载的时候,会把Session对象保存到 <%CATALINA_HOME%>/work/Catalina/honstname/applicatonname/SESSIONS.ser
      PersitentManager
      更加灵活的管理方式,配置性强
      可以存储在本地文件( (节点下添加如下节点)和数据库中(配置store节点 )

      sessions.ser的生成和加载:
      形式一:存储在本地文件中:配置conf目录里的context.xml文件 在节点下添加如下节点:

           debug=0     saveOnRestart="true"     maxActiveSession="-1"     minIdleSwap="-1"     maxIdleSwap="-1"     maxIdleBackup="-1"       
      

      形式二:存储在数据库中 配置store节点

      Tomcat在启动时加载,并不是需要时加载.

    • Servlet容器如何处理多个请求?
    • Servlet采用多线程来处理多个请求

      当容器收到一个访问Servlet的请求,调度者线程从线程池中选出一个工作者线程,将请求传递给该线程,然后由该线程来执行Servlet的service方法。 当这个线程正在执行的时候,容器收到另外一个请求,调度者线程将从池中选出另外一个工作者线程来服务新的请求,容器并不关系这个请求是否访问的是同一个Servlet还是另外一个Servlet。 当容器同时收到对同一Servlet的多个请求,那这个Servlet的service方法将在多线程中并发的执行。 (这里容易出问题,所以在servlet尽量减少成员变量(实例变量)的声明,如果必须使用 ,那么需要加上同步操作,保持同步。见下)
      线程池实际上是等待执行代码的一组线程叫做工作者线程(Worker Thread),Servlet容器使用一个调度线程来管理工作者线程(Dispatcher Thread)。 )

      变量的线程安全:
      实例变量是线程不安全的
      解决:
      实例变量—-》局部变量(方法级变量 每次方法调用的时候构造~结束的时候销毁)
      同步doXXX()方法
      (本地变量:每个线程都将拥有user变量的拷贝,线程在对自己栈中的本地变量的改变不会影响其他线程本地变量的拷贝)
      Servlet容器对它所接收到的每一个请求,都创建一个新的ServletRequest对象,所以ServletRequest对象只在一个线程中被访问。
      在这些窗口的访问请求,属于同一个session,为了同时处理多个这样的请求,Servlet容器会创建多个线程,而在这些线程中,就可以同时访问到Session对象的属性。

      并发编程探索-之写线程安全的Java代码

      我们在写Java程序的时候,何时需要进行并发控制,关键在于判断这段程序或这个类是否是线程安全的。

      当多个线程访问一个类时,如果不用考虑这些线程在运行时环境下的调度和交替执行,并且不需要额外的同步,这个类的行为仍然是正确的,那么称这个类是线程安全的。我们设计类就是要在有潜在并发问题存在情况下,设计线程安全的类。线程安全的类可以通过以下手段来满足:

      • 不跨线程共享变量
      • 使状态变量为不可变的
      • (就是通常所说的无状态)

      • 在任何访问状态变量的时候使用同步。
      • 每个共享的可变变量都需要由唯一一个确定的锁保护。

      满足线程安全的一些思路

      1)从源头避免并发问题

      很多开发者一想到有并发的可能就通过底层技术来解决问题,其实往往可以通过上层的架构设计和业务分析来避免并发场景。比如我们需要用多线程或分布式 集群来计算一堆客户的相关统计值,由于客户的统计值是共享数据,因此会有并发潜在可能。但从业务上我们可以分析出客户与客户之间数据是不共享的,因此可以 设计一个规则来保证一个客户的计算工作和数据访问只会被一个线程或一台工作机完成,而不是把一个客户的计算工作分配给多个线程去完成。这种规则很容易设 计。当你从源头就避免了并发问题的可能,下面的工作就完全可以不用担心线程安全问题。

      2)无状态就是线程安全

      多线程编程或者分布式编程最忌讳有状态,一有状态就不但限制了其横向扩展能力,也是产生并发问题的起源。当你设计的类是无状态的,那么它永远都是线程安全的。因此在设计阶段需要考虑如何用无状态的类来满足你的业务需求(到底如何把有状态的类变成无状态的呢?)

      3)分清原子性操作和复合操作

      所谓原子性,是说一个操作不会被其他线程打断,能保证其从开始到结束独享资源连续执行完这一操作。如果所有程序块都是原子性的,那么就不存在任何并 发问题。而很多看上去像是原子性的操作正式并发问题高灾区。比如所熟知的计数器(count++)和check-then-act,这些都是很容易被忽视 的,例如大家所常用的懒汉式初始化模式(单例模式中的一种形式),以下代码就不是线程安全的:

         @NotThreadSafe
         public class LazyInitRace {
          private ExpensiveObject instance = null;
             public ExpensiveObject getInstance() {
               if (instance == null) {
                 instance = new ExpensiveObject();
               }
               return instance;
            }
         }
      

      这段代码具体问题在于没有认识到if(instance==null)和instance = new ExpensiveObject();是两条语句,放在一起就不是原子性的,就有可能当一个线程执行完if(instance==null)后会被中断, 另一个线程也去执行if(instance==null),这次两个线程都会执行后面的instance = new ExpensiveObject();这也是这个程序所不希望发生的。(单例模式)

      虽然check-then-act从表面上看很简单,但却普遍存在与我们日常的开发中,特别是在数据库存取这一块。比如我们需要在数据库里存一个客 户的统计值,当统计值不存在时初始化,当存在时就去更新。如果不把这组逻辑设计为原子性的就很有可能产生出两条这个客户的统计值。(难道transaction不能保证?)

      在单机环境下处理这个问题还算容易,通过锁或者同步来把这组复合操作变为原子操作,但在分布式环境下就不适用了。一般情况下是通过在数据库端做文章,比如通过唯一性索引或者悲观锁来保障其数据一致性。当然任何方案都是有代价的,这就需要具体情况下来权衡。

      另外,java1.5以后提供了一套提供原子性操作的类,有兴趣的可以研究一下它是如何在软件层面保证原子性的。

      4)锁的合理使用

      大家都知道可以用锁来解决并发问题,但在具体使用上还有很多讲究,比如:

      • 每个共享的可变变量都需要由一个确定的锁保护。
      • 一旦使用了锁,就意味着这段代码的执行就丧失了操作系统多道程序的特性,会在一定程度上影响性能
      • 锁不能解决在分布式环境共享变量的并发问题

      来一道面试题 :razz: :线程有几种状态?
      在Java当中,线程通常都有五种状态,创建、就绪、运行、阻塞和死亡。
      第一是创建状态。在生成线程对象,并没有调用该对象的start方法,这是线程处于创建状态
      第二是就绪状态。当调用了线程对象的start方法之后,该线程就进入了就绪状态,但是此时线程调度程序还没有把该线程设置为当前线程,此时处于就绪状态。在线程运行之后,从等待或者睡眠中回来之后,也会处于就绪状态
      第三是运行状态。线程调度程序将处于就绪状态的线程设置为当前线程,此时线程就进入了运行状态,开始运行run函数当中的代码。
      第四是阻塞状态。线程正在运行的时候,被暂停,通常是为了等待某个时间的发生(比如说某项资源就绪)之后再继续运行。sleep,suspend等方法都可以导致线程阻塞。
      第五是死亡状态。如果一个线程的run方法执行结束,该线程就会死亡。对于已经死亡的线程,无法再使用start方法令其进入就绪状态。

      最后附上线程的状态转移图(复习啦~其实准备找工作和考研复习的东西还是差不多的~但是就是不做题。。哈哈):
      image

      线程状态

      image

      线程状态

      下面这个图有点丑
      image

      线程状态

      下面这个图很无语 :evil:
      image

      线程状态

      参考文章:http://blog.csdn.net/cutesource/archive/2010/08/01/5780486.aspx

      无觅相关文章插件,快速提升流量