JRockit读书笔记I — Java代码的高效执行

Posted on

JRockit读书笔记I — Java代码的高效执行

BlueDavy之技术blog

{互联网,OSGi,Java, High Scalability, High Performance,HA}

JRockit读书笔记I — Java代码的高效执行

Dec 16

bluedavyjvm java code generation, java代码执行, jit, jvm 10 Comments 《Oracle JRockit: The Definitive Guide》一书是由Oracle JRockit的两位资深开发人员写的,其中的Marcus Hirt更是JRockit Mission Control的leader,这本书详细的对Oracle JRockit进行了介绍,最突出的特点非常系统化的介绍了一个JVM通常是如何实现的,而JRockit这样一个极为优秀的JVM又是做了哪些优化,为什么做这些优化,这本书对于对JVM感兴趣的同学而言应该是必读的一本书,其实即使对于JVM兴趣不强的同学,里面的优化思路的介绍也是值得学习,本系列的blog主要是总结看这本书得到的一些收获,由于书中知识量巨大,因此得分成多篇blog来总结了。

书的第二章为:Adaptive Code Generation,在这章中作者向我们讲解了一个优秀的JVM是如何来实现代码的高效执行的,感兴趣的同学其实可以在不看下面blog内容之前,先考虑下如果是你做的话,你会怎么做来实现Java代码的高效执行呢,然后再对比下这章的内容,我想你能学到很多的,:)

用过Java的同学都知道,Java是通过javac将Java源码编译为class文件,然后通过ClassLoader装载此class文件,之后就可执行此class了,要最高效的执行这个class,最好的方法莫过于class文件直接就是机器码,这样直接执行就可以了,但Java是跨平台的,因此class文件就不能是机器码了。

由于class文件不是直接的机器码,要执行它最简单的方法就是采用纯粹的解释方式,解释方式由于每次都得将class文件中的指令翻译为对应的机器环境的指令,效率是很低的。

为了能更高效的执行,同时又保持跨平台的特性,另外一个方法就是在执行class时再将其翻译为对应的机器码,这个方法是比较靠谱的,因此无论是Hotspot、还是JRockit,都采用了这种方式,也就是大家熟知的JIT(Just In Time) Compiler。

OK,既然觉得在装载class后翻译成机器码去执行可以比较高效,那这个时候又会出现两种状况,是执行class的时候就立刻翻译成机器码,还是先用解释模式执行,然后到一定时机再翻译成机器码呢,之所以出现这两种状况,原因在于将class翻译为机器码是需要消耗时间的,因此如果执行class的时候就立刻翻译成机器码的话,也就会导致Java程序启动速度会比较慢,JRockit是这么认为的,JRockit的服务对象是server级应用,这类应用的特点是没那么在乎启动速度,而更在乎的是执行时的高效,而且如果执行的时候就立刻翻译成机器码的话,就意味着压根不需要实现解释器,因此JRockit采取的方法是在执行class时直接编译为机器码,而Hotspot由于需要同时支持client和server应用,对于client应用而言,启动速度非常重要,因此Hotspot采用的是先解释执行,到了一定时机后再翻译成机器码。

如果认为就这样就完成了Java代码的执行的实现,那就太小看JVM了,由于JVM能够知道代码运行的全部状况,自然还可以做出更多更出色的提升代码执行速度的优化,例如标量替换、更好的inline等,后面再来细说,因此这样就出现了一个状况,什么时候对哪些代码来做这些更猛的优化呢。

真正值得做更猛的优化的代码自然是所谓的”热点”代码,如何来发现哪些代码是热点代码呢,通常有三种方法: 1、方法调用计数器 方法调用计数器是常见的方式,hotspot采用的即为这种,这种方式不好的地方就在于计数器本身经常是cpu cache misses的,因此稍微会有点影响性能。 2、对线程进行采样 可采用软件或硬件方式来实现,软件方式实现不好的地方在于采样的时候需要暂停线程,好处是因为是采样,不需要对所有方法进行计数,硬件方式自然是最好的,但不是所有的硬件都支持的,支持的硬件中最典型的是intel IA-64的CPU。

在有了发现热点代码的方法后,接下来需要做的就是更猛的优化,有很多种,例如Java的代码中,通常会是接口方式的调用,但因为是接口方式的调用,所以其实默认情况下是不好做inline处理的,但JVM为了更高效的执行代码,如发现这代码为热点代码,那么就会做一些激进的优化,例如会假设这个接口只有一个实现,然后就可以直接将此实现对应的代码inline进来了(至于为什么inline后效率更高,这个请参考编译原理之类的书),这些激进优化同样适合于if、抛异常这些状况,当然,当激进优化的条件失效时,就会逆优化回到之前基本编译的代码。 而其他的更猛的优化还包括根据线程执行路径进行逃逸分析等,后面再专门写一篇blog来讲解下一些翻译为机器码的优化吧,其实大多都是编译原理的一些东西。

书中在介绍JRockit如何实现自己的JIT Compiler时,提到了Bytecode混淆以及bytecode优化,JRockit的态度是bytecode混淆时将name进行混淆是靠谱的,但如果对control flow进行混淆,就不太好了,因为这有可能会导致jit compile时的有些优化也做不了了,而bytecode优化,JRockit的态度是应该避免,因为没什么太大的意义,更主要的优化还是得靠jit compiler。

JRockit的JIT Compiler的实现和Hotspot另外一个很大的不同在于JRockit并未采用on-stack replacement,据JRockit的研究,这个没有太大必要,当然,对于编写benchmark代码时则要注意这个不同。

JIT Compiler在compile时还需要考虑的几个重点问题: 1、为GC提供必要的信息; 2、为查错提供必要的信息,例如代码的行数、变量名等;

从这章的内容可以看到,JRockit为了能够让Java代码能够高效的执行,是做出了非常多的努力的,也可以看到很多JRockit与Hotspot不同的地方,甚至可以看出Java代码的执行比C代码的执行高效都是有可能的,:)。

学习JVM的References Sun JDK 1.6内存管理

