JAVA基础增强

JAVA基础增强

JMS技术

什么是jms

​ JMS即Java消息服务(Java Message Service)应用程序接口是一个Java平台中关于面向消息中间件(MOM)的API,用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。Java消息服务是一个与具体平台无关的API,绝大多数MOM提供商都对JMS提供支持。

JMS是一种与厂商无关的 API,用来访问消息收发系统消息。它类似于JDBC(Java Database Connectivity):这里,JDBC 是可以用来访问许多不同关系数据库的 API,而 JMS 则提供同样与厂商无关的访问方法,以访问消息收发服务。许多厂商都支持 JMS,包括 IBM 的 MQSeries、BEA的 Weblogic JMS service和 Progress 的 SonicMQ,这只是几个例子。 JMS 使您能够通过消息收发服务(有时称为消息中介程序或路由器)从一个 JMS 客户机向另一个 JMS客户机发送消息。消息是 JMS 中的一种类型对象,由两部分组成:报头和消息主体。报头由路由信息以及有关该消息的元数据组成。消息主体则携带着应用程序的数据或有效负载。根据有效负载的类型来划分,可以将消息分为几种类型,它们分别携带:简单文本(TextMessage)、可序列化的对象 (ObjectMessage)、属性集合 (MapMessage)、字节流 (BytesMessage)、原始值流 (StreamMessage),还有无有效负载的消息 (Message)suoping

jms规范

专业技术规范

JMS(Java Messaging Service)是Java平台上有关面向消息中间件(MOM)的技术规范,它便于消息系统中的Java应用程序进行消息交换,并且通过提供标准的产生、发送、接收消息的接口简化企业应用的开发,翻译为Java消息服务。

体系架构

JMS由以下元素组成。

JMS提供者:连接面向消息中间件的,JMS接口的一个实现。提供者可以是Java平台的JMS实现,也可以是非Java平台的面向消息中间件的适配器。

JMS客户:生产或消费基于消息的Java的应用程序或对象。

JMS生产者:创建并发送消息的JMS客户。

JMS消费者:接收消息的JMS客户。

JMS消息:包括可以在JMS客户之间传递的数据的对象

JMS队列:一个容纳那些被发送的等待阅读的消息的区域。一旦一个消息被阅读,该消息将被从队列中移走。

JMS主题:一种支持发送消息给多个订阅者的机制

Java消息服务应用程序结构支持两种模型

  1. 点对点或队列模型

在点对点或队列模型下,一个生产者向一个特定的队列发布消息,一个消费者从该队列中读取消息。这里,生产者知道消费者的队列,并直接将消息发送到消费者的队列。

bcaef566ac46b0005f545710fd1b87c9.jpg

这种模式被概括为:

只有一个消费者将获得消息

生产者不需要在接收者消费该消息期间处于运行状态,接收者也同样不需要在消息发送时处于运行状态。

每一个成功处理的消息都由接收者签收

  1. 发布者/订阅者模型

发布者/订阅者模型支持向一个特定的消息主题发布消息。0或多个订阅者可能对接收来自特定消息主题的消息感兴趣。在这种模型下,发布者和订阅者彼此不知道对方。这种模式好比是匿名公告板。

752846b2ade1e2df144f8efa0b17b4c6.jpg这种模式被概括为:

多个消费者可以获得消息

在发布者和订阅者之间存在时间依赖性。发布者需要建立一个订阅(subscription),以便客户能够订阅。订阅者必须保持持续的活动状态以接收消息,除非订阅者建立了持久的订阅。在那种情况下,在订阅者未连接时发布的消息将在订阅者重新连接时重新发布。

常用jms实现

开源:Apache ActiveMQ

代码案例演示

注:新版直接解压即可运行

1.下载ActiveMQ

去官方网站下载:http://activemq.apache.org/

2.运行ActiveMQ

解压缩apache-activemq-5.5.1-bin.zip,

修改配置文件activeMQ.xml,将0.0.0.0修改为localhost

<transportConnectors>       <transportConnector name="openwire" uri="tcp://localhost:61616"/>       <transportConnector name="ssl"     uri="ssl://localhost:61617"/>       <transportConnector name="stomp"   uri="stomp://localhost:61613"/>      <transportConnector uri="http://localhost:8081"/>       <transportConnector uri="udp://localhost:61618"/>

然后双击apache-activemq-5.5.1\bin\win64\activemq.bat运行ActiveMQ程序。

启动ActiveMQ以后,登陆:http://localhost:8161/admin/,创建一个Queue,命名为FirstQueue。

3.运行代码
见代码仓库或者官网案例

反射

通过反射的方式可以获取class对象中的属性、方法、构造函数等,一下是案例:

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import org.junit.Before;
import org.junit.Test;

