《分布式java应用基础与实践 读后感》

消息方式的系统间通信,通常基于网络协议来实现,常用的实现系统间通信的协议有TCP/IP 和 UDP/IP

网络传输协议协议特点
TCP/IP:是一种可靠的网络数据传输的协议,TCP/IP 要求通信双方首先建立连接,之后再进行数据的传输。TCP/IP负载保证数据传输的可靠性,包括数据的可到达,数据到达的顺序等,但由于TCP/IP 需要保证连接及数据传输的可靠性,因此可能会牺牲一些性能。
UDP/IP:  是一种不保证数据一定到达的网络数据传输协议。UDP/IP 并不直接给通信的双方建立连接,而是发送到网络上进行传输。由于UDP/IP 不建立连接,并且不能保证数据传输的可靠,因此性能上表现相对较好,但可能会出现数据丢失以及数据乱序的现象。
总结:TCP/IP 和 UDP/IP 可用于完成数据的传输,但完成系统间通信,还需要对数据进行处理,例如读取和写入数据,按照posix 标准分为同步IO 和异步IO 两种。其中同步IO 最常用的是BIO(blocking io) 和 NIO(non-blocking io)

 各种协议

dubbo 协议

采用单一长连接和NIO异步通信,适用于“小数据量大并发”的服务调用,以及服务消费者机器数远大于服务提供者机器数的情况。

hessian协议

传入传出参数数据包较大,提供者比消费者个数多,提供者压力较大,可传输文件。

rmi协议

采用JDK标准的Java。rmi实现,常规远程方法调用,与原声RMI服务互相操作。

Http协议

可用浏览器查看,通过表单或URL传入参数。

WebService协议

soap文本序列化,多用于系统集成,跨语言调用。

Thrif协议

对thrift(Facebook 的rpc框架)的原生协议的扩展。

网络IOIO特点
BIO从程序角度而言,BIO 就是当发起IO 的读或写操作时,均为阻塞方式,只有当程序读到了流或将流写入操作系统后,才会释放资源。
NIONIO 是基于事件驱动思想的,实现上通常采用reactor模式,从程序角度而言,当发起IO的读或写操作时,是非阻塞的;当socket有流可读或可写入socket时,操作系统会相应的通知应用程序进行处理,应用再将流读取到缓冲区或写入操作系统。对于网络IO 而言,主要有连接建立,流读取及流写入三种时间,linux2.6以后的版本采用epoll 方式实现NIO。
AIOAIO :异步IO 方式,基于事件驱动思想,实现上采用proactor 模式,从程序角度而言,和NIO 不同,当进行读写操作时,只需要直接调用API 的read 或 write 方法即可。NIO 和 AIO 均为异步,对于读操作,当有流可读取时,操作系统会将可读的流传入read 方法的缓冲区,并通知应用程序;对于写操作,当操作系统将write 方法传递的流写入完毕时,操作系统主动通知应用程序。两者比较,AIO 一方面简化了程序的编写,流的读取和写入都由操作系统代替完成;另一方面省去了NIO 中程序要遍历事件通知队列的代价。windows基于IOCP5 实现了AIO,linux 目前只有基于epoll 模拟实现了AIO。

java对于TCP/IP & UDP/IP 都支持,在网络IO的操作上,jdk7 之前的版本只支持BIO 和 NIO。

基于远程调用方式实现系统间的通信: 当系统之间要通信时,可通过调用本地的一个java接口的方法,透明的调用远程的java 实现。这种方式在java中主要用来实现基于RMI 和 WebService 的应用。

基于java自身技术实现消息方式的系统间通信有四种方式TCP/IP+BIO ,TCP/IP+NIO,UDP/IP+BIO,UDP/IP+NIO

举例:

package com.aa;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Scanner;

/**
 * @author 凤凰小哥哥
 * @date 2020-09-30
 */
public class SocketServer extends Thread{

    ServerSocket server = null;
    Socket socket = null;