10 Comments (+add yours?)

  1. h Dec 16, 2010 @ 18:57:35 我还没有读完……啊……
  2. cauherk Dec 16, 2010 @ 20:44:19 JRockit好像不是在执行的时候进行jit 编译操作的吧。 打开jrmc的flight record,可以详细的看到jit编译的情况,其中并没有列出所有执行过的代码。
  3. jianying Dec 16, 2010 @ 23:09:24 运行时的内联优化的确是可能比C更快的。
  4. bluedavy Dec 17, 2010 @ 09:49:57 @cauherk JRockit是没有解释器的,如果不编译,你觉得它该怎么执行呢,具体可以看看书中的这章。 “When the method is first called, and control jumps to the trampoline, all it does is execute a call that tells JRockit that the real method needs to be generated.”
  5. bluedavy Dec 17, 2010 @ 09:51:28 @cauherk flight recorder多数情况是采样机制和阀值机制,因此没包含所有执行过的代码也是正常的。
  6. hanguokai Dec 17, 2010 @ 13:46:45 对线程进行采样,采了什么,如何判断热点?
  7. bluedavy Dec 17, 2010 @ 21:39:38 @hanguokai 对线程进行采样,看看线程在执行些什么方法,采样多次后,自然也就知道哪里是热点了。
  8. Wen Jan 11, 2011 @ 22:33:45 您好!我是一名在校大学生,看了您写的这篇博文,很感兴趣。我现在正在做一个关于JVM优化的问题,刚刚开始,用C写了一个简单的Bytecode解释器,能够进行类型转换,算术逻辑运算,控制转移等工作,我和我的同学希望能够做出一个面向移动嵌入式平台的小型JVM,现在遇到一些问题,能请您给我们一些建议吗? 我们的问题主要集中在优化的方向选择上,现在大致有两条思路:一个是将bytecode反编译为一种中间语言(比如C),再通过中间语言编译机器码实现其功能,另一个是优化bytecode编译的方式。这是大体思路,我们对JVM的理解还很粗浅,见笑了~ 谢谢!
  9. RednaxelaFX Jan 14, 2011 @ 15:36:25 @Wen 这两种思路都有现成的实现,如果想参考的话资源非常丰富。 例如说,同样是应用在嵌入式领域的小型JVM,Squawk(http://labs.oracle.com/projects/dashboard.php?id=155 https://squawk.dev.java.net/)就是先将Java字节码编译为C,然后再用C编译器编译为native code,再部署到设备上的。 如果想在运行时直接将字节码编译为native code,那么嵌入式领域可以参考phoneME(https://phoneme.dev.java.net/)开源的CDC HotSpot Implementation或者CLDC HotSpot Implementation。或者像是Google Android里的Dalvik虚拟机,里面也有JIT编译器,并且使用的是最近比较流行的trace-based compiler。

对这些感兴趣的话,也欢迎来JavaEye的高级语言虚拟机圈子来讨论 ^_^ http://hllvm.group.javaeye.com/

Leave a Reply

Cancel

Name (required)

Mail (required)

Website

CAPTCHA Image

Refresh Image

CAPTCHA Code /*


July 2013 M T W T F S S « Mar 1234567 891011121314 15161718192021 22232425262728 293031

Categories

Tags

btrace c1 c2 Deflater facebook gc gc tuning Grizzly HBase hotspot Inflater interpreter javac java code generation JavaOne javaone general technical session java代码执行 Java 并发 jit jvm memory management Native Memory Leak NoSQL oom oracle keynote pessimism policy references RPC serial gc SOA sun jdk sun jdk oom Web容量规划的艺术 yuanzhuo 书:分布式Java应用 书评 互联网技术 交流 内存管理 分布式Java应用 圆桌交流 容量规划 悲观策略 服务框架 硅谷公司

订阅

feedsky 抓虾 google reader 鲜果 九点

推荐书籍

My Book

© BlueDavy之技术blog 2013

Icons & Wordpress Theme by N.Design

GC悲观策略之Parallel GC篇

Posted on

GC悲观策略之Parallel GC篇

BlueDavy之技术blog

{互联网,OSGi,Java, High Scalability, High Performance,HA}

GC悲观策略之Parallel GC篇

Nov 07

bluedavyjvm gc, jvm, pessimism policy, 悲观策略 12 Comments 先来看段代码: ?1

2 3

4 5

6 7

8 9

10 11

12 13

14 import

java.util./*;

public

class

SummaryCase{

public

static

void

main(String[] args)

throws

Exception{

List caches=

new

ArrayList();

for

(

int

i=

0

;i<

7

;i++){

caches.add(

new

byte

[

1024

/*

1024

/*

3

]);

}

caches.clear();

for

(

int

i=

0

;i<

2

;i++){

caches.add(

new

byte

[

1024

/*

1024

/*

3

]);

}

Thread.sleep(

10000

);

}

}

当用-Xms30m -Xmx30m -Xmn10m -XX:+UseParallelGC执行上面的代码时会执行几次Minor GC和几次Full GC呢? 按照eden空间不足时触发minor gc的规则,上面代码执行后的GC应为:M、M、M、M,但实际上上面代码执行后GC则为:M、M、M、F、F。 这里的原因就在于Parallel Scavenge GC时的悲观策略,当在eden上分配内存失败时且对象的大小尚不需要直接在old上分配时,会触发YGC,代码片段如下:

?1

2 3

4 5

6 7

8 9

10 11

12 13

14 15

16 17

18 19

20 21

22 23

24 25

26 27

28 29

30 31

32 33

34 35

36 37void

PSScavenge::invoke(){

... bool

scavenge_was_done = PSScavenge::invoke_no_policy();

PSGCAdaptivePolicyCounters/* counters = heap->gc_policy_counters(); if

(UsePerfData)

counters->update_full_follows_scavenge(0); if

(!scavenge_was_done ||

policy->should_full_GC(heap->old_gen()->free_in_bytes())) { if

(UsePerfData)

counters->update_full_follows_scavenge(full_follows_scavenge);<

GCCauseSetter gccs(heap, GCCause::_adaptive_size_policy); if

(UseParallelOldGC) {

PSParallelCompact::invoke_no_policy(

false

); }

else

{

PSMarkSweep::invoke_no_policy(

false

); }

} ...

} PSScavenge::invoke_no_policy{

... if

(!should_attempt_scavenge()) {

return

false

; }

... }

bool

PSScavenge::should_attempt_scavenge() { ...

PSAdaptiveSizePolicy/* policy = heap->size_policy();

size_t

avg_promoted = (

size_t

) policy->padded_average_promoted_in_bytes(); size_t

promotion_estimate = MIN2(avg_promoted, young_gen->used_in_bytes());

bool

result = promotion_estimate < old_gen->free_in_bytes(); ...

return

result; }

在上面should_attempt_scavenge代码片段中,可以看到会比较之前YGC晋升到Old中的平均大小与当前新生代中已被使用的字节数大小,取更小的值与旧生代目前剩余空间大小对比,如更大,则返回false,就终止了YGC的执行了,当返回false时,PSScavenge::invoke就将触发Full GC了。 在PSScavenge:invoke中还有一个条件为:policy->should_full_GC(heap->old_gen()->free_in_bytes(),来看看这段代码片段:

?1

2 3

4 5bool

PSAdaptiveSizePolicy::should_full_GC(

size_t

old_free_in_bytes) {

bool

result = padded_average_promoted_in_bytes() > (

float

) old_free_in_bytes; ...

return

result; }

可看到,这段代码检查的也是之前YGC时晋升到old的平均大小是否大于了旧生代的剩余空间,如大于,则触发full gc。 总结上面分析的策略,可以看到采用Parallel GC的情况下,当YGC触发时,会有两个检查: 1、在YGC执行前,min(目前新生代已使用的大小,之前平均晋升到old的大小中的较小值) > 旧生代剩余空间大小 ? 不执行YGC,直接执行Full GC : 执行YGC; 2、在YGC执行后,平均晋升到old的大小 > 旧生代剩余空间大小 ? 触发Full GC : 什么都不做。

按照这样的说明,再来看看上面代码的执行过程中eden和old大小的变化状况: 代码 eden old YGC FGC 第一次循环 3 0 0 0 第二次循环 6 0 0 0 第三次循环 3 6 1 0 第四次循环 6 6 1 0 第五次循环 3 12 2 0 第六次循环 6 12 2 0 第七次循环 3 18 3 1 第八次循环 6 18 3 1 第九次循环 3 3 3 2 在第7次循环时,YGC后旧生代剩余空间为2m,而之前平均晋级到old的对象大小为6m,因此在YGC后会触发一次FGC。 而第9次循环时,在YGC执行前,此时新生代已使用的大小为6m,之前晋级到old的平均大小为6m,这两者去最小值为6m,这个值已大于old的剩余空间,因此就不执行YGC,直接执行FGC了。

Sun JDK之所以要有悲观策略,我猜想理由是程序最终是会以一个较为稳态的状况执行的,此时每次YGC后晋升到old的对象大小应该是差不多的,在YGC时做好检查,避免等YGC后晋升到Old的对象导致old空间不足,因此还不如干脆就直接执行FGC,正因为悲观策略的存在,大家有些时候可能会看到old空间没满但full gc执行的状况。 埋个伏笔,大家将上面的执行参数换为-XX:+UseSerialGC执行看看,会发生什么呢? :)

JavaOne美国之行–硅谷公司交流篇 GC悲观策略之Serial GC篇

12 Comments (+add yours?)

  1. xan Nov 08, 2010 @ 11:58:44 代码布局不怎么给力啊 :)
  2. bluedavy Nov 08, 2010 @ 12:23:52 确实不给力,我折腾下。
  3. imbeneo Nov 08, 2010 @ 13:12:45 我忘记了clean,结果总是OOM。
  4. oliver Apr 26, 2011 @ 11:34:35 我的疑问是为什么第一次full gc时old里面使用空间没有被回收掉,第二次full gc时old里面才回收?
  5. bluedavy Apr 26, 2011 @ 13:32:18 因为caches.clear是后来才调的…
  6. yinhex Jul 09, 2011 @ 22:38:20 这里我有个问题啊:我看你的分布式基础看到你能举例出很多大网站的设计。不知道你是从哪里可以看到这些信息的呢?可以细数一下你经常上去获取信息国外网站吗?
  7. bluedavy Jul 11, 2011 @ 10:01:48 @yinhex 一般来说,例如highscalability.com,还有各种国外的技术大会,各家著名公司的engineer的blog,都会有这些信息,:)
  8. Gary Sep 29, 2011 @ 11:09:58 -XX:+UseSerialGC gc.log发现是MMMMF,符合预期啊
  9. yangxuesong Nov 29, 2011 @ 17:31:49 一次full gc的执行流程是怎么用的呢?
  10. bluedavy Dec 03, 2011 @ 11:10:22 @yangxuesong 这个…要看是什么垃圾收集器…

    Leave a Reply

Cancel

Name (required)

Mail (required)

Website

CAPTCHA Image

Refresh Image

CAPTCHA Code /*


July 2013 M T W T F S S « Mar 1234567 891011121314 15161718192021 22232425262728 293031

Categories

Tags

btrace c1 c2 Deflater facebook gc gc tuning Grizzly HBase hotspot Inflater interpreter javac java code generation JavaOne javaone general technical session java代码执行 Java 并发 jit jvm memory management Native Memory Leak NoSQL oom oracle keynote pessimism policy references RPC serial gc SOA sun jdk sun jdk oom Web容量规划的艺术 yuanzhuo 书:分布式Java应用 书评 互联网技术 交流 内存管理 分布式Java应用 圆桌交流 容量规划 悲观策略 服务框架 硅谷公司

订阅

推荐书籍

My Book

© BlueDavy之技术blog 2013

Icons & Wordpress Theme by N.Design

BTrace使用简介

Posted on

BTrace使用简介

BlueDavy之技术blog

{互联网,OSGi,Java, High Scalability, High Performance,HA}

BTrace使用简介

Nov 11

bluedavyjvm btrace 13 Comments 很多时候在online的应用出现问题时,很多时候我们需要知道更多的程序的运行细节,但又不可能在开发的时候就把程序中所有的运行细节都打印到日志上,通常这个时候能采取的就是修改代码,重新部署,然后再观察,但这种方法对于online应用来说不是很好,另外一方面如果碰到不好改的代码,例如引用的其他的外部的包什么的,就很麻烦了,BTrace就是一个可以在不改代码、不重启应用的情况下,动态的查看程序运行细节的工具,其官方网站在此:http://kenai.com/projects/btrace/,在这篇blog中,就来看看如何用BTrace来动态的监测方法的一些运行细节状况。 BTrace通过动态的挂接用java写的代码到运行时上来获取到一些运行细节,例如典型的使用btrace的方法为: btrace -cp [btrace的jar所在的路径,默认为btrace/build下] [pid] [需要运行的java代码] 例如一段这样的代码: ?1

2 3

4 5

6 7

8 9

10 11

12 13

14 15

16 17

18 19

20 21

22 23

24 25

26 import

java.util.Random;

public

class

Case1{

public

static

void

main(String[] args)

throws

Exception{

Random random=

new

Random();

CaseObject object=

new

CaseObject();

boolean

result=

true

;

while

(result){

result=object.execute(random.nextInt(

1000

));

Thread.sleep(

1000

);

}

}

} public

class

CaseObject{

private

static

int

sleepTotalTime=

0

;

public

boolean

execute(

int

sleepTime)

throws

Exception{

System.out.println(

"sleep: "

+sleepTime);

sleepTotalTime+=sleepTime;

Thread.sleep(sleepTime);

return

true

;

}

}

如在程序运行的情况下,想知道调用CaseObject的execute方法的以下几种情况,在BTrace中可以这么做: 1、调用此方法时传入的是什么参数,返回的是什么值,当时sleepTotalTime是多少? BTrace脚本如下:

?1

2 3

4 5

6 7

8 9

10 11

12 13

14 15

16

import

static

com.sun.btrace.BTraceUtils./*;

import

com.sun.btrace.annotations./*;

@BTrace

public

class

TraceMethodArgsAndReturn{

@OnMethod

(

clazz=

"CaseObject"

,

method=

"execute"

,

location=

@Location

(Kind.RETURN)

)

public

static

void

traceExecute(

@Self

CaseObject instance,

int

sleepTime,

@Return

boolean

result){

println(

"call CaseObject.execute"

);

println(strcat(

"sleepTime is:"

,str(sleepTime)));

println(strcat(

"sleepTotalTime is:"

,str(get(field(

"CaseObject"

,

"sleepTotalTime"

),instance))));

println(strcat(

"return value is:"

,str(result)));

}

}

然后直接执行btrace -cp btrace/build [pid] TraceMethodArgsAndReturn.java就可以了。 当程序中调用到caseobject的execute方法时,就会在btrace的console中输出相应的信息。 2、execute方法执行耗时是多久? BTrace脚本如下:

?1

2 3

4 5

6 7

8 9

10 11

12 13

14 15

16 17

18 19

20 21

22 23

24

import

static

com.sun.btrace.BTraceUtils./*;

import

com.sun.btrace.annotations./*;

@BTrace

public

class

TraceMethodExecuteTime{

@TLS

static

long

beginTime;

@OnMethod

(

clazz=

"CaseObject"

,

method=

"execute"

)

public

static

void

traceExecuteBegin(){

beginTime=timeMillis();

}

@OnMethod

(

clazz=

"CaseObject"

,

method=

"execute"

,

location=

@Location

(Kind.RETURN)

)

public

static

void

traceExecute(

int

sleepTime,

@Return

boolean

result){

println(strcat(strcat(

"CaseObject.execute time is:"

,str(timeMillis()-beginTime)),

"ms"

));

}

}

3、谁调用了execute方法? BTrace脚本如下:

?1

2 3

4 5

6 7

8 9

10 11

12 13

import

static

com.sun.btrace.BTraceUtils./*;

import

com.sun.btrace.annotations./*;

@BTrace

public

class

TraceMethodCallee{

@OnMethod

(

clazz=

"CaseObject"

,

method=

"execute"

)

public

static

void

traceExecute(){

println(

"who call CaseObject.execute :"

);

jstack();

} }

4、有没有人调用CaseObject中的哪一行代码? BTrace脚本如下:

?1

2 3

4 5

6 7

8 9

10 11

12

import

static

com.sun.btrace.BTraceUtils./*;

import

com.sun.btrace.annotations./*;

@BTrace

public

class

TraceMethodLine{

@OnMethod

(

clazz=

"CaseObject"

,

location=

@Location

(value=Kind.LINE,line=

5

)

)

public

static

void

traceExecute(

@ProbeClassName

String pcn,

@ProbeMethodName

String pmn,

int

line){

println(strcat(strcat(strcat(

"call "

,pcn),

"."

),pmn));

}

}

从上面可看出,在有了BTrace后,要动态的跟踪代码的运行细节还是非常爽的,更多的细节的操作请大家查看BTrace的User Guide

GC悲观策略之Serial GC篇 学习JVM的References

13 Comments (+add yours?)

  1. ikbear Nov 11, 2010 @ 17:41:12 收藏了。
  2. pandonix Nov 11, 2010 @ 20:58:54 关于方法的执行时间和返回值,有个问题请教,如果调用量非常大的方法,有没有类似染色日志这样的功能:只需要打印参数符合某种条件的调用情况?
  3. bluedavy Nov 12, 2010 @ 11:42:06 :) ,这个自己可以在脚本里判断下…
  4. vitty Nov 14, 2010 @ 14:09:22 不知道这个对被监测程序的性能影响有多大?不过真的很爽呢。
  5. bluedavy Nov 14, 2010 @ 19:53:38 据使用来看,影响很小。
  6. bobo Nov 16, 2010 @ 10:16:58 学习,哈哈
  7. xuanyuanzhiyuan Dec 21, 2010 @ 15:37:22 学习了 :)
  8. pandonix Dec 27, 2010 @ 23:07:55 运行第一个例子:TraceMethodArgsAndReturn.java报错: TraceMethodArgsAndReturn.java:10: 找不到符号 符号: 类 CaseObject 位置: 类 TraceMethodArgsAndReturn 把@Self CaseObject instance修改成为@Self Object self,并且去掉println(strcat(“sleepTotalTime is:”,str(get(field(“CaseObject”,”sleepTotalTime”),instance)))); 就可以了,但是楼主希望获取sleepTotalTime的需求就满足不鸟了。还不太明白@Self的含义,再看看纠结的文档吧
  9. pandonix Dec 27, 2010 @ 23:44:37 找到原因了,是CaseObject不在classthpath中,如果用到了@Self CaseObject ,编译TraceMethodArgsAndReturn.java时会去找CaseObject类。 简单的将CaseObject.java放到跟TraceMethodArgsAndReturn.java同一目录即可
  10. 光夏 Jan 23, 2011 @ 11:34:36 使用TLS得注意递归调用的情况吧
  11. victorcai Jul 19, 2011 @ 15:03:18 请问各位大侠btrace能否在windows xp上使用,是否需要设置环境变量,我在windows xp上每次使用都失败

    Leave a Reply

Cancel

Name (required)

Mail (required)

Website

CAPTCHA Image

Refresh Image

CAPTCHA Code /*


July 2013 M T W T F S S « Mar 1234567 891011121314 15161718192021 22232425262728 293031

Categories

Tags

btrace c1 c2 Deflater facebook gc gc tuning Grizzly HBase hotspot Inflater interpreter javac java code generation JavaOne javaone general technical session java代码执行 Java 并发 jit jvm memory management Native Memory Leak NoSQL oom oracle keynote pessimism policy references RPC serial gc SOA sun jdk sun jdk oom Web容量规划的艺术 yuanzhuo 书:分布式Java应用 书评 互联网技术 交流 内存管理 分布式Java应用 圆桌交流 容量规划 悲观策略 服务框架 硅谷公司

订阅

推荐书籍

My Book

© BlueDavy之技术blog 2013

Icons & Wordpress Theme by N.Design

用apache的configuration实现实时配置

Posted on

用apache的configuration实现实时配置 - steven_cheng - ITeye技术网站

首页 新闻 论坛 问答 博客 招聘 更多 ▼

专栏 群组 搜索

您还未登录 ! 我的应用 登录 注册

steven_cheng

永久域名 http://steven-cheng.iteye.com

异常处理

2006-03-24

用apache的configuration实现实时配置

关键字: java 开源 apache下commons有一个configeration包,对于做配置很方便,尤其是实时热配置。可以自动监测到配置文件的更改而reload配置文件。在项目中使用所以进行了一下封装。 java 代码

  1. public class DefaultRealTimeXMLConfiger {
  2. private static Log logger = LogFactory.getLog(DefaultRealTimeXMLConfiger.class);
  3. private String fileName;
  4. private long reloadPeriod;
  5. private XMLConfiguration config;
  6. public void init()
  7. {
  8. String filePath = GlobalConfigerImpl.getConfDir()+"/"+fileName;
  9. logger.debug("will config with XML file["+filePath+"]");
  10. File file = new File(filePath);
  11. if (!file.exists() || !file.isFile()) {
  12. logger.error(" can't find file[" + filePath + "]");
  13. throw new IllegalArgumentException("config error! can't find file[" + filePath + "]");
  14. }
  15. this.init(file);
  16. }
  17. public void init(File file) {
  18. try {
  19. config = new XMLConfiguration(file);
  20. FileChangedReloadingStrategy fs = new FileChangedReloadingStrategy();
  21. fs.setConfiguration(config);
  22. if(this.reloadPeriod>0)
  23. {
  24. fs.setRefreshDelay(this.reloadPeriod);
  25. }
  26. config.setReloadingStrategy(fs);
  27. } catch (ConfigurationException e) {
  28. logger.error("error! configer error["+file.getPath()+"]");
  29. logger.error(e);
  30. e.printStackTrace();
  31. }
  32. }
  33. public Object getProperty(String name) {
  34. Object s = this.config.getProperty(name);
  35. return s;
  36. }
  37. public String getString(String name) {
  38. Object s = this.config.getProperty(name);
  39. String result = null;
  40. if (s != null)
  41. result = (String) s;
  42. return result;
  43. }
  44. public String[] getStringArray(String name) {
  45. String[] target = this.config.getStringArray(name);
  46. return target;
  47. }
  48. ///
  49. /* @return Returns the fileName.
  50. /*/
  51. public String getFileName() {
  52. return fileName;
  53. }
  54. ///
  55. /* @param fileName The fileName to set.
  56. /*/
  57. public void setFileName(String fileName) {
  58. this.fileName = fileName;
  59. }
  60. ///
  61. /* @return Returns the reloadPeriod.
  62. /*/
  63. public long getReloadPeriod() {
  64. return reloadPeriod;
  65. }
  66. ///
  67. /* @param reloadPeriod The reloadPeriod to set.
  68. /*/
  69. public void setReloadPeriod(long reloadPeriod) {
  70. this.reloadPeriod = reloadPeriod;
  71. }
  72. }
    异常处理