public class MyReflect {
	public String className = null;
	@SuppressWarnings("rawtypes")
	public Class personClass = null;
	/**
	 * 反射Person类
	 * @throws Exception 
	 */
	@Before
	public void init() throws Exception {
		className = "reflect.Person";
		personClass = Class.forName(className);
	}
	/**
	 *获取某个class文件对象
	 */
	@Test
	public void getClassName() throws Exception {
		System.out.println(personClass);
	}
	/**
	 *获取某个class文件对象的另一种方式
	 */
	@Test
	public void getClassName2() throws Exception {
		System.out.println(Person.class);
	}
	/**
	 *创建一个class文件表示的真实对象,底层会调用空参数的构造方法
	 */
	@Test
	public void getNewInstance() throws Exception {
		System.out.println(personClass.newInstance());
	}
	/**
	 *获取非私有的构造函数
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	@Test
	public void getPublicConstructor() throws Exception {
		Constructor  constructor  = personClass.getConstructor(Long.class,String.class);
		Person person = (Person)constructor.newInstance(100L,"zhangsan");
		System.out.println(person.getId());
		System.out.println(person.getName());
	}
	/**
	 *获得私有的构造函数
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	@Test
	public void getPrivateConstructor() throws Exception {
		Constructor con = personClass.getDeclaredConstructor(String.class);
		con.setAccessible(true);//强制取消Java的权限检测
		Person person2 = (Person)con.newInstance("zhangsan");
		System.out.println(person2.getName());
	}
	/**
	 *获取非私有的成员变量
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	@Test
	public void getNotPrivateField() throws Exception {
		Constructor  constructor  = personClass.getConstructor(Long.class,String.class);
		Object obj = constructor.newInstance(100L,"zhangsan");
		
		Field field = personClass.getField("name");
		field.set(obj, "lisi");
		System.out.println(field.get(obj));
	}
	/**
	 *获取私有的成员变量
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	@Test
	public void getPrivateField() throws Exception {
		Constructor  constructor  = personClass.getConstructor(Long.class);
		Object obj = constructor.newInstance(100L);
		
		Field field2 = personClass.getDeclaredField("id");
		field2.setAccessible(true);//强制取消Java的权限检测
		field2.set(obj,10000L);
		System.out.println(field2.get(obj));
	}
	/**
	 *获取非私有的成员函数
	 */
	@SuppressWarnings({ "unchecked" })
	@Test
	public void getNotPrivateMethod() throws Exception {
		System.out.println(personClass.getMethod("toString"));
		
		Object obj = personClass.newInstance();//获取空参的构造函数
		Object object = personClass.getMethod("toString").invoke(obj);
		System.out.println(object);
	}
	/**
	 *获取私有的成员函数
	 */
	@SuppressWarnings("unchecked")
	@Test
	public void getPrivateMethod() throws Exception {
		Object obj = personClass.newInstance();//获取空参的构造函数
		Method method = personClass.getDeclaredMethod("getSomeThing");
		method.setAccessible(true);
		Object value = method.invoke(obj);
		System.out.println(value);

	}
	/**
	 *
	 */
	@Test
	public void otherMethod() throws Exception {
		//当前加载这个class文件的那个类加载器对象
		System.out.println(personClass.getClassLoader());
		//获取某个类实现的所有接口
		Class[] interfaces = personClass.getInterfaces();
		for (Class class1 : interfaces) {
			System.out.println(class1);
		}
		//反射当前这个类的直接父类
		System.out.println(personClass.getGenericSuperclass());
		/**
		 * getResourceAsStream这个方法可以获取到一个输入流,这个输入流会关联到name所表示的那个文件上。
		 */
		//path 不以’/'开头时默认是从此类所在的包下取资源,以’/'开头则是从ClassPath根下获取。其只是通过path构造一个绝对路径,最终还是由ClassLoader获取资源。
		System.out.println(personClass.getResourceAsStream("/log4j.properties"));
		//默认则是从ClassPath根下获取,path不能以’/'开头,最终是由ClassLoader获取资源。
		System.out.println(personClass.getResourceAsStream("/log4j.properties"));
		
		//判断当前的Class对象表示是否是数组
		System.out.println(personClass.isArray());
		System.out.println(new String[3].getClass().isArray());
		
		//判断当前的Class对象表示是否是枚举类
		System.out.println(personClass.isEnum());
		System.out.println(Class.forName("cn.java.reflect.City").isEnum());
		
		//判断当前的Class对象表示是否是接口
		System.out.println(personClass.isInterface());
		System.out.println(Class.forName("cn.java.reflect.TestInterface").isInterface());
		
		
	}
}

java多线程增强

进程它是内存中的一段独立的空间,可以负责当前应用程序的运行。当前这个进程负责调度当前程序中的所有运行细节。
线程:它是位于进程中,负责当前进程中的某个具备独立运行资格的空间。
每个程序就是进程, 而每个进程中会有多个线程,而CPU是在这些线程之间进行时间片切换。多线程提高效率但是不能无休止开线程。

同步关键字

synchronized

  • synchronized是用来实现线程同步的!!!

​ 加同步格式:

​ synchronized( 需要一个任意的对象(锁) ){

​ 代码块中放操作共享数据的代码。

​ }

  • synchronized的缺陷

synchronized是java中的一个关键字,也就是说是Java语言内置的特性。

如果一个代码块被synchronized修饰了,当一个线程获取了对应的锁,并执行该代码块时,其他线程便只能一直等待,等待获取锁的线程释放锁,而这里获取锁的线程释放锁只会有两种情况:

  1)获取锁的线程执行完了该代码块,然后线程释放对锁的占有;

2)线程执行发生异常,此时JVM会让线程自动释放锁。

两种情况下导致线程阻塞等待的情况

例子1:

  如果这个获取锁的线程由于要等待IO或者其他原因(比如调用sleep方法)被阻塞了,但是又没有释放锁,其他线程便只能干巴巴地等待,试想一下,这多么影响程序执行效率。

  因此就需要有一种机制可以不让等待的线程一直无期限地等待下去(比如只等待一定的时间或者能够响应中断),通过Lock就可以办到。

例子2:

当有多个线程读写文件时,读操作和写操作会发生冲突现象,写操作和写操作会发生冲突现象,但是读操作和读操作不会发生冲突现象。

  但是采用synchronized关键字来实现同步的话,就会导致一个问题:

如果多个线程都只是进行读操作,当一个线程在进行读操作时,其他线程只能等待无法进行读操作。

  因此就需要一种机制来使得多个线程都只是进行读操作时,线程之间不会发生冲突,通过Lock就可以办到。

  另外,通过Lock可以知道线程有没有成功获取到锁。这个是synchronized无法办到的。

  总的来说,也就是说Lock提供了比synchronized更多的功能。

Lock

lock和synchronized的区别

  1. lock不是java内置的,lock是一个类,synchronized关键字一次是内置特效
  2. 采用synchronized不需要用户去手动释放锁,当synchronized方法或者synchronized代码块执行完之后,系统会自动让线程释放对锁的占用;而Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。

