Lost(本blog所有图片都放在picasa,如不能访问,请翻墙)

January 15, 2012

观察者模式

Filed under: 设计模式 — admin @ 5:23 am

1.应用场景:当有一对象的改变,需要通知多个接收方时,需要使用这种模式。

2.解决的问题:使用观察者模式,被观察对象和观察者之间耦合比较松(通过实现各自的接口来达到这个目的,被观察者不需要感知具体的观察者,只要知道观察者接口即可,其也不需要了解观察者的数量;观察者其实也可以不用感知具体的被观察对象,只要知道其接口即可),被观察对象可以挂载多个观察者,且添加和删除观察者,对于被观察对象没有任何影响。

场景举例:当发生一个新闻事件(Subject)时,会有多个通讯社(Observer)进行报道。使用jdk自带的Observable,Observer接口。

public class News extends Observable{

	private String content;

	public String getContent() {
		return content;
	}

	public void setContent(String content) {
		this.content = content;
		this.setChanged();
		this.notifyObservers(content);
	}

}

新闻类,观察者模式中的主题。

public class CCTVReporter implements Observer{

	@Override
	public void update(Observable o, Object arg) {
		System.out.println(this.getClass().getName()+ ":The news's content has changed!");

	}

}

观察者一,这个观察者比较二,不报道新闻内容。

public class BBCReporter implements Observer{

	@Override
	public void update(Observable o, Object arg) {
		System.out.println(this.getClass().getName()+ ":The news's content has changed! The content is:"+(String)arg);

	}
}

观察者二,这个观察者比较靠谱,能够报道新闻的内容。

@Test
	public void oneToMany() {

		News news = new News();
		news.addObserver(new BBCReporter());
		news.addObserver(new CCTVReporter());
		news.setContent("大事不好了!!!");
	}

一个subject上注册了多个Observer。

com.dazhexiang.designpattern.Observer.CCTVReporter:The news's content has changed!
com.dazhexiang.designpattern.Observer.BBCReporter:The news's content has changed! The content is:大事不好了!!!

输出内容

@Test
	public void manyToOne() {

		News goodNews = new News();

		goodNews.addObserver(new BBCReporter());

		goodNews.setContent("太好了!!!");

		News badNews = new News();

		badNews.addObserver(new BBCReporter());

		badNews.setContent("大事不好了!!!");

	}

一个Observer注册了多个Subject

com.dazhexiang.designpattern.Observer.BBCReporter:The news's content has changed! The content is:太好了!!!
com.dazhexiang.designpattern.Observer.BBCReporter:The news's content has changed! The content is:大事不好了!!!

输出内容。

November 7, 2011

职责链模式

Filed under: 设计模式 — admin @ 2:39 pm

解决的问题:调用者和其target解耦,可以动态增加target,可以在运行时根据每次调用的具体情况,来找到对应的target。

应用场景:对于一个请求,可能有很多target是潜在的处理者,具体是哪个target来处理,需要由该request的一些属性来决定,同时,target可能有很多,而且可能会不定期增加或者删除target。UNIX/IO,操作系统抽象出io stream,当具体的进程和各个不同的device打交道时,不用感知具体的device 的实现细节,而是就是通过io stream来打交道,各个device和其drvier,就形成职责连,拦截各个请求,发现是自己的请求,就进行处理。java servlet 里的filter机制。

职责连模式中,具体的每个节点叫interceptor。

职责链模式不像pipeline,当其中一个interceptor能够处理该请求时,该请求会在该interceptor中进行处理,而不会传递到下一个interceptor,而pipeline则会。

职责链模式和decorate模式的区别,我个人认为decorate是为了增加额外的行为(其使用decorator模式是,decorator需要感知被装饰类,而在职责链模式中,各个interceptor间就没有这种耦合);而职责链模式最大的好处是为了让请求和target解耦。

场景举例:一个公司,需要处理很多类型的订单。订单的种类不同,会导致需要处理的员工的类型也不同。比如小订单由初级员工处理,大订单由高级员工来处理。而且以后员工的类型可能还会再增加。

示例代码:


package com.dazhexiang.designpatter.COR;
public class Order {

private String name;
public String getName() {

return name;

}
public void setName(String name) {

this.name = name;

}
}

订单类,通过订单的name来区分订单类型。


public abstract class Employee {
protected Employee nextOne;

public abstract void dealOrder(Order order);

public void setNextEmployee(Employee e){

this.nextOne = e;

}

}

