关于代码的优化,tomcat的优化,以及jvm的优化总结

加油,新时代打工人!!!

关于代码的优化,tomcat的优化,以及jvm的优化总结:
优化不仅仅是在运行环境进行优化还需要在代码做优化,如果代码优化本身存在性能问题,那么再怎么优化也不可能达到效果最优!!!!!!!!!!!
(1)代码的优化:作为一个高级程序员必须要能去解读字节码,只有从字节码层面去去分析才能看到代码执行的效率,然而我现在的层次不够,没那么多时间去研究字节码,只是简单总结了下别人高手的一些意见以便写成更好的代码,这里简单总结下一些方案:

第一种:尽可能使用局部变量;局部变量存与栈中用完就释放,速度快不需要额外的垃圾回收;
第二种:关于遍历方面的,假设是list集合循环,应该写成这样的
int length=list.size();
for(int i=0;i<length;i++){…}
不用每次去拿size,当size很大时就减少了消耗;
对于map集合,比如HashMap,循环时应该用Entry,Entry一次性就遍历出所有的key和value,如果用keyset就要遍历两次;
for(Map.Entry<String,String> entry:map.EntrySet()){…} 第三种:了解容器底层的实现,选择合适的容器,比如ArrayList底层是数组,查询快.增删慢,LinkedList是查询慢.增删快;
第四种:容器初始化化时,如果知道它的长度就尽量指定长度,比如new ArrayList<>(10),new
HashMap<>(32),避免容器长度不足时扩容带来的性能损耗;
第五种:程序运行过程中避免使用反射,避免使用正则表达式,往往功能强大意味着效率不高;
第六种:对资源的close()建议分开,免得出异常时有资源没释放例如try{(xxx.close();yyy.close())}catch(…){}改成try{(xxx.close()}catch(…){}
try{(yyy.close()}catch(…){}
第七种:不要手动去调用System.gc();因为这样可能会影响到jvm对gc处理的判断,一般都会设置禁用手动调用gc;
第八种:使用线程池和数据库连接池; 第九种:不要将数组定义为public static
final,这样没意义,数组内容经常需要变的,定义为public更是一个安全漏洞;
第十种:尽量使用懒加载的策略,即在需要的时候才创建,免得创建好了又不用,浪费内存;
第十一种:不要创建一些无用的对象,不要导入一些不使用的类;
其实还有别的,只是不好懂,这里就不写了,至少可以跟别人说说,说明你平时在慢慢积累,提高代码的质量,有追求!!!

(2)tomcat的优化,其实包括了自身配置的优化以及tomcat运行时jvm虚拟机的调化?
tomcat自身配置的优化:
第一:禁用AJP服务,因为我们的架构一般都是Nginx+tomcat没用到Apache服务器或者IIS服务器,既然不用就该关掉,开着浪费资源;
第二:给tomcat加执行器(线程池);
第三:调整tomcat的运行模式,如果是tomcat8就用NIO2(AIO)模式,如果是版本8以下的就用NIO
(解释下8以下版本模式默认是BIO及同步阻塞,效率非常差;NIO指同步非阻塞,NIO2指异步非阻塞效率最高)这三个修改都是在server.xml文件中进行的,至于怎么修改,可以随便查查就知道了;
jvm虚拟机的优化:需要在catalina.sh脚本中添加配置
JAVA_OPTS=“-XX:+UseG1GC 意思是用G1垃圾收集器
-XX:+MaxGCPauseMillis=100 最大停顿时间
-Xms128m 指的是初始堆内存
-Xmx1024m 指的是最大堆内存

-Xloggc: …/logs/gc.log” 指gc的日志文件
具体的参数配置可以查
对于项目的监控可以用Apache Jmeter进行测试,生成报表具体看聚合报告:具体看3个参数,吞吐量(每秒处理请求的数量)平均响应时间和错误率(响应错误信息)
gc的日志文件可以用GCEasy(在线分析形成报表);
为何要用G1垃圾收集器后面具体讲;
(3)这里重点讲的是jvm的优化: Xms Xmx

  在项目上线运行过程中可能会遇到下面这些情况:
             内存溢出,报OutOfMemoryError?
             运行的应用突然卡住了,日志不输出,程序没反应(死锁)?
             服务器的cpu负载突然升高?
             .......................
       首先了解下jvm内存模型:
          jdk1.7版本:分为年轻代,年老代,以及永久区;
               年轻代分为Eden区和两个survivor区;
          jdk1.8分为年轻代和年老代,之前的永久区被元数据空间替代了,它占用的空间不是在虚拟机内部而是在本地内存空间;
       先说垃圾回收的算法: 第一个是引用计数法:每个对象有自己的引用计数器,如果该对象被别的对象引用,那么就加一,反之减一,如果计数器为0,那么该对象会被回收掉

它的好处是:回收时应用不必暂停,不会扫描全部对象
它最大的弊端:无法解决循环引用的问题;比如A和B相互引用,那么他们的计数器都是1,此时设置A=NULL和B=NULL,这两个对象本该回收的却回收不了;想象下如果使用这种算法,是不是很多对象不能回收可能会造成内存溢出;
第二个是标记清除法:分为两个阶段 标记:从根节点开始标记被引用的对象; 清除:如果没有被标记的对象就是垃圾要被清理;
优点:解决了循环引用问题;
缺点:效率低,两个阶段都要遍历所有的对象,清理没有结束需要暂停应用程序,对于交互要求高的应用而言体验非常差,清理出来的内存有严重的碎片问题,就是说内存不连续;
第三种是标记压缩法:比标记清除法多了一步压缩过程,就是说先将存活的对象压缩到一边再去清理掉垃圾对象; 优点:解决了标记清除法带来的碎片化问题
缺点:多了一步压缩,效率依旧低; 第四种是复制算法:将survivor区分为From和To两块一样的区域,
开始的时候对象存放在Eden区和名为From的survivor区,回收时将Eden区所有存活的对象复制到To区,From区中根据年龄值来决定去向,年龄达到阀值就存年老区,没有就存To区,清理完后Eden区和From清空,From和To交换角色,循环这样的操作,直到To区存满全部转移到年老区;
优点:在垃圾对象比较多的时候复制得少,效率很高,也没碎片
缺点:在垃圾比较少的时候不适应,比如年老区;survivor区分为相同的两块,同一时刻只能用一半
,内存使用较低;
第五种是分代算法:前面的算法都有优缺点,谁也不能代替谁,
所以要根据实际情况进行选择,那么jvm会根据回收对象的特点去选择一种算法就是所谓的分代算法,一般情况下年轻代适合用复制算法,年老代适合使用标记压缩算法;

接着讲垃圾收集器的种类: 第一种是串行垃圾收集器:单线程进行垃圾回收,回收垃圾时应用中的所有线程都要停止工作,效率非常低,不能采用;
第二种是并行垃圾收集器:在串行的基础上改为多线程而已 第三种是CMS(并发)垃圾回收器,这里不讲过程,很复杂;
第四种是我们需要用的G1垃圾回收器:注意是jdk1.7出来的,oracle公司计划是在jdk9中设置G1为默认的垃圾回收器,我们一般都是使用jdk1.8,所以都需要手动设置为G1;G1设置的原则就是简化jvm的性能调优,我们只需要完成三步就可以:
第一步:开启G1垃圾收集器;
第二步:设置堆的初始化内存大小和最大内存
第三步:设置最大停顿时间
-XX:+PrintGC:开启打印gc信息; -XX:+PrintGCDetails:打印gc详细信息。 G1提供了三种垃圾回收模式:Young GC,Mixed GC和Full
GC在不同情况下触发;它取消了年轻代和年老代的物理划分,取而代之的是逻辑上的划分,逻辑上分为E区,S区,O区,以及H区;
G1的亮点:有一个特殊的区域叫Humongous区域(及所谓的H区),它是用来存放巨型对象的(占分区容量50%以上的对象),以前默认会存入O区,但是如果是一个短期的对象存放在O区会对垃圾回收器产生负面影响,如果一个H区存不下就会启动Full
GC寻找连续的H区域,以便能存放该巨型对象
还有一个亮点就是RSet集合,每个对象都有一个rest集合,记录的是该对象被引用的情况,这样扫描的时候就只需要查看rset集合就可以,没必须去扫描全部对象,节约了大量时间;
Young
GC:主要是对逻辑上的Eden区进行垃圾回收的,Eden区被占满的时候就会被触发,E区的对象被转移到S区,如果S区空间不够就转移到O区,S区的数据复制到新的S区,也有部分会被复制到O区,最终E区被清空,GC停止工作,程序继续运行;
Mixed GC:当越来越多的对象晋升到O区,为了避免堆内存被耗尽,虚拟机会触发一个混合的垃圾收集器及Mixed
GC,会去回收整个年轻代(E区和S区)还有一部分O区,可以选择哪些O区进行收集,从而可以对垃圾回收耗时时间进行控制,注意的是Mixed
GC不是Full GC;触发由参数
-XX:InitatingHeapOccupanycyPercent=n来决定,默认是45%一般就用默认就行了,就是指O区内存占整个堆内存的百分比
Full GC:就是对整个堆内存去扫描,它是一个很重要的指标,通过报表统计查看有没做Full GC,Full
GC的成本很高,如果发现在报表中发现Full GC比较多,就要考虑堆内存设置是不是有问题; 总结下:如果面试问到谈谈垃圾回收的机制?
我们用的JDK版本是1.8,故选择的垃圾回收器是G1,它包括3种垃圾回收机制:Young GC,Mixed GC以及Full GC.
当逻辑上的Eden区内存被占满的时候会触发Young GC,采用复制算法对E区的对象进行回收,通过查看
Rset集合中对象引用的情况判断哪些对象是存活的,哪些对象要被清理.存活的对象被复制到S区,如果S区存不下就存入O区,回收完后E区被清空,当发现O区内存占对内存的比例达到45%的时候开始启动Mixed
GC对整个年轻代进行垃圾回收,当然也会回收部分O区;存放巨型对象的时候如果一个H区存放不下,那么会启动Full
GC来扫描整个堆内存来寻找连续的H区以便能存放下该巨型对象;

好了,现在回到那两个最重要的问题: 内存溢出原因和解决方案?(这个问题经常问到)
思路:工具GCEasy,gc的日志或者VisualVM->查看jvm配置Xms Xmx垃圾回收器是不是G1->查看有问题的线程,定位代码
对于运行的项目如果报内存溢出问题,我们需要定位去分析内存溢出的环节。我们一般是利用工具比如GCEasy来分析gc的日志或者利用VisualVM远程监控再者利用jmap命令和MAT工具去排查
首先我会去看jvm自身的配置有没问题,查看内存大小问题查看-Xms和-Xmx参数设置,如果小了要调整,以防止读到大文件时直接内存不够溢出,
还要查看下垃圾回收器的选择,如果发现是DefNew(串行垃圾回收器)说明它垃圾回收效率太低了,应该修改为G1使用这种效率最高的垃圾收集器;
如果这些配置没什么问题,说明应该是代码写得有问题,最直接的办法就是查看线程占用内存比例情况,如果发现某个线程占用内存比例非常高,说明这个线程里面的代码有问题,这时需要定点去排查该线程代码修改bug;出现的情况往往是这些:不能被回收的对象,从一次性加载的数据过多,从对象不断地加载垃圾回收器还来不及回收,我目前知道的就下面这些
比如一次从数据库里面读取太多数据到jvm中,超过最大内存或者有死循环或递归在不断地创建对象,或者有些强引用对象没释放导致内存泄露最终导致的内存溢出(比如ThreadLocal中存入的数据用完后没有手动释放等)还有使用线程池时利用的是工具类Executors创建线程池导致内存溢出(上面分析的)再者判断字符串是否为空时使用了trim()方法;
判断一个字符串为空,低级程序员会这样写:
if(token==null||token.trim().isEmpty()){…}
这里有个非常严重的问题:trim() 它底层原理是把字符串整体加载到内存,不断截取新的字符串,再判断串的长度,会加载多次,效率极低,而且非常容易造成内存溢出
那么正确的做法是 添加依赖

org.apache.commons
commons-lang3

然后修改为: if(StringUtils.isBlank(token)){…} 要善于利用工具…
至于另外一些情况比如运行的应用突然卡住了,日志不输出,程序没反应(死锁)?
同样是通过工具如VisualVM远程监控来分析线程的状态, (线程有6种状态)比如发现某条线程的状态报了Found one java-level deadLock就知道哪条线程发生了死锁,去修改代码即可;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Hello World呀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值