util.concurrent.locks包下常用类

Lock

首先要说明的就是Lock,通过查看Lock的源码可知,Lock是一个接口:

public interface Lock {
    void lock();
    void lockInterruptibly() throws InterruptedException;
    boolean tryLock();
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    void unlock();
}

Lock接口中每个方法的使用:
lock()、tryLock()、tryLock(long time, TimeUnit unit)、lockInterruptibly()是用来获取锁的。 unLock()方法是用来释放锁的。

四个获取锁方法的区别:
  - lock()方法是平常使用得最多的一个方法,就是用来获取锁。如果锁已被其他线程获取,则进行等待。
由于在前面讲到如果采用Lock,必须主动去释放锁,并且在发生异常时,不会自动释放锁。因此一般来说,使用Lock必须在try{}catch{}块中进行,并且将释放锁的操作放在finally块中进行,以保证锁一定被被释放,防止死锁的发生。

  • tryLock()方法是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false,也就说这个方法无论如何都会立即返回。在拿不到锁时不会一直在那等待。

  tryLock(long time, TimeUnit unit)方法和tryLock()方法是类似的,只不过区别在于这个方法在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false。如果如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。

  •   lockInterruptibly()方法比较特殊,当通过这个方法去获取锁时,如果线程正在等待获取锁,则这个线程能够响应中断,即中断线程的等待状态。也就使说,当两个线程同时通过lock.lockInterruptibly()想获取某个锁时,假若此时线程A获取到了锁,而线程B只有在等待,那么对线程B调用threadB.interrupt()方法能够中断线程B的等待过程。

   注意,当一个线程获取了锁之后,是不会被interrupt()方法中断的
  因此当通过lockInterruptibly()方法获取某个锁时,如果不能获取到,只有进行等待的情况下,是可以响应中断的。
  而用synchronized修饰的话,当一个线程处于等待某个锁的状态,是无法被中断的,只有一直等待下去。

ReentrantLock是唯一实现了Lock接口的类

直接使用lock接口的话,我们需要实现很多方法,不太方便,ReentrantLock是唯一实现了Lock接口的类,并且ReentrantLock提供了更多的方法,ReentrantLock,意思是“可重入锁”。

ReadWriteLock (可以区别对待读、写的操作)

ReadWriteLock也是一个接口,在它里面只定义了两个方法:

public interface ReadWriteLock {    
  Lock readLock();    
  Lock writeLock();
}

  一个用来获取读锁,一个用来获取写锁。也就是说将文件的读写操作分开,分成2个锁来分配给线程,从而使得多个线程可以同时进行读操作。

ReentrantReadWriteLock实现了ReadWriteLock接口
  • 不过要注意的是,如果有一个线程已经占用了读锁,则此时其他线程如果要申请写锁,则申请写锁的线程会一直等待释放读锁。

  • 如果有一个线程已经占用了写锁,则此时其他线程如果申请写锁或者读锁,则申请的线程会一直等待释放写锁。

总结lock和synchronized对比:Lock和synchronized的选择

  1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;

  2)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;

  3)Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;

  4)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。

  5)Lock可以提高多个线程进行读操作的效率。

  在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。

JAVA并发包util.concurrent

是专门用于多线程发编程的,充分利用了现代多处理器和多核心系统的功能以编写大规模并发应用程序,主要包含原子量、并发集合、同步器、可重入锁,并对线程池的构造提供了强力的支持。

线程池

线程池的5中创建方式:

1、 Single Thread Executor : 只有一个线程的线程池,因此所有提交的任务是顺序执行,

代码: Executors.newSingleThreadExecutor()

2、 Cached Thread Pool : 线程池里有很多线程需要同时执行,老的可用线程将被新的任务触发重新执行,如果线程超过60秒内没执行,那么将被终止并从池中删除,

代码:Executors.newCachedThreadPool()

3、 Fixed Thread Pool : 拥有固定线程数的线程池,如果没有任务执行,那么线程会一直等待,

代码: Executors.newFixedThreadPool(4)

在构造函数中的参数4是线程池的大小,你可以随意设置,也可以和cpu的核数量保持一致,获取cpu的数量int cpuNums = Runtime.getRuntime().availableProcessors();

4、 Scheduled Thread Pool : 用来调度即将执行的任务的线程池,

代码:Executors.newScheduledThreadPool()

5、 Single Thread Scheduled Pool : 只有一个线程,用来调度执行将来的任务,代码:Executors.newSingleThreadScheduledExecutor()

线程池的使用

所谓给线程池提交任务,就是:

1、你将任务(业务处理逻辑)写到一个runnable或者callable的执行方法<run() | call()>

2、将这个runnable对象提交给线程池即可

提交 Runnable ,任务完成后 Future 对象返回 null

见代码:ThreadPoolWithRunable

提交 Callable,该方法返回一个 Future 实例表示任务的状态

见代码:ThreadPoolWithcallable

java并发包消息队列及在开源软件中的应用

消息队列常用于有生产者和消费者两类角色的多线程同步场景

BlockingQueue也是java.util.concurrent下的主要用来控制线程同步的工具。

主要的方法是:put、take一对阻塞存取;add、poll一对非阻塞存取

BlockingQueue有四个具体的实现类,常用的两种实现类为:LinkedBlockingQueue和ArrayBlockingQueue区别:

LinkedBlockingQueue和ArrayBlockingQueue比较起来,它们背后所用的数据结构不一样,导致LinkedBlockingQueue的数据吞吐量要大于ArrayBlockingQueue,但在线程数量很大时其性能的可预见性低于ArrayBlockingQueue

  • 前者可以指定大小也可不指定默认Integer.MAX_VALUE决定;后者必须指定大小
  • 都是FIFO(先入先出)顺序排序

动态代理工作机制

动态代理:在不修改原业务的基础上,基于原业务方法,进行重新的扩展,实现新的业务。

