通过Java

Posted on

通过Java/JMX得到full GC次数? - 高级语言虚拟机

您还未登录 ! 登录 注册

ITeye3.0

群组首页编程语言高级语言虚拟机知识库JVM实战通过Java/JMX得到full GC次数? 原创作者: RednaxelaFX 阅读:2261次 评论:1条 更新时间:2011-05-26

今天有个同事问如何能通过JMX获取到某个Java进程的full GC次数: 引用

hi,问个问题,怎们在java中获取到full gc的次数呢? 我现在用jmx的那个得到了gc次数,不过不能细化出来full gc的次数 Java代码 收藏代码

  1. for (final GarbageCollectorMXBean garbageCollector
  2. : ManagementFactory.getGarbageCollectorMXBeans()) {
  3. gcCounts += garbageCollector.getCollectionCount();
  4. }
    for (final GarbageCollectorMXBean garbageCollector : ManagementFactory.getGarbageCollectorMXBeans()) { gcCounts += garbageCollector.getCollectionCount(); } 你比如我现在是这样拿次数的 我回答说因为full GC概念只有在分代式GC的上下文中才存在,而JVM并不强制要求GC使用分代式实现,所以JMX提供的标准MXBean API里不提供“full GC次数”这样的方法也正常。 既然“full GC”本来就是非常平台相关的概念,那就hack一点,用平台相关的代码来解决问题好了。这些GC的MXBean都是有名字的,而主流的JVM的GC名字相对稳定,非要通过JMX得到full GC次数的话,用名字来判断一下就好了。 举个例子来看看。通过JDK 6自带的JConsole工具来查看相关的MXBean的话,可以看到, GC的MXBean在这个位置: 这个例子是用server模式启动JConsole的,使用的是ParallelScavenge GC,它的年老代对应的收集器在这里: 该收集器的总收集次数在此,这也就是full GC的次数: 于是只要知道我们用的JVM提供的GC MXBean的名字与分代的关系,就可以知道full GC的次数了。 Java代码写起来冗长,这帖就不用Java来写例子了,反正API是一样的,意思能表达清楚就OK。 用一个Groovy脚本简单演示一下适用于Oracle (Sun) HotSpot与Oracle (BEA) JRockit的GC统计程序: Groovy代码 收藏代码

  5. import java.lang.management.ManagementFactory

  6. printGCStats = {
  7. def youngGenCollectorNames = [
  8. // Oracle (Sun) HotSpot
  9. // -XX:+UseSerialGC
  10. 'Copy',
  11. // -XX:+UseParNewGC
  12. 'ParNew',
  13. // -XX:+UseParallelGC
  14. 'PS Scavenge',
  15. // Oracle (BEA) JRockit
  16. // -XgcPrio:pausetime
  17. 'Garbage collection optimized for short pausetimes Young Collector',
  18. // -XgcPrio:throughput
  19. 'Garbage collection optimized for throughput Young Collector',
  20. // -XgcPrio:deterministic
  21. 'Garbage collection optimized for deterministic pausetimes Young Collector'
  22. ]
  23. def oldGenCollectorNames = [
  24. // Oracle (Sun) HotSpot
  25. // -XX:+UseSerialGC
  26. 'MarkSweepCompact',
  27. // -XX:+UseParallelGC and (-XX:+UseParallelOldGC or -XX:+UseParallelOldGCCompacting)
  28. 'PS MarkSweep',
  29. // -XX:+UseConcMarkSweepGC
  30. 'ConcurrentMarkSweep',
  31. // Oracle (BEA) JRockit
  32. // -XgcPrio:pausetime
  33. 'Garbage collection optimized for short pausetimes Old Collector',
  34. // -XgcPrio:throughput
  35. 'Garbage collection optimized for throughput Old Collector',
  36. // -XgcPrio:deterministic
  37. 'Garbage collection optimized for deterministic pausetimes Old Collector'
  38. ]
  39. R: {
  40. ManagementFactory.garbageCollectorMXBeans.each {
  41. def name = it.name
  42. def count = it.collectionCount
  43. def gcType;
  44. switch (name) {
  45. case youngGenCollectorNames:
  46. gcType = 'Minor Collection'
  47. break
  48. case oldGenCollectorNames:
  49. gcType = 'Major Collection'
  50. break
  51. default:
  52. gcType = 'Unknown Collection Type'
  53. break
  54. }
  55. println "$count <- $gcType: $name"
  56. }
  57. }
  58. }
  59. printGCStats()
    import java.lang.management.ManagementFactory printGCStats = { def youngGenCollectorNames = [ // Oracle (Sun) HotSpot // -XX:+UseSerialGC 'Copy', // -XX:+UseParNewGC 'ParNew', // -XX:+UseParallelGC 'PS Scavenge', // Oracle (BEA) JRockit // -XgcPrio:pausetime 'Garbage collection optimized for short pausetimes Young Collector', // -XgcPrio:throughput 'Garbage collection optimized for throughput Young Collector', // -XgcPrio:deterministic 'Garbage collection optimized for deterministic pausetimes Young Collector' ] def oldGenCollectorNames = [ // Oracle (Sun) HotSpot // -XX:+UseSerialGC 'MarkSweepCompact', // -XX:+UseParallelGC and (-XX:+UseParallelOldGC or -XX:+UseParallelOldGCCompacting) 'PS MarkSweep', // -XX:+UseConcMarkSweepGC 'ConcurrentMarkSweep', // Oracle (BEA) JRockit // -XgcPrio:pausetime 'Garbage collection optimized for short pausetimes Old Collector', // -XgcPrio:throughput 'Garbage collection optimized for throughput Old Collector', // -XgcPrio:deterministic 'Garbage collection optimized for deterministic pausetimes Old Collector' ] R: { ManagementFactory.garbageCollectorMXBeans.each { def name = it.name def count = it.collectionCount def gcType; switch (name) { case youngGenCollectorNames: gcType = 'Minor Collection' break case oldGenCollectorNames: gcType = 'Major Collection' break default: gcType = 'Unknown Collection Type' break } println "$count <- $gcType: $name" } } } printGCStats() 执行可以看到类似这样的输出: Command prompt代码 收藏代码

  60. 5 <- Minor Collection: Copy

  61. 0 <- Major Collection: MarkSweepCompact
    5 <- Minor Collection: Copy 0 <- Major Collection: MarkSweepCompact ↑这是用client模式的HotSpot执行得到的; Command prompt代码 收藏代码

  62. 0 <- Minor Collection: Garbage collection optimized for throughput Young Collector

  63. 0 <- Major Collection: Garbage collection optimized for throughput Old Collector
    0 <- Minor Collection: Garbage collection optimized for throughput Young Collector 0 <- Major Collection: Garbage collection optimized for throughput Old Collector ↑这是用JRockit R28在32位Windows上的默认模式得到的。 通过上述方法,要包装起来方便以后使用的话也很简单,例如下面Groovy程序: Groovy代码 收藏代码

  64. import java.lang.management.ManagementFactory

  65. class GCStats {
  66. static final List YoungGenCollectorNames = [
  67. // Oracle (Sun) HotSpot
  68. // -XX:+UseSerialGC
  69. 'Copy',
  70. // -XX:+UseParNewGC
  71. 'ParNew',
  72. // -XX:+UseParallelGC
  73. 'PS Scavenge',
  74. // Oracle (BEA) JRockit
  75. // -XgcPrio:pausetime
  76. 'Garbage collection optimized for short pausetimes Young Collector',
  77. // -XgcPrio:throughput
  78. 'Garbage collection optimized for throughput Young Collector',
  79. // -XgcPrio:deterministic
  80. 'Garbage collection optimized for deterministic pausetimes Young Collector'
  81. ]
  82. static final List OldGenCollectorNames = [
  83. // Oracle (Sun) HotSpot
  84. // -XX:+UseSerialGC
  85. 'MarkSweepCompact',
  86. // -XX:+UseParallelGC and (-XX:+UseParallelOldGC or -XX:+UseParallelOldGCCompacting)
  87. 'PS MarkSweep',
  88. // -XX:+UseConcMarkSweepGC
  89. 'ConcurrentMarkSweep',
  90. // Oracle (BEA) JRockit
  91. // -XgcPrio:pausetime
  92. 'Garbage collection optimized for short pausetimes Old Collector',
  93. // -XgcPrio:throughput
  94. 'Garbage collection optimized for throughput Old Collector',
  95. // -XgcPrio:deterministic
  96. 'Garbage collection optimized for deterministic pausetimes Old Collector'
  97. ]
  98. static int getYoungGCCount() {
  99. ManagementFactory.garbageCollectorMXBeans.inject(0) { youngGCCount, gc ->
  100. if (YoungGenCollectorNames.contains(gc.name))
  101. youngGCCount + gc.collectionCount
  102. else
  103. youngGCCount
  104. }
  105. }
  106. static int getFullGCCount() {
  107. ManagementFactory.garbageCollectorMXBeans.inject(0) { fullGCCount, gc ->
  108. if (OldGenCollectorNames.contains(gc.name))
  109. fullGCCount + gc.collectionCount
  110. else
  111. fullGCCount
  112. }
  113. }
  114. }
    import java.lang.management.ManagementFactory class GCStats { static final List YoungGenCollectorNames = [ // Oracle (Sun) HotSpot // -XX:+UseSerialGC 'Copy', // -XX:+UseParNewGC 'ParNew', // -XX:+UseParallelGC 'PS Scavenge', // Oracle (BEA) JRockit // -XgcPrio:pausetime 'Garbage collection optimized for short pausetimes Young Collector', // -XgcPrio:throughput 'Garbage collection optimized for throughput Young Collector', // -XgcPrio:deterministic 'Garbage collection optimized for deterministic pausetimes Young Collector' ] static final List OldGenCollectorNames = [ // Oracle (Sun) HotSpot // -XX:+UseSerialGC 'MarkSweepCompact', // -XX:+UseParallelGC and (-XX:+UseParallelOldGC or -XX:+UseParallelOldGCCompacting) 'PS MarkSweep', // -XX:+UseConcMarkSweepGC 'ConcurrentMarkSweep', // Oracle (BEA) JRockit // -XgcPrio:pausetime 'Garbage collection optimized for short pausetimes Old Collector', // -XgcPrio:throughput 'Garbage collection optimized for throughput Old Collector', // -XgcPrio:deterministic 'Garbage collection optimized for deterministic pausetimes Old Collector' ] static int getYoungGCCount() { ManagementFactory.garbageCollectorMXBeans.inject(0) { youngGCCount, gc -> if (YoungGenCollectorNames.contains(gc.name)) youngGCCount + gc.collectionCount else youngGCCount } } static int getFullGCCount() { ManagementFactory.garbageCollectorMXBeans.inject(0) { fullGCCount, gc -> if (OldGenCollectorNames.contains(gc.name)) fullGCCount + gc.collectionCount else fullGCCount } } } 用的时候: Groovysh代码 收藏代码

  115. D:>\sdk\groovy-1.7.2\bin\groovysh

  116. Groovy Shell (1.7.2, JVM: 1.6.0_20)
  117. Type 'help' or '\h' for help.

  118. groovy:000> GCStats.fullGCCount
  119. ===> 0
  120. groovy:000> System.gc()
  121. ===> null
  122. groovy:000> GCStats.fullGCCount
  123. ===> 1
  124. groovy:000> System.gc()
  125. ===> null
  126. groovy:000> System.gc()
  127. ===> null
  128. groovy:000> GCStats.fullGCCount
  129. ===> 3
  130. groovy:000> GCStats.youngGCCount
  131. ===> 9
  132. groovy:000> GCStats.youngGCCount
  133. ===> 9
  134. groovy:000> GCStats.youngGCCount
  135. ===> 9
  136. groovy:000> System.gc()
  137. ===> null
  138. groovy:000> GCStats.youngGCCount
  139. ===> 9
  140. groovy:000> GCStats.fullGCCount
  141. ===> 4
  142. groovy:000> quit
    D:>\sdk\groovy-1.7.2\bin\groovysh Groovy Shell (1.7.2, JVM: 1.6.0_20) Type 'help' or '\h' for help. -------------------------------------------------- groovy:000> GCStats.fullGCCount ===> 0 groovy:000> System.gc() ===> null groovy:000> GCStats.fullGCCount ===> 1 groovy:000> System.gc() ===> null groovy:000> System.gc() ===> null groovy:000> GCStats.fullGCCount ===> 3 groovy:000> GCStats.youngGCCount ===> 9 groovy:000> GCStats.youngGCCount ===> 9 groovy:000> GCStats.youngGCCount ===> 9 groovy:000> System.gc() ===> null groovy:000> GCStats.youngGCCount ===> 9 groovy:000> GCStats.fullGCCount ===> 4 groovy:000> quit 这是在Sun JDK 6 update 20上跑的。顺带一提,如果这是跑在JRockit上的话,那full GC的次数就不会增加——因为JRockit里System.gc()默认是触发young GC的;请不要因为Sun HotSpot的默认行为而认为System.gc()总是会触发full GC的。 关于JMX的MXBean的使用,也可以参考下面两篇文档: Groovy and JMX Monitoring the JVM Heap with JRuby 如何更快的启动eclipse

评论 共 1 条 请登录后发表评论

1 楼 xgj1988 2011-04-27 15:07

SUN jdk 使用FULL GC 是如下的几种之一? Java代码 收藏代码

  1. static final List OldGenCollectorNames = [
  2. // Oracle (Sun) HotSpot
  3. // -XX:+UseSerialGC
  4. 'MarkSweepCompact',
  5. // -XX:+UseParallelGC and (-XX:+UseParallelOldGC or -XX:+UseParallelOldGCCompacting)
  6. 'PS MarkSweep',
  7. // -XX:+UseConcMarkSweepGC
  8. 'ConcurrentMarkSweep',
  9. ]
    static final List OldGenCollectorNames = [ // Oracle (Sun) HotSpot // -XX:+UseSerialGC 'MarkSweepCompact', // -XX:+UseParallelGC and (-XX:+UseParallelOldGC or -XX:+UseParallelOldGCCompacting) 'PS MarkSweep', // -XX:+UseConcMarkSweepGC 'ConcurrentMarkSweep', ] 这几个是young gc? Java代码 收藏代码

  10. 'Copy',

  11. // -XX:+UseParNewGC
  12. 'ParNew',
  13. // -XX:+UseParallelGC
  14. 'PS Scavenge',
  15. 'Copy', // -XX:+UseParNewGC 'ParNew', // -XX:+UseParallelGC 'PS Scavenge', 根据名字判断来获取full gc

    发表评论

您还没有登录,请您登录后再发表评论

New-page

文章信息

知识库: 高级语言虚拟机

相关讨论

© 2003-2012 ITeye.com. [ 京ICP证110151号 京公网安备110105010620 ] 百联优力(北京)投资有限公司 版权所有

J2EE学习总结 思维方式和理念

Posted on

J2EE学习总结 思维方式和理念

J2EE学习总结:思维方式和理念 Webjx网页教学提示:不管怎么样我还是建议从基本的做起,学精J2SE,熟读它的源码,准确了解其设计理念,然后分头击破J2EE――一口吃不成一个胖子!不要贪多贪广!脚踏实地就可以了!

这篇文章写在我研究J2SE、J2EE近三年后。前3年我研究了J2SE的Swing、Applet、Net、RMI、Collections、IO、JNI……研究了J2EE的JDBC、Sevlet、JSP、JNDI…..不久我发现这些好像太浮浅了:首先,我发现自己知道的仅仅是java提供的大量的API,根本不能很好地使用它; 其次,我根本就没有学到任何有助于写程序的知识,此时我也只不过能写个几页的小程序。出于这个幼稚的想法我研究了JDK中Collections、Logger、IO…..的源代码,发现这个世界真的很神奇,竟然有如此的高手――利用java语言最最基本的语法,创造了这些优秀的Framework。

从此一发不可收拾,我继续研究了J2EE的部分,又发现这是一个我根本不能理解的方向(曾经有半年停滞不前),为什么只有接口没有实现啊!后来由于一直使用Tomcat、Derby等软件突然发现:哦!原来J2EE仅仅是一个标准,只是一个架构。真正的实现是不同提供商提供的。

接着我研究了MOM4J、OpenJMS、Mocki、HSQLD……发现这些就是J2EE的实现啊!原来软件竟会如此复杂,竟会如此做….规范和实现又是如何成为一体的呢?通过上面的研究发现:原来J2EE后面竟然有太多太多理念、太多太多的相似!这些相似就是其背后的理念――设计模式!(很幸运,在我学java的时候,我一般学java的一个方向就会读一些关于设计模式的书!很幸运,到能领略一点的时候能真正知道这是为什么!)其实模式就是一种思维方式、就是一种理念……模式是要运用到程序中的,只有从真正的项目中才能领会模式的含义…… 学得越多,发现懂得越少!在学习过程中发现一些很有用,很值得学习的开源项目,今天在此推荐给大家。

一、JavaServlet和JSP方向

很多人都是从Servlet和JSP步入J2EE的。它就是J2EE的表现层,用于向客户呈现服务器上的内容。J2EE很重要的方面。不罗嗦了!大家都知道的!下面就开始推荐吧!

  1. Jakarta Tomcat

Apache基金会提供的免费的开源的Serlvet容器,它是的Jakarta项目中的一个核心项目,由Apache、Sun和其它一些公司(都是IT界的大鳄哦)及个人共同开发而成,全世界绝大部分Servlet和Jsp的容器都是使用它哦!由于Sun的参与和支持,最新的Servlet和Jsp规范总能在Tomcat中得到体现。

不过它是一个非常非常全的Serlvet容器,全部源码可能有4000页,对于初学者或者一般的老手可能还是比较大了!在你有能力时推荐研究!下载地址:http://jakarta.apache.org/tomcat/index.html

下面推荐两个小一点的吧!

  1. Jetty

Jetty是一个开放源码的HTTP服务器和Java serverlet容器。源代码只有1000页左右,很值得研究。有兴趣可以去http://jetty.mortbay.com/下载看看。我曾经翻了一下,只是目前没有时间。(都化在博客上了,等博客基本定型,且内容完整了,再干我热衷的事件吧!)

  1. Jigsaw

Jigsaw是W3C开发的HTTP,基于Java 的服务器,提供了未来 Web 技术发展的蓝图。W3C知道吧!(太有名气了,很多标准都是它制订的!有空经常去看看吧!)下载网址:http://www.w3.org/Jigsaw代码仅仅1000页左右。

  1. Jo!

Jo!是一个纯Java的实现了Servlet API 2.2, JSP 1.1, 和HTTP/1.1的Web服务器。它的特性包括支持servlet tag,支持SSI,高级线程管理,虚拟主机,数据缓存,自动压缩text或HTML文件进行传输,国际化支持,自动重新加载Servlet、Jsp,自动重新加载web工程文件(WARs),支持WAR热部署和一个Swing控制台。jo!可以被用做jboss和jakarta avalon-phoenix的web容器。下载地址http://www.tagtraum.com/ 。我极力推荐大家在研究Tomcat之前研究该软件,主要是其比Tomcat小多了,且开发者提供比较全的手册。该方向研究这两个也就可以了!

二、JDBC方向

很多人都喜欢JDBC,数据库吗!很深奥的东西,一听就可以糊弄人。其实等你真正研究了数据库的实现后发现,接口其实真的太简单,太完美了!要想设计如此优秀的框架还是需要学习的。下面就推荐几个数据库的实现吧!

  1. Hypersonic SQL

Hypersonic SQL开源数据库方向比较流行的纯Java开发的关系型数据库。好像不是JDBC兼容的,JDBC的很多高级的特性都没有支持,不过幸好支持ANSI-92 标准 SQL语法。我推荐它主要是它的代码比较少1600页左右,如此小的数据库值得研究,而且他占的空间很小,大约只有160K,拥有快速的数据库引擎。推荐你的第一个开源数据库。下载地址:http://hsqldb.sourceforge.net/。

  1. Mckoi DataBase

McKoiDB 和Hypersonic SQL差不多,它是GPL 的license的纯Java开发的数据库。他的 JDBC Driver 是使用 JDBC version 3 的 Specifaction。 他也是遵循 SQL-92 的标准,也尽量支持新的 SQL 特色, 并且支持 Transaction 的功能。两个可以选一个吧!下载地址:http://mckoi.com/database/。

  1. Apache Derby

学Java的数据库我建议使用Apache Derby ,研究数据库想成为一个数据库的高手我建议你先研究Apache Derby。Apache Derby是一个高质量的、纯 Java开发的嵌入式关系数据库引擎,IBM® 将其捐献给Apache开放源码社区,同时IBM的产品CloudSpace是它对应的产品。Derby是基于文件系统,具有高度的可移植性,并且是轻量级的,这使得它非常便于发布。主要是没有商业用户的很好的界面,没有其太多的功能。不过对于我们使用数据库、研究数据库还是极其有用的。对于中小型的企业说老实话你也不要用什么Oracle、SqlServer了,用Derby就可以了,何况是开源的呢!只要能发挥其长处也不容易啊!下载地址:http://incubator.apache.org/derby。

不过在没有足够的能力前,不要试图读懂它!注释和源代码15000页左右,我一年的阅读量!能读下来并且能真正领会它,绝对高手!你能读完Derby的源代码只有两种可能:1.你成为顶尖的高手――至少是数据库这部分; 2.你疯了。选择吧!!!!作为我自己我先选择Hypersonic SQL这样的数据库先研究,能过这一关,再继续研究Derby!不就是一年的阅读量吗!我可以化3年去研究如何做一个数据库其实还是很值得的!有的人搞IT一辈子自己什么都没有做,也根本没有研究别人的东西!

作为一个IT落后于别国若干年的、从事IT的下游产业“外包”的国家的IT从业人员,我认为还是先研究别人的优秀的东西比较好!可以先研究别人的,然后消化,学为己用!一心闭门造车实在遗憾!

三、JMS方向

JMS可能对大家来说是一个比较陌生的方向!其实JMS是一个比较容易理解,容易上手的方向。主要是Java消息服务,API也是相当简单的。不过在企业应用中相当广泛。下面就介绍几个吧!

  1. MOM4J

MOM4J是一个完全实现JMS1.1规范的消息中间件并且向下兼容JMS1.0与1.02。它提供了自己的消息处理存储使它独立于关系数据与语言,它的客户端可以用任何语言开发。它可以算是一个小麻雀,很全实现也比较简单!它包含一个命名服务器,一个消息服务器,同时提供自己的持续层。设计也相当的巧妙,完全利用操作系统中文件系统设计的观念。代码也很少,250页左右,最近我在写该实现的源代码阅读方面的书,希望明年年中能与大家见面!下载地址:http://mom4j.sourceforge.net/index.html。

  1. OpenJMS

OpenJMS是一个开源的Java Message Service API 1.0.2 规范的实现,它包含有以下特性:

  1. 它既支持点到点(point-to-point)(PTP)模型和发布/订阅(Pub/Sub)模型。

  2. 支持同步与异步消息发送 。

  3. JDBC持久性管理使用数据库表来存储消息 。

  4. 可视化管理界面。

  5. Applet支持。

  6. 能够与Jakarta Tomcat这样的Servlet容器结合。

  7. 支持RMI, TCP, HTTP 与SSL协议。

  8. 客户端验证 。

  9. 提供可靠消息传输、事务和消息过滤。

很好的JMS方向的开源项目!我目前也在研究它的源代码!学习它可以顺便研究JNDI的实现、以及网络通信的细节。这是我JMS方向研究的第二个开源项目。代码量1600页左右吧!下载地址:http://openjms.sourceforge.net/index.html

  1. ActiveMQ

ActiveMQ是一个开放源码基于Apache 2.0 licenced 发布并实现了JMS 1.1。它能够与Geronimo,轻量级容器和任Java应用程序无缝的给合。主要是Apache的可以任意的使用和发布哦!个人比较喜欢Apache的源代码!下载地址:http://activemq.codehaus.org/

  1. JORAM

JORAM一个类似于openJMS分布在ObjectWeb之下的JMS消息中间件。ObjectWeb的产品也是非常值得研究的!下面我还会给大家另外一个ObjectWeb的产品。下载地址:http://joram.objectweb.org/

我个人推荐:OpenJMS和ActiveMQ!

四、EJB方向

EJB一个比较“高级”的方向。Sun公司曾经以此在分布式计算领域重拳出击。不过自从出现了Spring、Hibernation……后似乎没落了!这个方向单独开源的也比较少,主要EJB是和JNDI、JDBC、JMS、JTS、JTA结合在一起的是以很少有单独的。下面推荐两个不过好像也要下载其它类库。

  1. EasyBeans

ObjectWeb的一个新的项目,一个轻量级的EJB3容器,虽然还没有正式发布,但是已经可以从它们的subversion仓库中检出代码。代码量比较小600页左右,熟读它可以对网络编程、架构、RMI、容器的状态设计比较了解了!即学会EJB又能学习其它设计方法何乐而不为哦!下载地址:http://easybeans.objectweb.org/

  1. OpenEJB

OpenEJB是一个预生成的、自包含的、可移植的EJB容器系统,可以被插入到任意的服务器环境,包括应用程序服务器,Web服务器,J2EE平台, CORBA ORB和数据库等等。OpenEJB 被用于 Apple的WebObjects。听起来很好,我目前没有研究过。不知道我就不推荐了。下载地址:http://www.openejb.org/

五、J2EE容器

上面谈了这么多,都是J2EE的各个方向的。其实J2EE是一个规范,J2EE的产品一般要求专业提供商必须提供它们的实现。这些实现本身就是J2EE容器。市场上流行的J2EE容器很多,在开源领域流行的只有很少,很少。其中最著名的是JBoss。

  1. JBoss

在J2EE应用服务器领域,Jboss是发展最为迅速的应用服务器。由于Jboss遵循商业友好的LGPL授权分发,并且由开源社区开发,这使得Jboss广为流行。另外,Jboss应用服务器还具有许多优秀的特质。

其一,它将具有革命性的JMX微内核服务作为其总线结构;

其二,它本身就是面向服务的架构(Service-Oriented Architecture,SOA);

其三,它还具有统一的类装载器,从而能够实现应用的热部署和热卸载能力。因此,它是高度模块化的和松耦合的。Jboss用户的积极反馈告诉我们,Jboss应用服务器是健壮的、高质量的,而且还具有良好的性能。为满足企业级市场日益增长的需求,Jboss公司从2003年开始就推出了24/*7、专业级产品支持服务。同时,为拓展Jboss的企业级市场,Jboss公司还签订了许多渠道合作伙伴。比如,Jboss公司同HP、Novell、Computer Associates、Unisys等都是合作伙伴。

在2004年6月,Jboss公司宣布,Jboss应用服务器通过了Sun公司的J2EE认证。这是Jboss应用服务器发展史上至今为止最重要的里程碑。与此同时,Jboss一直在紧跟最新的J2EE规范,而且在某些技术领域引领J2EE规范的开发。因此,无论在商业领域,还是在开源社区,Jboss成为了第一个通过J2EE 1.4认证的主流应用服务器。现在,Jboss应用服务器已经真正发展成具有企业强度(即,支持关键级任务的应用)的应用服务器。

Jboss 4.0作为J2EE认证的重要成果之一,已经于2004年9月顺利发布了。同时,Jboss 4.0还提供了Jboss AOP(Aspect-Oriented Programming,面向方面编程)组件。近来,AOP吸引了大量开发者的关注。它提供的新的编程模式使得用户能够将方面(比如,事务)从底层业务逻辑中分离出来,从而能够缩短软件开发周期。用户能够单独使用Jboss AOP,即能够在Jboss应用服务器外部使用它。或者,用户也可以在应用服务器环境中使用它。Jboss AOP 1.0已经在2004年10月发布了。 很有名吧!可以下载一个用一下,下载地址:http://www.jboss.org/

关于JBoss的使用资料也非常多,甚至比商业软件的还多。有机会研究吧!

  1. JOnAS

JOnAS是一个开放源代码的J2EE实现,在ObjectWeb协会中开发。整合了Tomcat或Jetty成为它的Web容器,以确保符合Servlet 2.3和JSP 1.2规范。JOnAS服务器依赖或实现以下的Java API:JCA、JDBC、JTA 、JMS、JMX、JNDI、JAAS、JavaMail 。下载地址:http://jonas.objectweb.org/ 3.Apache Geronimo

Apache Geronimo 是 Apache 软件基金会的开放源码J2EE服务器,它集成了众多先进技术和设计理念。 这些技术和理念大多源自独立的项目,配置和部署模型也各不相同。 Geronimo能将这些项目和方法的配置及部署完全整合到一个统一、易用的模型中。作为符合J2EE标准的服务器,Geronimo提供了丰富的功能集和无责任 Apache 许可,具备“立即部署”式J2EE 1.4容器的各种优点,其中包括:

  1. 符合J2EE1.4标准的服务器 。

  2. 预集成的开放源码项目 。

  3. 统一的集成模型 。

  4. 可伸缩性、可管理性和配置管理功能。

我一直比较推荐Apache的产品。主要是可以任意自由地使用。下载地址:http://incubator.apache.org/projects/geronimo/

六、其它

讲了这么多大家可能很厌烦了!是不是很多很多啊!其实不然,我们不会的太多太多了!不会的太多太多了。不管你是不是J2EE高手,还是J2SE高手,有些东西你要绝对很精明的。例如:1.Java的Collections Framework就是java的数据结构了,不仅要吃透它,还要能按照需要扩展它,利用其思想创建一个自己的数据结构。2.网络编程肯定要会吧,现在以及以后很多程序都是不在同一台机器上的,不会网络怎么行哦!3.IO肯定要会的吧!你的程序难道不用输入输出数据啊!整个IO包加NIO也有600多页的源代码哦!4.JDBC你要会吧!数据库都不会,在你的企业应用中你的数据又保存到哪里啊!文件中――太落后了吧!典型的没有学过J2EE。尽管数据库背后也是采用文件保存的。5.Serverlet、JSp你要是做网页做网站,肯定要做到。问你一个简单的问题,网页中如何实现分页啊!有具体方法的就在本文章后发言吧!6. Ant要会吧!java语言中发布的工具,类似与c中的make工具。7.JUnit用过吧!单元测试软件。你不要啊!你的软件就没有bug!你牛!(建议大家研究研究其源代码,很有用的框架,包含大量的设计模式,源代码不到100页!看了只能感叹――高手就是高手)细心的朋友可以看到在你使用的很多IDE工具中都有JUnit哦!就是它。

一切的一切才刚刚开始!有兴趣,有需要你可以研究数据库连接池的框架,如:C3P0、Jakarta DBCP、 DBPool….可以研究J2EE框架Spring……. Web框架Struts……持久层框架Hibernate…..甚至开发工具Eclipse…..Sun领导的点对点通信的JXTA…..报表工具JFreeChart、JasperReports…..分布式网络编程的CORBA、网络通信的JGROUPS、XML解析的xerces…..(在不经意间开源已经步入你的电脑,不信啊!你JDK的安装目录jdk1.6.0 src com sun org apache就是Xerces,一个XML解析的著名的开源 项目)

不管怎么样我还是建议从基本的做起,学精J2SE,熟读它的源码,准确了解其设计理念,然后分头击破J2EE――一口吃不成一个胖子!不要贪多贪广!脚踏实地就可以了!


http://www.webjx.com/exam/java-15508.html

请别再拿“String s = new String(

Posted on

请别再拿“String s = new String("xyz");创建了多少个String实例”来面试了吧

这帖是用来回复高级语言虚拟机圈子里的一个问题,一道Java笔试题的。 本来因为见得太多已经吐槽无力,但这次实在忍不住了就又爆发了一把。写得太长干脆单独开了一帖。 顺带广告:对JVM感兴趣的同学们同志们请多多支持高级语言虚拟机圈子

以下是回复内容。文中的“楼主”是针对原问题帖而言。

楼主是看各种宝典了么……以后我面试人的时候就要专找宝典答案是错的来问,方便筛人orz 楼主要注意了:这题或类似的题虽然经常见,但使用这个描述方式实际上没有任何意义: 引用

问题:

Java代码 复制代码 收藏代码

  1. String s = new String("xyz");
    String s = new String("xyz");创建了几个String Object? 这个问题自身就没有合理的答案,楼主所引用的“标准答案”自然也就不准确了: 引用

答案:两个(一个是“xyz”,一个是指向“xyz”的引用对象s) (好吧这个答案的吐槽点很多……大家慢慢来) 这问题的毛病是什么呢?它并没有定义“创建了”的意义。 什么叫“创建了”?什么时候创建了什么? 而且这段Java代码片段实际运行的时候真的会“创建两个String实例”么? 如果这道是面试题,那么可以当面让面试官澄清“创建了”的定义,然后再对应的回答。这种时候面试官多半会让被面试者自己解释,那就好办了,好好晒给面试官看。

如果是笔试题就没有提问要求澄清的机会了。不过会出这种题目的地方水平多半也不怎么样。说不定出题的人就是从各种宝典上把题抄来的,那就按照宝典把那不大对劲的答案写上去就能混过去了

先换成另一个问题来问: 引用

问题:

Java代码 复制代码 收藏代码

  1. String s = new String("xyz");
    String s = new String("xyz");在运行时涉及几个String实例? 一种合理的解答是: 引用

答案:两个,一个是字符串字面量"xyz"所对应的、驻留(intern)在一个全局共享的字符串常量池中的实例,另一个是通过new String(String)创建并初始化的、内容与"xyz"相同的实例 这是根据Java语言规范相关规定可以给出的合理答案。考虑到Java语言规范中明确指出了: The Java Language Specification, Third Edition 写道

The Java programming language is normally compiled to the bytecoded instruction set and binary format defined in The Java Virtual Machine Specification, Second Edition (Addison-Wesley, 1999). 也就是规定了Java语言一般是编译为Java虚拟机规范所定义的Class文件,但并没有规定“一定”(must),留有不使用JVM来实现Java语言的余地。 考虑上Java虚拟机规范,确实在这段代码里涉及的常量种类为CONSTANT_String_info的字符串常量也只有"xyz"一个。CONSTANT_String_info是用来表示Java语言中String类型的常量表达式的值(包括字符串字面量)用的常量种类,只在这个层面上考虑的话,这个解答也没问题。 所以这种解答可以认为是合理的。 值得注意的是问题中“在运行时”既包括类加载阶段,也包括这个代码片段自身执行的时候。下文会再讨论这个细节与楼主原本给出的问题的关系。 碰到这种问题首先应该想到去查阅相关的规范,这里具体是Java语言规范Java虚拟机规范,以及一些相关API的JavaDoc。很多人喜欢把“按道理说”当作口头禅,规范就是用来定义各种“道理”的——“为什么XXX是YYY的意思?”“因为规范里是这样定义的!”——无敌了。 在Java虚拟机规范中相关的定义有下面一些: The Java Virtual Machine Specification, Second Edition 写道

2.3 Literals A literal is the source code representation of a value of a primitive type (§2.4.1), the String type (§2.4.8), or the null type (§2.4). String literals and, more generally, strings that are the values of constant expressions are "interned" so as to share unique instances, using the method String.intern. The null type has one value, the null reference, denoted by the literal null. The boolean type has two values, denoted by the literals true and false. 2.4.8 The Class String Instances of class String represent sequences of Unicode characters (§2.1). A String object has a constant, unchanging value. String literals (§2.3) are references to instances of class String. 2.17.6 Creation of New Class Instances A new class instance is explicitly created when one of the following situations occurs:

  • Evaluation of a class instance creation expression creates a new instance of the class whose name appears in the expression.
  • Invocation of the newInstance method of class Class creates a new instance of the class represented by the Class object for which the method was invoked. A new class instance may be implicitly created in the following situations:

  • Loading of a class or interface that contains a String literal may create a new String object (§2.4.8) to represent that literal. This may not occur if the a String object has already been created to represent a previous occurrence of that literal, or if the String.intern method has been invoked on a String object representing the same string as the literal.

  • Execution of a string concatenation operator that is not part of a constant expression sometimes creates a new String object to represent the result. String concatenation operators may also create temporary wrapper objects for a value of a primitive type (§2.4.1). Each of these situations identifies a particular constructor to be called with specified arguments (possibly none) as part of the class instance creation process. 5.1 The Runtime Constant Pool ... ● A string literal (§2.3) is derived from a CONSTANT_String_info structure (§4.4.3) in the binary representation of a class or interface. The CONSTANT_String_info structure gives the sequence of Unicode characters constituting the string literal. ● The Java programming language requires that identical string literals (that is, literals that contain the same sequence of characters) must refer to the same instance of class String. In addition, if the method String.intern is called on any string, the result is a reference to the same class instance that would be returned if that string appeared as a literal. Thus, Java代码 复制代码 收藏代码
  1. ("a" + "b" + "c").intern() == "abc"

("a" + "b" + "c").intern() == "abc" must have the value true. ● To derive a string literal, the Java virtual machine examines the sequence of characters given by the CONSTANT_String_info structure. ○ If the method String.intern has previously been called on an instance of class String containing a sequence of Unicode characters identical to that given by the CONSTANT_String_info structure, then the result of string literal derivation is a reference to that same instance of class String. ○ Otherwise, a new instance of class String is created containing the sequence of Unicode characters given by the CONSTANT_String_info structure; that class instance is the result of string literal derivation. Finally, the intern method of the new String instance is invoked. ... The remaining structures in the constant_pool table of the binary representation of a class or interface, the CONSTANT_NameAndType_info (§4.4.6) and CONSTANT_Utf8_info (§4.4.7) structures are only used indirectly when deriving symbolic references to classes, interfaces, methods, and fields, and when deriving string literals. 把Sun的JDK看作参考实现(reference implementation, RI),其中String.intern()的JavaDoc)为: JavaDoc 写道

public String intern() Returns a canonical representation for the string object. A pool of strings, initially empty, is maintained privately by the class String. When the intern method is invoked, if the pool already contains a string equal to this String object as determined by the equals(Object) method, then the string from the pool is returned. Otherwise, this String object is added to the pool and a reference to this String object is returned. It follows that for any two strings s and t, s.intern() == t.intern() is true if and only if s.equals(t) is true. All literal strings and string-valued constant expressions are interned. String literals are defined in §3.10.5 of the Java Language Specification Returns:

    a string that has the same contents as this string, but is guaranteed to be from a pool of unique strings.

=============================================================== 再换一个问题来问: 引用

问题:

Java代码 复制代码 收藏代码

  1. String s = new String("xyz");
    String s = new String("xyz"); 涉及用户声明的几个String类型的变量? 答案也很简单: 引用

答案:一个,就是String s。 把问题换成下面这个版本,答案也一样: 引用

问题:

Java代码 复制代码 收藏代码

  1. String s = null;
    String s = null; 涉及用户声明的几个String类型的变量? Java里变量就是变量,引用类型的变量只是对某个对象实例或者null的引用,不是实例本身。声明变量的个数跟创建实例的个数没有必然关系,像是说: Java代码 复制代码 收藏代码

  2. String s1 = "a";

  3. String s2 = s1.concat("");
  4. String s3 = null;
  5. new String(s1);

String s1 = "a";

String s2 = s1.concat(""); String s3 = null;

new String(s1); 这段代码会涉及3个String类型的变量, 1、s1,指向下面String实例的1 2、s2,指向与s1相同 3、s3,值为null,不指向任何实例 以及3个String实例, 1、"a"字面量对应的驻留的字符串常量的String实例 2、""字面量对应的驻留的字符串常量的String实例 (String.concat())是个有趣的方法,当发现传入的参数是空字符串时会返回this,所以这里不会额外创建新的String实例)

3、通过new String(String)创建的新String实例;没有任何变量指向它。

回到楼主开头引用的问题与“标准答案” 引用

问题:

Java代码 复制代码 收藏代码

  1. String s = new String("xyz");
    String s = new String("xyz");创建了几个String Object? 答案:两个(一个是“xyz”,一个是指向“xyz”的引用对象s) 用归谬法论证。假定问题问的是“在执行这段代码片段时创建了几个String实例”。如果“标准答案”是正确的,那么下面的代码片段在执行时就应该创建4个String实例了: Java代码 复制代码 收藏代码

  2. String s1 = new String("xyz");

  3. String s2 = new String("xyz");

String s1 = new String("xyz");

String s2 = new String("xyz"); 马上就会有人跳出来说上下两个"xyz"字面量都是引用了同一个String对象,所以不应该是创建了4个对象。 那么应该是多少个? 运行时的类加载过程与实际执行某个代码片段,两者必须分开讨论才有那么点意义。 为了执行问题中的代码片段,其所在的类必然要先被加载,而且同一个类最多只会被加载一次(要注意对JVM来说“同一个类”并不是类的全限定名相同就足够了,而是<类全限定名, 定义类加载器>一对都相同才行)。 根据上文引用的规范的内容,符合规范的JVM实现应该在类加载的过程中创建并驻留一个String实例作为常量来对应"xyz"字面量;具体是在类加载的resolve阶段进行的。这个常量是全局共享的,只在先前尚未有内容相同的字符串驻留过的前提下才需要创建新的String实例。 等到真正执行原问题中的代码片段时,JVM需要执行的字节码类似这样: Java bytecode代码 复制代码 收藏代码

  1. 0: new /#2; //class java/lang/String
  2. 3: dup
  3. 4: ldc /#3; //String xyz
  4. 6: invokespecial /#4; //Method java/lang/String."":(Ljava/lang/String;)V
  5. 9: astore_1

0: new /#2; //class java/lang/String

3: dup 4: ldc /#3; //String xyz

6: invokespecial /#4; //Method java/lang/String."":(Ljava/lang/String;)V 9: astore_1 这之中出现过多少次new java/lang/String就是创建了多少个String对象。也就是说原问题中的代码在每执行一次只会新创建一个String实例。 这里,ldc指令只是把先前在类加载过程中已经创建好的一个String对象("xyz")的一个引用压到操作数栈顶而已,并不新创建String对象。 所以刚才用于归谬的代码片段: Java代码 复制代码 收藏代码

  1. String s1 = new String("xyz");
  2. String s2 = new String("xyz");

String s1 = new String("xyz");

String s2 = new String("xyz");

每执行一次只会新创建2个String实例。

为了避免一些同学犯糊涂,再强调一次: 在Java语言里,“new”表达式是负责创建实例的,其中会调用构造器去对实例做初始化;构造器自身的返回值类型是void,并不是“构造器返回了新创建的对象的引用”,而是new表达式的值是新创建的对象的引用。 对应的,在JVM里,“new”字节码指令只负责把实例创建出来(包括分配空间、设定类型、所有字段设置默认值等工作),并且把指向新创建对象的引用压到操作数栈顶。此时该引用还不能直接使用,处于未初始化状态(uninitialized);如果某方法a含有代码试图通过未初始化状态的引用来调用任何实例方法,那么方法a会通不过JVM的字节码校验,从而被JVM拒绝执行。 能对未初始化状态的引用做的唯一一种事情就是通过它调用实例构造器,在Class文件层面表现为特殊初始化方法“”。实际调用的指令是invokespecial,而在实际调用前要把需要的参数按顺序压到操作数栈上。在上面的字节码例子中,压参数的指令包括dup和ldc两条,分别把隐藏参数(新创建的实例的引用,对于实例构造器来说就是“this”)与显式声明的第一个实际参数("xyz"常量的引用)压到操作数栈上。 在构造器返回之后,新创建的实例的引用就可以正常使用了。

关于构造器的讨论,可以参考我之前的一帖,实例构造器是不是静态方法?

以上讨论都只是针对规范所定义的Java语言与Java虚拟机而言。概念上是如此,但实际的JVM实现可以做得更优化,原问题中的代码片段有可能在实际执行的时候一个String实例也不会完整创建(没有分配空间)。 例如说,在x86、Windows Vista SP2、Sun JDK 6 update 14的fastdebug版上跑下面的测试代码: Java代码 复制代码 收藏代码

  1. public class C2EscapeAnalysisDemo {
  2. private static void warmUp() {
  3. IFoo[] array = new IFoo[] {
  4. new FooA(), new FooB(), new FooC(), new FooD()
  5. };
  6. for (int i = 0; i < 1000000; i++) {
  7. array[i % array.length].foo(); // megamorphic callsite to prevent inlining
  8. }
  9. }
  10. public static void main(String[] args) {
  11. while (true) {
  12. warmUp();
  13. }
  14. }
  15. }
  16. interface IFoo {
  17. void foo();
  18. }
  19. class FooA implements IFoo {
  20. public void foo() {
  21. String s1 = new String("xyz");
  22. }
  23. }
  24. class FooB implements IFoo {
  25. public void foo() {
  26. String s1 = new String("xyz");
  27. String s2 = new String("xyz");
  28. }
  29. }
  30. class FooC implements IFoo {
  31. public void foo() {
  32. String s1 = new String("xyz");
  33. String s2 = new String("xyz");
  34. String s3 = new String("xyz");
  35. }
  36. }
  37. class FooD implements IFoo {
  38. public void foo() {
  39. String s1 = new String("xyz");
  40. String s2 = new String("xyz");
  41. String s3 = new String("xyz");
  42. String s4 = new String("xyz");
  43. }
  44. }

public class C2EscapeAnalysisDemo {

private static void warmUp() { IFoo[] array = new IFoo[] {

  new FooA(), new FooB(), new FooC(), new FooD()
};

for (int i = 0; i < 1000000; i++) {
  array[i % array.length].foo(); // megamorphic callsite to prevent inlining

}

}

public static void main(String[] args) {

while (true) {
  warmUp();

}

}

}

interface IFoo { void foo();

}

class FooA implements IFoo { public void foo() {

String s1 = new String("xyz");

}

}

class FooB implements IFoo { public void foo() {

String s1 = new String("xyz");
String s2 = new String("xyz");

} }

class FooC implements IFoo {

public void foo() { String s1 = new String("xyz");

String s2 = new String("xyz");
String s3 = new String("xyz");

} }

class FooD implements IFoo {

public void foo() { String s1 = new String("xyz");

String s2 = new String("xyz");
String s3 = new String("xyz");

String s4 = new String("xyz");

}

} 照常用javac用默认参数编译,然后先用server模式的默认配置来跑,顺带打出GC和JIT编译日志来看 Command prompt代码 复制代码 收藏代码

  1. java -server -verbose:gc -XX:+PrintCompilation C2EscapeAnalysisDemo

java -server -verbose:gc -XX:+PrintCompilation C2EscapeAnalysisDemo 看到的日志的开头一段如下: Hotspot log代码 复制代码 收藏代码

  1. 1 java.lang.String::charAt (33 bytes)
  2. 2 java.lang.Object:: (1 bytes)
  3. 3 java.lang.String:: (61 bytes)
  4. 1% C2EscapeAnalysisDemo::warmUp @ 47 (71 bytes)
  5. 4 FooA::foo (11 bytes)
  6. 5 FooB::foo (21 bytes)
  7. 6 FooC::foo (31 bytes)
  8. 7 FooD::foo (42 bytes)
  9. [GC 3072K->168K(32768K), 0.0058325 secs]
  10. [GC 3240K->160K(32768K), 0.0104623 secs]
  11. [GC 3232K->160K(32768K), 0.0027323 secs]
  12. [GC 3232K->160K(35840K), 0.0026220 secs]
  13. [GC 6304K->160K(35840K), 0.0173733 secs]
  14. [GC 6304K->144K(41664K), 0.0059720 secs]
  15. [GC 12432K->144K(41664K), 0.0353320 secs]
  16. [GC 12432K->144K(54016K), 0.0139333 secs]
  17. 8 C2EscapeAnalysisDemo::warmUp (71 bytes)
  18. [GC 24720K->160K(54016K), 0.0697970 secs]
  19. [GC 24736K->160K(68800K), 0.0261921 secs]
  20. [GC 39520K->160K(68800K), 0.0958433 secs]
  21. [GC 39520K->160K(87168K), 0.0433377 secs]
  22. [GC 57888K->160K(87168K), 0.0542482 secs]
  23. [GC 57888K->148K(87168K), 0.0533140 secs]
  24. [GC 57876K->164K(84288K), 0.0533537 secs]
  25. [GC 55204K->164K(81728K), 0.0596820 secs]
  26. [GC 52644K->164K(79488K), 0.0515090 secs]
  27. [GC 50212K->164K(76992K), 0.0491227 secs]
  28. [GC 47908K->164K(74944K), 0.0450666 secs]
  29. [GC 45668K->164K(72640K), 0.0467671 secs]
  30. [GC 43556K->152K(70784K), 0.0420757 secs]
  31. [GC 41560K->168K(68736K), 0.0391296 secs]
  32. [GC 39656K->168K(67072K), 0.0397539 secs]
  33. [GC 37864K->188K(65216K), 0.0360861 secs]

    1 java.lang.String::charAt (33 bytes)

    2 java.lang.Object:: (1 bytes) 3 java.lang.String:: (61 bytes)

    1% C2EscapeAnalysisDemo::warmUp @ 47 (71 bytes) 4 FooA::foo (11 bytes)

    5 FooB::foo (21 bytes) 6 FooC::foo (31 bytes)

    7 FooD::foo (42 bytes) [GC 3072K->168K(32768K), 0.0058325 secs]

[GC 3240K->160K(32768K), 0.0104623 secs] [GC 3232K->160K(32768K), 0.0027323 secs]

[GC 3232K->160K(35840K), 0.0026220 secs] [GC 6304K->160K(35840K), 0.0173733 secs]

[GC 6304K->144K(41664K), 0.0059720 secs] [GC 12432K->144K(41664K), 0.0353320 secs]

[GC 12432K->144K(54016K), 0.0139333 secs] 8 C2EscapeAnalysisDemo::warmUp (71 bytes)

[GC 24720K->160K(54016K), 0.0697970 secs] [GC 24736K->160K(68800K), 0.0261921 secs]

[GC 39520K->160K(68800K), 0.0958433 secs] [GC 39520K->160K(87168K), 0.0433377 secs]

[GC 57888K->160K(87168K), 0.0542482 secs] [GC 57888K->148K(87168K), 0.0533140 secs]

[GC 57876K->164K(84288K), 0.0533537 secs] [GC 55204K->164K(81728K), 0.0596820 secs]

[GC 52644K->164K(79488K), 0.0515090 secs] [GC 50212K->164K(76992K), 0.0491227 secs]

[GC 47908K->164K(74944K), 0.0450666 secs] [GC 45668K->164K(72640K), 0.0467671 secs]

[GC 43556K->152K(70784K), 0.0420757 secs] [GC 41560K->168K(68736K), 0.0391296 secs]

[GC 39656K->168K(67072K), 0.0397539 secs] [GC 37864K->188K(65216K), 0.0360861 secs] 上面的日志中,后面的方法名的行是JIT编译的日志,而以GC开头的是minor GC的日志。 程序一直跑,GC的日志还会不断的打出来。这是理所当然的对吧?HotSpot的堆就那么大,而测试代码在不断新创建String对象,肯定得不断触发GC的。 用不同的VM启动参数来跑的话, Command prompt代码 [复制代码 收藏代码

  1. java -server -verbose:gc -XX:+PrintCompilation -XX:+DoEscapeAnalysis -XX:+EliminateAllocations C2EscapeAnalysisDemo

java -server -verbose:gc -XX:+PrintCompilation -XX:+DoEscapeAnalysis -XX:+EliminateAllocations C2EscapeAnalysisDemo 还是同样的Java测试程序,同样的Sun JDK 6 update 14,但打开了逃逸分析和空间分配消除功能,再运行,看到的全部日志如下: Hotspot log代码 复制代码 收藏代码

  1. 1 java.lang.String::charAt (33 bytes)
  2. 2 java.lang.Object:: (1 bytes)
  3. 3 java.lang.String:: (61 bytes)
  4. 1% C2EscapeAnalysisDemo::warmUp @ 47 (71 bytes)
  5. 5 FooB::foo (21 bytes)
  6. 4 FooA::foo (11 bytes)
  7. 6 FooC::foo (31 bytes)
  8. 7 FooD::foo (42 bytes)
  9. [GC 3072K->176K(32768K), 0.0056527 secs]
  10. 8 C2EscapeAnalysisDemo::warmUp (71 bytes)

    1 java.lang.String::charAt (33 bytes)

    2 java.lang.Object:: (1 bytes) 3 java.lang.String:: (61 bytes)

    1% C2EscapeAnalysisDemo::warmUp @ 47 (71 bytes) 5 FooB::foo (21 bytes)

    4 FooA::foo (11 bytes) 6 FooC::foo (31 bytes)

    7 FooD::foo (42 bytes) [GC 3072K->176K(32768K), 0.0056527 secs]

    8 C2EscapeAnalysisDemo::warmUp (71 bytes) 继续跑下去也没有再打出GC日志了。难道新创建String对象都不吃内存了么? 实际情况是:经过HotSpot的server模式编译器的优化后,FooA、FooB、FooC、FooD四个版本的foo()实现都不新创建String实例了。这样自然不吃内存,也就不再触发GC了。 经过的分析和优化笼统说有方法内联(method inlining)、逃逸分析(escape analysis)、标量替换(scalar replacement)、无用代码削除(dead-code elimination)之类。 FooA.foo()最短,就以它举例来大致演示一下优化的过程。 它其实就是创建并初始化了一个String对象而已。调用的构造器的源码是: Java代码 复制代码 收藏代码

  11. public String(String original) {

  12. int size = original.count;
  13. char[] originalValue = original.value;
  14. char[] v;
  15. if (originalValue.length > size) {
  16. // The array representing the String is bigger than the new
  17. // String itself. Perhaps this constructor is being called
  18. // in order to trim the baggage, so make a copy of the array.
  19. int off = original.offset;
  20. v = Arrays.copyOfRange(originalValue, off, off+size);
  21. } else {
  22. // The array representing the String is the same
  23. // size as the String, so no point in making a copy.
  24. v = originalValue;
  25. }
  26. this.offset = 0;
  27. this.count = size;
  28. this.value = v;
  29. }

public String(String original) {

int size = original.count;
char[] originalValue = original.value;

char[] v;
if (originalValue.length > size) {

   // The array representing the String is bigger than the new
   // String itself.  Perhaps this constructor is being called

   // in order to trim the baggage, so make a copy of the array.
  int off = original.offset;

  v = Arrays.copyOfRange(originalValue, off, off+size);
} else {

   // The array representing the String is the same
   // size as the String, so no point in making a copy.

  v = originalValue;
}

this.offset = 0;
this.count = size;

this.value = v;

} 因为参数是"xyz",可以确定在我们的测试代码里不会走到构造器的if分支里,下面为了演示方便就省略掉那部分代码(实际代码还是存在的,只是没执行而已) Java代码 复制代码 收藏代码

  1. public String(String original) {
  2. int size = original.count;
  3. char[] originalValue = original.value;
  4. char[] v;
  5. if (originalValue.length > size) {
  6. // 省略
  7. } else {
  8. v = originalValue;
  9. }
  10. this.offset = 0;
  11. this.count = size;
  12. this.value = v;
  13. }

public String(String original) {

int size = original.count;
char[] originalValue = original.value;

char[] v;
if (originalValue.length > size) {

   // 省略
} else {

  v = originalValue;
}

this.offset = 0;
this.count = size;

this.value = v;

} 那么把构造器内联到FooA.foo()里, Java代码 复制代码 收藏代码

  1. public FooA implements IFoo {
  2. public void foo() {
  3. String s = AllocateString(); // 这里虚构一个只分配空间,不调用构造器的函数
  4. String original = "xyz";
  5. // 下面就是内联进来的构造器内容
  6. int size = original.count;
  7. char[] originalValue = original.value;
  8. char[] v;
  9. if (originalValue.length > size) {
  10. // 省略
  11. } else {
  12. v = originalValue;
  13. }
  14. s.offset = 0;
  15. s.count = size;
  16. s.value = v;
  17. }
  18. }

public FooA implements IFoo {

public void foo() { String s = AllocateString(); // 这里虚构一个只分配空间,不调用构造器的函数

String original = "xyz";


// 下面就是内联进来的构造器内容
int size = original.count;

char[] originalValue = original.value;
char[] v;

if (originalValue.length > size) {
   // 省略

} else {
  v = originalValue;

}


s.offset = 0;
s.count = size;

s.value = v;

}

} 然后经过逃逸分析与标量替换, Java代码 复制代码 收藏代码

  1. public FooA implements IFoo {
  2. public void foo() {
  3. String original = "xyz";
  4. // 下面就是内联进来的构造器内容
  5. int size = original.count;
  6. char[] originalValue = original.value;
  7. char[] v;
  8. if (originalValue.length > size) {
  9. // 省略
  10. } else {
  11. v = originalValue;
  12. }
  13. // 原本s的实例变量被标量替换为foo()的局部变量
  14. int sOffset = 0;
  15. int sCount = size;
  16. char[] sValue = v;
  17. }
  18. }

public FooA implements IFoo {

public void foo() { String original = "xyz";

// 下面就是内联进来的构造器内容

int size = original.count;
char[] originalValue = original.value;

char[] v;
if (originalValue.length > size) {

   // 省略
} else {

  v = originalValue;
}


// 原本s的实例变量被标量替换为foo()的局部变量

int sOffset = 0;
int sCount = size;

char[] sValue = v;

}

} 注意,到这里就已经把新创建String在堆上分配空间的代码全部削除了,原本新建的String实例的字段变成了FooA.foo()的局部变量。 最后再经过无用代码削除,把sOffset、sCount和sValue这三个没被读过的局部变量给削除掉, Java代码 复制代码 收藏代码

  1. public FooA implements IFoo {
  2. public void foo() {
  3. String original = "xyz";
  4. // 下面就是内联进来的构造器内容
  5. int size = original.count;
  6. char[] originalValue = original.value;
  7. char[] v;
  8. if (originalValue.length > size) {
  9. // 省略
  10. } else {
  11. v = originalValue;
  12. }
  13. // 几个局部变量也干掉了
  14. }
  15. }

public FooA implements IFoo {

public void foo() { String original = "xyz";

// 下面就是内联进来的构造器内容

int size = original.count;
char[] originalValue = original.value;

char[] v;
if (originalValue.length > size) {

   // 省略
} else {

  v = originalValue;
}


// 几个局部变量也干掉了

} } 这就跟FooA.foo()被优化编译后实际执行的代码基本一致了。 实际执行的x86代码如下: X86 asm代码 复制代码 收藏代码

  1. 0x0247aeec: mov %eax,-0x4000(%esp) ; 检查栈是否溢出(stack bang),若溢出则这条指令会引发异常
  2. 0x0247aef3: push %ebp ; 保存老的栈帧指针
  3. 0x0247aef4: sub $0x18,%esp ; 为新栈帧分配空间
  4. 0x0247aefa: mov $0x1027e9a8,%edi ; String original // EDI // = "xyz"
  5. 0x0247aeff: mov 0x8(%edi),%ecx ; char[] originalValue // ECX // = original.value;
  6. 0x0247af02: mov 0x8(%ecx),%ebx ; EBX = originalValue.length
  7. 0x0247af05: mov 0x10(%edi),%ebp ; int size // EBP // = original.count;
  8. 0x0247af08: cmp %ebp,%ebx ; 比较originalValue.length与size
  9. 0x0247af0a: jg 0x0247af17 ; 如果originalValue.length > size则跳转到0x0247af17
  10. ; 实际不会发生跳转(就是不会执行if分支),所以后面代码省略
  11. 0x0247af0c: add $0x18,%esp ; 撤销栈帧分配的空间
  12. 0x0247af0f: pop %ebp ; 恢复老的栈帧指针
  13. 0x0247af10: test %eax,0x310000 ; 方法返回前检查是否需要进入safepoint ({poll_return})
  14. 0x0247af16: ret ; 方法返回
  15. 0x0247af17: ; 以下是if分支和异常处理器的代码,因为实际不会执行,省略

0x0247aeec: mov %eax,-0x4000(%esp) ; 检查栈是否溢出(stack bang),若溢出则这条指令会引发异常

0x0247aef3: push %ebp ; 保存老的栈帧指针 0x0247aef4: sub $0x18,%esp ; 为新栈帧分配空间

0x0247aefa: mov $0x1027e9a8,%edi ; String original // EDI // = "xyz" 0x0247aeff: mov 0x8(%edi),%ecx ; char[] originalValue // ECX // = original.value;

0x0247af02: mov 0x8(%ecx),%ebx ; EBX = originalValue.length 0x0247af05: mov 0x10(%edi),%ebp ; int size // EBP // = original.count;

0x0247af08: cmp %ebp,%ebx ; 比较originalValue.length与size 0x0247af0a: jg 0x0247af17 ; 如果originalValue.length > size则跳转到0x0247af17

                                  ;   实际不会发生跳转(就是不会执行if分支),所以后面代码省略

0x0247af0c: add $0x18,%esp ; 撤销栈帧分配的空间

0x0247af0f: pop %ebp ; 恢复老的栈帧指针 0x0247af10: test %eax,0x310000 ; 方法返回前检查是否需要进入safepoint ({poll_return})

0x0247af16: ret ; 方法返回 0x0247af17: ; 以下是if分支和异常处理器的代码,因为实际不会执行,省略 看,确实没有新创建String对象了。 另外三个版本的foo()实现也是类似,HotSpot成功的把无用的new String("xyz")全部干掉了。 关于逃逸分析的例子,可以参考我以前一篇帖,HotSpot 17.0-b12的逃逸分析/标量替换的一个演示

再回头看看楼主的原问题,问题中的代码片段执行的时候(对应到FooA.foo()被调用的时候)一个String对象也没有新建。于是那“标准答案”在现实中的指导意义又有多少呢?

另外,楼主还提到了PermGen: QM42977 写道

"xyz"在perm gen应该还会生成一个对象,因为常量("xyz")都会保存在perm gen中 这里也是需要强调一点:永生代(“Perm Gen”)只是Sun JDK的一个实现细节而已,Java语言规范和Java虚拟机规范都没有规定必须有“Permanent Generation”这么一块空间,甚至没规定要用什么GC算法——不用分代式GC算法哪儿来的“永生代”? HotSpot的PermGen是用来实现Java虚拟机规范中的“方法区”(method area)的。如果使用“方法区”这个术语,在讨论概念中的JVM时就安全得多——大家都必须实现出这个表象。 当然如何实现又是另一回事了。Oracle JRockit没有PermGen,IBM J9也没有,事实上有这么一块空间特别管理的反而是少数吧orz 事实上新版HotSpot VM也在计划去除PermGen,转而使用native memory来实现方法区存储元数据。在JDK8的HotSpot VM中已经实现了这点。

可以参考这帖:http://rednaxelafx.iteye.com/blog/905273

费那么多口舌,最后点题:请别再拿“String s = new String("xyz");创建了多少个String实例”来面试了吧,既没意义又不涨面子。 困,睡觉去……

JVM调优总结(三)

Posted on

JVM调优总结(三)-基本垃圾回收算法

可以从不同的的角度去划分垃圾回收算法:

按照基本回收策略分

引用计数(Reference Counting):

比较古老的回收算法。原理是此对象有一个引用,即增加一个计数,删除一个引用则减少一个计数。垃圾回收时,只用收集计数为0的对象。此算法最致命的是无法处理循环引用的问题。

标记-清除(Mark-Sweep):

此算法执行分两阶段。第一阶段从引用根节点开始标记所有被引用的对象,第二阶段遍历整个堆,把未标记的对象清除。此算法需要暂停整个应用,同时,会产生内存碎片。

复制(Copying):

此算法把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾回收时,遍历当前使用区域,把正在使用中的对象复制到另外一个区域中。次算法每次只处理正在使用中的对象,因此复制成本比较小,同时复制过去以后还能进行相应的内存整理,不会出现“碎片”问题。当然,此算法的缺点也是很明显的,就是需要两倍内存空间。

标记-整理(Mark-Compact):

此算法结合了“标记-清除”和“复制”两个算法的优点。也是分两阶段,第一阶段从根节点开始标记所有被引用对象,第二阶段遍历整个堆,把清除未标记对象并且把存活对象“压缩”到堆的其中一块,按顺序排放。此算法避免了“标记-清除”的碎片问题,同时也避免了“复制”算法的空间问题。

按分区对待的方式分

增量收集(Incremental Collecting):实时垃圾回收算法,即:在应用进行的同时进行垃圾回收。不知道什么原因JDK5.0中的收集器没有使用这种算法的。

分代收集(Generational Collecting):基于对对象生命周期分析后得出的垃圾回收算法。把对象分为年青代、年老代、持久代,对不同生命周期的对象使用不同的算法(上述方式中的一个)进行回收。现在的垃圾回收器(从J2SE1.2开始)都是使用此算法的。

按系统线程分

串行收集:串行收集使用单线程处理所有垃圾回收工作,因为无需多线程交互,实现容易,而且效率比较高。但是,其局限性也比较明显,即无法使用多处理器的优势,所以此收集适合单处理器机器。当然,此收集器也可以用在小数据量(100M左右)情况下的多处理器机器上。

并行收集:并行收集使用多线程处理垃圾回收工作,因而速度快,效率高。而且理论上CPU数目越多,越能体现出并行收集器的优势。

并发收集:相对于串行收集和并行收集而言,前面两个在进行垃圾回收工作时,需要暂停整个运行环境,而只有垃圾回收程序在运行,因此,系统在垃圾回收时会有明显的暂停,而且暂停时间会因为堆越大而越长。