    public SocketServer(int port) {
            try {
                server = new ServerSocket(port);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void run() {

            super.run();
            try {
                System.out.println(getdate() + "  等待客户端连接...");
                socket = server.accept();
                new sendMessThread().start();// 连接并返回socket后,再启用发送消息线程
                System.out.println(getdate() + "  客户端 (" + socket.getInetAddress().getHostAddress() + ") 连接成功...");
                InputStream in = socket.getInputStream();
                int len = 0;
                byte[] buf = new byte[1024];
                while ((len = in.read(buf)) != -1) {
                    System.out.println(getdate() + "  客户端: ("
                            + socket.getInetAddress().getHostAddress() + ")说:"
                            + new String(buf, 0, len, "UTF-8"));
                }

            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        public static String getdate() {
            return new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date());
        }

        class sendMessThread extends Thread {
            @Override
            public void run() {
                super.run();
                Scanner scanner = null;
                OutputStream out = null;
                try {
                    if (socket != null) {
                        scanner = new Scanner(System.in);
                        out = socket.getOutputStream();
                        String in = "";
                        do {
                            in = scanner.next();
                            out.write(("" + in).getBytes("UTF-8"));
                            out.flush();// 清空缓存区的内容
                        } while (!in.equals("q"));
                        scanner.close();
                        try {
                            out.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        // 函数入口
        public static void main(String[] args) {
            SocketServer server = new SocketServer(1234);
            server.start();
        }
}
package com.aa;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Scanner;

/**
 * @author 凤凰小哥哥
 * @date 2020-09-30
 */
public class Client extends Thread{
    //定义一个Socket对象
    Socket socket = null;

    public Client(String host, int port) {
        try {
            //需要服务器的IP地址和端口号,才能获得正确的Socket对象
            socket = new Socket(host, port);
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    @Override
    public void run() {
        //客户端一连接就可以写数据给服务器了
        new sendMessThread().start();
        super.run();
        try {
            // 读Sock里面的数据
            InputStream s = socket.getInputStream();
            byte[] buf = new byte[1024];
            int len = 0;
            while ((len = s.read(buf)) != -1) {
                System.out.println(getdate() + "  服务器说:  "+new String(buf, 0, len,"UTF-8"));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //往Socket里面写数据,需要新开一个线程
    class sendMessThread extends Thread{
        @Override
        public void run() {
            super.run();
            //写操作
            Scanner scanner=null;
            OutputStream os= null;
            try {
                scanner=new Scanner(System.in);
                os= socket.getOutputStream();
                String in="";
                do {
                    in=scanner.next();
                    os.write((""+in).getBytes());
                    os.flush();
                } while (!in.equals("bye"));
            } catch (IOException e) {
                e.printStackTrace();
            }
            scanner.close();
            try {
                os.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static String getdate() {
        return new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date());
    }

    //函数入口
    public static void main(String[] args) {
        //需要服务器的正确的IP地址和端口号
        Client clientTest=new Client("127.0.0.1", 1234);
        clientTest.start();
    }
}
系统间通信方式特点
TCP/IP+BIO:TCP/IP+BIO: 在java中可基于socket,serverSocket 来实现TCP/IP+BIO的系统间通信。socket主要用于实现建立连接及网络IO的操作,serverSocket 主要用于实现服务器端端口的监听及socket对象的获取。
TCP/IP+NIO :TCP/IP+NIO : 在java 中可基于java.io.channels 中的channel 和 selector 的相关类来实现TCP/IP+NIO方式的系统间通信,channel 有socketChannel和serverSocketChannel 两种,socketChannel用于建立连接,监听事件及操作读写,serverSocketChannel 用于监听端口连接事件;程序通过selector 来获取是否有要处理的事件。
UDP/IP+BIO :UDP/IP+BIO : java 对UDP/IP 方式的网络数据传输同样采用socket 机制,只是UDP/IP 下的socket 没有建立连接的要求。由于UDP/IP 是无连接的,因此无法进行双向的通信。如果要双向通信,必须两端都称为UDPServer。
UDP/IP+NIO:UDP/IP+NIO: 在java 中可通过datagramChannel 和byteBuffer来实现UDP/IP+NIO 方式系统间通信,datagramChannel负责监听端口和进行读写,byteBuffer 则用于数据流传输。

在java中可基于datagramSocket 和 datagramPacket 实现UDP/IP+BIO 方式的系统间通信,datagramSocket负责监听端口及读写数据。 datagramPacket作为数据流对象进行传输。

Mina 是Apache 的项目,基于java NIO构建,同时支持TCP/IP 和 UDP/IP 两种协议。Mina 对外屏蔽了java NIO 使用的复杂性,并在性能上做了不少的优化。在使用Mina时,关键的类为IoConnector,IoAcceptor,IoHandler 及IoSession,Mina 采用Filter Chain的方式封装消息发送和接收的流程,在这个Filter Chain 过程中可进行消息的处理,消息的发送和接受等。

IoConnector 负责配置客户端的消息处理器,IO事件处理线程池,消息发送、接受的Filter Chain等。

IoAcceptor 负责配置服务器端的IO事件处理线程池,消息发送、接受的Filter Chain等。

IoHandler 作为Mina 和应用的接口,当发生了连接事件,IO 事件或异常事件时,Mina都会通知应用所实现的IoHandler。

IoSession类似socketChannel 的封装,不过Mina 对连接做了进一步的抽象,因此可进行更多连接的控制及流信息的输出。

远程调用方式主要有RMI & WebService:

远程调用方式特点
RMI(remote method invocation)

RMI(remote method invocation) 是java用于实现透明远程调用的重要机制。在远程调用中,客户端仅有服务器端提供的接口,通过此接口实现对远程服务器端的调用。

spring RMI 是spring remoting 中的一个子框架,基于spring RMI 可以很简单的实现RMI 方式的java 远程调用。

webService :

webService : 是一种跨语言的系统间交互标准。在这个标准中,对外提供功能的一方以http的方式提供服务,该服务采用wsdl(web service description language)描述,在这个文件中描述服务所使用的协议,所期望的参数,返回的参数格式等。调用端和服务端通过SOAP(simple object access protocol) 方式来进行交互。

在java 中使用webservice 首先将服务端的服务根据描述生成对应的WSDL文件,并将应用及此wsdl 文件放入http 服务器中,借助java 辅助工具根据wsdl文件生成客户端stub 代码。此代码的作用是将产生的对象请求信息封装为标准的SOAP 格式数据,并发送请求到服务器端,服务器端在接手到soap 格式数据时进行转化,反射调用相应的java类

CXF: 是Apache 的项目,也是目前java 社区中用来实现webservice 流行的一个开源框架,基于CXF 可以非常简单的以webservice 的方式来实现java 甚至是跨语言的远程调用。

SOA:全称面向服务架构,强调系统之间以标准的服务方式进行交互,各个系统可以采用不同的语言,不同的框架来实现,交互则全部通过服务的方式进行。

SOA带来的问题

1.服务多级调用带来的延迟:

   功能调用A,A->B,B->C ,如果B错误,就应该直接抛出异常给调用端。

2.调试/跟踪困难:需要跟踪代码才知道哪里出了问题。
3.更高的安全、监测的要求:单体系统,对系统的安全和监测都只要在一个系统上做即可,在拆分后,就要在每个系统上都有相应的安全控制和监测。
4.现有应用移植:移植的功能越多,花费的时间越多。
5.Qos 的支持:每个服务提供者能够支撑的访问量是有限的,因此设定服务的Qos 非常重要,并且尽可能采取一系列的措施来保证Qos,例如流量控制,机器资源分配等。
6.高可用和高度可伸缩。
7.多版本和依赖管理。

类加载机制指的是 .class 文件加载到JVM ,并形成class 对象的机制,之后应用就可以对class 对象进行实例化并调用,类加载机制可在运行时动态加载外部的类,远程网络下载过来的class 文件等。除了该动态化的优点外,还可以通过JVM 的类加载机制来达到类隔离的效果。

JVM 将类加载过程划分为三个步骤:装载,链接,初始化。装载和链接过程完成后(将二进制的字节码转换成class 对象)。初始化过程不是加载时必须触发的,但最迟必须在初次主动使用对象前执行,其所作的动作为给静态变量赋值,调用<clinit> ()等。

类加载过程
名称解释
装载:load:

装载过程负责找到二进制字节码并加载到JVM 中,JVM  通过类的权限定名及类加载器完成类的加载[package + classname。

链接:link:

链接过程负责对二进制字节码的格式进行校验,初始化装载类中的静态变量及解析类中调用的接口,类。校验不通过,抛出校验异常

初始化:initialize:

初始化过程就是执行类中的静态初始化代码,构造器代码及静态属性的初始化,以下四种情况会被触发执行:

触发过程:

1.调用了new

2.反射调用了类中的方法

3.子类调用了初始化

4.JVM启动过程中指定的初始化类。

JVM 的类加载通过classloader 及其子类来完成:
boostrap classLoader
extension classLoader
system classLoader,
user-defied classLoader。

线程在创建后,都会产生程序计数器PC 和 栈 stack。 程序计数器存放了下一条要执行的指令在方法内的偏移量;栈中存放了栈帧,每个方法每次调用都会产生栈帧。栈帧主要分为局部变量区和操作数栈两部分,局部变量用于存放方法中的局部变量和参数。操作数栈用于存放方法执行过程中产生的中间结果。

反射:基于反射可动态调用某对象实例中对应的方法,访问查看对象的属性等,无需在编写代码时就确定要创建的对象。

Class action = Class.forName(“类”);

Method method = action.getMethod(“execute”,null);

Object obj = action.newInstance();

Method.invoke(obj,null);

方法区方法区存放了要加载的类的信息(名称,修饰符等),类中的静态变量,类中定义为final 类型想常量,类中的field 信息,类中的方法信息,当开发人员在程序中通过class 对象的getName,isInterface 等方法来获取信息时,这些数据都来源于方法区域。方法区域也是全局共享的,在一定条件下会回GC,当方法区域要使用的内存超过其允许的大小时,会抛出outOfMemory的错误信息。
堆用于存储对象示例及数组值,可以认为java中所有通过new 创建的对象的内存都在此分配,heap 中对象所占用的内存由GC进行回收,在32位操作系统上最大为2GB,在64位操作系统上则没有限制,其大小可通过-Xms 和-Xmx控制。-Xms为JVM 启动时申请的最小heap 内存,默认为物理内存的1/64,但小于1GB;-Xmx 是JVM 可申请的最大heap 内存,默认物理内存的1/4 ,但小于1GB,默认当空余堆内存小于40%时,JVM会增大heap 到-Xmx 指定的大小,可通过-XX:MinHeapFreeRatio= 来指定比例;当空余堆内存大于70%时,JVM 会减小heap 的大小到-Xms指定的大小,可通过-XX:MaxHeapFreeRatio = 来指定这个比例,对于运行系统而言,为避免在运行时频繁调整heap 的大小,通常将-Xms 和 -Xmx 的值设成一样。

在sun jdk 中这块区域对应permanet generation ,又称为持久代,默认最小值为16MB,最大值为64MB,可通过-XX:PermSize 及 -XX:MaxPermSize 来指定最小值和最大值。

为了让内存回收更加高效,JDK从1.2开始对堆采用了分代管理的方式。

1,新生代 new generation大多数情况下java 程序中新建的对象都从新生代分配内存,新生代由eden space 和两块相同大小的survivor space(通常又称为S0,S1或From & to) 构成,可通过-Xmn 参数来指定新生代的大小,也可以通过-XX:SurvivorRatio 来调整Eden space 和 survivor space 的大小,不同的GC 方式会以不同的方式按此值来划分eden space 和 survivor space。有些GC方式还会根据运行状态来动态调整eden,S0,s1的大小。
2.旧生代 old generation 或 tenuring generation :用于存放新生代中经过多次垃圾回收仍然存活的对象,例如缓存对象,新建的对象也有可能在旧生代上直接分配内存。主要有两种情况(由不同的GC实现来决定):一种是大对象,可通过在启动参数上设置-XX:PretenureSizeThreshold=1024(单位是字节,默认值为0)来代表当对象超过多大时就不在新生代分配,而是直接在旧生代分配。此参数在新生代采用parallel scavenge gc 时无效。parallel Scavenge GC会根据运行状况决定什么对象直接在旧生代上分配内存;另一种为大的数组对象,且数组中无引用外部对象。

旧生代所占用的内存大小为-Xmx 对应的值减去-Xmn 对应的值。 

jvm 存储位置的区分
本地方法栈本地方法栈用于支持native 方法的执行,存储了每个native方法调用的状态,在sun jdk 的实现中本地方法栈和JVM 方法栈是同一个。
PC 寄存器和JVM 方法栈每个线程均会创建PC寄存器和JVM方法栈,PC寄存器占用的可能为CPU寄存器或操作系统内存,JVM 方法栈占用的为操作系统内存,JVM方法栈为线程私有,其在内存分配上非常高效。当方法运行完毕时,其对应的栈帧所占用的内存也会自动释放。
内存分配 java 对象所占用的内存主要从堆上进行分配,堆是所有线程共享的,因此在堆上分配内存时需要进行加锁,这导致了创建对象开销比较大。当堆上空间不足时,会触发GC,如果GC 后空间仍然不足,则抛出OutOfMemory 错误信息。

当JVM 方法栈空间不足时,会抛出StackOverflowError 的错误,在sun jdk 中可以通过-Xss 来指定其大小。

sun jdk 为了提升内存奉陪的效率,会为每个新创建的线程在新生代的eden space 上分配一块独立的空间,这块空间称为TLAB(thread local allocation buffer),其大小由JVM 根据运行情况计算而得。

内存回收

收集器 JVM通过GC 来回收堆和方法区中的内存,GC的基本原理首先会找到程序中不再被使用的对象,然后回收这些对象所占用的内存,通常采用收集器的方法实现GC,主要的收集器有引用计数收集器跟踪收集器

收集器特点
1.引用计数收集器1.引用计数收集器:引用计数收集器采用的分散式的管理方式通过计数器记录对象是否被引用。当计数器为零时,说明此对象已经不再被使用,于是进行回收

引用计数需要在每次对象赋值时进行引用计数器的增减,它有一定的消耗。另外,引用计数器对于循环引用的场景没有办法实现回收。

2.跟踪收集器

2.跟踪收集器:跟踪收集器采用集中式的管理方式全局记录数据的引用状态。基于一定条件的触发定时,空间不足时)执行时需要从根集合来扫描对象的引用关系,这可能会造成应用程序暂停。

主要有三种算法,分别是:

1.复制copying

2.标记-清除mark-sweep

3.标记-压缩 三种实现算法。

跟踪收集器又分为:
跟踪收集器的method特点
*1.复制copying:*1.复制copying:复制采用的方式为从根集合扫描出存活的对象,并将找到的存活对象复制到一块新的完全未使用的空间中。复制收集器算法仅仅需要从根集合扫描所有存活的对象,当要回收的空间存活对象较少时,复制算法会比较高效。其带来的成本是要增加一块空的内存空间及进行对象的移动。
*2.标记-清除mark-sweep:*2.标记-清除mark-sweep: 标记-清除采用的方式从根集合开始扫描,对存活的对象进行标记。标记完毕后,再扫描整个空间中未标记的对象,并进行回收。标记-清除动作不需要进行对象的一移动,且仅仅对不存活的对象进行处理。在空间中存活对象较多的情况下比较高效,但由于标记-清除采用的为直接回收不存活对象所占用的内存,因此会造成内存碎片。
*3.标记-压缩mark-compact:*3.标记-压缩mark-compact: 标记-压缩采用和标记-清除一样的方式对存活的对象进行标记,但在清除时不同。在回收不存货对象所占用的内存空间后,会将其他所有存活对象都往左端空闲空间进行移动,并更新引用其对象的指针。标记-压缩在标记-清除的基础上需要进行对象的移动,成本相对较高,好处是不产生内存碎片。

sun JDK 三种跟踪收集器算法各有利弊,JDK根据新生代和旧生代中对象的存活时间提供了不同的GC实现。

新生代可用GC: sun jdk 根据新生代中的对象通常存活时间较短,所以选择复制-标记copying 算法来实现对新生代对象的回收,对新生代对象所占用的内存进行GC 又称为Minor GC。

sun JDK 提供了串行GC,并行回收GC和 并发GC三种方式回收新生代对象所占用的内存对新生代对象所占用的内存进行的GC 又称为Minor GC。

1.串行GC(Serial GC): 当采用串行GC 时,survivorRatio 的值对应eden space/survivor space 。survivorRatio 默认为8.

选项引用关系有4种,分别是:强引用,软引用,弱引用,虚引用。

引用关系
引用类型回收时机
1.强引用:

A a = new A();

就是一个强引用,强引用的对象只有在主动释放了引用后才会被GC。

2.软引用:

Object= object = new Object();
SoftReference<Object> softRef = new SoftReference<Object>(object);
object = null;

软引用采用softReference 来实现,采用软引用来建立引用的对象,当JVM 内存不足时会被回收。因此软引用很适合用于实现缓存。当GC认为扫描到的软引用不经常使用时,也会进行回收,存活时间可用过-XX:SoftRefLRUPolicyMSPerMB 来进行控制,其含义为每兆堆空闲空间中softReference的存活时间,默认为1秒。

注意:当需要获取时,可通过softRef.get 来获取,但softRef.get有可能会返回null。

3.弱引用:

Object object = new Object();
WeakReference<Object> weakRef = new WeakReference<Object> (object);
object = null;

弱引用采用weakReference 来实现,采用弱引用建立引用的对象没有强引用后,GC时就会被自动自动释放。

注意:当需要获取时,可通过weakRef.get 来获取,但是weakRef.get 有可能会返回null。
可传入一个ReferenceQueue 对象到WeakReference 的构造器中,当object 对象被标识为可回收时,执行weakRef.isEnqueued 会返回true。

虚引用:

Object object = new Object();
ReferenceQueue<Object> refQueue = new ReferenceQueue<Object>();
PhantomReference<Object> ref = new PhantomReference<Object> (object.refQueue);
OBJECT = null;

虚引用采用PhantomReference 来实现,采用虚引用可跟踪对象是否已从内存中删除。

注意ref.get 永远返回null,当object 从内存中删除时,调用ref.isEnqueued()会返回true。

JDK 提供了串行,并行及并发三种GC来对旧生代及持久代对象所占用的内存进行回收。

1.串行:

串行GC分为三个阶段来执行:

1.从根集合对象开始扫描,按照三色着色的方式对对象进行标识。
2.遍历整个旧生代或持久代空间,找出其中未标识的对象,并回收其内存。
3.执行滑动压缩,将存活的对象向旧生代空间的开始处进行滑动,最终留出一块连续的到结尾处的空间。
  
注意:串行执行的整个过程需要暂停应用,且采用单线程方式,通常要耗费较多的时间,可通过增加-XX:+PrintGCApplicationStoppedTime 来查看GC造成的应用暂停的时间。

2.并行:

并行GC也分为三个阶段来执行:


1.首先将代空间划分为并行线程个数的区域,然后根据集合可直接访问到的对象及并行线程的个数进行划分,并行的对这些对象进行三色着色。当对象被着色时,同事更新其所在的区域存活的大小及存活对象所在的位置。

2.大部分情况下经过多次GC后,通常旧生代空间左边存放的是一些活跃的对象。GC 采用从左往右开始扫描与区,知道找到第一个值进行压缩移动的区域,并将此区域左边的与区作为密度高区,对这些区域则不进行回收; 然后继续往右扫描,对于右边的区域根据存活的空间来决定压缩移动的源区域和目标区域,切换引用这些对象的指针,并在区域上做标志,同时清除区域中其他不存活对象所占用的空间。
  
注意:此过程为单线程进行。
  
3.基于区域上分析的信息,找到需要操作的目标区域及完全没有存活对象的区域,并行的进行对象移动和区域回收。
  
总结:比串行造成的暂停时间会缩短,但旧生代较大,造扫描和标识对象上需要花费较长的时间。

3.并发:并发方式要对整个空间中的对象进行扫描并标记,但暂停时间较长。
总结:Full GC: 当旧生代和持久代触发GC时,其实对新生代,旧生代,持久代都进行GC,所以又称为Full GC 当Full GC 触发时,首先按照新生代所配置的GC方式对新生代进行GC,然后按照旧生代的GC方式对旧生代,持久代进行GC。
触发Full GC 执行的四种原因:
原因原因分析解决方案
1.旧生代空间不足:旧生代空间只有在新生代对象转入及创建为大对象,大数组时才会出现不足的现象,当执行Full GC 后空间仍然不足,则抛出java.lang.OutOfMemoryError: java heap sapce。所以避免Full GC,调优时尽量做到让对象在Minor GC阶段被回收,让对象在新生代多存活一段时间及不要创建过大的对象和数组。
2.permanet generation 空间满permanet generation中存放的是一些class的信息等,当系统中要加载的类,反射的类和调用的方法较多时,permanet generation 可能会被占满,在未配置为采用CMS GC 的情况下执行Full GC,如果经过Full GC 仍然回收不了,JVM抛出如下错误:java.lang.OutOfMemoryError: permgen space。为避免perm gen占满造成Full GC现象,可采用的方法为增大perm gen 空间或转为使用CMS GC。
3.CMS GC 出现 promotion failed 和 concurrent model failure对于采用CMS 进行旧生代GC 的程序而言,尤其要注意GC 日志中是否有 promotion failed 和 concurrent model failure两种情况,当这两种状态出现时可能会触发Full GC。原因: promotion failed是在进行Minor GC 时,survivor space 放不下,对象只能放入旧生代,而此时旧生代也放不下造成的;concurrent model failure 是在执行CMS GC 的过程中同时有对象要放入旧生代,而此时旧生代空间不足造成的。增大survivor space ,旧生代空间或调低触发并发GC 的比率。
4.统计得到的Minor GC 晋升到旧生代的平均大小大于旧生代的剩余空间hotspot 为了避免由于新生代对象晋升到旧生代导致旧生代空间不足的现象,在进行Minor GC 时,做了一个判断,如果之前统计得到的Minor GC 晋升到旧生代的平均大小大于旧生代剩余空间,那么就直接出发Full GC。

JVM 内存状态查看方法和分析工具:

1.jconsole.exe
位置:C:\Program Files\Java\jdk1.8.0_73\bin\jconsole.exe
启动:双击jconsole.exe

2.jvisualvm.exe :jvisualvm.exe 类似JProfiler 工具,基于此工具可查看内存的消耗情况,线程的执行状况和程序中消耗CPU,内存的动作。

位置:C:\Program Files\Java\jdk1.8.0_73\bin\jvisualvm.exe
启动:双击jvisualvm

java 程序采用多线程的方式来支撑大量的并发请求处理。 JVM 的线程实现及调度方式(抢占式,协作式)取决于操作系统。

JVM 中以下操作是顺序执行的

1.同一个线程上的操作一定是顺序执行的

2.对于main memory 上的同一个变量的操作一定是顺序执行的,也就是不可能两个请求同时读取变量值。

3.对于加了锁的main memory 上的对象操作,一定是顺序执行的,也就是两个以上加了lock 的操作,同时肯定只有一个在执行。

为了避免资源操作的脏数据问题,JVM 提供了synchronized关键字,volatile 关键字和lock,unlock 机制。

解决资源操作的脏数据问题的三种方案
方案使用方法
synchronized

synchronized 除了可以直接写在方法上,还可以直接定义在对象上,区别在于JVM 会根据这些情况来决定lock 标记打在什么上,注意:如果synchronized 标记在一个static 方法上,那么执行时锁的粒度为当前class。

特点:数据/方法同步

lock/unlock

lock/unlock 机制的原理和synchronized 相同,他们都可用于保证某段代码执行的原子性,由于锁会阻塞其他线程同样需要锁的部分的执行,因此在使用锁机制时要避免死锁的产生,尤其是多把锁的情况下。

缺点:

1.线程循环调用会造成死锁;
2.lock/unlock 总是成对出现,否则就有线程会出现锁饿死的现象。

volatile:volatile:仅仅用与控制线程中对象的可见性,但并不能保证在此对象上操作的原子性。

protected  synchronized int getNextId(int id){
        return id++;
}

线程之间除了会产生资源的竞争外,还会有交互的需求。

Collection{list & set} Map

list: arrayList ,linkedList,vector,stack

set: hashSet,treeSet

数据结构和特点
数据结构特点
arrayList:

1.arrayList 基于数组方式实现,无容量的限制

2.arrayList 在执行插入元素时可能要扩容,在删除元素时并不会减小数组的容量(可以手动调用trimToSize减小容量)。在查找元素时要遍历数组,对于非null 的元素采取equals 的方式寻找

3.arrayList是非线程安全的。

linkedList:

linkedList: 基于双向链表机制的实现双向链表机制就是集合中的每个元素都知道其前一个元素和其后一个元素的位置。在linkedList 中,以一个内部的Entry 类为代表集合中的元素,元素的值赋值给element 属性,Entry 中的next属性指向元素的后一个元素,Entry 的previous 属性指向元素的前一个元素,基于这样的机制可以快速实现集合中元素的移动。

总结:
1.linkedList 基于双向链表机制实现

2.linkedList 在插入元素时,必须建一个新的entry 对象,并切换相应元素的前后元素的引用;在查找元素时,需要遍历链表;在删除元素时,要遍历链表,找到要删除的元素,然后从链表上将次元素删除即可;

3.linkedList 是非线程安全的。

vector:

vector和 arrayList 一样,也是基于object 数组的方式来实现的。因为在add,remove 都有synchronized ,所以保证了安全性。

总结:vector 是基于 synchronized 实现的线程安全的arrayList ,但在插入元素时容量扩充的机制和arrayList 稍有不同,并可以通过传入capacityIncrement 来控制容量的扩充。

stack:

stack 继承于vector ,在其基础上实现了stack 所要求的后进先出LIFO的弹出及压入操作,其提供了push,pop,peek三个主要的方法。

push:push 操作通过调用vector的addElement来完成。
pop: pop  操作通过调用peek 来获取元素,并同时删除数组的最后一个元素。
peek: peek 操作通过获取当前object数组的大小,并获取数组上的最后一个元素。

总结:stack 基于vector 实现,支持后进先出LIFO。

hashSet:

hashSet 是set接口的实现,set和list最明显的区别在于set不允许元素重复,而list允许重复。set 为了做到不允许元素重复,采用的是基于hashMap实现。

总结:
1.hashSet 基于hashMap实现,无容量限制。
2.hashSet 是非线程安全的。

treeSet:

TreeSet 和hashSet 的主要不同在于treeSet 对于排序的支持,treeSet基于treeMap 实现。提供了一些排序方面的支持,例如传入comparator实现,descendingSet。

总结:
1.treeSet 基于treeMap 实现,支持排序。
2.treeSet 是非线程安全的。

hashMap:

hashMap:将loadFactor 设为默认的0.75,threshold 设置为12,并创建一个大小为16的entry 对象数组。
对于key 为null 的情况,hashMap 的做法是获取Entry数组中的第一个entry 对象,并基于entry对象的next属性遍历,当找到了其中entry 对象的key 属性为null 时,则将其value 赋值为新的value。

hash 冲突:不同的key找到相同的存储位置,就是hash冲突。

hash冲突解决方案:在找到要存储的目标数组的位置后,获取该数组对应的entry对象,通过调用entry 对象的next来进行遍历,寻找hash值和计算出来的hash值相等,且key 相等或equals的entry对象,如果寻找到,则替换此entry对象的值,完成put 操作,并返回旧的值;如果未找到,就往对应的数组位置增加新的entry对象,增加时采取的方法和key 为null 的情况基本相同,只是它是替换指定位置的entry对象hashMap解决hash 冲突采用的是链表的方式,而不是开放定址的方法。

总结:
1.hashMap 采用数组方式存储key,value 构成的entry对象,无容量限制。

2.hashMap 基于key hash 寻找entry 对象存放到数组的位置对于hash冲突采用链表的方式来解决

3.hashMap 在插入元素时可能会要扩大数组的容量,在扩大容量时需要重新计算hash,并复制对象到新的数组中。

4.hashMap 是非线程安全的

treeMap: treeMap 是一个支持排序的map 实现。当调用put时,先判断root 属性是否为null,如果为空,则创建一个新的entry对象,并赋值给root 属性。如果不为空,则首先判断是否传入了指定的comparator 实现,如果已经传入,则基于红黑树的方式遍历,基于comparator来比较key应该放到树的坐标还是右边,如果找到相同的key,就直接替换其value。
    
总结:
1.treeMap 基于红黑树实现,无容量限制。
2.treeMap 是非线程安全的。
concurrentHashMap :concurrentHashMap 是线程安全的hashMap的实现concurrentHashMap 采用的方法即为遍历每个分段中的hashEntry 对象数组,完成集合中所有对象的读取concurrentHashMap默认情况下采用将数据分为16个段进行存储,并且16个段分别持有各自的锁,锁仅仅用于put 和remove 等改变集合对象的操作,基于voliate 及hashEntry 链表的不变性实现读取的不加锁。这样使得concurrentHashMap 能够保持极好的并发支持。
CopyOnWriteArrayList:CopyOnWriteArrayList 是一个线程安全,并且在读操作时无锁的arrayList。add方法通过reentrantLock保证线程安全。读多写少的并发场景中,copyOnWriteArrayList 比ArrayList 是更好的选择。
 
CopyOnWriteArraySet基于CopyOnWriteArrayList实现唯一不同的是在add 时调用的是CopyOnWriteArrayList 的addIfAbsend方法。addIfAbsend方法同样采取锁保护。
ArrayBlockingQueue :ArrayBlockingQueue 基于固定大小的数组,先进先出,线程安全的集合类,其特点是可实现指定时间的阻塞读写,并且容量是可以限制的。

ArrayBlockingQueue: 是一个基于固定大小数组,reentrantlock 以及condition 实现的可阻塞的先进先出的队列queue。

LinkedBlockingQueue实现的不同为采用对象的next构成链表的方式来存储对象由于读只操作对头,而写只操作队尾对于put &offer 采用一把锁,对于take & poll 采用另外一把锁避免了读写时互相竞争锁的现象。
在高并发读写操作都多的情况下,性能会较ArrayBlockingQueue好很多。
AtomicInteger

AtomicInteger 是一个支持原子性操作的Integer类,在没有使用AtomicInteger 前,要实现一个按顺序获取的id,就必须在每次在每次获取时进行加锁操作,以求避免并发时获取到同样的id 的现象。

基于CAS 的操作可认为是无阻塞的,由于CAS操作是CPU的原语,所以性能会好于加锁实现。

而使用AtomicInteger,就可以如下操作;

    private static AtomicInteger atomicInteger = new AtomicInteger();
    public static int getNextId(){
        return atomicInteger.incrementAndGet();
    }
注解:基于CAS 的操作可认为是无阻塞的,由于CAS操作是CPU的原语,所以性能会好于加锁实现。

基于CAS 的方式要比synchronized 的方式性能高2倍多,因此尽量选择使用atomic的类。

ThreadPoolExecutor 是并发包中提供的一个线程池的服务,基于ThreadPoolExecutor 可以很容易的将一个实现了Runnable 接口的任务放入线程池中执行。

execute 方法负责将Runnable 任务放入线程池中执行。

ThreadPoolExecutor 的四种拒绝策略
callRunsPolicy: 采用这么策略的情况下,当线程池中的线程数等于最大线程数后,则交由调用者线程来执行此Runnable任务执行。
aboryPolicy: 采用这种策略,当线程池中的线程数等于最大线程数时,直接抛出rejectedExcutionException。
discardPolicy: 当线程池中的线程数等于最大线程数时,不做任何动作。
discardOldestPolicy:当线程池中的线程数等于最大线程数时,抛弃要执行的最后一个runnable 任务,并执行此新传入的runnable 任务。
执行runnable任务的时机
1.高性能:如果希望高性能的执行runnable 任务,即当线程池中的线程数尚未到达最大个数,则立刻提交给线程执行或在最大线程数量的保护下创建线程来执行可以选择SynchronousQueue作为任务缓冲的队列。SynchronousQueue 在进行offer 时,如没有其他线程调用poll,则直接返回false,按照ThreadPoolExecutor的实现,此时就会在最大线程数允许的情况下创建线程;如果其他线程调用poll,就返回true。按照ThreadPoolExecutor execute 方法的实现,采用这样的Queue 就可以实现要执行的任务不会在队列里缓冲,而是直接交由线程执行。
2.缓冲队列:希望runnable 任务尽量被corePoolSize 范围的线程执行掉,可以使用
ArrayBlockingQueue 和 LinkedBlockingQueue作为任务缓冲的队列当线程等于或超过corePoolSize后,会先加入到缓冲队列中,而不是直接交由线程执行。在这种情况下,ThreadPoolExecutor 最多能支持的最大线程数+blockingQueue大小的任务数的执行。

 Executors :executors 提供了一些方便创建ThreadPoolExecutor的方法。

Executors 提供的四种线程池的方法
1.newFixedThreadPool创建固定大小的线程池,线程keepAliveTime 为0,默认情况下,ThreadPoolExecutor 中启动的corePoolSize 数量的线程启动后就一直运行,并不会由于keepAliveTime 时间到达后扔没有任务需要执行而退出。缓冲任务的队列为LinkedBlockingQueue,大小为整数的最大数。当使用此线程池时,在同时执行的task数量超过传入的线程池大小值后,将会放入linkedBlockingQueue ,在linkedBlockingQueue中的task需要等待线程空闲后来执行,当放入linkedBlockingQueue 中的task 超过整型最大数时,抛出RejectedExecutionException。
2.newSingleThreadExecutor():相当于创建大小为1单位的固定线程池,当使用此线程池时,同时执行的task只有一个,其他task 都在linkedBlockingQueue 中。
3.newCachedThreadPool():创建corePoolSize 为0,最大线程数为整型的最大数,线程keepAliveTime 为1 分钟,缓存任务的队列为SynchronousQueue 的线程池。在使用时,放入线程池的task都会复用线程或启动新线程来执行直到启动的线程数达到整型最大数值后抛出rejectedExecutionException ,启动后的线程存活时间为1分钟。
4.newScheduledThreadPool(int):

创建corePoolSize 为传入参数,最大线程数为整型的最大数,线程keepAliveTime 为0,缓存任务的队列为delayedWordQueue 的线程池。业务中,需要定时或延迟执行的任务,可以使用这个。

join & futureTask & countDownLatch & cyclicBarrier 的区别
join调用一个子线程的join()方法后,该线程会一直被阻塞直到线程先运行完毕
countDownLatchcountDownLatch则使用计数器来允许子线程运行完毕或者在运行中递归计数,也就是CountDownLatch可以线程池运行的任何时候让await方法返回而不一定必须等到线程结束。另外,使用线程池来管理线程时一般都是直接添加Runable 到线程池,这时候就没有办法再调用线程的join 了,就是说countDownLatch 相比join 方法让我们对线程同步有更灵活的控制。
FutureTaskFutureTask: FutureTask 可用于异步要获取执行结果或取消执行任务的场景通过传入runnable 或callable 的任务给FutureTask,直接调用其run方法或放入线程池执行,之后可以在外部通过FutureTask的get 异步获取执行结果。FutureTask可以确保即使调用了多次run方法,它都只会执行一次runnable 或callable 任务,或者通过cancel取消FutureTask的执行等。
CyclicBarrierCyclicBarrier: CyclicBarrier 和 couontDownLatch 不同,CyclicBarrier是当await 的数量到达了设定的数量后,才继续往下执行。基于ReentrantLock & condition 来实现的。
SemaphoreSemaphore 是并发包中提供的用于控制某资源同时被访问的个数的类,例如连接池中通常要控制创建的连接的个数。是基于cas 操作的。
总结:Semaphore & FutrueTask 一样,都充分采用了CAS 来尽量避免锁的使用,提升高并发下的性能。

couontDownLatch:couontDownLatch 是并发包中提供的可用于控制多个线程同时开始某动作的类,其采用的方式为减计数的方式。当计数减到零时,位于latch.await 后的代码才会被执行。

ReentrantReadWriteLock : ReentrantReadWriteLock 和 ReentrantLock 没有任何继承关系ReentrantReadWriteLock 提供了读锁readLock和写锁writeLock,相比较ReentrantLock 只有一把锁机制而言,读写锁分离的好处在读多写少的环境中大幅度提升读的性能。基于AbstractQueuedSynchronizer来实现,自行实现判断是否获取读锁或写锁。当调用读锁的lock方法时,如果没有线程持有写锁,就可以获得读锁,这也说明只要进行读的时候没有其他线程在进行写操作,读的操作是无阻塞的。当调用写锁的lock方法时,如果此时没有线程持有读锁或写锁,则可继续执行,这也说明要进行写动作时,如果其他线程在读或写,就会被阻塞,因此写的性能有可能会下降。

读写锁在使用时尤其要注意锁的升级和降级机制:

在同一线程中,持有读锁后,不能直接调用写锁的lock 方法。如果调用,会造成死锁,也称为读锁不可升级。

在同一线程中,持有写锁后,可调用读锁的lock方法,在此之后如果调动写锁的unlock方法,那么当前锁将降级为读锁。

对于java 的网络通信,将对象转化为二进制流,然后进行网络传输是最基本的方法,把对象转为流,和把流还原成对象,最常用的方法就是java自带的序列化。

序列化:将对象转化为二进制流,通过网络传输给接收方,这就是序列化。把二进制流还原为对象,这就是反序列化。

资源主要消耗在CPU,文件IO,网络IO 以及内存方面,机器的资源有限,当某资源消耗过多时,通常会造成系统的响应速度慢。

上下文切换每个CPU 在同一时间只能执行一个线程,linux 采用的是抢占式调度。即为每个线程分配一定的执行之间,当达到执行时间,线程中有IO阻塞或优先级线程要执行时,linux 将切换执行的线程,在切换时要存储目前线程的状态,并恢复要执行的线程的状态,这个过程就是上下文切换。对于java应用,进行IO操作,锁等待或线程sleep 时,当前线程会进入阻塞或休眠状态,从而触发上下文切换,上下文切换过多会造成内核占据较多的CPU使用,使得应用的响应速度下降。

运行队列:每个CPU核都维护了一个可运行的线程队列。

利用率:CPU利用率是CPU 在用户进程,内核,中断处理,IO等待以及空闲五个部分使用百分比,这五个值是用来分析CPU消耗情况的关键指标。

通过top 或 pidstat 的方式查看CPU消耗。

cpu消耗严重主要体现在:主要体现在us,sy,wa,hi的值变高。

java应用:CPU消耗严重体现在us,sy两个值。

CPU 消耗严重的原因分析
usus值变高的原因分析: 线程一直处于可运行(runnable)状态通常这些线程在执行无阻塞,循环,正则或纯粹的计算等动作造成也有可能是频繁的GC。可能每次请求都需要分配较多的内存,当访问量高的时候就将导致不断的进行GC,系统响应下降,所以造成堆积的请求更多,消耗内存更严重,最严重的时候有可能会导致系统不断进行Full GC。
sy: sy: 当sy 值变高时,表示linux 花费了更多的时间在进行线程切换,java应用造成这种现象的主要原因是启动的线程比较多,并且这些线程多数都处于不断的阻塞(锁等待,IO等待状态)和执行状态的变化过程中,这就导致了操作系统不断的切换执行的线程,产生大量的上下文切换。(kill 掉吧)
wawa 的值是IO等待造成的
hihi的值变高主要是硬件中断造成的,比如网卡接收数据频繁。
文件IO 消耗分析:

​​​​​​​ linux 在操作文件时,将数据放入文件缓存区,直到内存不够或系统要释放内存给用户进程使用,所以在查看linux 内存状况时经常会发现可用(free)的内存不多,但cached 用了很多,这是linux 提升文件IO速度的一种做法。这种做法,如果物理空闲内存够用,通常在linux上只有写文件和第一次读取文件时会产生真正的文件IO。 linux系统,跟踪线程的文件IO消耗,主要通过pidstat来查找。

java应用造成文件IO消耗严重主要是多个线程需要进行大量内容写入(如频繁的日志写入);

或磁盘设备本身的处理速度慢;

或者文件系统慢;

或者操作的文件本身很大;

网络IO的消耗分析: 分布式java应用,更应该关注网络IO 的消耗。要注意网卡中断是不是均衡的分配到了各个CPU,linux 中可采用sar来分析网络IO的消耗。
内存消耗:​​​​​​​Java应用对于内存消耗主要是在JVM 堆内存上,在正式环境中,多数Java应用都会将-Xms 和 -Mmx 设为相同的值,避免运行期要不断的申请内存。

Linux 中可通过vmstat,sar,top,pidstat 等方式来查看swap 和物理内存的情况。

Java 对JVM heap 区的消耗。在Java 程序出现内存消耗过多,频繁GC或OutOfMemoryError 的情况后,首先要分析其耗费的是JVM外的物理内存还是JVM head 堆区。如果JVM外的物理内存,则要分析程序中线程的数量以及direct bytebuffer 的使用情况;如果是JVM heap 堆,则要结合JDK提供的工具或外部的工具来分析程序中具体对象的内存占用情况。


在命令行输入vmstat ,其中的信息和内存相关的memory下的swpd,free,buff,cache以及swap 下的si 和so。

swpd :虚拟内存已使用的部分,单位KB
free :空闲的物理内存
buff :用于缓冲的内存
cache:用于缓存的内存
si:每秒从disk读到内存的数据量
so:每秒从内存写入disk的数据量

swpd值过高通常是物理内存不够用了。

程序执行慢原因分析:
原因:场景(解决方案)
1.锁竞争激烈数据库连接池一共就那么多,比如说10个,但此时请求线程有50个,那么其中40个就会处于等待状态,CPU消耗不高,但程序很慢。
2.未充分使用硬件资源:比如多核CPU,但程序都是单线程串行的操作。未充分考虑使用多线程,发挥多核CPU的优势
3.数据量增长。数据量太多,是否考虑迁移出历史数据

调优可以通过:硬件、操作系统、JVM、程序四种方法:

调优的区别
调优方式使用注意事项

JVM 调优

JVM 调优主要是内存管理方面的调优,包括各个代的大小,GC策略等,由于GC动作会挂起应用线程,严重影响性能。

各个代的大小设置设计决定了minor GC 和FUll GC 触发的时机。

在正式环境中,多数Java应用都会将-Xms 和 -Mmx 设为相同的值,避免运行期要不断的申请内存。这个值决定了JVM heap 堆所能使用的最大空间。

-Xmn 决定了新生代空间的大小,新生代中Eden ,S0,S1 三个区域的比率可通过-XX:SurvivorRatio 来控制

-XX:MaxTenuringThreshold 控制对象在经历多少次Minor GC后才转入老年代,通常又将它称为新生代存货周期,此参数只有在串行GC时有效。

  3-1* 避免新生代设置过小,新生代太小,minor GC的次数更加频繁;二是有可能导致minor gc 对象直接进入旧生代,占据了旧生代剩余空间,则触发Full GC。
  
  3-2* 避免新生代的设置过大,新生代太大,旧生代太小,就会Full GC 频繁的执行;二minor GC 的耗时大幅度增加。
  
  3-3* 避免survivor 区域过小或过大。采用串行GC时,默认情况下Eden ,S0,S1 的大小比例:8:1:1。
  
  3-4* 合理设置新生代存活周期。新生代存活周期的值决定了新生代的对象经过了多少次minor GC 后进入旧生代。JVM 参数上这个值对应的为-XX:MaxTenuringThreshold 默认值为15次。

只要新生代的空间能够支撑到临时对象集合tempObjects 中的对象填充完毕,那么下次minor GC就可以把临时对象集合tempObjects所占用的空间全部回收掉,避免掉这次不必要的Full GC。

CPU调优

CPU us 高的原因主要是执行线程无任何挂起动作,且一直执行,导致CPU没有机会去调动执行其他的线程,造成线程饿死现象。针对于这种情况,可以使用Thread.sleep,以释放CPU的执行权,减低CPU的消耗。

如果某线程要等其他线程改变了值后才可以继续执行,则采用wait/notify 机制。

CPU sy 高的原因有两个:

1.线程过多,运行状态切换。解决方案:是线程的运行状态要经常切换,对于这种情况,就是减少线程数。

2.线程之间锁竞争激烈,尽可能降低线程之间的锁竞争也是常见的优化方法。

文件IO调优

造成文件IO 消耗严重的原因就是多个线程在写大量的数据到同一个文件,导致文件很快变得很大,从而写入速度越来越慢,并造成各线程激烈争抢文件锁。

优化方案:

1.异步写文件。避免应用由于写文件慢而性能下降太多,比如日志使用log4j提供的AsyncAppender。

2.批量读写。

3.限流。

4.文件大小的限制

网络IO优化造成网络IO 消耗严重的主要原因是同时需要发送和接受的package太多。造成网络IO 消耗严重的主要原因是同时需要发送和接受的包太多。调优方法是限流,限制发送packet的频率,从而在网络IO 消耗可以接受的情况下发送packet。
内存优化对于内存消耗严重的情况:最明显的在于消耗了过多的JVM heap内存,造成GC 频繁执行的现象。解决方案:  
     1.释放不必要的引用。
     2.使用对象缓存池。
     3.采用合理的缓存失效算法。
     4.合理使用softReference 和 weakReference.
JVM 调优参数的细节

-Xms  & -Xmx 

-Xms  & -Xmx 适用于调整整个JVM堆 heap 区大小,在内存不够用的情况下可适当加大此值,这个值能调整到多大取决于操作系统位数以及CPU 的能力。

-Xmn

-Xmn 适用于调整新生代的大小,新生代的大小决定了多少比例的对象有机会在minor GC阶段被回收,也决定了旧生代的大小。新生代越大,意味着多数对象能够在minor GC 阶段被回收掉,但同时意味着旧生代空间变小,可能会造成更频繁的Full GC ,甚至是outofmeryError。

-XX:SurvivorRation

-XX:SurvivorRation 适用于调整Eden 区和Survivor 区的大小,Eden 区越大代表minor GC 发生的频率越低。Survivor 区太小,导致对象在经过minor GC 直接进入旧生代,从而更频繁的触发Full GC,取决于当Eden 区满的时候其中存货对象的比例。

总结:         对于代大小的调优,主要是合理调整-Xms,-Xmx,-Xmn 以及-XX:SurvivorRation 的值,尽可能减少GC 所占用的时间。

1.硬件
2.操作系统
3.JVM
 
  
4.程序

  4-1* CPU us 高的解决方法。CPU us高的原因主要是执行现场无任何挂起动作,且一直执行,导致CPU没有机会去调用执行其他的线程,造成线程饿死的现象。优化方法是对这种线程的动作增加Thread.sleep,以释放CPU 的执行权,降低CPU的消耗。

  4-2* CPU sy 高的解决方法。CPU sy 高主要原因就是线程的运行状态要经常切换,对于这种情况,优化方法是减少线程数。
  
  4-3* 文件IO 消耗严重的解决方法: 造成文件IO消耗严重的原因主要是多个线程在写大量的数据到同一个文件,导致文件很快就变得很大,从而写入速度越来越慢,并造成各个线程激烈争抢文件锁,解决方案有:
    1.异步写文件。将写文件的同步动作改为异步动作,避免应用由于写文件而性能下降太多。
    
    2.批量读写。频繁的读写操作堆IO 消耗会很严重,批量操作将大幅度提升IO操作的性能。
    
    3.限流。频繁读写的另一个调优方式是限流,从而将文件IO消耗控制到一个可以接受的范围内。
    
    4.限制文件的大小。
    
  4-4* 网络IO 消耗严重的解决方法:造成网络IO 消耗严重的主要原因是同时需要发送和接受的包太多。调优方法是限流,限制发送packet的频率,从而在网络IO 消耗可以接受的情况下发送packet。

  4-5* 对于内存消耗严重的情况:最明显的在于消耗了过多的JVM heap内存,造成GC 频繁执行的现象。
    
  4-5.对于资源消耗不多,但程序执行慢的情况的主要原因:锁竞争激烈和未充分发挥硬件资源两种情况。
    *.锁竞争激烈解决方法:
      1.使用并发包中的类。
      2.使用Treiber算法。
      3.使用Michael-Scott 非阻塞队列算法。
      4.尽可能的少用锁。
      5.拆分锁
      6.去除读写操作的互斥锁。

      
    未充分发挥硬件资源的解决方案:合理运用多线程。

负载均衡,ip 选址策略:
1.随机选择
2.hash选择
3.round_robin 选择(根据地址列表选择)
4.按权重选择(权重选择又分为静态权重和动态权重两种)
5.按负载选择
6.按连接选择 





 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值