例如下面的例子:

1、 旧业务

买家调用action,购买衣服,衣服在数据库的标价为50元,购买流程就是简单的调用。

2、 新业务

在原先的价格上可以使用优惠券,但是这个功能在以前没有实现过,我们通过代理类,代理了原先的接口方法,在这个方法的基础上,修改了返回值。

动态代理工作机制图

6827b2d35e0ca4532211c5f6bc9042d2.png

代理实现流程:

1、 书写代理类和代理方法,在代理方法中实现代理Proxy.newProxyInstance

2、 代理中需要的参数分别为:被代理的类的类加载器soneObjectclass.getClassLoader(),被代理类的所有实现接口new Class[] { Interface.class },句柄方法new InvocationHandler()

3、 在句柄方法中复写invoke方法,invoke方法的输入有3个参数Object proxy(代理类对象), Method method(被代理类的方法),Object[] args(被代理类方法的传入参数),在这个方法中,我们可以定制化的开发新的业务。

4、 获取代理类,强转成被代理的接口

5、 最后,我们可以像没被代理一样,调用接口的认可方法,方法被调用后,方法名和参数列表将被传入代理类的invoke方法中,进行新业务的逻辑流程。

代码案例如下:

原业务接口IBoss

public interface IBoss {//接口
	int yifu(String size);
}

原业务实现类

public class Boss implements IBoss{
	public int yifu(String size){
		System.err.println("天猫小强旗舰店,老板给客户发快递----衣服型号:"+size);
		//这件衣服的价钱,从数据库读取
		return 50;
	}
	public void kuzi(){
		System.err.println("天猫小强旗舰店,老板给客户发快递----裤子");
	}
}

原业务调用

public class SaleAction {
		@Test
	public void saleByBossSelf() throws Exception {
		IBoss boss = new Boss();
		System.out.println("老板自营!");
		int money = boss.yifu("xxl");
		System.out.println("衣服成交价:" + money);
	}
}

代理类

public static IBoss getProxyBoss(final int discountCoupon) throws Exception {
	Object proxedObj = Proxy.newProxyInstance(Boss.class.getClassLoader(),
			new Class[] { IBoss.class }, new InvocationHandler() {
				public Object invoke(Object proxy, Method method,
						Object[] args) throws Throwable {
						Integer returnValue = (Integer) method.invoke(new Boss(),
								args);// 调用原始对象以后返回的值
						return returnValue - discountCoupon;
				}
			});
	return (IBoss)proxedObj;
}
}

新业务调用

public class ProxySaleAction {
		@Test
	public void saleByProxy() throws Exception {
		IBoss boss = ProxyBoss.getProxyBoss(20);// 将代理的方法实例化成接口
		System.out.println("代理经营!");
		int money = boss.yifu("xxl");// 调用接口的方法,实际上调用方式没有变
		System.out.println("衣服成交价:" + money);
	}
}

socket编程

编程模型介绍

  • jdk1.4之前是BIO(同步阻塞IO)
  • 1.4之后引入NIO(同步非阻塞IO)
  • 1.7之后引入AIO(NIO2)(异步非阻塞IO)

基本变成模型

服务端

核心API :ServerSocket
流程:

  1. 先创建一个服务,然后绑定服务器ip和端口
  2. 等待客户端链接请求
  3. 收到请求,接受,建立一个TCP链接
  4. 从连接中获取到socket输入输出流(两个流都是同步阻塞的)
  5. 通过两个流进行数据的交互

客户端

核心API: Socket
流程:

  1. 先向服务器请求链接
  2. 被服务器接受,创建好链接
  3. 从tcp链接获取socket输入输出流
  4. 通过两个流进行交互

NIO原理介绍

阻塞和非阻塞

阻塞和非阻塞是进程访问数据的时候,数据是否准备就绪的一种处理方式。
当数据没有准备好的时候

  • 阻塞:往往需要等待缓冲区中的数据准备好之后才处理,否则一直等待
  • 非阻塞:当我们的进程访问我们的数据缓冲区的时候,数据没准备好的时候,直接返回,不等待。有数据,也直接返回。

同步和异步

都是基于应用程序和操作系统处理IO事件所采用的方式

  • 同步:应用程序要直接参与IO事件操作
  • 异步:所有IO事件交给操作系统去处理

同步的方式处理IO事件的时候,必须阻塞在某个方法上线等待我们的IO事件完成(阻塞在io事件或者通过应用程序去轮询io事件的方式)即阻塞和轮询都是同步!
异步的方式:所有io读写都交给操作系统,这个时候程序可以做其他事情,并不需要参与真正的io操作,当操作系统完成io操作给应用程序一个通知即可。

同步有两种实现模式:

  1. 阻塞在IO事件上,阻塞到read或者write方法上,这个时候我们完全不能做其他事情(这种情况下,我们只能把读写方法放置到线程中,然后阻塞线程的方式实现并发服务,对线程的性能开销极大)

  2. IO事件的轮询 --在linux c语言编程中叫做多路复用技术(select模式)
    读写事件交给一个专门的线程处理,这个线程完成io事件的注册功能,还有就是不断地去轮询我们的读写缓冲区(操作系统),看是否有数据,然后通知我们相应的业务处理线程,这样的话,我们的业务处理线程就可以做其他的事情,在这种模式下阻塞的不是所有io线程,而是阻塞的只是select线程。

原理图如下:

dae5127e17d1f3eb6389a3ff3035bd8b.png

异步原理图如下:

e513e7d5c4e3b79d6a2b8bc4ff066a3f.png
NIO多路复用代码案例详解
32f807eea3d93ad2a8300ce237140afa.png
图片补充:mark是数据的写到的位置标记,flip操作就是把position指向本次写的数据的开头,limit指向写的数据的结尾。

netty使用案例一

netty是一个基于javanio的通信工具