发表评论

表情图标

字体颜色: 标准深红红色橙色棕色黄色绿色橄榄青色蓝色深蓝靛蓝紫色灰色白色黑色 字体大小: 标准1 (xx-small)2 (x-small)3 (small)4 (medium)5 (large)6 (x-large)7 (xx-large) 对齐: 标准居左居中居右

提示:选择您需要装饰的文字, 按上列按钮即可添加上相应的标签

您还没有登录,请登录后发表评论(快捷键 Alt+S / Ctrl+Enter)

steven_cheng的博客

steven_cheng

搜索本博客

最近访客 >>更多访客

Loudyn的博客

Loudyn

hepeng19861212的博客

hepeng19861212 osacar的博客

osacar

kevin_gzhz的博客

kevin_gzhz

博客分类

其他分类

存档

声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。若作者同意转载,必须以超链接形式标明文章原始出处和作者。 © 2003-2011 ITeye.com. All rights reserved. [ 京ICP证110151号 ]

深入浅出 Java Concurrency (2)

Posted on

深入浅出 Java Concurrency (2): 原子操作

从相对简单的Atomic入手(java.util.concurrent是基于Queue的并发包,而Queue,很多情况下使用到了Atomic操作,因此首先从这里开始)。很多情况下我们只是需要一个简单的、高效的、线程安全的递增递减方案。注意,这里有三个条件:简单,意味着程序员尽可能少的操作底层或者实现起来要比较容易;高效意味着耗用资源要少,程序处理速度要快;线程安全也非常重要,这个在多线程下能保证数据的正确性。这三个条件看起来比较简单,但是实现起来却难以令人满意。