employee 抽象基类,通过setNextEmployee来构建employee chain。


public class JuniorEmployee extends Employee {
@Override

public void dealOrder(Order order) {

if(order.getName().equalsIgnoreCase("small order")){

System.out.println("junior dealed it!");

}else{

if(null != this.nextOne){

this.nextOne.dealOrder(order);

}else{

System.out.println("no one can deal it!");

}

}

}
}
public class SeniorEmployee extends Employee {

	@Override
	public void dealOrder(Order order) {
		if (order.getName().equalsIgnoreCase("big order")) {

			System.out.println("senior dealed it!");
		} else {

			if (null != this.nextOne) {

				this.nextOne.dealOrder(order);
			} else {

				System.out.println("no one can deal it!");
			}
		}

	}

}

employee 的2个具体子类


public class CORExample {
public static void main(String args[]){

Employee employeeChain = new JuniorEmployee();

employeeChain.setNextEmployee(new SeniorEmployee());
Order smallOrder = new Order();

smallOrder.setName("small order");

employeeChain.dealOrder(smallOrder);

Order bigOrder = new Order();

bigOrder.setName("big order");

employeeChain.dealOrder(bigOrder);	}}

构建职责链,此例子中,interceptor是一个具体的Employee类,其中,处理规则是JuniorEmployee处理小订单,而SeniorEmployee来处理大订单。这个例子里,由于Employee的子类不是很多,还没有特别体现出职责链的优势,可以设想一下,如果Employee子类很多,而又不实用职责链模式的话,为了实现业务逻辑,势必在主函数里会写大段的if{}else{}判断,增加代码的复杂度。

职责链的变体:如果在业务中,当一个请求进来时,可能需要多个interceptor组合进行处理,比如在上述例子中,如果出现了HugeOrder,需要SeniorEmployee和ManagerEmployee来进行处理,此时如何处理?我觉得大体上有2种方法,一种是将SeniorEmployee和ManagerEmployee组合起来,构建一个新的Employee链;还有一种方法是在每个employee里,根据自己需要处理的Order类型,来写一些比较复杂的逻辑,判断是否需要进行处理。前面的解决办法,如果组合情况一多,有可能造成产生多种Employee链;后一种方法的话,需要每个interceptor写一些比较复杂的判断逻辑。

上述例子的UML图:

October 18, 2011

自己总结的一些编程实践

Filed under: Uncategorized — admin @ 7:13 am

1.尽量保持简单
2.尽量代码复用,一旦发现有重复代码,一定要想办法消除掉
3.建立适当的抽象层次&分层
4.尽早做单元测试,测试不要叠加;多个没有进过测试的类或模块叠加起来进行测试,其代价可能比每个单独测试要大的多
5.对于遗留代码,在新写代码时,要保持不作恶的原则;即也许你没有时间去修改老的恶心代码,但是当你新增加代码时,就不要继续增加恶心代码,而是应该遵守新的标准。
未完,待续

June 16, 2011

hotspot内部对synchronize的实现

Filed under: open jdk — admin @ 8:31 am

背景:
Java语言相较于c,c++的一个优势,是其从语言层面就提供了对多线程同步以及相互协作的支持;而不像c,c++,要通过其他的库才能写多线程程序。在Java语言里,通过synchronize关键字来同步一段关键代码片断(critical area),而通过wait,notify来实现线程间相互协作。本文主要描述一下sun的hotspot是如何来支持synchronize及wait和notify的。

在hotspot的实现里,每个java对象有一个对应的c++对象,每个用来表达java对象的c++对象都有一个header,占用2个word,第一个word成为mark word,第二个word则用来指向一个klass,该klass用来表征该对象的类型信息。第一个mark word,在某些情况下会用来表示同步信息,所以需要我们重点探究一下。

1.Mark Word
mark word是用来表征对象一些信息的对象头,目前依据jvm是32位还是64位,而分为32位64位2种形式。由于2者差别不大,所以我们这里以32位为例子,来讲解一下mark word。
在32位jvm上,markword为32位长,根据具体各个bit的数值的不同,可以组合成4种大类(表明该头markword所在的对象当前的状态),具体如下图所示。

我们现在需要关注的是前2类,即当markword表示该对象一个普通java对象或者被biased locking的对象。