rpc是利用进程和进程之间的调用,可以利用netty来做。

netty server端代码案例ServerBootstrap类

package myjava.RPCnio.netty.sendorder.server;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
/**
 * majia
 * 19-3-20
 * 配置服务器功能,入线程端口,实现服务器处理程序,它包含业务逻辑,
 * 决定当有一个请求链接火接收数据时该做什么
 */
public class EchoServer {
    private final int port;
    public EchoServer(int port){
        this.port=port;
    }
    public  void  start() throws Exception{
        EventLoopGroup eventLoopGroup=null;
        try{
            //server 端引导类
            ServerBootstrap serverBootstrap = new ServerBootstrap();//驱动类
            //连接池处理数据
            eventLoopGroup = new NioEventLoopGroup();//线程组
            serverBootstrap.group(eventLoopGroup).channel(NioServerSocketChannel.class)//装配线程组和channel类型
                    .localAddress("localhost",port)
                    .childHandler(new ChannelInitializer<Channel>() {//业务逻辑
                        @Override
                        protected void initChannel(Channel channel) throws Exception {
                            //注册两个InboundHandler,执行顺序为注册顺序
                            //注册两个OutBoundHandler执行顺序为逆序
                            channel.pipeline().addLast(new EchoInHandler1());
                            channel.pipeline().addLast(new EchoOutHandler1());
                            channel.pipeline().addLast(new EchoOutHandler2());
                            channel.pipeline().addLast(new EchoInHandler2());
                        }
                    });

            //最后绑定服务器等待知道绑定完成,调用sync()方法会阻塞到服务器绑定完成
            ChannelFuture channelFuture = serverBootstrap.bind().sync();
            System.out.println("开始监听,端口为:"+channelFuture.channel().localAddress());
            //等待channel关闭,因为使用sync所以关闭操作也会被阻塞
            channelFuture.channel().closeFuture().sync();
        }finally {
            eventLoopGroup.shutdownGracefully().sync();
        }
    }
    public static void main(String[] args) throws Exception{
        new EchoServer(20000).start();
    }
}

handler案例

package myjava.RPCnio.netty.sendorder.server;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

/**
 * majia
 * 19-3-20
 */
public class EchoInHandler1 extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("in");
        //用fireChannelRead发送到下一个InBoundHandler
        ctx.fireChannelRead(msg);

    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();//刷新后才将数据发出到socketchannel

    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

package myjava.RPCnio.netty.sendorder.server;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

import java.util.Date;

/**
 * majia
 * 19-3-20
 */
public class EchoInHandler2 extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("in2");
        ByteBuf buf= (ByteBuf) msg;
        byte[] req = new byte[buf.readableBytes()];
        buf.readBytes(req);
        String body = new String(req, "UTF-8");
        System.out.println("接收客户端数据:"+body);
        //向客户度写数据
        System.out.println("server向client发送数据");
        String currentTime = new Date(System.currentTimeMillis()).toString();
        ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes());
        ctx.write(resp);
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

package myjava.RPCnio.netty.sendorder.server;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.ChannelPromise;

/**
 * majia
 * 19-3-20
 */
public class EchoOutHandler2 extends ChannelOutboundHandlerAdapter {

    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
        System.out.println("out2");
        ctx.write(msg);
    }
}

package myjava.RPCnio.netty.sendorder.server;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.ChannelPromise;

/**
 * majia
 * 19-3-20
 */
public class EchoOutHandler1 extends ChannelOutboundHandlerAdapter {
    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
        System.out.println("out1");
        System.out.println(msg);
        ctx.write(msg);
        ctx.flush();
    }
}

client端代码Bootstrap类

package myjava.RPCnio.netty.sendorder.client;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;

import java.net.InetSocketAddress;

/**
 * majia
 * 19-3-20
 * 链接服务器,写数据到服务器,等待接收服务器返回相同的数据,关闭链接
 */
public class EchoClient {
    private final String host;
    private final int port;

    public EchoClient(String host, int port) {
        this.host = host;
        this.port = port;
    }
    public void start() throws Exception{
        EventLoopGroup nioEventLoopGroup=null;
        try{
//            客户端引导类
            Bootstrap bootstrap = new Bootstrap();
            //eventloopgroup可以理解为咸亨池,用来处理链接接收发送数据
            nioEventLoopGroup = new NioEventLoopGroup();
            bootstrap.group(nioEventLoopGroup)//多线程处理
            .channel(NioSocketChannel.class)
                    .remoteAddress(new InetSocketAddress(host,port))
                    .handler(new ChannelInitializer<SocketChannel>() {//注册handler
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            socketChannel.pipeline().addLast(new EchoClientHandler());
                        }
                    });
            //绑定服务器
            ChannelFuture channelFuture = bootstrap.connect().sync();
            channelFuture.channel().closeFuture().sync();
        }finally {
            nioEventLoopGroup.shutdownGracefully().sync();
        }
    }
    public static void main(String[] args) throws Exception {
      new   EchoClient("localhost",2000).start();
    }
}

client handler

package myjava.RPCnio.netty.sendorder.client;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

/**
 * majia
 * 19-3-20
 */
public class EchoClientHandler  extends SimpleChannelInboundHandler<ByteBuf> {
    //客户端链接服务器后被调用

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("客户端链接服务器,开始发送数据。。。");
        byte[] req = "QUERY TIME ORDER".getBytes();//消息
        ByteBuf firstMsg = Unpooled.buffer(req.length);//穿件一个空的bytebuf用于缓存即将发送的数据
        firstMsg.writeBytes(req);//发送
        ctx.writeAndFlush(firstMsg);

    }
//从服务器接收到数据后调用
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf) throws Exception {
        System.out.println("client 读取server数据。。");
        //服务器返回消息后
        ByteBuf buf=byteBuf;
        byte[] req=new byte[buf.readableBytes()];
        buf.readBytes(req);
        String body = new String(req, "UTF-8");
        System.out.println("服务端数据为:"+body);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("client exceptionCaught..");
        //释放资源
        ctx.close();
    }
}

