CodeCache整理

  • JIT

    • JIT C1
      • Client模式启动速度较快
      • 桌面应用,加载速度比server模式快10%,而运行速度为server模式的10分之一
    • JIT C2
      • Server模式启动较慢
      • 服务器应用
      • 启动进入稳定期长期运行之后Server模式的程序运行速度比Client要快很多
      • 这是因为Server模式启动的JVM采用的是重量级的编译器,对程序采用了更多的优化
      • 而Client模式启动的JVM采用的是轻量级的编译器
      • 所以Server启动慢,但稳定后速度比Client远远要快
  • JIT Check

    • java -version

      java version "1.6.0_21"
      Java(TM) SE Runtime Environment (build 1.6.0_21-b06)
      Java HotSpot(TM) Client VM (build 17.0-b16, mixed mode, sharing)
      - java -version
      > java version "1.6.0_06"
      Java(TM) SE Runtime Environment (build 1.6.0_06-b02)
      Java HotSpot(TM) Server VM (build 10.0-b22, mixed mode)

  • JIT switch

    • java -version(查看jvm是64 or 32 bits)

      java version "1.7.0_15"
      Java(TM) SE Runtime Environment (build 1.7.0_15-b03)
      Java HotSpot(TM) 64-Bit Server VM (build 23.7-b01, mixed mode)
      - 查看JAVA_HOME/jre/bin目录下是否存在client或server目录
      - 32位的JDK一般都支持server和client两种模式
      - 64位的只支持server模式,没有client目录
      - 临时切换
      - java -client
      - java -server
      - JVM启动时 - jvm.cfg
      - 在32位JDK中,jvm.cfg位置为:JAVA_HOME/jre/lib/i386/jvm.cfg
      - 在64位JDK中,jvm.cfg位置为:JAVA_HOME/jre/lib/amd64/jvm.cfg
      - JVM 64-bit
      > -server KNOWN
      -client IGNORE
      -hotspot ALIASED_TO -server
      -classic WARN
      -native ERROR
      -green ERROR
      - JVM 32-bit
      > -client KNOWN
      -server KNOWN
      -hotspot ALIASED_TO -client
      -classic WARN
      -native ERROR
      -green ERROR

  • 解释or编译

    • -Xint
      • 不经过jit直接由解释器解释执行所有字节码,执行效率不高
    • -Xcomp
      • 不加筛选的将全部代码进行编译机器码不论其执行频率是否有编译价值,在程序响应时间的限制下,编译器没法采用编译耗时较高的优化技术(因为JIT的编译是首次运行或启动的时候进行的),所以,在纯编译执行模式下的java程序执行效率跟C/C++也是具有较大差距的
    • -Xmixed 默认
    • java -version

      java version "1.7.0_15"
      Java(TM) SE Runtime Environment (build 1.7.0_15-b03)
      Java HotSpot(TM) 64-Bit Server VM (build 23.7-b01, mixed mode)
      - mixed mode
      1、源代码经javac编译成字节码,class文件
      2、程序字节码经过JIT环境变量进行判断,是否属于“热点代码”(多次调用的方法,或循环等)
      3、如是,走JIT编译为具体硬件处理器(如sparc、intel)机器码
      4、如否,则直接由解释器解释执行
      5、操作系统及类库调用
      6、硬件
      - jit并不一定总能提高程序的执行效率甚至适得其反,这很大一部分取决于开发人员所写的程序质量,作为优秀的工程师应该会写出对jit友好的程序

  • 分层编译

    • jdk6u25开始引入了一种分层编译的方式,分层编译方式是一种折衷方式,在系统启动之初执行频率比较高的代码将先被C1编译器编译,以便尽快进入编译执行。随着时间推进,一些执行频率高的代码会被C2编译器再次编译,从而达到更高的性能。
    • 在实际测试时会发现不同启动方式之间启动时间差距并不明显,这是因为应用启动时还需要加载类和资源文件等,这些磁盘操作比编译更耗时,所以编译方式对启动时间的影响会被弱化
    • -XX:+TieredCompilation
    • 在jdk8中,当以server模式启动时,分层编译默认开启
    • 需要注意的是,分层编译方式只能用于server模式中,如果以client模式启动,-XX:+TieredCompilation参数将会被忽略
  • codeCache

    • 编译后的代码
    • JNI
    • 默认大小
      • 64-bit server, Java 7
        • 48 MB
      • 64-bit server with Tiered Compilation, Java 7
        • 96 MB
  • codeCache用光了?

    • 在jdk1.7.0_4之前,你会在jvm的日志里看到这样的输出:
      • Java HotSpot(TM) 64-Bit Server VM warning: CodeCache is full. Compiler has been disabled.
      • Jit编译器被停止了,并且不会被重新启动。已经被编译过的代码仍然以编译方式执行,但是尚未被编译的代码就只能以解释方式执行了
  • solution

    • 针对这种情况,jvm提供了一种比较激进的codeCache回收方式:Speculative flushing
      在jdk1.7.0_4之后这种回收方式默认开启
      而之前的版本需要通过一个启动参数来开启:-XX:+UseCodeCacheFlushing
      在Speculative flushing开启的情况下
      当codeCache将要耗尽时,最早被编译的一半方法将会被放到一个old列表中等待回收
      在一定时间间隔内,如果方法没有被调用,这个方法就会被从codeCache充清除
    • 很不幸的是,在jdk1.7中,当codeCache耗尽时,Speculative flushing释放了一部分空间,
      但是从编译日志来看,jit编译并没有恢复正常,并且系统整体性能下降很多,出现大量超时
      在Oracle官网上看到这样一个bug:http://bugs.java.com/bugdatabase/view_bug.do?bug_id=8006952
      由于codeCache回收算法的问题,当codeCache满了之后会导致编译线程无法继续,并且消耗大量cpu导致系统运行变慢
      Bug里影响版本是jdk8,但是从网上其他地方的信息看,jdk7应该也存在相同的问题,并且没有被修复。
  • 启动后负载很高的问题

    • 纯编译方式和分层编译方式都可以解决或缓解启动后负载过高的问题
    • 关于启动后负载高的原因,网上很多文章都说是由于启动后随着代码的执行,jvm的jit编译器将部分热点代码编译为目标机器代码,由于编译线程占用了大量的cpu导致系统负载高。
      为了验证这个说法,在系统启动后使用jstack获取线程dump,并通过top –H –p查看当前进程中哪些线程在大量消耗cpu。
      结果发现,编译线程虽然cpu占用率比其他线程略高,但是差距并不明显。
      另外还发现,resin处理请求的线程每一个cpu占用率虽然都不是很高,但是加起来的总占用率就相当可观了。
      因此猜测,由于jit编译器需要代码执行超过一定频率才会将其编译,系统刚启动的时候大部分代码都是出于解释执行阶段,
      而解释执行的性能会比编译执行慢很多,也因此会导致这个阶段负载很高。
      等主要的热点代码都进入了编译执行阶段,系统负载自然就恢复了
    • Jvm提供了一个参数-Xcomp,可以使jvm运行在纯编译模式下,所有方法在第一次被调用的时候就会被编译成机器代码。加上这个参数之后,系统启动之后负载确实不会上升了,但是随之而来的问题是启动时间变得很长,是原来的2倍还多
    • 分层编译方式是一种折衷方式,在系统启动之初执行频率比较高的代码将先被C1编译器编译,以便尽快进入编译执行。随着时间推进,一些执行频率高的代码会被C2编译器再次编译,从而达到更高的性能。
    • 在实际测试时会发现不同启动方式之间启动时间差距并不明显,这是因为应用启动时还需要加载类和资源文件等,这些磁盘操作比编译更耗时,所以编译方式对启动时间的影响会被弱化
    • java -XX:+PrintFlagsFinal -version | grep TieredCompilation
      • bool TieredCompilation = false {pd product}
    • 分层编译方式只能用于server模式中
    • 测试环境加上分层编译参数之后,效果很明显,在大多数情况下启动之后负载都不会升高,有时候即使有会升高,也比默认的恢复快很多
  • CodeCache

    • -XX:ReservedCodeCacheSize 一种推荐的设置思路是设置为当前值(或者默认值)的2倍 对于64位jvm,由于内存空间足够大,codeCache设置的过大不会对应用产生明显影响
    • -XX:+UseCodeCacheFlushing
      • 当codeCache将要耗尽时,最早被编译的一半方法将会被放到一个old列表中等待回收,在一定时间间隔内,如果方法没有被调用,这个方法就会被从codeCache充清除
      • jdk7在codeCache的回收方面做的很不好
      • jdk8对codeCache的回收有了很明显的改善
    • 在jdk8中,提供了一个启动参数XX:+PrintCodeCache在jvm停止的时候打印出codeCache的使用情况
  • 综合

    • mixed mode, -server, -XX:+TieredCompilation, CICompilerCount, ReservedCodeCacheSize, UseCodeCacheFlushing(jdk7做的不太好)
    • java -XX:+PrintFlagsFinal -version | grep CICompilerCount
      intx CICompilerCount = 2 {product}
      bool CICompilerCountPerCPU = false {product}
    • -XX:CICompilerCount参数这个值默认是2,也就是说2个c2的编译线程来进行编译,改为cpu core数的一半,重新启动了下效果明显比以前好了很多,load还是会冲高,不过下降的很快,因此说明这个参数是work的
    • 既然启动的时候访问量比较大,如果一直耗在解释执行时状况其实也不会多好,确实不如多拿几个线程来做编译,加快达到高峰性能的速度,而到达了高峰后,多这几个编译线程对整体并不会有什么影响

http://blog.csdn.net/lihuifeng/article/details/51672976
http://hellojava.info/?p=195
http://hellojava.info/?tag=usecodecacheflushing
https://docs.oracle.com/javase/8/embedded/develop-apps-platforms/codecache.htm#A1095915
http://blog.csdn.net/xlnjulp/article/details/26354567
http://docs.oracle.com/javase/6/docs/technotes/guides/vm/

  • References
    • Tomcat重启负载高问题定位
    • JVM的Server与Client运行模式区别与切换
    • java codeCache
    • 关于Java启动性能的一个解决方法

你可能感兴趣的:(CodeCache整理)