2.hotspot当前对synchronize的实现类型及其应用场景
当前的hotspot共有3种类型的锁,来实现synchronize的语义,之所以有3种,是因为这3种要解决的问题不同,所做的优化也不同。这3种锁分别为biased locking,stack lock,infalted(ObjectMonitor).简单除暴的来讲,从轻量级上来说,biased lock最优,inflated 最差。

为了便于大家的认知,先从优化做的最少的Infalte锁开始讲起。
2.1 Inflate锁的实现以及其应用场景
当该锁被不同的线程争用或者有线程调用该对象那个的wait方法时,会使用Infalte锁。Inflate的实现方式是将该object的markword指向一个ObjectMonitor对象,然后利用该ObjectMonitor来实现同步以及wait,notify功能。
下面来简单介绍一些ObjectMonitor的主要字段及方法,其主要字段有:
1._owner,用来表示当前持有该monitor的线程,如果没有没有被线程持有,则为null;本质上来讲,一个线程要想持有该monitor,就必须在该monitor的_owner字段为null时,使用原子操作来将该字段替换成自己。
2._cxq,一个ObjectWaiter类型的链表,ObjectWaiter代表了一个等待该锁的线程对象。在hotspot里,把最近的需要获取该锁而block住的线程都放入到_cxq里。
3._EntryList,也是一个ObjectWaiter类型的链表,像较于_cxq,其区别是_EntryList里存放的线程可能等待的时间都比较久了。之所以要分成2个链表,一来是这样可以使得_cxq的元素数量比较适中,这样当当前线程要释放该锁时,如果采取首先从_cxq中去寻找下一个线程来获取该锁,时间会缩短。当一个线程释放锁时,根据策略,可能会将_cxq和_EntryList合并成一个链表。

4._WaitSet,用来存放调用该锁的wait()方法而还未被notify的线程。

5.Responsible,Thread类型,由于hotspot在锁释放时时,做了一些优化(没有做锁释放后,调用membar指令),很可能导致释放锁的线程已经将该锁释放,而想要竞争该锁的线程由于看到的是陈旧的数据(认为锁还是被释放线程持有),从而出现Stranding现象。因此,为了使Stranding现象尽可能的短,需要从众多竞争线程中指定一个Responsible线程,该线程在竞争锁失败后,不是调用永久的park,而是定期醒过来,检查当前的monitor是否已经可以空闲了。这个Responsible字段就是用来存放该线程的。

ObjectMonitor的主要方法的流程:

1.enter方法大致流程(线程要争用该锁时,调用此方法):1.如果该monitor还空闲,通过CAS来获取该锁。2.处理递归获取锁的问题(当前线程已经持有该锁了)。3.试图通过spin方式来获取monitor。4.调用EnterI方法来获取锁。

2.EnterI方法大致流程(争用锁过程中的一个内部方法):1.企图使用tryLock方式来获取monitor。2.企图使用spin方式来获取monitor。3.将自己放入到_cxq队列进行等待。4.进入一个无限循环,在该循环里,不断的通过tryLock方式来获取锁,一旦获取到锁,退出该循环。5.调用UnlinkAfterAcquire方法,将自己从_cxq和EntryList中剔除(因为此时已经获得锁了)。6.如果发现Responsible的值是自己,将Responsible字段赋值为null.

3.wait方法大致流程(当线程调用该锁的wait方法时,会调用此方法):1.将自己包装成WaiterObject,并且放入到_WaitSet链表里去,然后调用exit方法释放对锁的控制;2.如果此时发现线程被唤醒,则调用unpark方法,让线程开始运行。3.否则调用park方法,让本线程block。4,当线程从park方法返回时,说明有其他线程调用了该线程的unpark方法,此时线程将自己从_WaitSet中脱离出来,并对自己的状态进行处理,如果经过处理后,线程的状态为run,则调用enter方法,以便重新获取锁。5.如果线程状态不是run,则会进入retenter处理流程。6.当线程获得锁后,会坚持之前的过程是否有interrupt发生,如果有,抛出Interrupted异常。

4.exit方法大致流程(当线程要释放该锁时):1.处理递归退出情况(即该线程已经获取该monitor多次),2.判断继承人是否自己已经准备好,如果已经准备好了,则本线程直接退出,否则,进入步骤4,挑出一个继承者,并调用unpark激活其运行。否则,调用park方法来讲当前线程进行阻塞。

上面描述的是这些方法的大致流程,这一块的复杂性还在于为了确保对变量的可见性,需要在很多地方调用内存同步原语,比如OrderAccess::fence(),OrderAccess::storeload()等,非常的细节和专业。