通常情况下,在Java里面,++i或者--i不是线程安全的,这里面有三个独立的操作:或者变量当前值,为该值+1/-1,然后写回新的值。在没有额外资源可以利用的情况下,只能使用加锁才能保证读-改-写这三个操作时“原子性”的。

Doug Lea在未将backport-util-concurrent合并到JSR 166里面来之前,是采用纯Java实现的,于是不可避免的采用了synchronized关键字。

public final synchronized void set(int newValue);

public final synchronized int getAndSet(int newValue);

public final synchronized int incrementAndGet();

同时在变量上使用了volatile (后面会具体来讲volatile到底是个什么东东)来保证get()的时候不用加锁。尽管synchronized的代价还是很高的,但是在没有JNI的手段下纯Java语言还是不能实现此操作的。

JSR 166提上日程后,backport-util-concurrent就合并到JDK 5.0里面了,在这里面重复使用了现代CPU的特性来降低锁的消耗。后本章的最后小结中会谈到这些原理和特性。在此之前先看看API的使用。

一切从java.util.concurrent.atomic.AtomicInteger开始。

int addAndGet(int delta) 以原子方式将给定值与当前值相加。 实际上就是等于线程安全版本的i =i+delta操作。

boolean compareAndSet(int expect, int update) 如果当前值 == 预期值,则以原子方式将该值设置为给定的更新值。 如果成功就返回true,否则返回false,并且不修改原值。