案例二:利用netty自定义一个rpc(远程过程调用协议)框架

框架的设计需求分析如下图

e582fd729ec7cb94a3012ef715659647.png
实现方式把netty集成到spring中实现(spring的自定义注解+加载自定义注解的实现类两步)思路

a2dbb2d905f90e359cd27f1d4d4c4132.png

第一步:spring自定义注解

package myjava.spring.userdefinedannotation;

import org.springframework.stereotype.Component;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * majia
 * 19-3-20
 */
@Target({ElementType.TYPE})//注解用在类上
@Retention(RetentionPolicy.RUNTIME)//VM将在运行期间也保留注释,因此可以通过反射机制读取注解调用的业务实现类
@Component
public @interface RPCService  {
    String value();//可以给返回字符串即使用时@RPCService("str")的字符串
}

第二步:spring启动上下文扫描componet,测试自定义注解

package myjava.spring.userdefinedannotation.test;

import myjava.spring.userdefinedannotation.RPCService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Map;

/**
 * majia
 * 19-3-20
 */
@Component
public class MyServer implements ApplicationContextAware  {
    @SuppressWarnings("resource")
    public static void main(String[] args) {//启动spirng上下文
        ClassPathXmlApplicationContext ctx=new ClassPathXmlApplicationContext("spring.xml");
    }
    @Override
    public void setApplicationContext(ApplicationContext ctx)  {//获取指定注解的实例化对象及注解的value
        Map<String, Object> rpcServiceBeanMap = ctx.getBeansWithAnnotation(RPCService.class);
        try {
            for(Object serviceBean:rpcServiceBeanMap.values()){
                //获取自定义注解上的value
                String value = serviceBean.getClass().getAnnotation(RPCService.class).value();
                System.out.println("注解上的value: "+value);
                //反射被注解类,并调用指定方法
                Method method = serviceBean.getClass().getMethod("hello", new Class[ ]{String.class});
                Object invoke=method.invoke(serviceBean,"aaa");
                System.out.println(invoke);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

spring的单元测试

package myjava.spring.userdefinedannotation.test;

import myjava.spring.userdefinedannotation.HelloService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/**
 * majia
 * 19-3-20
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations= "classpath:spring.xml")
@Component
public class MyServer2 {
    @Autowired
    HelloService helloService;
    @Test
    public void helloTest1(){//spring集成单元测试,自动启动context上下文进行注解扫描
        System.out.println("开始junit测试。。。");
        helloService.hello("ccc");
        System.out.println(hello);
    }
}

rpc框架高可用的整体架构图:如下两个图一个意思

822501dace5823b1bce7a2e0d4f83aec.png
2019-03-20_22-51.png

项目代码目录架构图:

7feca903352cb63d28ed28c35f1132c8.png

项目代码见gitee

https://gitee.com/jiamacodes/pjava.git

提供给用户使用时的依赖管理

d0f35098d24e0d42704a1597b7867110.png

JVM

从计算机的层面讲:包含6个部分

  1. JVM解释器(cpu)
  2. 指令系统(操作码+操作数)、
  3. 寄存器(pc(程序计数器)、optop(操作数栈顶指针)、frame(当前执行环境指针)和vars(指向当前执行环境中第一个局部变量的指针)
  4. JVM栈(每一个方法创建一个栈框架,以保存该方法的状态信息。每个栈框架包括以下三类信息:局部变量、执行环境、操作数栈。操作数栈用于存储运算所需操作数及运算的结果。)
  5. 存储区(常量缓冲池和方法区。常量缓冲池用于存储类名称、方法和字段名称以及串常量。方法区则用于存储Java方法的字节码)
  6. 碎片回收区:分代内存回收

监控工具使用

jconsole

集成了jvm命令的可视化工具,可以分析jvm内存使用情况和线程信息;启动jdk/bin/下的jconsole后,将自动搜索本机运行的虚拟机进程,不需要用户jps查询了,双击其中一个进程即可监控。也可以远程链接服务器,对远程虚拟机监控。

连接java线程获取线程运行信息

d3d8e036282a3916cb4aa52634e3d278.png

观察内存溢出:

861d2c30a445530f615971daf50efa07.png

内存溢出演示

suopingpackage myjava.jvmdemo;

import java.util.ArrayList;

/**
 * majia
 * 19-3-22
 * 64kb/50毫秒
 * -Xms2m -Xmx2m
 */
public class TestMemory {
    static class OOMObject{
        public byte[] placeholder= new byte[64*1024];
    }
    public static void fillHeap(int num) throws Exception{
        ArrayList<OOMObject> list = new ArrayList<>();
        for (int i = 0; i < num; i++) {
            Thread.sleep(50);
            list.add(new OOMObject());
        }
    }

    public static void main(String[] args) throws Exception{
        Thread.sleep(10000);
        fillHeap(10000000);
        Thread.sleep(200000000);
    }
}

典型的HeapOOM:OutOfMemoryError

99baa8a695891d38196bf059a959cfb3.png

栈溢出演示
package myjava.jvmdemo;

/**
 * majia
 * 19-3-22
 * 线程导致内存溢出异常
 * -Xss 2m
 * 容易导致系统假死
 * 线程过多(线程栈过多)溢出
 */
public class JavaVMStackOOM {
    private void dontStop(){
        while(true){

        }
    }
    public void stackLeakByThread(){
        while (true){
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    dontStop();
                }
            });
            thread.start();
        }
    }

    public static void main(String[] args) {
        JavaVMStackOOM oom = new JavaVMStackOOM();
        oom.stackLeakByThread();
    }
}
/**
 * majia
 * 19-3-22
 * 某一个线程的栈帧过多导致该线程栈空间溢出(系统不会假死)
 * 虚拟机栈和本地方法栈oom测试
 */
public class JavaVMStackSOF {
    private int stackLength=1;
    public void stackLeak(){
        stackLength++;
        stackLeak();
    }

