Tomact优化

154 阅读8分钟

Tomact优化

1.为什么要学习JVM?

单体项目----单体优化---集群部署---微服务架构

  • 单体项目/集群节点/分布式系统中的服务,如果想要更好的对外提供服务,就需要进行优化;如果对服务器进行优化,就需要先了解JVM

2.学习JVM要学哪些内容?

  • 计算机的内存模型
  • JMM Java内存模型
  • JVM内存结构
  • GC优化

3.现代计算机的内存模型

计算机的硬件组成:主板/母板、CPU[x86复杂指令,ARM简易指令]、内存、磁盘

CPU

  • x86复杂指令

    • 加法运算 --- 加法AUX
    • 减法运算 --- 减法AUX
    • ...
  • ARM简易指令

    • 加法运算 --- 加法AUX 2+2=4
    • 乘法运算 --- 2*5 ==> 2+2+2+2+2

计算机内存模型

1593310223039.png

4.Java内存模型—JMM

4.1 JMM介绍

Java内存模型(Java Memory Model,JMM)是一种服务内存模型规范、屏蔽了各种硬件及操作系统之间存在的访问差异,保证了Java程序在各种平台下对内存的访问都能够保证一致的机制与规范。

JMM规范:

  • 屏蔽了各种硬件及操作系统之间存在的访问差异
  • 保证多线程并发操作数据一致性

1593311400659.png

4.2 JMM的原子操作
  • read(读取):作用于主内存变量,把一个变量从主内存传输到线程的工作内存中,以便随后的 load 动作使用。

  • load(载入):作用于工作内存变量,把 read 操作从主内存中得到的变量值放入工作内存的变量副本中。

  • use(使用):作用于工作内存变量,把工作内存中的一个变量值传递给执行引擎,每当虚拟机遇到一个需要

  • 使用变量值的字节码指令时执行此操作。

  • assign(赋值):作用于工作内存变量,把一个从执行引擎接收的值赋值给工作内存的变量,每当虚拟机遇到一个需要给变量进行赋值的字节码指令时执行此操作。

  • store(存储):作用于工作内存变量,把工作内存中一个变量的值传递到主内存中,以便后续 write 操作。

  • write(写入):作用于主内存变量,把 store 操作从工作内存中得到的值放入主内存变量中。

  • lock(锁定):作用于主内存变量,把一个变量标识为一条线程独占状态。

  • unlock(解锁):作用于主内存变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。

1593315507439.png

4.3 并发编程的三大特性

并发编程的三大特性:原子性、可见性、有序性

  • 原子性:即一个或者多个操作作为一个整体,要么全部执行,要么都不执行,并且操作在执行过程中不会被线程调度机制打断;而且这种操作一旦开始,就一直运行到结束,中间不会有任何上下文切换。

  • 可见性:可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值

    多个线程共享的变量使用volatile修饰,触发JMM嗅探机制(MESI缓存一致性协议)

  • 有序性:即程序执行的顺序按照代码的先后顺序执行

    指令重排:指令重排序,一般来说,处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。

    • 指令重排不会影响单线程的执行

      int i = 0;    //语句1
      int j = 0;    //语句2
      i = i + 10;   //语句3
      j = i * i;    //语句4
      ​
      这段代码有4个语句,那么可能的一个执行顺序是:
      语句2 -> 语句1 -> 语句3 -> 语句4
      那么可不可能是这个执行顺序:
      语句2 -> 语句1 -> 语句4 -> 语句3。
      不可能,因为处理器在进行重排序时是会考虑指令之间的数据依赖性,如果一个指令Instruction 2必须用到Instruction 1的结果,那么处理器会保证Instruction 1会在Instruction 2之前执行。
      
    • 但是可能导致线程并发执行存在问题

      private boolean flag = false;
      private Context context = null;
      ​
      //线程1
      context = loadContext();  //语句1
      flag = true;              //语句2
      ​
      //线程2 
      while(!flag){
          Thread.sleep(1000L);
      }
      dowork(context);
      ​
      由于在线程1中语句1、语句2是没有依赖性的,所以可能会出现指令重排。如果发生了指令重排,线程1先执行语句2,这时候线程2开始执行,此时flag值为true,因此线程2继续执行dowrk(context),此时context并没有初始化,因此就会导致程序错误。
      
  • 要想并发程序正确地执行,必须要保证原子性、可见性以及有序性。只要有一个没有被保证,就有可能会导致程序运行不正确。

5.JVM内存结构(JVM)

Java程序内存的分配是在JVM虚拟机内存分配机制下完成的

Java虚拟机内存规范:

Java虚拟机管理的内存分为5大区域

1593327746497.png

6.GC

GC,Java中的垃圾回收器,用于释放堆区中的无用对象

6.1 什么样的对象是无用对象
  • 没有被引用的对象是无用对象

1593328318468.png

6.2 GC的回收算法
  • 复制算法

    • 只能使用内存的1/2
  • 标记清除算法

    • 会导致内存的碎片化
  • 标记整理算法

    • 整理过程需要资源消耗
6.3 堆区内存结构

Java的堆是JVM中最大的一块内存区域,主要村塾Java中各种类的实例