总结:所以Inflated(ObjectMonitor)锁,由于其内部有各种字段,能够处理一般的锁争用,也能够处理wait,notify等情况。当要block一个锁时,其本质上调用和操作系统实现相关的park方法来阻塞当前线程,相当于是和系统函数有交互,成本较大同时,为了处理争用情况,调用了大量的内存同步原语,这些同步原语相对而言,会导致比较长时间的latency(相对于普通的计算机指令周期而言),而且如果使用Inflated锁,则需要为每个锁分配一个对应的ObjectMonitor对象,对内存也有一些损耗。

2.2Stack lock的实现以及其应用场景
当一个锁只被一个线程使用(不存在不同线程的争用情况),从而也没有wait,notify操作时,只要使用stack lock即可。stack lock是在持有该lock的线程堆栈上分配的,其自身有一个类型为markOop的_displaced_header字段,可以用来存放该锁所在对象的markword。

stack lock时线程获取锁的过程:
1.判断该锁所在的对象的markword信息是否表明该锁还没有被持有,如果是这样,则先将此markword信息存放进stack lock(此stack lock由jit从此线程的frame空间中分配,并且一个线程的所有lock都会被集中管理,便于处理递归获取与递归释放的情形)的_displaced_header字段,然后通过原子替换,将对象头的markword值替换为stack lock,如果成功,说明此线程第一次获取到该锁,退出。2.如果第一步没有成功,则判断该锁是否属于这个线程的堆栈空间,如果是这种情况,则表明该线程已经持有了该锁,这次行为是想再一次持有该锁,此时只需要将该lock的_displaced_header设置为NULL。表明此lock代表的是该线程对该锁的一次递归持有(非第一次持有)。

stack lock时线程释放锁的过程:
1.判断此时的stack lock的_displaced_header是否为null,如果是,则表明是一个代表递归锁的lock,do nothing,直接返回;2.如果_displaced_header不为null,则表明是最后一次释放该锁,通过原子替换,将_displaced_header里面存放的原始markword值拷贝会该锁所在的对象头。3.如果上述2种情况都不是,或者原子替换不成功,则说明此锁发生了多线程间的争用情况,系统对其进行inflate,将其变为inflate锁。

一些还未经证实的推测(这部分内容是通过在openjdk 邮件组上发问题,别人回复给我的,还没有查看相关代码,所以还未经证实):如果使用stack lock,则线程会在其frames里,持有一个stack lock chain,最外面的stack lock代表该线程第一次获取该锁(_displaced_header的值为markword),而后续的stack lock,则表示递归持有,其_displaced_header皆为null;当要释放一个锁时,会取出最近的stack lock,如果发现它的_displaced_header为null,则直接忽略,以此类推,只有当真正拿到最后一个stack lock时,其_displaced_header不会null,此时,该线程真正释放了该锁,需要将_displaced_header指向的markwrod放回到该锁所在对象的头部,从而进行复原,表明此时该锁已经没有线程持有了。

总结:stack lock锁,适用于同一个线程持有一个锁(可以多次持有),当其第一次持有和释放时,需要进行原子操作(递归持有和释放时,则不需要进行原子操作,代价小很多)。

2.3Biased lock的实现以及其应用场景
所谓biased lock,只译过来就是偏向锁,则是针对如果该锁在成个程序运行期间只被一个线程持有的情况做的优化。对于stack lock,毕竟当第一次获取锁和最终释放锁时,会产生CAS操作,而CAS操作在SMP机器上,由于需要同步cache和主存上的内容,会造成较大的延时(大概是几百个cpu指令周期),hotspot的开发人员觉得其性能太慢了( 这可能是我们长期写上层应用程序的人没法理解的),而biased lock的初衷就是为了减少CAS操作。同时,sun的研究人员也指出,现在的cpu设计人员,已经将很多精力focus在提高这类原子操作的性能上了,这也逐渐造成了biased lock收益越来越小的这样一个现实。

