zoukankan      html  css  js  c++  java
  • JVM优化

    3.jvm 优化

    3.1、什么是垃圾回收?

    程序的运行必然需要申请内存资源,无效的对象资源如果不及时处理就会一直占有内存资源,最终将导致内存溢出,所以对内存资源的管理是非常重要了

    3.1.1、C/C++语言的垃圾回收

    在C/C++语言中,没有自动垃圾回收机制,是通过new关键字申请内存资源,通过delete关键字释放内存资源。

    如果,程序员在某些位置没有写delete进行释放,那么申请的对象将一直占用内存资源,最终可能会导致内存溢出

    3.1.2、Java语言的垃圾回收

    为了让程序员更专注于代码的实现,而不用过多的考虑内存释放的问题,所以,在Java语言中,有了自动的垃圾回收机制,也就是我们熟悉的GC。有了垃圾回收机制后,程序员只需要关心内存的申请即可,内存的释放由系统自动识别完成

    3.2、垃圾回收的常见算法

    自动化的管理内存资源,垃圾回收机制必须要有一套算法来进行计算,哪些是有效的对象,哪些是无效的对象,对于无效的对象就要进行回收处理。

     常见的垃圾回收算法有:引用计数法、标记清除法、标记压缩法、复制算法、分代算法等。
    

    3.2.1 引用计数法


    1. 原理
      假设有一个对象A,任何一个对象对A的引用,那么对象A的引用计数器+1,当引用失败时,对象A的引用计数器就-1,如果对象A的计数器的值为0,就说明对象A没有引用了,可以被回收。

    2. 优缺点
      优点:

    • 实时性较高,无需等到内存不够的时候,才开始回收,运行时根据对象的计数器是否为0,就可以直接回收。
    • 在垃圾回收过程中,应用无需挂起。如果申请内存时,内存不足,则立刻报outofmember错误。
    • 区域性,更新对象的计数器时,只是影响到该对象,不会扫描全部对象。

    缺点:

    • 每次对象被引用时,都需要去更新计数器,有一点时间开销。浪费CPU资源,即使内存够用,仍然在运行时进行计数器的统计。无法解决循环引用问题。(最大的缺点)
    1. 什么是循环引用
    Aa=newA();
    Bb=newB();
    a.b=b;
    b.a=a;
    a=null;
    b=null;
    
    • 虽然a和b都为null,但是由于a和b存在循环引用,这样a和b永远都不会被回收

    3.2.2、标记清除法

    标记清除算法,是将垃圾回收分为2个阶段,分别是标记和清除。

    • 标记:从根节点开始标记引用的对象。
    • 清除:未被标记引用的对象就是垃圾对象,可以被清理

    1.原理
    按照根搜索算法,所有从root对象可达的对象就被标记为了存活的对象,没有被标记的对象将会回收清除掉,而被标记的对象将会留下,并且会将标记位重新归0。
    2.优缺点

    • 效率较低,标记和清除两个动作都需要遍历所有的对象,并且在GC时,需要停止应用程序,对于交互性要求比较高的应用而言这个体验是非常差的。
    • 通过标记清除算法清理出来的内存,碎片化较为严重,因为被回收的对象可能存在于内存的各个角落,所以清理出来的内存是不连贯的。

    3.2.3、标记压缩算法

    1.原理

    • 按照根搜索算法,所有从root对象可达的对象就被标记为了存活的对象,在清理阶段,并不是简单的清理未标记的对象,而是将存活的对象压缩到内存的一端,然后清理边界以外的垃圾,从而解决了碎片化的问题

    2.优缺点

    • 优缺点同标记清除算法,解决了标记清除算法的碎片化的问题,同时,标记压缩算法多了一步,对象移动内存位置的步骤,其效率也有有一定的影响

    3.2.4、复制算法

    1.原理

    将原有的内存空间一分为二,每次只用其中的一块,在垃圾回收时,将正在使用的对象复制到另一个内存空间中,然后将该内存空间清空,交换两个内存的角色,完成垃圾的回收

    • 如果内存中的垃圾对象较多,需要复制的对象就较少,这种情况下适合使用该方式并且效率比较高,反之,则不适合

    2.优缺点

    • 优点:在垃圾对象多的情况下,效率较高清理后,内存无碎片
    • 缺点:在垃圾对象少的情况下,不适用,如:老年代内存 。分配的2块内存空间,在同一个时刻,只能使用一半,内存使用率较低

    3.2.4、复制算法

    分代算法其实就是这样的,根据回收对象的特点进行选择,在jvm中,年轻代适合使用复制算法,老年代适合使用标记清除或标记压缩算法

    3.3.垃圾收集器以及内存分配

    在jvm中,实现了多种垃圾收集器,包括:串行垃圾收集器、并行垃圾收集器、CMS(并发)垃圾收集器、G1垃圾收集器

    3.3.1 串行垃圾收集器

    • 串行垃圾收集器,是指使用单线程进行垃圾回收,垃圾回收时,只有一个线程在工作,并且java应用中的所有线程都要暂停,等待垃圾回收的完成。这种现象称之为STW(Stop-The-World)
      对于交互性较强的应用而言,这种垃圾收集器是不能够接受的。一般在Javaweb应用中是不会采用该收集器的。

    设置垃圾回收为串行收集器

    #-XX:+UseSerialGC 指定年轻代和老年代都使用串行垃圾收集器
    #-XX:+PrintGCDetails打印垃圾回收的详细信息
    #为了测试GC,将堆的初始和最大内存都设置为16M
    [root@fgfg]>java  ‐XX:+UseSerialGC ‐XX:+PrintGCDetails ‐Xms16m ‐Xmx16m
    

    GC日志信息解读:
    年轻代的内存GC前后的大小:

    • DefNew 表示使用的是串行垃圾收集器。
    • 4416K->512K(4928K) 表示年轻代GC前,占有4416K内存,GC后,占有512K内存,总大小4928K
    • 0.0046102secs 表示GC所用的时间,单位为毫秒。
    • 4416K->1973K(15872K) 表示GC前,堆内存占有4416K,GC后,占有1973K,总大小为15872K
    • FullGC 表示内存空间全部进行GC

    3.3.1 串行垃圾收集器

    并行垃圾收集器在串行垃圾收集器的基础之上做了改进,将单线程改为了多线程进行垃圾回收,这样可以缩短垃圾回收的时间。(这里是指,并行能力较强的机器)当然了,并行垃圾收集器在收集的过程中也会暂停应用程序,这个和串行垃圾回收器是一样的,只是并行执行,速度更快些,暂停的时间更短一些。

    1. ParNew垃圾收集器
      ParNew垃圾收集器是工作在年轻代上的,只是将串行的垃圾收集器改为了并行。
      通过-XX:+UseParNewGC参数设置年轻代使用ParNew回收器,老年代使用的依然是串行收集器。

    测试

    #-XX:+UseParNewGC参数设置年轻代使用ParNew回收器
    #-XX:+PrintGCDetails打印垃圾回收的详细信息
    #为了测试GC,将堆的初始和最大内存都设置为16M
    [root@fgfg]>java ‐XX:+UseParNewGC ‐XX:+PrintGCDetails ‐Xms16m ‐Xmx16m
    
    1. ParallelGC垃圾收集器
      ParallelGC收集器工作机制和ParNewGC收集器一样,只是在此基础之上,新增了两个和系统吞吐量相关的参数,使得其使用起来更加的灵活和高效。

    测试

    #-XX:+UseParNewGC参数设置年轻代使用ParNew回收器
    #-XX:+PrintGCDetails打印垃圾回收的详细信息
    #为了测试GC,将堆的初始和最大内存都设置为16M
    [root@fgfg]>java ‐XX:+UseParallelGC ‐XX:+UseParallelOldGC ‐XX:MaxGC 
    	PauseMillis=100‐XX:+PrintGCDetails ‐Xms16m ‐Xmx16m
    

    相关参数如下:

    • -XX:+UseParallelGC 年轻代使用ParallelGC垃圾回收器,老年代使用串行回收器
    • -XX:+UseParallelOldGC 年轻代使用ParallelGC垃圾回收器,老年代使用ParallelOldGC垃圾回收器。
    • -XX:MaxGCPauseMillis
      • 设置最大的垃圾收集时的停顿时间,单位为毫秒
      • 需要注意的时,ParallelGC为了达到设置的停顿时间,可能会调整堆大小或其他的参数,如果堆的大小设置的较小,就会导致GC工作变得很频繁,反而可能会影响到性能。
      • 该参数使用需谨慎。
    • -XX:GCTimeRatio
      • 设置垃圾回收时间占程序运行时间的百分比,公式为1/(1+n)。
      • 它的值为0~100之间的数字,默认值为99,也就是垃圾回收时间不能超过1%
    • -XX:UseAdaptiveSizePolicy
      • 自适应GC模式,垃圾回收器将自动调整年轻代、老年代等参数,达到吞吐量、堆大小、停顿时间之间的平衡。
      • 一般用于,手动调整参数比较困难的场景,让收集器自动进行调整。
    1. CMS垃圾收集器

    CMS全称ConcurrentMarkSweep,是一款并发的、使用标记-清除算法的垃圾回收器,该回收器是针对老年代垃圾回收的,通过参数-XX:+UseConcMarkSweepGC进行设置。
    垃圾回收过程:

    • 初始化标记(CMS-initial-mark),标记root,会导致stw;
    • 并发标记(CMS-concurrent-mark),与用户线程同时运行;
    • 预清理(CMS-concurrent-preclean),与用户线程同时运行;
    • 重新标记(CMS-remark),会导致stw;
    • 并发清除(CMS-concurrent-sweep),与用户线程同时运行;
    • 调整堆大小,设置CMS在清理之后进行内存压缩,目的是清理内存中的碎片;
    • 并发重置状态等待下次CMS的触发(CMS-concurrent-reset),与用户线程同时运行;

    测试

    #-XX:+UseParNewGC参数设置年轻代使用ParNew回收器
    #-XX:+PrintGCDetails打印垃圾回收的详细信息
    #为了测试GC,将堆的初始和最大内存都设置为16M
    [root@fgfg]>java ‐XX:+UseConcMarkSweepGC ‐XX:+PrintGCDetails ‐Xms16m ‐Xmx16m
    

    3.4 G1垃圾收集器(重点)

    G1垃圾收集器是在jdk1.7中正式使用的全新的垃圾收集器,oracle官方计划在jdk9中将G1变成默认的垃圾收集器,以替代CMS。

    • G1的设计原则就是简化JVM性能调优,开发人员只需要简单的三步即可完成调优:
    1. 第一步,开启G1垃圾收集器
    2. 第二步,设置堆的最大内存
    3. 第三步,设置最大的停顿时间
    • G1中提供了三种模式垃圾回收模式,YoungGC、MixedGC和FullGC,在不同的条件下被触发。

    3.4.1 原理

    G1垃圾收集器相对比其他收集器而言,最大的区别在于它取消了年轻代、老年代的物理划分,取而代之的是将堆划分为若干个区域(Region),这些区域中包含了有逻辑上的年轻代、老年代区域。
    这样做的好处就是,我们再也不用单独的空间对每个代进行设置了,不用担心每个代内存是否足够。

    在G1划分的区域中,年轻代的垃圾收集依然采用暂停所有应用线程的方式,将存活对象拷贝到老年代或者Survivor空间,G1收集器通过将对象从一个区域复制到另外一个区域,完成了清理工作。
    这就意味着,在正常的处理过程中,G1完成了堆的压缩(至少是部分堆的压缩),这样也就不会有cms内存碎片问题的存在了。

    在G1中,有一种特殊的区域,叫Humongous区域。

    • 如果一个对象占用的空间超过了分区容量50%以上,G1收集器就认为这是一个巨型对象。
    • 这些巨型对象,默认直接会被分配在老年代,但是如果它是一个短期存在的巨型对象,就会对垃圾收集器造成负面影响。
    • 为了解决这个问题,G1划分了一个Humongous区,它用来专门存放巨型对象。如果一个H区装不下一个巨型对象,那么G1会寻找连续的H分区来存储。为了能找到连续的H区,有时候不得不启动FullGC。

    3.4.2. YoungGC

    YoungGC主要是对Eden区进行GC,它在Eden空间耗尽时会被触发。

    • Eden空间的数据移动到Survivor空间中,如果Survivor空间不够,Eden空间的部分数据会直接晋升到年老代空间 * Survivor区的数据移动到新的Survivor区中,也有部分数据晋升到老年代空间中。
    • 最终Eden空间的数据为空,GC停止工作,应用线程继续执行。

    每个Region初始化时,会初始化一个RSet,该集合用来记录并跟踪其它Region指向该Region中对象的引用,每个Region默认按照512Kb划分成多个Card,所以RSet需要记录的东西应该是xxRegion的xxCard。

    3.4.3. MixedGC

    1. 当越来越多的对象晋升到老年代oldregion时,为了避免堆内存被耗尽,虚拟机会触发一个混合的垃圾收集器,即MixedGC,该算法并不是一个OldGC,除了回收整个YoungRegion,还会回收一部分的OldRegion,这里需要注意:是一部分老年代,而不是全部老年代,可以选择哪些oldregion进行收集,从而可以对垃圾回收的耗时时间进行控制。也要注意的是MixedGC并不是FullGC。

      MixedGC什么时候触发?由参数-XX:InitiatingHeapOccupancyPercent=n决定。默认:45%,该参数的意思是:当老年代大小占整个堆大小百分比达到该阀值时触发。

    它的GC步骤分2步:
    1.全局并发标记(globalconcurrentmarking)
    2.拷贝存活对象(evacuation)

    1.全局并发标记
    全局并发标记,执行过程分为五个步骤:

    • 初始标记(initialmark,STW)
      * 标记从根节点直接可达的对象,这个阶段会执行一次年轻代GC,会产生全局停顿。
    • 根区域扫描(rootregionscan)
      * G1GC在初始标记的存活区扫描对老年代的引用,并标记被引用的对象。
      * 该阶段与应用程序(非STW)同时运行,并且只有完成该阶段后,才能开始下一次STW年轻代垃圾回收。
    • 并发标记(ConcurrentMarking): G1GC在整个堆中查找可访问的(存活的)对象。该阶段与应用程序同时运行,可以被STW年轻代垃圾回收中断。
    • 重新标记(Remark,STW): 该阶段是STW回收,因为程序在运行,针对上一次的标记进行修正。
    • 清除垃圾(Cleanup,STW): 清点和重置标记状态,该阶段会STW,这个阶段并不会实际上去做垃圾的收集,等待evacuation阶段来回收。

    2.拷贝存活对象(evacuation)
    Evacuation阶段是全暂停的。该阶段把一部分Region里的活对象拷贝到另一部分Region中,从而实现垃圾的回收清理。

    测试

    [root@vfvg]# java ‐XX:+UseG1GC‐XX:MaxGCPauseMillis=100‐XX:+PrintGCDetails‐Xmx256m
    #扫描根节点
    [ExtRootScanning(ms):Min:0.2,Avg:0.3,Max:0.3,Diff:0.1,Sum:0.8]
    #更新RS区域所消耗的时间
    [UpdateRS(ms):Min:1.8,Avg:1.9,Max:1.9,Diff:0.2,Sum:5.6][ProcessedBuffers:Min:1,Avg:1.7,Max:3,Diff:2,Sum:5][ScanRS(ms):Min:0.0,Avg:0.0,Max:0.0,Diff:0.0,Sum:0.0][CodeRootScanning(ms):Min:0.0,Avg:0.0,Max:0.0,Diff:0.0,Sum:0.0]
    #对象拷贝
    [ObjectCopy(ms):Min:1.1,Avg:1.2,Max:1.3,Diff:0.2,Sum:3.6]
    ....
    [ClearCT:0.0ms]#清空CardTable
    [Other:0.7ms][ChooseCSet:0.0ms]#选取CSet
    [RefProc:0.5ms]#弱引用、软引用的处理耗时
    [RefEnq:0.0ms]#弱引用、软引用的入队耗时
    [RedirtyCards:0.0ms][HumongousRegister:0.0ms]#大对象区域注册耗时
    [HumongousReclaim:0.0ms]#大对象区域回收耗时
    ....
    

    参数说明:

    • -XX:+UseG1GC: 使用G1垃圾收集器
    • -XX:MaxGCPauseMillis: 设置期望达到的最大GC停顿时间指标(JVM会尽力实现,但不保证达到),默认值是200毫秒。
    • -XX:G1HeapRegionSize=n 设置的G1区域的大小。值是2的幂,范围是1MB到32MB之间。目标是根据最小的Java堆大小划分出约2048个区域。默认是堆内存的1/2000。
    • -XX:ParallelGCThreads=n 设置STW工作线程数的值。将n的值设置为逻辑处理器的数量。n的值与逻辑处理器的数量相同,最多为8。
    • -XX:ConcGCThreads=n 设置并行标记的线程数。将n设置为并行垃圾回收线程数(ParallelGCThreads)的1/4左右。
    • -XX:InitiatingHeapOccupancyPercent=n 设置触发标记周期的Java堆占用率阈值。默认占用率是整个Java堆的45%。

    对于G1垃圾收集器优化建议

    • 年轻代大小
      * 避免使用-Xmn选项或-XX:NewRatio等其他相关选项显式设置年轻代大小。
      * 固定年轻代的大小会覆盖暂停时间目标。
    • 暂停时间目标不要太过严苛
      * G1GC的吞吐量目标是90%的应用程序时间和10%的垃圾回收时间。
      * 评估G1GC的吞吐量时,暂停时间目标不要太严苛。目标太过严苛表示您愿意承受更多的垃圾回收开销,而这会直接影响到吞吐量

    GC日志输出参数

    ‐XX:+PrintGC     输出GC日志
    ‐XX:+PrintGCDetails    输出GC的详细日志
    ‐XX:+PrintGCTimeStamps   输出GC的时间戳(以基准时间的形式)
    ‐XX:+PrintGCDateStamps    输出GC的时间戳(以日期的形式,如2013‐05‐04T21:53:59.234+0800)
    ‐XX:+PrintHeapAtGC    在进行GC的前后打印出堆的信息
    ‐Xloggc:../logs/gc.log   日志文件的输出路径
    
    [root@vfvg]# java ‐XX:+UseG1GC‐XX:MaxGCPauseMillis=100‐Xmx256m‐XX:+PrintGCDetails‐XX:+PrintGCTimeStamps‐XX:+PrintGCDateStamps‐XX:+PrintHeapAtGC‐Xloggc:F://test//gc.log
    

    4. JVM优化02

    知识要点:

    • 4.1 Tomcat8的优化
    • 4.2 看懂Java底层
    • 4.3 字节码编码的优化建议

    4.1 Tomcat8的优化


    tomcat服务器在JavaEE项目中使用率非常高,所以在生产环境对tomcat的优化也变得非常重要了。对于tomcat的优化,主要是从2个方面入手,一是,tomcat自身的配置,另一个是tomcat所运行的jvm虚拟机的调优。

    4.1.1 搭建tomcat环境

    tar‐xvf apache‐tomcat‐8.5.34.tar.gz 
    cd apache‐tomcat‐8.5.34/conf #修改配置文件,配置tomcat的管理用户
    vim tomcat‐users.xml #写入如下内容:
    <rolerolename="manager"/> <rolerolename="manager‐gui"/>
    <rolerolename="admin"/> <rolerolename="admin‐gui"/>
    <userusername="tomcat" password="tomcat" roles="admin‐gui,admin,manager‐gui,manager"/>
    #保存退出#如果是tomcat7,配置了tomcat用户就可以登录系统了,但是tomcat8中不行,还需要修改另一个配置文件,否则访问不了,提示403vimwebapps/manager/META‐INF/context.xml#将<Valve的内容注释掉
    
    <Context  antiResourceLocking="false" privileged="true">
     <!‐‐<ValveclassName="org.apache.catalina.valves.RemoteAddrValve"allow="127.d+.d+.d+|::1|0:0:0:0:0:0:0:1"/>‐‐>
     <ManagersessionAttributeValue ClassNameFilter="java.lang.(?:Boolean|Integer|Long|Number|String)|org.apache.catalina.filters.CsrfPreventionFilter$LruCache(?:$1)?|java.util.(?:Linked)?HashMap"/>
    </Context>
    #保存退出即可#启动tomcatcd/tmp/apache‐tomcat‐8.5.34/bin/./startup.sh&&tail‐f../logs/catalina.out
    #打开浏览器进行测试访问http://192.168.40.133:8080/
    

    4.1.2.禁用AJP连接

    • 什么是AJP呢?

    AJP(ApacheJServerProtocol)AJPv13协议是面向包的。WEB服务器和Servlet容器通过TCP连接来交互;为了节省SOCKET创建的昂贵代价,WEB服务器会尝试维护一个永久TCP连接到servlet容器,并且在多个请求和响应周期过程会重用连接。

    我们一般是使用Nginx+tomcat的架构,所以用不着AJP协议,所以把AJP连接器禁用。修改conf下的server.xml文件,将AJP服务禁用掉即可。

    <!‐‐ <Connectorport="8009"protocol="AJP/1.3"redirectPort="8443"/> -->
    

    4.1.3 执行器(线程池)


    在tomcat中每一个用户请求都是一个线程,所以可以使用线程池提高性能。修改server.xml文件:

    <!‐‐将注释打开‐‐>
    <Executor name="tomcatThreadPool" namePrefix="catalina‐exec‐" maxThreads="500"  minSpareThreads="50"  prestartminSpareThreads="true" maxQueueSize="100"/>
    <!‐‐
    参数说明:
    maxThreads:最大并发数,默认设置200,一般建议在500~1000,根据硬件设施和业务来判断
    minSpareThreads:Tomcat初始化时创建的线程数,默认设置25
    prestartminSpareThreads:在Tomcat初始化的时候就初始化minSpareThreads的参数值,如果不等于true,minSpareThreads的值就没啥效果了maxQueueSize,最大的等待队列数,超过则拒绝请求
    ‐‐>
    <!‐‐在Connector中设置executor属性指向上面的执行器‐‐>
    <Connector executor="tomcatThreadPool" port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443"/>
    

    4.1.4 3种运行模式


    tomcat的运行模式有3种:

    1. bio
    • 默认的模式,性能非常低下,没有经过任何优化处理和支持.
    1. nio
    • nio(newI/O),是JavaSE1.4及后续版本提供的一种新的I/O操作方式(即java.nio包及其子包)。Javanio是一个基于缓冲区、并能提供非阻塞I/O操作的JavaAPI,因此nio也被看成是non-blockingI/O的缩写。它拥有比传统I/O操作(bio)更好的并发运行性能。
    1. pr
    • 安装起来最困难,但是从操作系统级别来解决异步的IO问题,大幅度的提高性能.

    推荐使用nio,不过,在tomcat8中有最新的nio2,速度更快,建议使用nio2.设置nio2:

    <Connector executor="tomcatThreadPool" port="8080" protocol="org.apache.coyote.http11.Http11Nio2Protocol" connectionTimeout="20000"redirectPort="8443"/>
    

    使用ApacheJMeter进行测试
    ApacheJmeter是开源的压力测试工具,我们借助于此工具进行测试,将测试出tomcat的吞吐量等信息。下载安装下载地址:http://jmeter.apache.org/download_jmeter.cgi

    4.1.5 调整tomcat参数进行优化

    通过上面测试可以看出,tomcat在不做任何调整时,吞吐量为73次/秒。
    主要有几个方面优化:

      1. 禁用AJP服务.
      1. 设置nio2的运行模式
    <!‐‐设置nio2‐->
    <Connector executor="tomcatThreadPool"port="8080" protocol="org.apache.coyote.http11.Http11Nio2Protocol"connectionTimeout="20000" redirectPort="8443"/>
    
      1. 设置线程池参数
        通过设置线程池,调整线程池相关的参数进行测试tomcat的性能。
    <!‐‐最大线程数为500,初始为50 吞吐量为128次/秒,性能有所提升‐‐>
    <Executorname="tomcatThreadPool" namePrefix="catalina‐exec‐"maxThreads="500" minSpareThreads="50" prestartminSpareThreads="true"/>
    <!‐‐最大等待数为100‐‐>
    <Executorname="tomcatThreadPool"namePrefix="catalina‐exec‐"maxThreads="500"minSpareThreads="100"prestartminSpareThreads="true"maxQueueSize="100"/>
    

    测试结果:
    - 平均响应时间:3.1秒响应时间明显缩短
    - 错误率:49.88%错误率提升到一半,也可以理解,最大线程为500,测试的并发为1000
    - 吞吐量:238次/秒吞吐量明显
    提升结论:响应时间、吞吐量这2个指标需要找到平衡才能达到更好的性能。

    • 4.调整JVM参数进行优化

    1.设置并行垃圾回收器

    #年轻代、老年代均使用并行收集器,初始堆内存64M,最大堆内存512M
    JAVA_OPTS="‐XX:+UseParallelGC ‐XX:+UseParallelOldGC ‐Xms64m ‐Xmx512m ‐XX:+PrintGCDetails ‐XX:+PrintGCTimeStamps ‐XX:+PrintGCDateStamps ‐XX:+PrintHeapAtGC ‐Xloggc:../logs/gc.log"
    

    测试结果与默认的JVM参数结果接近.

    1:年轻代的gc有74次,次数稍有多,说明年轻代设置的大小不合适需要调整
    2:FullGC有8次,说明堆内存的大小不合适,需要调整
    3:吞吐量表现不错,但是gc时,线程的暂停时间稍有点长。
    4:在报告中显示,在5次GC时,系统所消耗的时间大于用户时间,这反应出的服务器的性能存在瓶颈,调度CPU等资源所消耗的时间要长一些
    5:从GC原因的可以看出,年轻代大小设置不合理,导致了多次GC
    

    2.调整年轻代大小

    JAVA_OPTS=" ‐XX:+UseParallelGC ‐XX:+UseParallelOldGC ‐Xms128m ‐Xmx1024m ‐XX:NewSize=64m ‐XX:MaxNewSize=256m ‐XX:+PrintGCDetails ‐XX:+PrintGCTimeStamps ‐XX:+PrintGCDateStamps ‐XX:+PrintHeapAtGC ‐Xloggc:../logs/gc.log"
    

    3:设置G1垃圾回收器

    #设置了最大停顿时间100毫秒,初始堆内存128m,最大堆内存1024m
    JAVA_OPTS="‐XX:+UseG1GC ‐XX:MaxGCPauseMillis=100 ‐Xms128m ‐Xmx1024m ‐XX:+PrintGCDetails ‐XX:+PrintGCTimeStamps ‐XX:+PrintGCDateStamps ‐XX:+PrintHeapAtGC ‐Xloggc:../logs/gc.log"
    

    4.2 JVM字节码


    前面我们通过tomcat本身的参数以及jvm的参数对tomcat做了优化,其实要想将应用程序跑的更快、效率更高,除了对tomcat容器以及jvm优化外,应用程序代码本身如果写的效率不高的,那么也是不行的,所以,对于程序本身的优化也就很重要了。

    对于程序本身的优化,可以借鉴很多前辈们的经验,但是有些时候,在从源码角度方面分析的话,不好鉴别出哪个效率高,如对字符串拼接的操作,是直接“+”号拼接效率高还是使用StringBuilder效率高?
    这个时候,就需要通过查看编译好的class文件中字节码,就可以找到答案。
    我们都知道,java编写应用,需要先通过javac命令编译成class文件,再通过jvm执行,jvm执行时是需要将class文件中的字节码载入到jvm进行运行的
    

    4.2.1 查看字节码

    通过javap命令查看class文件的字节码内容
    javap‐v Test1.class>Test1.txt #查看并追加到test1.txt文件中
    javap 用法:javap javap -help
    内容大致分为4个部分:
    第一部分:显示了生成这个class的java源文件、版本信息、生成时间等。
    第二部分:显示了该类中所涉及到常量池,共35个常量。
    第三部分:显示该类的构造器,编译器自动插入的。
    第四部分:显示了main方的信息。(这个是需要我们重点关注的)

    1:常量池

    # https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.4-140
    ConstantType	Value	说明
    CONSTANT_Class	7	类或接口的符号引用
    CONSTANT_Fieldref	9	字段的符号引用
    CONSTANT_Methodref	10	类中方法的符号引用
    CONSTANT_InterfaceMethodref	11	接口中方法的符号引用
    CONSTANT_String	8	字符串类型常量
    CONSTANT_Integer	3		整形常量
    CONSTANT_Float	4	浮点型常量
    CONSTANT_Long	5	长整型常量
    CONSTANT_Double	6		双精度浮点型常量
    CONSTANT_NameAndType	12	字段或方法的符号引用
    CONSTANT_Utf81UTF-8	编码的字符串
    CONSTANT_MethodHandle	15	表示方法句柄
    CONSTANT_MethodType	16	标志方法类型
    CONSTANT_InvokeDynamic	18	表示一个动态方法调用点
    

    2: 字段描述符


    # https://docs.or传智播客acle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.3.2
    B 	byte	 signedbyte 
    C	char UnicodecharactercodepointintheBasicMultilingualPlane,encodedwithUTF-16
    D	double	double-precisionfloating-pointvalue
    F	float	single-precisionfloating
    I	int	integer
    J	long	longinteger
    LClassName	reference	aninstanceofclassClassName
    S	short	signedshort
    Z	boolean	trueorfalse
    [	reference
    

    2: 字段描述符


    Objectm(inti,doubled,Threadt){...}
    解析为:(IDLjava/lang/Thread;)Ljava/lang/Object;
    

    3:解读字节码

    public static void main(java.lang.String[]);
    descriptor:([Ljava/lang/String;)V//方法描述,V表示该方法的放回值为void
    flags:ACC_PUBLIC,ACC_STATIC//方法修饰符,public、static的Code://stack=2,操作栈的大小为2、locals=4,本地变量表大小,args_size=1,参数的个数stack=2,locals=4,args_size=10:iconst_2//将数字2值压入操作栈,位于栈的最上面
    1:istore_1//从操作栈中弹出一个元素(数字2),放入到本地变量表中,位于下标为1的位置(下标为0的是this)
    2:iconst_5//将数字5值压入操作栈,位于栈的最上面
    3:istore_2//从操作栈中弹出一个元素(5),放入到本地变量表中,位于第下标为2个位置
    4:iload_2//将本地变量表中下标为2的位置元素压入操作栈(5)
    5:iload_1//将本地变量表中下标为1的位置元素压入操作栈(2)
    6:isub//操作栈中的2个数字相减
    7:istore_3//将相减的结果压入到本地本地变量表中,位于下标为3的位置
    //通过#2号找到对应的常量,即可找到对应的引用8:getstatic#2
    //Fieldjava/lang/System.out:Ljava/io/PrintStream;
    11:iload_3//将本地变量表中下标为3的位置元素压入操作栈(3)
    //通过#3号找到对应的常量,即可找到对应的引用,进行方法调用
    12:invokevirtual	#3	//Methodjava/io/PrintStream.println:(I)V
    15:return//返回
    LineNumberTable://行号的列表
    line	6:	0
    line	7:	2
    line	8:	4
    line	9:	8
    line	10:	15
    LocalVariableTable:	//本地变量表
    Start	Length	Slot	NameSignature
    0	16	0	args	[Ljava/lang/String;
    ...
    }
    SourceFile:"Test1.java"
    
    • 研究i++与++i的不同
      区别:
      i++

      • 只是在本地变量中对数字做了相加,并没有将数据压入到操作栈
      • 将前面拿到的数字1,再次从操作栈中拿到,压入到本地变量中
        ++i
      • 将本地变量中的数字做了相加,并且将数据压入到操作栈
      • 将操作栈中的数据,再次压入到本地变量中小结:可以通过查看字节码的方式对代码的底层做研究,探究其原理。
    • 字符串拼接


    字符串的拼接在开发过程中使用是非常频繁的,常用的方式有三种:
    * +号拼接:str+"456"
    * StringBuilder拼接
    * StringBuffer拼接

     StringBuffer是保证线程安全的,效率是比较低的,我们更多的是使用场景是不会涉及到线程安全的问题的,所以更多的时候会选择StringBuilder,效率会高一些。
      那么,问题来了,StringBuilder和“+”号拼接,哪个效率高呢?接下来我们通过字节码的方式进行探究。
    
    • 从解字节码中可以看出,m1()方法源码中是使用+号拼接,但是在字节码中也被编译成了StringBuilder方式。
      以得出结论,字符串拼接,+号和StringBuilder是相等的,效率一样. m1()方法中的循环体内,每一次循环都会创建StringBuilder对象,效率低于m2()方法。做循环拼接或大量字符串拼接时,就会创建大量StringBuilder,从而降低性能。

    4.2.2 代码优化

    1. 尽可能使用局部变量
    2. 尽量减少对变量的重复计算
    for(inti=0;i<list.size();i++){...}
    替换为
    intlength=list.size();
    for(inti=0,i<length;i++){...}
    
    1. 尽量采用懒加载的策略,即在需要的时候才创建
    2. 异常不应该用来控制程序流程
    3. 不要将数组声明为public static final 被外部类所改变
    4. 不要创建一些不使用的对象,不要导入一些不使用的类
    5. 程序运行过程中避免使用反射
    6. 使用数据库连接池和线程池
    7. 容器初始化时尽可能指定长度
    8. ArrayList随机遍历快,LinkedList添加删除快
    9. 使用Entry遍历Map
    10. 不要手动调用System.gc();
    11. String尽量少用正则表达式(replace()不支持正则replaceAll()支持正则)
    12. 日志的输出要注意级别
    13. 对资源的close()建议分开操作
  • 相关阅读:
    PAT (Advanced Level) Practice 1100 Mars Numbers (20分)
    PAT (Advanced Level) Practice 1107 Social Clusters (30分) (并查集)
    PAT (Advanced Level) Practice 1105 Spiral Matrix (25分)
    PAT (Advanced Level) Practice 1104 Sum of Number Segments (20分)
    PAT (Advanced Level) Practice 1111 Online Map (30分) (两次迪杰斯特拉混合)
    PAT (Advanced Level) Practice 1110 Complete Binary Tree (25分) (完全二叉树的判断+分享致命婴幼儿错误)
    PAT (Advanced Level) Practice 1109 Group Photo (25分)
    PAT (Advanced Level) Practice 1108 Finding Average (20分)
    P6225 [eJOI2019]异或橙子 树状数组 异或 位运算
    P4124 [CQOI2016]手机号码 数位DP
  • 原文地址:https://www.cnblogs.com/yiyangyu/p/jvm0003.html
Copyright © 2011-2022 走看看