int decrementAndGet() 以原子方式将当前值减 1。 相当于线程安全版本的--i操作。

int get() 获取当前值。

int getAndAdd(int delta) 以原子方式将给定值与当前值相加。 相当于线程安全版本的t=i;i+=delta;return t;操作。

int getAndDecrement() 以原子方式将当前值减 1。 相当于线程安全版本的i--操作。

int getAndIncrement() 以原子方式将当前值加 1。 相当于线程安全版本的i++操作。

int getAndSet(int newValue) 以原子方式设置为给定值,并返回旧值。 相当于线程安全版本的t=i;i=newValue;return t;操作。

int incrementAndGet() 以原子方式将当前值加 1。 相当于线程安全版本的++i操作。

void lazySet(int newValue) 最后设置为给定值。 延时设置变量值,这个等价于set()方法,但是由于字段是volatile类型的,因此次字段的修改会比普通字段(非volatile字段)有稍微的性能延时(尽管可以忽略),所以如果不是想立即读取设置的新值,允许在“后台”修改值,那么此方法就很有用。如果还是难以理解,这里就类似于启动一个后台线程如执行修改新值的任务,原线程就不等待修改结果立即返回(这种解释其实是不正确的,但是可以这么理解)。

void set(int newValue) 设置为给定值。 直接修改原始值,也就是i=newValue操作。