biased lock时线程释获取锁的过程:
1.线程通过判断该锁所在对象的头,确定其是否biasable & unbiased,如果是这种情况,则通过一个CAS操作,对该对象的头部进行操作,是得该对象头部的前25位为此Thread的地址。2.如果该锁已经被持有,则判断是否是本线程持有,如果是,则do nothing,直接获取锁。3.如果前2种情况都不是,则说明该锁有抢占,则会等待safepoint的情况,然后对持有该锁的线程进行操作,将其替换成stack lock形式(这里没有直接替换成infalte 形式的锁,sun的相关人员回答是因为如果在这里替换成inflate锁,代码会很复杂,不符合关注点分离原则)。
biased lock时线程释放锁的过程:
1.biased lock被释放时,不会对对象头做任何改变。直到有其他线程需要争用这个锁时,该锁才会被revoke或者rebiase,从而改变对象头。隐含的含义:当一个对象的对象头是biased,并且其markword的前25bit指向了一个线程时,系统并不能通过这些来真正断定该25bit指向的线程就拥有了该biased lock,系统还需要review该线程的frame,查看在frame里面的lock record相关的信息(有点类似于stack lock chain),要结合这些lock record,才能真正判定该线程是否真正持有该biased lock。

hotspot在biased lock上的一些实现特点:1.由于markword的复用性,所以biased lock和hashcode其实使用了同一个markword,这样的后果是如果一个lock时使用biased方式来表示的,而当我们对该对象调用了hashcode方法时,系统会计算其hashcode,而hashcode的值会覆盖biased的内容,从而造成biased lock被revoke成stack lock。这一点我个人感觉是一个很大弊端,因为在现实的应用中,计算一个object的hashcode是非常普遍的操作,而如果因此造成biased lock被revoke的话,这样其实对性能影响挺大的。2.sun还根据epoch来确定是否对某一个type的所有对象都进行revoke,而epoch的值则根据运行时的各个对象的biased lock的revoke情况统计计算出来的。

biased lock和stack lock使用场景的异同:
1.前面已经说过了,stack lock的使用场景是没有竞争的情景;那么biased lock的使用场景则是既没有竞争,又没有共享的情况(即该lock在整个生命周期里,只为一个线程持有)。
2.在同一个线程里,当执行如下代码时:

public void synchronized foo(){
doSomeThing();
}

public void synchronized bar(){
doSomeThing();
}

public void test(){
for(int i = 0; i < 10; i++){
foo();
bar();
}
}

如果只有一个线程执行这个test方法时,如果使用biased locking,则只会产生一次CAS,第一次获取锁时,需要通过原子操作来将Thread的地址设置到markword里(退出test方法时,markword的值仍不改变,只有当其他线程来获取该锁时,才会被改变);如果只使用普通的stack lock(没有使用coarseing lock技术时),则需要进行40次的CAS。