    public static void main(String[] args) throws Throwable {
        JavaVMStackSOF javaVMStackSOF = new JavaVMStackSOF();
        try {
            javaVMStackSOF.stackLeak();
        } catch (Throwable e) {
            System.out.println("stack length:"+javaVMStackSOF.stackLength);
            throw e;
        }
    }
}

有名的stackOverFlowError

748371572234686280a104a373166f1a.png

线程等待:

ee18a0ed329bc3978c85e9a23416bc0f.png

线程死锁:

package myjava.jvmdemo;

/**
 * majia
 * 19-3-22
 */
public class TestDeadThread implements Runnable {
    int a,b;
    public TestDeadThread(int a,int b){
        this.a=a;
        this.b=b;
    }
    @Override
    public void run() {
        synchronized (Integer.valueOf(a)){
            synchronized (Integer.valueOf(b)){
                System.out.println(a+b);
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread.sleep(3000);
        for (int i = 0; i < 100; i++) {
            new Thread(new TestDeadThread(1,2)).start();
            new Thread(new TestDeadThread(2,1)).start();
        }
    }
}

897abebd86c197865663dd6cbe533f15.png

jvisualvm

d6314ae636d20e57c43b0ee679552d54.png

java内存模型

内存模型图解

e2cc5eb501278b930b1fe0d435a4d0cf.png

如上图:java虚拟机运行的数据区域分五个部分

  • 方法区:用于存储类结构信息的地方,包括常量池,静态变量,构造函数等。虽然jvm规范吧方法去描述为堆的一个逻辑部分,他还有一个别名(non-heap),方法区还包含一个运行时常量池。
  • java堆:存储java是咧或者对象的地方,这块是gc的主要区域。方法区和堆是被所有java线程共享的
  • java栈:栈总是和线程关联在一起,每创建一个线程时,就会为这个线程创建一个栈,在栈中又会包含多个栈帧,每运行一个方法就创建一个栈帧,用于存储局部变量表,操作栈,方法返回值等。每个方法从调用直至执行完成的过程,就对应一个栈帧在java栈中入栈到出栈的过程,所以java栈是线程私有的。(栈过多也会内存溢出)
  • 程序计数器(PC Register):用于保存当前线程执行的内存地址。由于jvm程序是多线程执行的,所以为了保证线程切换回来后,还能恢复到原先状态,就需要一个独立的计数器,记录之前终端的地方,计数器也是线程私有的。
  • 本地方法栈:与java栈用处差不多,不过是为java虚拟机使用到的native方法服务的。

堆是jvm中内存最大一块随虚拟机启动而创建,物理上不连续逻辑上连续的内存空间;实际运用中,我们的对象和数组就放在堆里面。
他是线程共享的区域,操作共享区域的成员就有了线程锁和同步的机制
与堆相关的还有垃圾回收机制GC,堆是垃圾回收的主要区域。

堆空间区域的概念划分:

b9d02fc4ba7a5cb8908f0713298d1d9f.png

栈空间信息图

bdd4af169f7187688b54e81ffd532220.png

局部变脸表存放了编译期间的各种基本数据类型和对象引用的等信息。

总结

线程私有区域:java栈,本地方法栈

线程共享区域:堆,方法区

GC算法

标记-清除(mark-sweep)算法

标记需要清除的对象,统一回收

缺点:会产生大量不连续的内存碎片,导致程序以后分配较大对象时找不到足够大的连续的内存不得不触发另一次垃圾回收。

复制算法(copying)

  1. 将可用内存按容量分为大小相等的两块,每次只使用其中的一块
  2. 当这一块内存用完了,就将活着的对象复制到另一块上面,然后把使用过的这一块内存一次性清理掉。
    缺点:可用内存缩小一半,在对象存活率较高的时候要进行较多的复制操作效率变低。

标记-整理(mark-Compact)算法

  1. 标记
  2. 让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存
    2019-03-22_14-36.png

此算法结合了上面两种算法的优点,分两阶段:第一阶段从根节点开始标记所有被引用的对象,第二阶段遍历整个堆,把清除未标记对象并且把存活对象“压缩”到堆的其中一块,按顺序排放,避免了碎片问题和空间问题。

垃圾回收器

参考:https://blog.csdn.net/superviser3000/article/details/81664317

分代收集方法论

不同类型的对象不同的存活周期;去划分不同的区域,分成年代。不同区域用不同的垃圾回收机制(主要的垃圾收集器算法不同)

分代垃圾收集器

  • 年轻代(复制算法):是所有对象产生的地方,被分为三个部分enden区和两个survivor区(from/to相对);每次使用Eden空间和其中的一块Survivor空间,当Enden区满,就会MinorGc,把存活的对象复制到另外一个Survivor区,然后清理掉Eden和刚才使用过的Survivor空间,这样总会有一个空的Survivor区。

  • 老年代(标记整理算法):在年轻带经理N次(有阀值)回收仍没清除的对象就会转移到年老代,在年老等待内存被占满时通常会出发(majorGc)FullGC回收整个堆内存。

  • 永久代(即方法区):存放静态文件,比如java类,方法等。他存储class对象和字符串常量。所以这块内存区域绝对不是永久的存放从老年代存活下来的对象的。在这块内存中有可能发生垃圾回收。发生在这里垃圾回收也被称为major GC。

分代回收效果图如下:

2019-03-22_15-13.png

垃圾回收器概览

2019-03-22_16-43.png

  • Serial收集器(新生代)
  1. 单线程收集器,“stop the world"
  2. 对于运行在client模式下的虚拟机来说是一个很好的选择
  3. 简单高效
    2019-03-22_15-23.png
  • ParNew收集器(新生代)
  1. serial串行收集器的多线程版本
  2. 单cpu不如Serial
  3. Server模式下新生代首选,目前只有他能与CMS收集器配合工作
  4. 使用-XX:+UseConcMarkSweepGC选项后的偶人新生代收集器,也可以使用-XX:+UseParNewGC选项强制指定它。
  5. -XX:ParallelGCThreads:限制垃圾收集器的线程数。
    2019-03-22_15-31.png
  • Prallel Scavenge收集器(新生代;应用线程和垃圾回收可以并存:必须解决同一个对象一边标记一遍清除两个线程间的数据安全问题)
  1. 新生代收集器,复制算法,并行的多线程收集器
  2. 目标是达到了一个可控制的吞吐量(Throughput).吞吐量=运行用户代码时间/(运行用户时间+垃圾收集时间),虚拟机工运行了100分钟,其中垃圾收集花掉1分钟,吞吐量就是99%
  3. 两个参数用于精确控制吞吐量。
    -XX:MaxGCPauseMillis是控制最大垃圾收集停顿事件
    -XX:GCTimeRatio直接设置吞吐量大小
    -XX:+UseAdaptiveSizePolicy动态设置新生代大小,Eden与Survivor区的比例,今生老年代对象年龄。
  4. 并行(Parallel):指多条垃圾收集线程工作,但此时用户线程仍然处于等待状态
  5. 并发(Concurrent):指用户线程与垃圾收集线程同事执行(但不一定是并行的,可能会交替执行),用户程序在继续运行,而垃圾收集程序运行于另一个cpu上。
  • Serial Old收集器(老年代)
  1. Serial的老年代版本 使用单线程线程和”标记-整理“算法
  2. 主要意义还是在client模式下使用
  3. 如果server模式,有两个用途
    一种是在jdk1.5以及以前的版本中与ParallelScavenge收集器搭配使用
    一种是做为cms收集器的预备案,在并发收集发生oncurrent Mode Failure时使用。
  • Parallel Scavenge(老年代)
  1. 使用多线程和”标记整理“算法
  2. 在注重吞吐量和cpu资源敏感的场合,都可以有限考虑parallel两类收集器
  • CMS(并发GC)收集器(老年代)
  1. 以获取最短回收停顿时间为目标

  2. 非常符合服务端,重视响应速度,希望系统停顿时间最短的应用。

  3. 基于”标记-清除“算法

  4. CMS收集器的内存回收过程与用户线程一起并发执行的。

  5. 他的运作过程分4个步骤:

    • 初始标记,”stop the world",只是标记一下GC Roots能直接关联到的对象,速度很快。
    • 并发标记:并发标记阶段就是进行GC RootsTracing 的过程
    • 重新标记:“stop the word”,是为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,但远比并发标记的时间短。
    • 并发清除:(cms concurrent sweep)

优缺点:

优点--并发收集,低停顿

缺点--对cpu资源非常敏感,无法处理浮动垃圾,可能出现“Concurrent Mode Failture"失败而导致的另一次FullGc(一般Serial收集器处理)的产生,停顿就长了。

2019-03-22_16-23.png

  • G1(Garbage-first)收集器(新生代+老年代)
  1. 当今收集器技术发展的最前沿之一

  2. 面向服务端的垃圾回收器

  3. 优点:

    • 并行和并发:充分利用多cpu,多核硬件

    • 分代收集:不需要其他收集器配合就能独立管理整个GC堆

    • 空间整合:”标记整理“实现收集器,局部基于”复制“算法不会产生内存碎片

    • 可预测的停顿:能让使用者明确指定在一个长度M毫秒的事件片段内,消耗在收集器上的事件不超过N毫秒。

  4. G1运作步骤

    • 初始标记
    • 并发标记:从GCRoot开始可达性分析,耗时较长,与用户程序并发执行
    • 最终标记:修正并发标记因用户程序导致标记变动的部分标记记录
    • 筛选回收:对各个Region的回收价值和成本排序,根据用户所期望的Gc停顿事件制定回收计划。

hadoop集群jvm调优综合案例演示:

09.jvm的监控工具--JVM综合调优案例提取码: 56ke

  1. 修改mapred-site.xml的各项参数配置

  2. 远程链接jstatd(jvm提供远程调试的服务)

    其配置是在/jdk/bin目录下增加文件jstatd.all.policy

    vi jstatd.all.policy
    grant codebase "file:${java.home}/../lib/tools.jar"{
        permission java.security.AllPermission;
    };
    

    然后在目标机器上启动jstatd服务

    jstatd -J-Djava.security.policy=root/apps/jdk/bin/jstatd.all.policy -J-Djava.rmi.server.hostname=主机名
    
  3. 利用jvisualvm工具远程连接目标主机观察内存服务(会列出目标主机的所有java进程)

    1)打开yarn的web界面
    2)启动hive执行hive命令

create table t_tmp as select a11.a,a1.b,a1.c,a1.d,ar.e,ar.f,ar.g,ar.h,ar.i from t_a a1 join t_a ar on a1.b=ar.b;

死掉的task:可以看出-xms大小是200m(eden+sur+old+per)

2019-03-22_20-34.png

2019-03-22_20-17.png

频繁gc的task:

2019-03-22_20-23.png

2019-03-22_20-28.png

2019-03-22_20-30.png

可以看出慢下来的task与发生gc的task所在机器是一台

看出问题来了就是调参优化参考mapreduce参数列表:

可以看出可能跟reduce.memory.mb参数有关但是配置文件是1024m,jvm工具显示200m因此不是这个配置项,这个配置项是reduce申请的yarnchild容器大小

也可能跟reduce.java.opts有关配置可以调整重启hive任务发现jvm工具观察对应改变说明就是这个配置项;不过这个参数值必须小于容器内存大小(即reduce.memory.mb配置)

posted @ 2021-02-23 10:58  编程未来  阅读(137)  评论(0编辑  收藏  举报