JVM将堆区分成了2个区域

  • 新生代(Young) 占堆区内存的1/3

    • 新生代又分为Eden和Survivor区
    • Eden和Survivor的比例是4:1
  • 老年代(Old)占堆区内存的2/3

1593331450048.png

7.Tomcat优化

7.1 线程池优化
  1. 最大线程数
  2. 最小线程数
  3. 最大等待时间
<!--
Executor配置的是线程池
name是线程池名称,可以自定义
namePrefix:线程池中线程名称的前缀。
maxThreads:最大线程数,默认是200
maxIdleTime:超过最小线程数的线程最大等待时间,单位毫秒。
minSpareThreads:初始线程数,最小空闲线程数(跟CPU核数一致)。
maxSpareThreads:最大空闲线程数,超过的空闲线程会被关闭。
acceptCount:最大等待请求数,默认100,当所有可以使用的处理请求的线程数都被使用时,可以放到处理队列中的请求数,超过这个数的请求将不予处理。
-->
<Executor name="qianfeng" namePrefix="catalina-exec-"
maxThreads="300" minSpareThreads="4" /><Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000"
redirectPort="8443" executor="qianfeng"/>
7.2 JVM内存优化

JVM堆内存结构

  • 堆的分代

    • Java的堆是JVM中最大的一块内存区域,主要保存Java中各种类的实例。为了更好的管理堆中各个对象的内存,包括分配内存和回收内存。

    • JVM将堆分成了几块区域:

      • 新生代(Young) 新生代占堆的1/3空间

        • 新生代又分为: Eden、 Survivor1、 Survivor2
        • 新生代中的Eden\Survivor1\Survivor2空间占比为 8:1:1
      • 老年代(Old) 老年代占堆的2/3空间

      • 永久代(Perm) 64M

Tomcat内存优化设置

  • 打开catalina.bat文件,添加内存配置选项到Catalina.bat文档中,参考如下
<!--
-server:启用jdk的server版本。
-Xms:虚拟机初始化时的最小堆内存。
-Xmx:虚拟机可使用的最大堆内存。 #-Xms与-Xmx设成一样的值,避免JVM因为频繁的GC导致性能大起大落
-XX:PermSize:设置非堆内存初始值,默认是物理内存的1/64。
-XX:MaxNewSize:新生代占整个堆内存的最大值。
-XX:MaxPermSize:Perm(俗称方法区)占整个堆内存的最大值,也称内存最大永久保留区域。
-XX:NewRadio:年轻代与老年代的内存空间比
-
-->
JAVA_OPTS="-server -Xms256M -Xmx512M -Xss256K -Djava.awt.headless=true -Dfile.encoding=utf-8 -XX:MaxPermSize=64M -XX:PermSize=128M"

内存分析总结

  1. 内存空间

    • 新生代和年老代空间都被使用完,则会出现内存溢出的BUG。

    • 优化方案

      • 如果新创建对象较多且回收频繁,则将新生代扩容
      • 如果经常使用的对象较多,则将老年代扩容
      • JVM内存大小(最小堆内存与最大堆内存设置一致)
  2. 程序代码的优化

    • 尽量不要使用全局变量,特别是在servlet中(单例多线程)
    • Springmvc 单例多线程,所以在spring项目中不要有全局变量,不要将数组和集合定义为全局变量
    • session不要保存过大的对象
    • 对象只要不再使用了,记得释放
  3. GC回收频率 尽量让gc回收频率低

    • 如果创建对象过多,并且是项目需要这么新创建多对象,则认为修改新生代的内存大小。
    • 少创建一些对象:SpringIoC全局对象管理,默认创建的对象是单例,经常被使用的对象交给spring创建,不经常使用的对象作为局部变量创建。
    • 年老代内存不足也会导致gc回收
  4. GC算法

    • Full GC - 标记算法
    • Minor GC - 复制算法
  • JVM配置官方文档:docs.oracle.com/javase/8/do…

    -XX:NewRatio=ratio
        -XX:NewRatio=1
    ​
    -XX:NewSize=size
        -XX:NewSize=256m
        -XX:NewSize=262144k
        -XX:NewSize=268435456
    ​
    -XX:SurvivorRatio=ratio
        Sets the ratio between eden space size and survivor space size. By default, this option is set to 8. 
        The following example shows how to set the eden/survivor space ratio to 4:
        -XX:SurvivorRatio=4
    
  • GC回收官方文档:www.oracle.com/technetwork…

7.3 I/O运行模式
  • BIO:阻塞IO流(早期版本默认BIO)

  • NIO:非阻塞IO流(8.0版默认模式,并发性能比较好)

  • APR:从操作系统级别来解决并发IO问题

    • Tomcat可以使用APR(ApachePortable Runtime)来提供优越的可伸缩性、性能以及本地服务器技术的更好的继承,让Tomcat的并发性能更加的优秀。

    • 下载APR:

      tomcat.apache.org/download-na…

      • tomcat-native-1.2.23-win32-src.zip
      • tomcat-native-1.2.23-openssl-1.1.1c-win32-bin.zip
    • 将下载的APR包下的tcnative-1.dll拷贝到tomcat的bin目录

    • 配置server.xml,修改protocol如下:

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

\