最后,贴上一张图(引用至http://wikis.sun.com/display/HotSpotInternals/Synchronization),来表明在各种状态下,markword里的内容:

1.当后3位为101时,表明是biasable lock。
2.当后3位为001时,表明是regular object。
3.当后2位为00时,表明是stack lock,前面的bit用来指向在线程堆栈中的lock record。
4.当后2位为10时,表明是object monitor,前面的bit用来指向object monitor的地址。
一些链接:
biased locking(from Dave)Dave应该是sun里主要负责线程同步这一块的研究人员,人很热心,我在学习过程中碰到问题,发了封邮件给他,他给我回了封很长的邮件,详细进行了解答
HotSpotInternals_Synchronization

推荐的pdf:
Eliminating synchronization-related atomic operations with biased locking and bulk rebiasing.pdf

May 31, 2011

hotspot 中的oop层级关系以及主要oop的说明

Filed under: open jdk — Tags: — admin @ 12:54 pm

在hotspot中,所有通过gc来管理的堆内存对象(包括java堆和perm gen),都表现为oopDesc及其子类,所以了解oop非常重要,这里做了一副图,将主要的oop的层级关系及其作用做了描述。

May 17, 2011

openjdk 系列–hotspot相关的一些术语

Filed under: open jdk — admin @ 4:32 pm

翻译自http://openjdk.java.net/groups/hotspot/docs/HotSpotGlossary.html,如果要深入研究open jdk,最好能够对这些术语有一个比较透彻的了解。
ps:有些翻译是根据原文意思翻译过来,而且在翻译过程中可能做了缩减和提炼,因为个人觉得老外有的时候描述一件事情确实比较罗嗦。

1.adaptive spinning(自适应自旋锁):用来自旋锁优化线程如何等待某件事的发生,因为如果事件能很快就发生的话,自旋要比让线程阻塞再唤起的代价小很多(因为这会发生线程切换)。而自适应是指根据一种策略来确定当线程已经自旋了多久后,如果该事件还没发生,则需要将线程进行阻塞。
2.biased locking(偏向锁):一种优化技术,当一个线程持有某个锁时,哪怕该线程已经申明释放了该锁,jvm底层还是会让该线程逻辑上持有该锁,除非这个时候有其他线程试图获取这个锁;这样当原有的线程想继续持有该锁的时候,代价会低很多。其实这个技术本质上还是利用了局部性原理。
3.启动类装载器:逻辑上用来装载java 平台核心类的装载器,比较底层,通常都是作为VM的一部分来进行实现,所以从java api层面上,是没法接触到该类装载器。
4.字节码验证:在连接阶段对class进行的一种处理,用来分析class里的方法的字节码,以便确认其类型安全型。
5.C1 compiler:快速,轻量级的字节码编译器。能够执行内联,class分析,value numbering(what is this?)等工作。使用一个简单的基于CFG的SSA来作为”高”IR,一个基于机器的来作为”低“IR,同时还拥有一个线性的寄存器分配扫描器,以及一个基于模板的代码生成器。C1 compiler适用于client machine的场景。
6. C2 compiler:也被称作”opto”,是经过高度优化的字节码编译器。。。。。有些看不懂。。。汗。。。
7.card table:用来记录所有发生变化的oops的一种remember set。
8.类数据共享:用来记录某些类在内存中的位置,这样这些类可以在后续被直接共享使用,而不再需要从对应的类文件里加载到内存中,这是一个启动优化措施。
9.类层级结构分析:也被称作“CHA”,编译器使用这种分析来确定某些虚函数是否在类层级结构上只有一个实现,如果是这样,这可以使用内联或者其他静态化调用等优化技术来优化这个函数。
10.代码cache:一个用来存放编译过代码的堆,这些对象不会被gc重新定位,但是由于这些代码可能包含oops,所以会被当成gc 的根节点。
11.compaction:垃圾回收的一种技术,将存活的对象压缩到一个集中的内存区域,从而避免内存碎片。
12.concurrency:在计算机科学里,一般用来指在逻辑上同时执行多件事情,如果硬件条件成立(比如有多核),则可以实现在物理上同时执行多见事。此时成为parallelism。
13.concurrent garbage collector:一种特定类型的垃圾收集器,该收集器的特定是其大部分的工作都是和工作线程并发执行的,只有小部分的工作需要stop the world。
14.handle:对oop的再一次包装,这样jvm底层的c/c++代码都是通过handle来操作oop,而垃圾收集器也通过操作handle,能够更容易找到根引用(root reference)。同时,当进行垃圾回收时(也就是c/c++ 代码处于safepoint时),垃圾收集器可能会对handle里的oop做修改(比如将有些对象进行了压缩,或者从新生代移动到了老生代等),而此时对于那些c/c++代码来说,由于它们操作的handle没有变(只是handle里的oop变了而已),所以它们不用感知到这个变化,等其通过handle去获取里面的oop时,此时才感知到变化。所以handle的功能概括来就是,通过引入一个间接层,屏蔽其他代码(非垃圾回收代码)对于oop变动的感知。Handle既有全局的,也有线程可见的。不同的oop会有其对应的handle,而垃圾回收器需要了解所有的这些handle。
15.oop.:指向gc托管的heap的对象指针,实现上是本机内存地址;oops可以被java代码(解释执行的或者被编译的)操作,因为在这些代码里,gc知道oop指向的地址和其是否存活。oop也能被c/c++代码操作,但是当经历safepoints,这些代码必须通过handle来操作oop。

May 12, 2011

open jdk 系列–如何编译安装open jdk

Filed under: open jdk — Tags: , , , , — admin @ 12:51 am

原来编译的时候对于碰到的问题做了一个总结,结果那台电脑归还给公司了,当时的那篇文档没有拷贝到新电脑上来,所以有些忘记了,只能凭记忆来写。

我进行编译的os是redhat enterprise 5,编译的是open jdk7,如果你也是在linux上面进行编译的话,需要你对linux环境下的编译,安装,以及linux的共享连接库机制有一定了解,我编译时,主要是参考了这几篇文章:
1. open jdk 官方build 文档
2. 杭州本土一家IT公司写的文章
3. csdn上面的一篇文章

我安装时碰到的主要问题:
1.为了编译open jdk7,需要安装一个bootstrap jdk,按照官方文档的指示,我下载并安装了open jdk6了作为bootstrap jdk,后来导致了很多问题,回头检查,发现默认安装的open jdk6,只安装了jre,没有安装jdk;其实这个bootstrap jdk,完全可以安装sun 成熟的jdk,效果都是一样的,以后大家进行编译,推荐使用sun的jdk,而不要去用open jdk。
2.我也碰到了libXi.so的问题,不过我安装的系统上,已经有了该动态链接库的64位版本,所以只要做一个软连接即可。
3.如果你对某项配置已经进行了修改,但是make sanity还是报原来的错误的话,可以考虑使用make clean先进行清楚,再make sanity

March 12, 2011

自己改写rt.jar

Filed under: jvm — admin @ 4:37 am

前几天有一个项目,测试过程中发现perm区在无限增大,当然,用完以后会触发full gc,进行内存回收。跟踪了一下,用jmap -permstat pid来查看perm区的使用情况,发现其实load的class 数量增长很缓慢,但是intern的string占有的内存区增长很快。于是想用btrace拦截一下,看系统里哪些地方调用了String的intern方法,发现因为String的intern方法是native的,btrace还没发进行拦截。于是没有办法,后来突然想到,能否替换rt.jar包里String类(然后自己的String类里面让intern方法非native),用自己编译的String类来进行替换,最后做了实验,发现可行。
基本命令如下:
解压jar 包:jar -xvf rt.jar
然后找到对应的String.class,进行替换。
生成jar包:jar -cvf rt.jar ./
ps:一开始替换的时候,因为在String 类里调用了System.out.println,jvm启动时会报错,stack overflow上面的人解释说是因为这个时候System还没准备好。同时,查看了oracle发布的对double parse那个漏洞修复的补丁,其原理也是一样的,用已经修复好的类替换原有的类,重新打包rt.jar包。

November 21, 2010

Thinking clearly about performance 总结

Filed under: Uncategorized — Tags: — admin @ 2:02 pm

总体来说,这篇文章还是比较通俗易懂,讲了一些性能的基本概念,以及这些概念的内在关系。从自己来说,觉得可以概括为以下几点:
1.响应时间:系统完成一个指定任务需要的时间
2.吞吐量:系统在一个指定时间间隔内完成任务的总数(一般是一秒)
3.吞吐量和响应时间没有一个严格的倒数关系,比如一个系统的吞吐量是1000qps,那么不能简单的推断出每个任务的响应时间是1ms,这里面不存在简单的倒数关系。
4.系统的响应时间的百分比分布比一个简单的平均响应时间更为有用。因为前者和人的真实感受更为接近。
5.Load可以定义为在执行任务时,对资源的抢占情况
6.在一个完美的扩展性系统中,响应时间等于排队时间(为了等待资源而进行的排队)+处理时间
7.The knee:knee没有查具体什么意思,但是从文章看,我觉得应该是最佳吞吐量的那个点,作者自己定义这个点的方法是找出响应时间/资源占用率 的最大值的那个点,但是后来他介绍了他朋友Neil gunther的观点,他朋友的观点最佳吞吐量应该是开发人员觉得系统能容忍的最大响应时间下的吞吐量。对于互联网系统,我觉得应该还是后者适合一些。

性能优化的一些方法论:
1.应该先尽量优化各个应用,而不要直接去优化一个全局的系统(比如多个应用都在使用同一个数据库,那么应该先优化各应用,再去优化数据库,直接优化数据库,肯可能出现满足了这个应用的性能,但是另外的系统性能可能恶化的局面出现)
2.优化的时候需要考虑成本收益及风险的关系,不一定要非得优化性能最差的那个点

November 7, 2010

Netty的Timer管理–开源的魅力

Filed under: java 开源项目 — admin @ 3:23 am

这里的Timer,是指定时器,现代操作系统,定时器无处不在,以至于有些将linux kernel的书,都需要单独列出一章,来将linux是如何管理这些定时器的。管理定时器其实主要的步骤有以下3步:
1.生成定时器(这里面最终要的是给予定时器其到期时间以及到期后要产生的动作)
2.将定时器放入一个数据结构中,以便系统能够定时的扫描检查这些定时器是否到期,是否需要触发
3.定期检查定时器是否到期

一般能够想到的最直观的实现定时器的办法,就是在定时器里插入一个绝对时间,然后将定时器放入一个链表,每次轮询时,将该链表整体轮询一遍,从而检查有哪些定时器到期了。那么这样的时间复杂度为:插入定时器 o(1); 每次轮询o(n).

现在的linux内核中,对timer的管理其实是根据timer离当前时间的远近,将其存放到不同的双向队列数组中,每个队列数组长度一样,比如都是2的8次方;(具体是2的几次方,这个还要依赖其他条件,这里只是举个例子)间隔时间小于2的8次方的,存放在第一个双向队列数组里;间隔时间大于等于2的8次方但小于2的16次方的,则存放在第二个双向队列数组里,以此类推;
然后每个2的8次方个周期,对上面的双向队列数组进行重新安排,比如这个时候,需要将第二个数组里的有些队列移动到地一个数组里。这样在实现时,插入定时器o(1),其只需要计算该timer的时间和当前时间的关系,从而计算出应该将其放置第几个数组的第几个双向队列上(同一个数组的某个双向对列上,存放的都是时间一样的timer)即可(基本上是最简单而直接的hash算法);插入定时器o(1),平时轮询时,只要取最近的那个数组里的对应timer即可,时间复杂度为o(1),每隔指定的周期后(比如2的8次方个周期后)论询一次,这个时候因为要对所有的现存的timer进行调整,最坏时间复杂度是o(n).

而Netty中的Timer管理,则是使用了所谓的Hashed Wheel模式,在数据结构上,其有一个循环数组,这个循环数组有n个bucket(假设为8),可以用来存放Timer链表;Hash Wheel里有2个周期,一个周期是单次轮询的周期,假设为100个Tick;第二个周期是轮询完数组的周期,那么其大小为n*Tick(8*100就为800),假设叫round 周期;那么在每次插入一个timer时,会根据timer的触发时间,计算出其应该存放在哪个bucket的链表中(比如timer距离当前周期为2550tick,那么该Timer就会放在(2550%(n*100Tick))/100Tick=2,这里取celling),在timer插入到bucket中的同时,按照这种算法,相隔round周期整数倍的Timer会存放在相同的bucket的链表中,为了区分round周期,所以每个timer在存放时,还会有一个数值用来村放其距离当前时间还剩多少个round周期(刚才的例子round为3)。当timer插入后,这样轮询的时候就比较方便,轮询时,会有一个cursor,每隔一个周期就加一,论询时该cursor指向数组的哪个bucket,就检查该bucket中的所有timer,主要是检查该timer的round周期是否为0,如果不是0,则将其值减1,然后将其归还(cursor不断增加,当cursor下次指向该bucket时,经过的时间为一个round周期)。而如果round周期已经为0了,则说明该timer需要被触发了,从而触发该timer。这样计算时间复杂度的话,插入的时候,其时间复杂度为o(1),而在轮询的时候,其时间复杂度也可理解为o(1).

Hashed Wheel Timing

Hashed Wheel Timing原理图

比较一下linux内核里的timer管理和netty的timer管理,会发现,如果管理的timer的触发时间都距离当前时间比较近,那么linux和netty的效率应该都差不多(linux这个时候,当间隔的大周期发生的时候,也基本不用调整数组,从而不会发生时间复杂度为o(n)的操作)。而如果管理了较多的距离当前时间很长的timer(此时的linux,会在除了第一个数组外的其他数组里也要存放timer;而netty中,则会在每个bucket的链表中,存放较多的round周期大于0的timer),那么这个时候,linux平时轮询时,;处理的都是确实需要被触发的timer,而netty,很可能会碰到很多不要触发的timer,然后把这些timer的round周期减一,这种情况下,对于平时的轮询,linux是要优于netty;而当碰到大周期时,linux的耗时会超过netty。相当与在这种情形下,netty是把对长时间timer的处理分散在每次轮询中,而linux则是把它集中在一个周期里来做。linux的这种做法,在一般的情景下应该是ok的,但是在realtime os的情况下,应该是有问题的。

相比较与公司自己实现的一个二方库,里面其实也需要用到超时机制,但是实现起来就是很直观,这也没有办法,毕竟大家都时间有限,有的时候还是以快速实现业务为主,没有太多时间把一些技术点考虑的很深远,但是这个时候我们还是可以借鉴一下开源软件中的类似解决方案,拿来为己所用。开源软件能够汇集互联网上很多人的智慧,这可能是很多公司的技术力量都不能够比拟的,这可能也就是开源的魅力所在。

Older Posts »

Powered by WordPress