boolean weakCompareAndSet(int expect, int update) 如果当前值 == 预期值,则以原子方式将该设置为给定的更新值。JSR规范中说:以原子方式读取和有条件地写入变量但 创建任何 happen-before 排序,因此不提供与除 weakCompareAndSet 目标外任何变量以前或后续读取或写入操作有关的任何保证。大意就是说调用weakCompareAndSet时并不能保证不存在happen-before的发生(也就是可能存在指令重排序导致此操作失败)。但是从Java源码来看,其实此方法并没有实现JSR规范的要求,最后效果和compareAndSet是等效的,都调用了unsafe.compareAndSwapInt()完成操作。 下面的代码是一个测试样例,为了省事就写在一个方法里面来了。 package xylz.study.concurrency.atomic; import java.util.concurrent.atomic.AtomicInteger; import org.junit.Test; import static org.junit.Assert./*; public class AtomicIntegerTest { @Test public void testAll() throws InterruptedException{ final AtomicInteger value = new AtomicInteger(10); assertEquals(value.compareAndSet(1, 2), false); assertEquals(value.get(), 10); assertTrue(value.compareAndSet(10, 3)); assertEquals(value.get(), 3); value.set(0); // assertEquals(value.incrementAndGet(), 1); assertEquals(value.getAndAdd(2),1); assertEquals(value.getAndSet(5),3); assertEquals(value.get(),5); // final int threadSize = 10; Thread[] ts = new Thread[threadSize]; for (int i = 0; i < threadSize; i++) { ts[i] = new Thread() { public void run() { value.incrementAndGet(); } }; } // for(Thread t:ts) { t.start(); } for(Thread t:ts) { t.join(); } // assertEquals(value.get(), 5+threadSize); } } 由于这里例子比较简单,这里就不做过多介绍了。 AtomicInteger和AtomicLong、AtomicBoolean、AtomicReference差不多,这里就不介绍了。在下一篇中就介绍下数组、字段等其他方面的原子操作。 参考资料: (1)http://stackoverflow.com/questions/2443239/java-atomicinteger-what-are-the-differences-between-compareandset-and-weakcompar (2)http://stackoverflow.com/questions/1468007/atomicinteger-lazyset-and-set

来源: [http://www.blogjava.net/xylz/archive/2010/07/01/324988.html](http://www.blogjava.net/xylz/archive/2010/07/01/324988.html)

在这一部分开始讨论数组原子操作和一些其他的原子操作。

AtomicIntegerArray/AtomicLongArray/AtomicReferenceArray的API类似,选择有代表性的AtomicIntegerArray来描述这些问题。

int get(int i)

获取位置

i 的当前值。很显然,由于这个是数组操作,就有索引越界的问题(IndexOutOfBoundsException异常)。

对于下面的API起始和AtomicInteger是类似的,这种通过方法、参数的名称就能够得到函数意义的写法是非常值得称赞的。在《重构:改善既有代码的设计》《代码整洁之道》中都非常推崇这种做法。

void set(int i, int newValue) void lazySet(int i, int newValue) int getAndSet(int i, int newValue) boolean compareAndSet(int i, int expect, int update) boolean weakCompareAndSet(int i, int expect, int update) int getAndIncrement(int i) int getAndDecrement(int i) int getAndAdd(int i, int delta) int incrementAndGet(int i) int decrementAndGet(int i) int addAndGet(int i, int delta)

整体来说,数组的原子操作在理解上还是相对比较容易的,这些API就是有多使用才能体会到它们的好处,而不仅仅是停留在理论阶段。

现在关注字段的原子更新。

AtomicIntegerFieldUpdater/AtomicLongFieldUpdater/AtomicReferenceFieldUpdater是基于反射的原子更新字段的值。

相应的API也是非常简单的,但是也是有一些约束的。

(1)字段必须是volatile类型的!在后面的章节中会详细说明为什么必须是volatile,volatile到底是个什么东西。

(2)字段的描述类型(修饰符public/protected/default/private)是与调用者与操作对象字段的关系一致。也就是说调用者能够直接操作对象字段,那么就可以反射进行原子操作。但是对于父类的字段,子类是不能直接操作的,尽管子类可以访问父类的字段。

(3)只能是实例变量,不能是类变量,也就是说不能加static关键字。

(4)只能是可修改变量,不能使final变量,因为final的语义就是不可修改。实际上final的语义和volatile是有冲突的,这两个关键字不能同时存在。

(5)对于AtomicIntegerFieldUpdaterAtomicLongFieldUpdater只能修改int/long类型的字段,不能修改其包装类型(Integer/Long)。如果要修改包装类型就需要使用AtomicReferenceFieldUpdater

在下面的例子中描述了操作的方法。

package xylz.study.concurrency.atomic; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; public class AtomicIntegerFieldUpdaterDemo { class DemoData{ public volatile int value1 = 1; volatile int value2 = 2; protected volatile int value3 = 3; private volatile int value4 = 4; } AtomicIntegerFieldUpdater getUpdater(String fieldName) { return AtomicIntegerFieldUpdater.newUpdater(DemoData.class, fieldName); } void doit() { DemoData data = new DemoData(); System.out.println("1 ==> "+getUpdater("value1").getAndSet(data, 10)); System.out.println("3 ==> "+getUpdater("value2").incrementAndGet(data)); System.out.println("2 ==> "+getUpdater("value3").decrementAndGet(data)); System.out.println("true ==> "+getUpdater("value4").compareAndSet(data, 4, 5)); } public static void main(String[] args) { AtomicIntegerFieldUpdaterDemo demo = new AtomicIntegerFieldUpdaterDemo(); demo.doit(); } }

在上面的例子中DemoData的字段value3/value4对于AtomicIntegerFieldUpdaterDemo类是不可见的,因此通过反射是不能直接修改其值的。

AtomicMarkableReference类描述的一个的对,可以原子的修改Object或者Boolean的值,这种数据结构在一些缓存或者状态描述中比较有用。这种结构在单个或者同时修改Object/Boolean的时候能够有效的提高吞吐量。

AtomicStampedReference类维护带有整数“标志”的对象引用,可以用原子方式对其进行更新。对比AtomicMarkableReference类的AtomicStampedReference维护的是一种类似的数据结构,其实就是对对象(引用)的一个并发计数。但是与AtomicInteger不同的是,此数据结构可以携带一个对象引用(Object),并且能够对此对象和计数同时进行原子操作。

在后面的章节中会提到“ABA问题”,而AtomicMarkableReference/**AtomicStampedReference在解决“ABA问题”上很有用。**


原子操作的使用大概就是这么多,大体来说还算是比较清晰的,在下一个章节中,将对象原子操作进行总结,重点介绍下原子操作的原理和设计思想。 来源: [http://www.blogjava.net/xylz/archive/2010/07/02/325079.html](http://www.blogjava.net/xylz/archive/2010/07/02/325079.html)

在这个小结里面重点讨论原子操作的原理和设计思想。

由于在下一个章节中会谈到锁机制,因此此小节中会适当引入锁的概念。

Java Concurrency in Practice中是这样定义线程安全的: 当多个线程访问一个类时,如果不用考虑这些线程在运行时环境下的调度和交替运行,并且不需要额外的同步及在调用方代码不必做其他的协调,这个类的行为仍然是正确的,那么这个类就是线程安全的。

显然只有资源竞争时才会导致线程不安全,因此无状态对象永远是线程安全的

原子操作的描述是: 多个线程执行一个操作时,其中任何一个线程要么完全执行完此操作,要么没有执行此操作的任何步骤,那么这个操作就是原子的。

枯燥的定义介绍完了,下面说更枯燥的理论知识。

指令重排序

Java语言规范规定了JVM线程内部维持顺序化语义,也就是说只要程序的最终结果等同于它在严格的顺序化环境下的结果,那么指令的执行顺序就可能与代码的顺序不一致。这个过程通过叫做指令的重排序。指令重排序存在的意义在于:JVM能够根据处理器的特性(CPU的多级缓存系统、多核处理器等)适当的重新排序机器指令,使机器指令更符合CPU的执行特点,最大限度的发挥机器的性能。

程序执行最简单的模型是按照指令出现的顺序执行,这样就与执行指令的CPU无关,最大限度的保证了指令的可移植性。这个模型的专业术语叫做顺序化一致性模型。但是现代计算机体系和处理器架构都不保证这一点(因为人为的指定并不能总是保证符合CPU处理的特性)。

我们来看最经典的一个案例。 package xylz.study.concurrency.atomic; public class ReorderingDemo { static int x = 0, y = 0, a = 0, b = 0; public static void main(String[] args) throws Exception { for (int i = 0; i < 100; i++) { x=y=a=b=0; Thread one = new Thread() { public void run() { a = 1; x = b; } }; Thread two = new Thread() { public void run() { b = 1; y = a; } }; one.start(); two.start(); one.join(); two.join(); System.out.println(x + " " + y); } } } 在这个例子中one/two两个线程修改区x,y,a,b四个变量,在执行100次的情况下,可能得到(0 1)或者(1 0)或者(1 1)。事实上按照JVM的规范以及CPU的特性有很可能得到(0 0)。当然上面的代码大家不一定能得到(0 0),因为run()里面的操作过于简单,可能比启动一个线程花费的时间还少,因此上面的例子难以出现(0,0)。但是在现代CPU和JVM上确实是存在的。由于run()里面的动作对于结果是无关的,因此里面的指令可能发生指令重排序,即使是按照程序的顺序执行,数据变化刷新到主存也是需要时间的。假定是按照a=1;x=b;b=1;y=a;执行的,x=0是比较正常的,虽然a=1在y=a之前执行的,但是由于线程one执行a=1完成后还没有来得及将数据1写回主存(这时候数据是在线程one的堆栈里面的),线程two从主存中拿到的数据a可能仍然是0(显然是一个过期数据,但是是有可能的),这样就发生了数据错误。

在两个线程交替执行的情况下数据的结果就不确定了,在机器压力大,多核CPU并发执行的情况下,数据的结果就更加不确定了。

Happens-before法则

Java存储模型有一个happens-before原则,就是如果动作B要看到动作A的执行结果(无论A/B是否在同一个线程里面执行),那么A/B就需要满足happens-before关系。

在介绍happens-before法则之前介绍一个概念:JMM动作(Java Memeory Model Action),Java存储模型动作。一个动作(Action)包括:变量的读写、监视器加锁和释放锁、线程的start()和join()。后面还会提到锁的的。

happens-before完整规则: (1)同一个线程中的每个Action都happens-before于出现在其后的任何一个Action。

(2)对一个监视器的解锁happens-before于每一个后续对同一个监视器的加锁。

(3)对volatile字段的写入操作happens-before于每一个后续的同一个字段的读操作。

(4)Thread.start()的调用会happens-before于启动线程里面的动作。

(5)Thread中的所有动作都happens-before于其他线程检查到此线程结束或者Thread.join()中返回或者Thread.isAlive()==false。

(6)一个线程A调用另一个另一个线程B的interrupt()都happens-before于线程A发现B被A中断(B抛出异常或者A检测到B的isInterrupted()或者interrupted())。

(7)一个对象构造函数的结束happens-before与该对象的finalizer的开始

(8)如果A动作happens-before于B动作,而B动作happens-before与C动作,那么A动作happens-before于C动作。

volatile语义

到目前为止,我们多次提到volatile,但是却仍然没有理解volatile的语义。

volatile相当于synchronized的弱实现,也就是说volatile实现了类似synchronized的语义,却又没有锁机制。它确保对volatile字段的更新以可预见的方式告知其他的线程。

volatile包含以下语义:

(1)Java 存储模型不会对valatile指令的操作进行重排序:这个保证对volatile变量的操作时按照指令的出现顺序执行的。

(2)volatile变量不会被缓存在寄存器中(只有拥有线程可见)或者其他对CPU不可见的地方,每次总是从主存中读取volatile变量的结果。也就是说对于volatile变量的修改,其它线程总是可见的,并且不是使用自己线程栈内部的变量。也就是在happens-before法则中,对一个valatile变量的写操作后,其后的任何读操作理解可见此写操作的结果。

尽管volatile变量的特性不错,但是volatile并不能保证线程安全的,也就是说volatile字段的操作不是原子性的,volatile变量只能保证可见性(一个线程修改后其它线程能够理解看到此变化后的结果),要想保证原子性,目前为止只能加锁!

volatile通常在下面的场景:

volatile boolean done = false; while( ! done ){ dosomething(); }

应用volatile变量的三个原则: (1)写入变量不依赖此变量的值,或者只有一个线程修改此变量

(2)变量的状态不需要与其它变量共同参与不变约束

(3)访问变量不需要加锁

这一节理论知识比较多,但是这是很面很多章节的基础,在后面的章节中会多次提到这些特性。

本小节中还是没有谈到原子操作的原理和思想,在下一节中将根据上面的一些知识来介绍原子操作。

参考资料:

(1)Java Concurrency in Practice

(2)正确使用 Volatile 变量 来源: [http://www.blogjava.net/xylz/archive/2010/07/03/325168.html](http://www.blogjava.net/xylz/archive/2010/07/03/325168.html)

在JDK 5之前Java语言是靠synchronized关键字保证同步的,这会导致有锁(后面的章节还会谈到锁)。

锁机制存在以下问题:

(1)在多线程竞争下,加锁、释放锁会导致比较多的上下文切换和调度延时,引起性能问题。

(2)一个线程持有锁会导致其它所有需要此锁的线程挂起。

(3)如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能风险。

volatile是不错的机制,但是volatile不能保证原子性。因此对于同步最终还是要回到锁机制上来。

独占锁是一种悲观锁,synchronized就是一种独占锁,会导致其它所有需要锁的线程挂起,等待持有锁的线程释放锁。而另一个更加有效的锁就是乐观锁。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。

CAS 操作

上面的乐观锁用到的机制就是CAS,Compare and Swap。

CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。

非阻塞算法 (nonblocking algorithms) 一个线程的失败或者挂起不应该影响其他线程的失败或挂起的算法。

现代的CPU提供了特殊的指令,可以自动更新共享数据,而且能够检测到其他线程的干扰,而 compareAndSet() 就用这些代替了锁定。

拿出AtomicInteger来研究在没有锁的情况下是如何做到数据正确性的。 private volatile int value;

首先毫无以为,在没有锁的机制下可能需要借助volatile原语,保证线程间的数据是可见的(共享的)。

这样才获取变量的值的时候才能直接读取。 public final int get() { return value; }

然后来看看++i是怎么做到的。

public final int incrementAndGet() { for (;;) { int current = get(); int next = current + 1; if (compareAndSet(current, next)) return next; } }

在这里采用了CAS操作,每次从内存中读取数据然后将此数据和+1后的结果进行CAS操作,如果成功就返回结果,否则重试直到成功为止。

而compareAndSet利用JNI来完成CPU指令的操作。 public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update); }

整体的过程就是这样子的,利用CPU的CAS指令,同时借助JNI来完成Java的非阻塞算法。其它原子操作都是利用类似的特性完成的。

而整个J.U.C都是建立在CAS之上的,因此对于synchronized阻塞算法,J.U.C在性能上有了很大的提升。参考资料的文章中介绍了如果利用CAS构建非阻塞计数器、队列等数据结构。

CAS看起来很爽,但是会导致“ABA问题”。

CAS算法实现一个重要前提需要取出内存中某时刻的数据,而在下时刻比较并替换,那么在这个时间差类会导致数据的变化。

比如说一个线程one从内存位置V中取出A,这时候另一个线程two也从内存中取出A,并且two进行了一些操作变成了B,然后two又将V位置的数据变成A,这时候线程one进行CAS操作发现内存中仍然是A,然后one操作成功。尽管线程one的CAS操作成功,但是不代表这个过程就是没有问题的。如果链表的头在变化了两次后恢复了原值,但是不代表链表就没有变化。因此前面提到的原子操作AtomicStampedReference/AtomicMarkableReference就很有用了。这允许一对变化的元素进行原子操作。

参考资料:

(1)非阻塞算法简介

(2)流行的原子 来源: [http://www.blogjava.net/xylz/archive/2010/07/04/325206.html](http://www.blogjava.net/xylz/archive/2010/07/04/325206.html)