
vip java高级开发工程师
能力说明:
了解变量作用域、Java类的结构,能够创建带main方法可执行的java应用,从命令行运行java程序;能够使用Java基本数据类型、运算符和控制结构、数组、循环结构书写和运行简单的Java程序。
暂时未有相关云产品技术能力~
阿里云技能认证
详细说明JDK7之前JDK7之前的版本在释放资源的时候,使用的try-catch-finally来操作资源。 其中,try代码块中使用获取、修改资源,catch捕捉资源操作异常,finally代码块来释放资源。 try { fos = new FileOutputStream("test.txt"); dos = new DataOutputStream(fos); dos.writeUTF("JDK7"); } catch (IOException e) { // error处理 } finally { fos.close(); dos.close(); }问题来了,finally代码块中的fos.close()出现了异常,将会阻止dos.close()的调用,从而导致dos没有正确关闭而保持开放状态。 解决方法是把finally代码块中的两个fos和dos的关闭继续套在一个try-catch-finally代码块中。 FileOutputStream fos = null;DataOutputStream dos = null;try { fos = new FileOutputStream("test.txt") dos = new DataOutputStream(fos); // 写一些功能 } catch (IOException e) { // error处理 } finally { try { fos.close(); dos.close(); } catch (IOException e) { // log the exception } } JDK7及之后JDK7之后有了带资源的try-with-resource代码块,只要实现了AutoCloseable或Closeable接口的类或接口,都可以使用该代码块来实现异常处理和资源关闭异常抛出顺序。 try(FileOutputStream fos = new FileOutputStream("test.txt")){ // 写一些功能 } catch(Exception e) { // error处理 }我们可以发现,在新版本的资源try-catch中,我们已经不需要对资源进行显示的释放,而且所有需要被关闭的资源都能被关闭。需要注意的是,资源的close方法的调用顺序与它们的创建顺序是相反的。
1 package reviewTest; 2 3 /** 4 * @ClassName: ReturnTest 5 * @Description: 测试return在trycatch中的执行 6 * @author Kingram 7 * @date 2018年7月27日 8 * 9 */10 public class ReturnTest {11 12 public static void main(String[] args) {13 System.out.println(new ReturnTest().test());14 }15 16 private int test() {17 int x = 1;18 try {19 int[] arr = new int[2];20 x = arr[5];21 return x;22 } catch (Exception e) {23 x = 10;24 return x;25 } finally {26 ++x;27 return x;28 }29 }30 31 }复制代码程序执行分析: 当程序执行到第20行时会产生数组下标越界异常,这时直接跳到catch语句块,此时x==10,并没有return。 最终执行finally语句块此时x==11,并返回x,最终输出结果为11。 生活中也许做的事情暂时看不到成果,但是不要害怕,你不是没有成长,而是在扎根,别担心,你所有的付出,都有意义,人生没有白吃的苦,你吃过的苦都是为将来的幸福打下基础,人生没有白走的路,每一步都算数。 由此可见,当catch 模块遇到return 的时候,在return 之前执行 finally模块语句,然后再执行return
业务需求中经常有需要用到计数器的场景:譬如一个手机号一天限制发送5条短信、一个接口一分钟限制多少请求、一个接口一天限制调用多少次等等。使用Redis的Incr自增命令可以轻松实现以上需求。以一个接口一天限制调用次数为例: /** * 是否拒绝服务 * @return */ private boolean denialOfService(String userId){ long count=JedisUtil.setIncr(DateUtil.getDate()+"&"+userId+"&"+"queryCarViolation", 86400); if(count<=10){ return false; } return true; } /** * 查询违章 * @param plateNumber车牌 * @param vin 车架号 * @param engineNo发动机 * @param request * @param response * @throws Exception */ @RequestMapping("/queryCarViolationList.json") @AuthorizationApi public void queryCarViolationList(@CurrentToken Token token,String plateNumber,String vin, String engineNo,HttpServletRequest request,HttpServletResponse response) throws Exception { String userId=token.getUserId(); //超过限制,拦截请求 if(denialOfService(userId)){ apiData(request, response, ReqJson.error(CarError.ONLY_5_TIMES_A_DAY_CAN_BE_FOUND)); return; } //没超过限制,业务逻辑…… }每次调用接口之前,先获得下计数器自增后的值,如果小于限制,放行,执行后面的代码。如果大于限制,则拦截掉。 JedisUtil工具类: public class JedisUtil { protected final static Logger logger = Logger.getLogger(JedisUtil.class); private static JedisPool jedisPool; @Autowired(required = true) public void setJedisPool(JedisPool jedisPool) { JedisUtil.jedisPool = jedisPool; } /** * 对某个键的值自增 * @author liboyi * @param key 键 * @param cacheSeconds 超时时间,0为不超时 * @return */ public static long setIncr(String key, int cacheSeconds) { long result = 0; Jedis jedis = null; try { jedis = jedisPool.getResource(); result =jedis.incr(key); if (cacheSeconds != 0) { jedis.expire(key, cacheSeconds); } logger.debug("set "+ key + " = " + result); } catch (Exception e) { logger.warn("set "+ key + " = " + result); } finally { jedisPool.returnResource(jedis); } return result; } } 原文:https://blog.csdn.net/qq_33556185/article/details/79427271 版权声明:本文为博主原创文章,转载请附上博文链接!
转自谈谈高并发系统的限流https://www.cnblogs.com/haoxinyue/p/6792309.html 开涛大神在博客中说过:在开发高并发系统时有三把利器用来保护系统:缓存、降级和限流。本文结合作者的一些经验介绍限流的相关概念、算法和常规的实现方式。 缓存缓存比较好理解,在大型高并发系统中,如果没有缓存数据库将分分钟被爆,系统也会瞬间瘫痪。使用缓存不单单能够提升系统访问速度、提高并发访问量,也是保护数据库、保护系统的有效方式。大型网站一般主要是“读”,缓存的使用很容易被想到。在大型“写”系统中,缓存也常常扮演者非常重要的角色。比如累积一些数据批量写入,内存里面的缓存队列(生产消费),以及HBase写数据的机制等等也都是通过缓存提升系统的吞吐量或者实现系统的保护措施。甚至消息中间件,你也可以认为是一种分布式的数据缓存。 降级服务降级是当服务器压力剧增的情况下,根据当前业务情况及流量对一些服务和页面有策略的降级,以此释放服务器资源以保证核心任务的正常运行。降级往往会指定不同的级别,面临不同的异常等级执行不同的处理。根据服务方式:可以拒接服务,可以延迟服务,也有时候可以随机服务。根据服务范围:可以砍掉某个功能,也可以砍掉某些模块。总之服务降级需要根据不同的业务需求采用不同的降级策略。主要的目的就是服务虽然有损但是总比没有好。 限流限流可以认为服务降级的一种,限流就是限制系统的输入和输出流量已达到保护系统的目的。一般来说系统的吞吐量是可以被测算的,为了保证系统的稳定运行,一旦达到的需要限制的阈值,就需要限制流量并采取一些措施以完成限制流量的目的。比如:延迟处理,拒绝处理,或者部分拒绝处理等等。 限流的算法常见的限流算法有:计数器、漏桶和令牌桶算法。 计数器 计数器是最简单粗暴的算法。比如某个服务最多只能每秒钟处理100个请求。我们可以设置一个1秒钟的滑动窗口,窗口中有10个格子,每个格子100毫秒,每100毫秒移动一次,每次移动都需要记录当前服务请求的次数。内存中需要保存10次的次数。可以用数据结构LinkedList来实现。格子每次移动的时候判断一次,当前访问次数和LinkedList中最后一个相差是否超过100,如果超过就需要限流了。 image 很明显,当滑动窗口的格子划分的越多,那么滑动窗口的滚动就越平滑,限流的统计就会越精确。 示例代码如下: 复制代码//服务访问次数,可以放在Redis中,实现分布式系统的访问计数Long counter = 0L;//使用LinkedList来记录滑动窗口的10个格子。LinkedList ll = new LinkedList(); public static void main(String[] args){ Counter counter = new Counter(); counter.doCheck(); } private void doCheck(){ while (true) { ll.addLast(counter); if (ll.size() > 10) { ll.removeFirst(); } //比较最后一个和第一个,两者相差一秒 if ((ll.peekLast() - ll.peekFirst()) > 100) { //To limit rate } Thread.sleep(100); } }复制代码 漏桶算法 漏桶算法即leaky bucket是一种非常常用的限流算法,可以用来实现流量整形(Traffic Shaping)和流量控制(Traffic Policing)。贴了一张维基百科上示意图帮助大家理解: image 漏桶算法的主要概念如下: 一个固定容量的漏桶,按照常量固定速率流出水滴; 如果桶是空的,则不需流出水滴; 可以以任意速率流入水滴到漏桶; 如果流入水滴超出了桶的容量,则流入的水滴溢出了(被丢弃),而漏桶容量是不变的。 漏桶算法比较好实现,在单机系统中可以使用队列来实现(.Net中TPL DataFlow可以较好的处理类似的问题,你可以在这里找到相关的介绍),在分布式环境中消息中间件或者Redis都是可选的方案。 令牌桶算法 令牌桶算法是一个存放固定容量令牌(token)的桶,按照固定速率往桶里添加令牌。令牌桶算法基本可以用下面的几个概念来描述: 令牌将按照固定的速率被放入令牌桶中。比如每秒放10个。桶中最多存放b个令牌,当桶满时,新添加的令牌被丢弃或拒绝。当一个n个字节大小的数据包到达,将从桶中删除n个令牌,接着数据包被发送到网络上。如果桶中的令牌不足n个,则不会删除令牌,且该数据包将被限流(要么丢弃,要么缓冲区等待)。如下图: image 令牌算法是根据放令牌的速率去控制输出的速率,也就是上图的to network的速率。to network我们可以理解为消息的处理程序,执行某段业务或者调用某个RPC。 漏桶和令牌桶的比较 令牌桶可以在运行时控制和调整数据处理的速率,处理某时的突发流量。放令牌的频率增加可以提升整体数据处理的速度,而通过每次获取令牌的个数增加或者放慢令牌的发放速度和降低整体数据处理速度。而漏桶不行,因为它的流出速率是固定的,程序处理速度也是固定的。 整体而言,令牌桶算法更优,但是实现更为复杂一些。 限流算法实现Guava Guava是一个Google开源项目,包含了若干被Google的Java项目广泛依赖的核心库,其中的RateLimiter提供了令牌桶算法实现:平滑突发限流(SmoothBursty)和平滑预热限流(SmoothWarmingUp)实现。 常规速率: 创建一个限流器,设置每秒放置的令牌数:2个。返回的RateLimiter对象可以保证1秒内不会给超过2个令牌,并且是固定速率的放置。达到平滑输出的效果 复制代码public void test(){ /** * 创建一个限流器,设置每秒放置的令牌数:2个。速率是每秒可以2个的消息。 * 返回的RateLimiter对象可以保证1秒内不会给超过2个令牌,并且是固定速率的放置。达到平滑输出的效果 */ RateLimiter r = RateLimiter.create(2); while (true) { /** * acquire()获取一个令牌,并且返回这个获取这个令牌所需要的时间。如果桶里没有令牌则等待,直到有令牌。 * acquire(N)可以获取多个令牌。 */ System.out.println(r.acquire()); } }复制代码上面代码执行的结果如下图,基本是0.5秒一个数据。拿到令牌后才能处理数据,达到输出数据或者调用接口的平滑效果。acquire()的返回值是等待令牌的时间,如果需要对某些突发的流量进行处理的话,可以对这个返回值设置一个阈值,根据不同的情况进行处理,比如过期丢弃。 image 突发流量: 突发流量可以是突发的多,也可以是突发的少。首先来看个突发多的例子。还是上面例子的流量,每秒2个数据令牌。如下代码使用acquire方法,指定参数。 System.out.println(r.acquire(2)); System.out.println(r.acquire(1)); System.out.println(r.acquire(1)); System.out.println(r.acquire(1)); 得到如下类似的输出。 image 如果要一次新处理更多的数据,则需要更多的令牌。代码首先获取2个令牌,那么下一个令牌就不是0.5秒之后获得了,还是1秒以后,之后又恢复常规速度。这是一个突发多的例子,如果是突发没有流量,如下代码: System.out.println(r.acquire(1)); Thread.sleep(2000); System.out.println(r.acquire(1)); System.out.println(r.acquire(1)); System.out.println(r.acquire(1)); 得到如下类似的结果: image 等了两秒钟之后,令牌桶里面就积累了3个令牌,可以连续不花时间的获取出来。处理突发其实也就是在单位时间内输出恒定。这两种方式都是使用的RateLimiter的子类SmoothBursty。另一个子类是SmoothWarmingUp,它提供的有一定缓冲的流量输出方案。 复制代码/** 创建一个限流器,设置每秒放置的令牌数:2个。速率是每秒可以210的消息。 返回的RateLimiter对象可以保证1秒内不会给超过2个令牌,并且是固定速率的放置。达到平滑输出的效果 设置缓冲时间为3秒*/ RateLimiter r = RateLimiter.create(2,3,TimeUnit.SECONDS); while (true) { /** * acquire()获取一个令牌,并且返回这个获取这个令牌所需要的时间。如果桶里没有令牌则等待,直到有令牌。 * acquire(N)可以获取多个令牌。 */ System.out.println(r.acquire(1)); System.out.println(r.acquire(1)); System.out.println(r.acquire(1)); System.out.println(r.acquire(1)); }复制代码输出结果如下图,由于设置了缓冲的时间是3秒,令牌桶一开始并不会0.5秒给一个消息,而是形成一个平滑线性下降的坡度,频率越来越高,在3秒钟之内达到原本设置的频率,以后就以固定的频率输出。图中红线圈出来的3次累加起来正好是3秒左右。这种功能适合系统刚启动需要一点时间来“热身”的场景。 image Nginx 对于Nginx接入层限流可以使用Nginx自带了两个模块:连接数限流模块ngx_http_limit_conn_module和漏桶算法实现的请求限流模块ngx_http_limit_req_module。 ngx_http_limit_conn_module 我们经常会遇到这种情况,服务器流量异常,负载过大等等。对于大流量恶意的攻击访问,会带来带宽的浪费,服务器压力,影响业务,往往考虑对同一个ip的连接数,并发数进行限制。ngx_http_limit_conn_module 模块来实现该需求。该模块可以根据定义的键来限制每个键值的连接数,如同一个IP来源的连接数。并不是所有的连接都会被该模块计数,只有那些正在被处理的请求(这些请求的头信息已被完全读入)所在的连接才会被计数。 我们可以在nginx_conf的http{}中加上如下配置实现限制: 复制代码 限制每个用户的并发连接数,取名one limit_conn_zone $binary_remote_addr zone=one:10m; 配置记录被限流后的日志级别,默认error级别 limit_conn_log_level error; 配置被限流后返回的状态码,默认返回503 limit_conn_status 503;复制代码然后在server{}里加上如下代码: 限制用户并发连接数为1 limit_conn one 1;然后我们是使用ab测试来模拟并发请求: ab -n 5 -c 5 http://10.23.22.239/index.html 得到下面的结果,很明显并发被限制住了,超过阈值的都显示503: image 另外刚才是配置针对单个IP的并发限制,还是可以针对域名进行并发限制,配置和客户端IP类似。 http{}段配置 limit_conn_zone $ server_name zone=perserver:10m; server{}段配置 limit_conn perserver 1; ngx_http_limit_req_module 上面我们使用到了ngx_http_limit_conn_module 模块,来限制连接数。那么请求数的限制该怎么做呢?这就需要通过ngx_http_limit_req_module 模块来实现,该模块可以通过定义的键值来限制请求处理的频率。特别的,可以限制来自单个IP地址的请求处理频率。 限制的方法是使用了漏斗算法,每秒固定处理请求数,推迟过多请求。如果请求的频率超过了限制域配置的值,请求处理会被延迟或被丢弃,所以所有的请求都是以定义的频率被处理的。 在http{}中配置 区域名称为one,大小为10m,平均处理的请求频率不能超过每秒一次。 limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;在server{}中配置 设置每个IP桶的数量为5 limit_req zone=one burst=5;上面设置定义了每个IP的请求处理只能限制在每秒1个。并且服务端可以为每个IP缓存5个请求,如果操作了5个请求,请求就会被丢弃。 使用ab测试模拟客户端连续访问10次:ab -n 10 -c 10 http://10.23.22.239/index.html 如下图,设置了通的个数为5个。一共10个请求,第一个请求马上被处理。第2-6个被存放在桶中。由于桶满了,没有设置nodelay因此,余下的4个请求被丢弃。 http://images2015.cnblogs.com/blog/15700/201705/15700-20170501174046164-903720634.png
server { listen 8111; server_name localhost; location / { root /root/jarServer; try_files $uri $uri/ /index.html; index index.html; autoindex on; # 开启目录文件列表 autoindex_exact_size on; # 显示出文件的确切大小,单位是bytes autoindex_localtime on; # 显示的文件时间为文件的服务器时间 charset utf-8,gbk; # 避免中文乱码 } } nginx.conf文件设置。像ftp那样显示文件列表,nginx默认是不支持的,需要通过在location、server或http配置段添加额外参数: autoindex on; # 开启目录文件列表autoindex_exact_size on; # 显示出文件的确切大小,单位是bytesautoindex_localtime on; # 显示的文件时间为文件的服务器时间charset utf-8,gbk; # 避免中文乱码
Java并发问题--乐观锁与悲观锁以及乐观锁的一种实现方式-CAS 首先介绍一些乐观锁与悲观锁: 悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。再比如Java里面的同步原语synchronized关键字的实现也是悲观锁。 乐观锁:顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。 乐观锁的一种实现方式-CAS(Compare and Swap 比较并交换): 锁存在的问题: Java在JDK1.5之前都是靠 synchronized关键字保证同步的,这种通过使用一致的锁定协议来协调对共享状态的访问,可以确保无论哪个线程持有共享变量的锁,都采用独占的方式来访问这些变量。这就是一种独占锁,独占锁其实就是一种悲观锁,所以可以说 synchronized 是悲观锁。 悲观锁机制存在以下问题: 1. 在多线程竞争下,加锁、释放锁会导致比较多的上下文切换和调度延时,引起性能问题。 2. 一个线程持有锁会导致其它所有需要此锁的线程挂起。 3. 如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能风险。 对比于悲观锁的这些问题,另一个更加有效的锁就是乐观锁。其实乐观锁就是:每次不加锁而是假设没有并发冲突而去完成某项操作,如果因为并发冲突失败就重试,直到成功为止。 乐观锁: 乐观锁( Optimistic Locking )在上文已经说过了,其实就是一种思想。相对悲观锁而言,乐观锁假设认为数据一般情况下不会产生并发冲突,所以在数据进行提交更新的时候,才会正式对数据是否产生并发冲突进行检测,如果发现并发冲突了,则让返回用户错误的信息,让用户决定如何去做。 上面提到的乐观锁的概念中其实已经阐述了它的具体实现细节:主要就是两个步骤:冲突检测和数据更新。其实现方式有一种比较典型的就是 Compare and Swap ( CAS )。 CAS: CAS是乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。 CAS 操作中包含三个操作数 —— 需要读写的内存位置(V)、进行比较的预期原值(A)和拟写入的新值(B)。如果内存位置V的值与预期原值A相匹配,那么处理器会自动将该位置值更新为新值B。否则处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该位置的值。(在 CAS 的一些特殊情况下将仅返回 CAS 是否成功,而不提取当前值。)CAS 有效地说明了“ 我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。 ”这其实和乐观锁的冲突检查+数据更新的原理是一样的。 这里再强调一下,乐观锁是一种思想。CAS是这种思想的一种实现方式。 JAVA对CAS的支持: 在JDK1.5 中新增 java.util.concurrent (J.U.C)就是建立在CAS之上的。相对于对于 synchronized 这种阻塞算法,CAS是非阻塞算法的一种常见实现。所以J.U.C在性能上有了很大的提升。 以 java.util.concurrent 中的 AtomicInteger 为例,看一下在不使用锁的情况下是如何保证线程安全的。主要理解 getAndIncrement 方法,该方法的作用相当于 ++i 操作。 1 public class AtomicInteger extends Number implements java.io.Serializable { 2 private volatile int value; 3 4 public final int get() { 5 return value; 6 } 7 8 public final int getAndIncrement() { 9 for (;;) { 10 int current = get(); 11 int next = current + 1; 12 if (compareAndSet(current, next)) 13 return current; 14 } 15 } 16 17 public final boolean compareAndSet(int expect, int update) { 18 return unsafe.compareAndSwapInt(this, valueOffset, expect, update); 19 } 20 } 在没有锁的机制下,字段value要借助volatile原语,保证线程间的数据是可见性。这样在获取变量的值的时候才能直接读取。然后来看看 ++i 是怎么做到的。 getAndIncrement 采用了CAS操作,每次从内存中读取数据然后将此数据和 +1 后的结果进行CAS操作,如果成功就返回结果,否则重试直到成功为止。 而 compareAndSet 利用JNI(Java Native Interface)来完成CPU指令的操作: 1 public final boolean compareAndSet(int expect, int update) { 2 return unsafe.compareAndSwapInt(this, valueOffset, expect, update); 3 } 其中unsafe.compareAndSwapInt(this, valueOffset, expect, update);类似如下逻辑: 1 if (this == expect) { 2 this = update 3 return true; 4 } else { 5 return false; 6 } 那么比较this == expect,替换this = update,compareAndSwapInt实现这两个步骤的原子性呢? 参考CAS的原理 CAS原理: CAS通过调用JNI的代码实现的。而compareAndSwapInt就是借助C来调用CPU底层指令实现的。 下面从分析比较常用的CPU(intel x86)来解释CAS的实现原理。 下面是sun.misc.Unsafe类的compareAndSwapInt()方法的源代码: 1 public final native boolean compareAndSwapInt(Object o, long offset, 2 int expected, 3 int x); 可以看到这是个本地方法调用。这个本地方法在JDK中依次调用的C++代码为: 1 #define LOCK_IF_MP(mp) __asm cmp mp, 0 \ 2 __asm je L0 \ 3 __asm _emit 0xF0 \ 4 __asm L0: 5 6 inline jint Atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value) { 7 // alternative for InterlockedCompareExchange 8 int mp = os::is_MP(); 9 __asm { 10 mov edx, dest 11 mov ecx, exchange_value 12 mov eax, compare_value 13 LOCK_IF_MP(mp) 14 cmpxchg dword ptr [edx], ecx 15 } 16 } 如上面源代码所示,程序会根据当前处理器的类型来决定是否为cmpxchg指令添加lock前缀。如果程序是在多处理器上运行,就为cmpxchg指令加上lock前缀(lock cmpxchg)。反之,如果程序是在单处理器上运行,就省略lock前缀(单处理器自身会维护单处理器内的顺序一致性,不需要lock前缀提供的内存屏障效果)。 CAS缺点: 1. ABA问题: 比如说一个线程one从内存位置V中取出A,这时候另一个线程two也从内存中取出A,并且two进行了一些操作变成了B,然后two又将V位置的数据变成A,这时候线程one进行CAS操作发现内存中仍然是A,然后one操作成功。尽管线程one的CAS操作成功,但可能存在潜藏的问题。如下所示: 现有一个用单向链表实现的堆栈,栈顶为A,这时线程T1已经知道A.next为B,然后希望用CAS将栈顶替换为B: head.compareAndSet(A,B); 在T1执行上面这条指令之前,线程T2介入,将A、B出栈,再pushD、C、A,此时堆栈结构如下图,而对象B此时处于游离状态: 此时轮到线程T1执行CAS操作,检测发现栈顶仍为A,所以CAS成功,栈顶变为B,但实际上B.next为null,所以此时的情况变为: 其中堆栈中只有B一个元素,C和D组成的链表不再存在于堆栈中,平白无故就把C、D丢掉了。 从Java1.5开始JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法作用是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。 1 public boolean compareAndSet( 2 V expectedReference,//预期引用 3 4 V newReference,//更新后的引用 5 6 int expectedStamp, //预期标志 7 8 int newStamp //更新后的标志 9 ) 实际应用代码: 1 private static AtomicStampedReference<Integer> atomicStampedRef = new AtomicStampedReference<Integer>(100, 0); 2 3 ........ 4 5 atomicStampedRef.compareAndSet(100, 101, stamp, stamp + 1); 2. 循环时间长开销大: 自旋CAS(不成功,就一直循环执行,直到成功)如果长时间不成功,会给CPU带来非常大的执行开销。如果JVM能支持处理器提供的pause指令那么效率会有一定的提升,pause指令有两个作用,第一它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率。 3. 只能保证一个共享变量的原子操作: 当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁,或者有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。比如有两个共享变量i=2,j=a,合并一下ij=2a,然后用CAS来操作ij。从Java1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行CAS操作。 CAS与Synchronized的使用情景: 1、对于资源竞争较少(线程冲突较轻)的情况,使用synchronized同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗cpu资源;而CAS基于硬件实现,不需要进入内核,不需要切换线程,操作自旋几率较少,因此可以获得更高的性能。 2、对于资源竞争严重(线程冲突严重)的情况,CAS自旋的概率会比较大,从而浪费更多的CPU资源,效率低于synchronized。 补充: synchronized在jdk1.6之后,已经改进优化。synchronized的底层实现主要依靠Lock-Free的队列,基本思路是自旋后阻塞,竞争切换后继续竞争锁,稍微牺牲了公平性,但获得了高吞吐量。在线程冲突较少的情况下,可以获得和CAS类似的性能;而线程冲突严重的情况下,性能远高于CAS。 concurrent包的实现: 由于java的CAS同时具有 volatile 读和volatile写的内存语义,因此Java线程之间的通信现在有了下面四种方式: 1. A线程写volatile变量,随后B线程读这个volatile变量。 2. A线程写volatile变量,随后B线程用CAS更新这个volatile变量。 3. A线程用CAS更新一个volatile变量,随后B线程用CAS更新这个volatile变量。 4. A线程用CAS更新一个volatile变量,随后B线程读这个volatile变量。 Java的CAS会使用现代处理器上提供的高效机器级别原子指令,这些原子指令以原子方式对内存执行读-改-写操作,这是在多处理器中实现同步的关键(从本质上来说,能够支持原子性读-改-写指令的计算机器,是顺序计算图灵机的异步等价机器,因此任何现代的多处理器都会去支持某种能对内存执行原子性读-改-写操作的原子指令)。同时,volatile变量的读/写和CAS可以实现线程之间的通信。把这些特性整合在一起,就形成了整个concurrent包得以实现的基石。如果我们仔细分析concurrent包的源代码实现,会发现一个通用化的实现模式: 1. 首先,声明共享变量为volatile; 2. 然后,使用CAS的原子条件更新来实现线程之间的同步; 3. 同时,配合以volatile的读/写和CAS所具有的volatile读和写的内存语义来实现线程之间的通信。 AQS,非阻塞数据结构和原子变量类(java.util.concurrent.atomic包中的类),这些concurrent包中的基础类都是使用这种模式来实现的,而concurrent包中的高层类又是依赖于这些基础类来实现的。从整体来看,concurrent包的实现示意图如下: JVM中的CAS(堆中对象的分配): Java调用new object()会创建一个对象,这个对象会被分配到JVM的堆中。那么这个对象到底是怎么在堆中保存的呢? 首先,new object()执行的时候,这个对象需要多大的空间,其实是已经确定的,因为java中的各种数据类型,占用多大的空间都是固定的(对其原理不清楚的请自行Google)。那么接下来的工作就是在堆中找出那么一块空间用于存放这个对象。 在单线程的情况下,一般有两种分配策略: 1. 指针碰撞:这种一般适用于内存是绝对规整的(内存是否规整取决于内存回收策略),分配空间的工作只是将指针像空闲内存一侧移动对象大小的距离即可。 2. 空闲列表:这种适用于内存非规整的情况,这种情况下JVM会维护一个内存列表,记录哪些内存区域是空闲的,大小是多少。给对象分配空间的时候去空闲列表里查询到合适的区域然后进行分配即可。 但是JVM不可能一直在单线程状态下运行,那样效率太差了。由于再给一个对象分配内存的时候不是原子性的操作,至少需要以下几步:查找空闲列表、分配内存、修改空闲列表等等,这是不安全的。解决并发时的安全问题也有两种策略: 1. CAS:实际上虚拟机采用CAS配合上失败重试的方式保证更新操作的原子性,原理和上面讲的一样。 2. TLAB:如果使用CAS其实对性能还是会有影响的,所以JVM又提出了一种更高级的优化策略:每个线程在Java堆中预先分配一小块内存,称为本地线程分配缓冲区(TLAB),线程内部需要分配内存时直接在TLAB上分配就行,避免了线程冲突。只有当缓冲区的内存用光需要重新分配内存的时候才会进行CAS操作分配更大的内存空间。 虚拟机是否使用TLAB,可以通过-XX:+/-UseTLAB参数来进行配置(jdk5及以后的版本默认是启用TLAB的)。 转自:http://www.cnblogs.com/qjjazry/p/6581568.html
一个list集合,添加到数据库中,在mybatis中就是mapper.xml中的配置: <insert id="insertByList" parameterType="java.util.List"> insert into action_table_former_relation (id, action_id, target_table,former_table, former_tables_actions_id, task_owner, create_time, update_time) values<foreach collection="list" item="record" index= "index" separator =","> ( #{record.id,jdbcType=BIGINT}, #{record.actionId,jdbcType=INTEGER}, #{record.targetTable,jdbcType=VARCHAR}, #{record.formerTable,jdbcType=VARCHAR}, #{record.formerTablesActionsId,jdbcType=VARCHAR}, #{record.taskOwner,jdbcType=VARCHAR}, #{record.createTime,jdbcType=TIMESTAMP}, #{record.updateTime,jdbcType=TIMESTAMP} )</foreach></insert> 对应的Mapper.java int insertByList(List<ActionTableFormerRelation> records); 说明Mapper.java中只有一个参数,所以XML文件中可以用collection可以使用list标识。 foreach的主要用在构建in条件中,它可以在SQL语句中进行迭代一个集合。 foreach元素的属性主要有 item,index,collection,open,separator,close。 item表示集合中每一个元素进行迭代时的别名, index指 定一个名字,用于表示在迭代过程中,每次迭代到的位置, open表示该语句以什么开始,separator表示在每次进行迭代之间以什么符号作为分隔 符, close表示以什么结束, 在使用foreach的时候最关键的也是最容易出错的就是collection属性,该属性是必须指定的, 但是在不同情况 下,该属性的值是不一样的,主要有一下3种情况: 1.如果传入的是单参数且参数类型是一个List的时候,collection属性值为list 2.如果传入的是单参数且参数类型是一个array数组的时候,collection的属性值为array 3.如果传入的参数是多个的时候,我们就需要把它们封装成一个Map了,当然单参数也可以封装成map
git rebase merge 一、基本 git rebase用于把一个分支的修改合并到当前分支。 假设你现在基于远程分支"origin",创建一个叫"mywork"的分支。 $ git checkout -b mywork origin 假设远程分支"origin"已经有了2个提交,如图 图1 现在我们在这个分支做一些修改,然后生成两个提交(commit). $ 修改文件 $ git commit $ 修改文件 $ git commit 但是与此同时,有些人也在"origin"分支上做了一些修改并且做了提交了. 这就意味着"origin"和"mywork"这两个分支各自"前进"了,它们之间"分叉"了 图2 在这里,你可以用"pull"命令把"origin"分支上的修改拉下来并且和你的修改合并; 结果看起来就像一个新的"合并的提交"(merge commit): 图3 但是,如果你想让"mywork"分支历史看起来像没有经过任何合并一样,你也许可以用 git rebase: $ git checkout mywork $ git rebase origin 这些命令会把你的"mywork"分支里的每个提交(commit)取消掉,并且把它们临时 保存为补丁(patch)(这些补丁放到".git/rebase"目录中),然后把"mywork"分支更新 为最新的"origin"分支,最后把保存的这些补丁应用到"mywork"分支上。 图4 当'mywork'分支更新之后,它会指向这些新创建的提交(commit),而那些老的提交会被丢弃。 如果运行垃圾收集命令(pruning garbage collection), 这些被丢弃的提交就会删除. (请查看 git gc) 图5 二、解决冲突 在rebase的过程中,也许会出现冲突(conflict). 在这种情况,Git会停止rebase并会让你去解决 冲突;在解决完冲突后,用"git-add"命令去更新这些内容的索引(index), 然后,你无需执行 git-commit,只要执行: $ git rebase --continue 这样git会继续应用(apply)余下的补丁。 在任何时候,你可以用--abort参数来终止rebase的行动,并且"mywork" 分支会回到rebase开始前的状态。 $ git rebase --abort 三、git rebase和git merge的区别 现在我们可以看一下用合并(merge)和用rebase所产生的历史的区别: 图6 当我们使用Git log来参看commit时,其commit的顺序也有所不同。 假设C3提交于9:00AM,C5提交于10:00AM,C4提交于11:00AM,C6提交于12:00AM, 对于使用git merge来合并所看到的commit的顺序(从新到旧)是:C7 ,C6,C4,C5,C3,C2,C1 对于使用git rebase来合并所看到的commit的顺序(从新到旧)是:C7 ,C6‘,C5',C4,C3,C2,C1 因为C6'提交只是C6提交的克隆,C5'提交只是C5提交的克隆, 从用户的角度看使用git rebase来合并后所看到的commit的顺序(从新到旧)是:C7 ,C6,C5,C4,C3,C2,C1 另外,我们在使用git pull命令的时候,可以使用--rebase参数,即git pull --rebase,这里表示把你的本地当前分支里的每个提交(commit)取消掉,并且把它们临时 保存为补丁(patch)(这些补丁放到".git/rebase"目录中),然后把本地当前分支更新 为最新的"origin"分支,最后把保存的这些补丁应用到本地当前分支上。 使用方式: $ git pull --rebase <远程主机名> <远程分支名>:<本地分支名>
原始状态 假如我们要在 master 分支上进行开发,在远端的 master 分支上右键,检出 一个自己的开发分支 dev-1 做一些开发,提交到本地,不要推送(push)到远端 切换到 master 分支,拉取远端的 master 更新,(这里另一个同事在 master 分支上提交了 dev 2 的更新) 切换到自己的开发分支 dev-1 选中 master 分支,右键,选择 将当前变更变基到 master 如果有冲突则合并冲突, 点击左上角的加号,选择 继续变基 此时我们的本地更新是基于最新的 master 分支 最后’推送’我们的开发分支 dev-1到远端 切换到master分支,点击 拉取,拉取 dev-1 的更新到 master 分支 再推送 master 分支,就保证了git分支的整洁 sourceTree rebase git
Java内存模型封装了底层的实现后提供给开发人员一系列和并发处理相关的关键字,,比如volatile、Synchronized、final等,在开发多线程代码的时候,我们可以直接使用 这些关键词来控制并发,从而不需要关心底层的编译器优化、缓存一致性的问题了,所以JMM除了定义了一套规范外,还给开发人员提供了一套在底层封装后的开放的指令。 一.原子性 在java中提供了两个高级的字节码指令monitorenter和monitorexit,使用对应的关键字Synchronized来保证代码块内的操作是原子的 二.可见性 在Java中可以使用volatile关键字来保证多线程操作时变量的可见性。volatile的功能是被其修饰的变量在被修改后可以立即同步到主内存,而被其修饰的变量在每次使用之前都会从主内存刷新。除此之外,synchronized和final两个关键字也可以实现可见性 。volatile也可以看作是轻量级的锁,在其内部使用了Lock指令来解决可见性问题。 volatile关键字修饰的共享变量,在进行写操作的时候会多出一个lock前缀的汇编指令,这个指令会触发总线锁或者缓存锁,通过缓存一致性协议来解决可见性问题。对于声明了volatile的变量进行写操作时,JVM就会向处理器发送一条Lock前缀的指令,把这个变量所在的缓存行的数据写回到系统内存,再根据MESI的缓存一致性协议,来保证多核CPU下的各个高速缓存中的数据的 一致性。 volatile用法总结: 对一些可变的对象,属性做修饰,修饰以后能保证可见性和防止内存重排序。 保证可见性:会使得#Lock指令(汇编指令)基于CPU的缓存锁和MESI的协议实现缓存一致性。 防止内存重排:内存屏障。 volatile对复合操作无法保证原子性。对单操作可以保证原子性。 对一个原子递增的操作,会分为三个步骤:1.读取volatile变量的值到local;2.增加变量的值;3.把local的值写回让 其他线程可见 volatile使用场景: 1.停止线程,主线程改变了变量的值要对子线程可见,因为这个变量是子线程停止线程的标志。 三.有序性 在Java中,可以使用synchronized和volatile来保证多线程之间操作的有序性。只是实现方式有所区别: volatile关键字会禁止指令重排,synchronized关键字保证同一时刻只允许一个线程的操作。 volatile防止指令重排: 指令重排的目的是为了最大化的提高CPU利用率以及性能,CPU的乱序执行优化在单核时代并不影响正确性,但是 在多核时代,多线程能够在不同的核心上实现真正的并行,一旦线程之间共享数据,再加上CPU的乱序执行优化(CPU执行汇编指令),就可能会出现一些不可预料的问题。 指令重排序必须要遵循的原则是,不影响代码执行的最终结果。编译器和处理器不会改变存在数据依赖关系(仅仅是针对单个处理器中执行的指令和操作)的两个操作的执行顺序,不管怎么重排序,单线程程序的执行结果不会改变,编译器、处理器都必 须遵守这个原则。 指令重排序,从代码开始编写到最后执行出结果的过程,特点是能提升性能和合理利用CPU资源,不会改变代码原有的语义,包括编译器的优化重排序:会导致可见性问题(保证语义的正确性);CPU的指令重排序:CPU包括寄存器(存储本地变量,函数的参数等)和高速缓存l1l2l3(提高CPU和内存之间交互的性能),缓存离CPU越远性能越低,效率排名,寄存器>L1>L2>L3;内存系统的重排序: L1,l2缓存不共享,用缓存一致性协议MESI解决,当一个CPU在进行写操作的时候,其他的CPU在等待,为了减少等待时间,引入了storebuffer.loadbuffer来缓冲等待的时间,提高性能。相对于MESI来说,这就是异步机制,而MESI可以看成是同步阻塞的。 L2,L3在多核心CPU上缓存是共享的。 重排序会导致可见性问题,但是重排序带来的问题的严重性可能远远大于可见性,因为并不是 所有指令都是简单的读或写,比如DCL(synchnozied双重检查锁)的部分初始化问题。 现在需要解决两个问题,一个是编译器的优化乱序,另一个是CPU的执行乱序,我们可以分别使用优化 屏障和内存屏障这两个机制来解决。 内存屏障 先从CPU层面来了解一下什么是内存屏障? CPU的乱序执行,本质还是由于在多CPU的机器上,每个CPU都存在cache,当某个数据第一次被一个 CPU获取时,由于在该CPU缓存中不存在这个数据,就会从内存中去获取,从而被加载到CPU高速缓存中后就能从缓存中快速访 问。当某个CPU进行写操作时,必须确保其他的CPU已经将这个数据从他们各自的缓存中移除,这样才能让其他CPU 在各自的缓存中安全的修改数据。显然,存在多个cache时,我们必须通过缓存一致性协议来避免数据不一致的问题,而这 个通讯的过程就可能导致乱序访问,也就是运行时的内存乱序访问。 现在的CPU架构一般都提供了内存屏障功能,不同CPU架构的内存屏障是不一样 的,有的CPU支持内存强一致性,就不需要内存屏障了。 内存屏障分为:写屏障(store barrier)、读屏障(load barrier)和全屏障(Full Barrier),主要的作用有两点:防止指令之间的重排序和保证数据的可见性 。 写屏障(store barrier):相当于storestore barrier, 强制所有在storestore内存屏障之前的所有执行,都要在该 内存屏障之前执行,并发送缓存失效的信号。所有在storestore barrier指令之后的store指令,都必须在 storestore barrier屏障之前的指令执行完后再被执行。也就是禁止了写屏障前后的指令进行重排序,使得所有 store barrier之前发生的内存更新都是可见的(修改值可见及操作结果可见) 读屏障(load barrier):相当于loadload barrier,强制所有在load barrier读屏障之后的load指令,都在load barrier屏障之后执行。也就是禁止对load barrier读屏障前后的load指令进行重排序, 配合store barrier,使得所 有store barrier之前发生的内存更新,对load barrier之后的load操作是可见的 全屏障(Full Barrier):相当于storeload,是一个全能型的屏障,因为它同时具备前面两种屏障的效果。强制了 所有在storeload barrier之前的store/load指令,都在该屏障之前被执行,所有在该屏障之后的的store/load指 令,都在该屏障之后被执行。禁止对storeload屏障前后的指令进行重排序。 综上所述:内存屏障只是解决了顺序一致性问题,不解决缓存一致性问题,缓存一致性是由cpu的缓存锁以及MESI协议来 完成的。而缓存一致性协议只关心缓存一致性,不关心顺序一致性。 优化屏障 编译器层面如何解决指令重排序问题? 在编译器层面,通过volatile关键字,取消编译器层面的缓存和重排序。保证编译程序时在优化屏障之前的指令不 会在优化屏障之后执行。这就保证了编译时期的优化不会影响到实际代码运行时的逻辑顺序。 如果硬件架构本身已经保证了内存可见性,那么volatile就是一个空标记,不会插入相关语义的内存屏障。如果硬 件架构本身不进行处理器重排序,有更强的重排序语义,那么volatile就是一个空标记,不会插入相关语义的内存 屏障。 在JMM中把内存屏障指令分为4类,是用来解决编译器的重排序和CPU的指令重排序问题的,通过在不同的语义下使用不同的内存屏障来禁止特定类型的处理器重排序,从 而来保证内存的可见性。 LoadLoad Barriers, load1 ; LoadLoad; load2 , 确保load1数据的装载优先于load2及所有后续装载指令的装载 StoreStore Barriers,store1; storestore;store2 , 确保store1数据对其他处理器可见优先于store2及所有后续存储 指令的存储 LoadStore Barries, load1;loadstore;store2, 确保load1数据装载优先于store2以及后续的存储指令刷新到内存 StoreLoad Barries, store1; storeload;load2, 确保store1数据对其他处理器变得可见, 优先于load2及所有后续 装载指令的装载;这条内存屏障指令是一个全能型的屏障,它同时具 有其他3条屏障的效果 如果加了这两个操作loadload,,loadstore,操作之前的指令必须要在操作之后的指令之前执行完,即后续的指令必须要晚于这个屏障之后去执行。 原文:https://blog.csdn.net/lx_Frolf/article/details/82686201 版权声明:本文为博主原创文章,转载请附上博文链接!
/** * 去掉文本中的html标签 * * @param inputString * @return */ public static String html2Text(String inputString) { if (StringUtils.isEmpty(inputString)) { return null; } String htmlStr = inputString; String textStr = ""; java.util.regex.Pattern p_script; java.util.regex.Matcher m_script; java.util.regex.Pattern p_style; java.util.regex.Matcher m_style; java.util.regex.Pattern p_html; java.util.regex.Matcher m_html; java.util.regex.Pattern p_html1; java.util.regex.Matcher m_html1; try { String regEx_script = "<[\\s]*?script[^>]*?>[\\s\\S]*?<[\\s]*?\\/[\\s]*?script[\\s]*?>"; // 定义script的正则表达式{或<script[^>]*?>[\\s\\S]*?<\\/script> // } String regEx_style = "<[\\s]*?style[^>]*?>[\\s\\S]*?<[\\s]*?\\/[\\s]*?style[\\s]*?>"; // 定义style的正则表达式{或<style[^>]*?>[\\s\\S]*?<\\/style> // } String regEx_html = "<[^>]+>"; // 定义HTML标签的正则表达式 String regEx_html1 = "<[^>]+"; p_script = Pattern.compile(regEx_script, Pattern.CASE_INSENSITIVE); m_script = p_script.matcher(htmlStr); htmlStr = m_script.replaceAll(""); // 过滤script标签 p_style = Pattern .compile(regEx_style, Pattern.CASE_INSENSITIVE); m_style = p_style.matcher(htmlStr); htmlStr = m_style.replaceAll(""); // 过滤style标签 p_html = Pattern.compile(regEx_html, Pattern.CASE_INSENSITIVE); m_html = p_html.matcher(htmlStr); htmlStr = m_html.replaceAll(""); // 过滤html标签 p_html1 = Pattern .compile(regEx_html1, Pattern.CASE_INSENSITIVE); m_html1 = p_html1.matcher(htmlStr); htmlStr = m_html1.replaceAll(""); // 过滤html标签 textStr = htmlStr; // 替换&amp;nbsp; textStr = textStr.replaceAll("&amp;", "").replaceAll("nbsp;", ""); } catch (Exception e) { System.err.println("Html2Text: " + e.getMessage()); } return textStr;// 返回文本字符串 }
1.mac系统,idea安装目录: localhost:bin ningzhu$ pwd /Applications/IntelliJ IDEA.app/Contents/bin 2.下载破解(crack) jar 包 当前最新补丁:idea15-2018.1.5激活补丁:http://idea.lanyus.com/jar/JetbrainsCrack-2.10-release-enc.jar http://idea.lanyus.com/ 3.下载好了的 crack jar包 放到 idea 的 bin 目录下,此处放入/Applications/IntelliJ IDEA.app/Contents/bin目录下 localhost:Downloads ningzhu$ cp /Users/ningzhu/Downloads/JetbrainsCrack-2.10-release-enc.jar /Applications/IntelliJ\ IDEA.app/Contents/bin/ 4.修改 bin 目录下的 idea.vmoptions 文件 把 idea.vmoptions 文件加一行如下的配置,根据你保存的文件名自行变更 -javaagent:../bin/JetbrainsCrack-2.10-release-enc.jar 5.在 hosts 文件里面添加如下配置 0.0.0.0 account.jetbrains.com 6.打开 idea,输入如下激活码 去这里获取最新的注册码 http://idea.lanyus.com/getkey 以上。
在开发中,测试提出了一个bug,在某搜索中,搜索 _,结果把不包含下划线的内容也查了出来!这是什么问题呢?今天特此记录一下,顺便给大家分享下! 原sql:select * from table where condition like '%_%'; 结果: 搜索出来的是全部。 原来,在mysql 中,下划线 _ 代表 全部 基本上等同于 *。 解决方案: 对sql 用 \ 进行转义: 最终达到的 sql效果 :select * from table where condition like '%\_%'; 在此之前,在程序中对该关键字进行转义。 方法如下:(我接触的是nodejs,此处就用js来表达); const search = function(str){ if(str){ let aStr = Array.from(str); // 将字符转成数组 for(let i = 0;i < aStr.length; i++){ // 遍历数组 if(aStr[i]=='_'){ // 如果检测到下划线 aStr[i] = '\\_'; // 此处需要转译 \_ 所以用两个\ } } str = aStr.join(''); // 把数组转成字符串 } // 下面执行 你的数据库操作 ... }
一、优化Connector http://www.aikaiyuan.com/8466.html tomcat的运行模式有3种 1)bio 默认的模式,性能非常低下,没有经过任何优化处理和支持. 2)nio 利用java的异步io护理技术,noblocking IO技术. 想运行在该模式下,直接修改server.xml里的Connector节点,修改protocol为 protocol=”org.apache.coyote.http11.Http11NioProtocol” 启动后,就可以生效。 ( http://www.365mini.com/page/tomcat-connector-mode.htm ) 3)apr 安装起来最困难,但是从操作系统级别来解决异步的IO问题,大幅度的提高性能. 二、Apache Tomcat 8 WebSocket How-To 中文翻译版 http://blog.csdn.net/hills/article/details/39368951 Java WebSocket 1.0 规范要求在一个不同的线程上执行到发起写入的线程的异步写入回调。 由于容器线程池不是通过Servlet API被暴露,因此WebSocket实现必须提供自己的线程池。该线程池可以通过下列servlet context初始化参数被控制: org.apache.tomcat.websocket.executorCoreSize: executor线程池的核心大小。如果不设置,则默认为0。 org.apache.tomcat.websocket.executorMaxSize:executor线程池所允许的最大值。如果不设置,则默认为200。 org.apache.tomcat.websocket.executorKeepAliveTimeSeconds:executor线程池中空闲进程所保留的最大时间。如果未指定,则默认为60秒。 设置方法: web.xml中 <!--websocket executor 线程池的核心容量大小 --> <context-param> <param-name>org.apache.tomcat.websocket.executorCoreSize</param-name> <param-value>200</param-value> </context-param> <!--websocket executor 线程池的最大容量大小 --> <context-param> <param-name>org.apache.tomcat.websocket.executorMaxSize</param-name> <param-value>1000</param-value> </context-param> 1 2 3 4 5 6 7 8 9 10 三、设置最大线程数 apache-tomcat-7.0.73\conf\server.xml <Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" /> 1 2 3 修改为: <Executor name="tomcatThreadPool" namePrefix="catalina-exec-" maxThreads="1000" minSpareThreads="4"/> <Connector executor="tomcatThreadPool" port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />总结:我采用第三种方案优化,使用tomcat8.5.3 , <Executor name="tomcatThreadPool" namePrefix="catalina-exec-" maxThreads="500" minSpareThreads="20" /> <Connector executor="tomcatThreadPool" port="8080" protocol="HTTP/1.1" connectionTimeout="40000" redirectPort="8443" acceptCount="1000" /> 静待效果中参考:https://blog.csdn.net/jkxqj/article/details/72640037
背景 最近在做Spring Websocket后台程序的压力测试,但是当并发数目在10个左右时,服务器的CPU使用率一直在160%+,出现这个问题后,一开始很纳闷,虽然服务器配置很低,但也不至于只有10个并发吧。。服务器的主要配置如下: CPU:2核 Intel(R) Xeon(R) CPU E5-2682 v4 @ 2.50GHz 内存:4GB 使用top命令查看资源占用情况,发现pid为9499的进程占用了大量的CPU资源,CPU占用率高达170%,内存占用率也达到了40%以上。 问题排查 首先使用jstat命令来查看一下JVM的内存情况,如下所示: jstat -gcutil 9499 1000 S0 S1 E O M CCS YGC YGCT FGC FGCT GCT 0.00 0.00 100.00 94.92 97.44 95.30 24 0.651 8129 1147.010 1147.661 0.00 0.00 100.00 94.92 97.44 95.30 24 0.651 8136 1148.118 1148.768 0.00 0.00 100.00 94.92 97.44 95.30 24 0.651 8143 1149.139 1149.789 0.00 0.00 100.00 94.92 97.44 95.30 24 0.651 8150 1150.148 1150.799 0.00 0.00 100.00 94.92 97.44 95.30 24 0.651 8157 1151.160 1151.811 0.00 0.00 100.00 94.92 97.44 95.30 24 0.651 8164 1152.180 1152.831 0.00 0.00 100.00 94.92 97.44 95.30 24 0.651 8170 1153.051 1153.701 0.00 0.00 100.00 94.92 97.45 95.30 24 0.651 8177 1154.061 1154.712 0.00 0.00 100.00 94.93 97.45 95.30 24 0.651 8184 1155.077 1155.728 0.00 0.00 100.00 94.93 97.45 95.30 24 0.651 8191 1156.089 1156.739 0.00 0.00 100.00 94.93 97.45 95.30 24 0.651 8198 1157.134 1157.785 0.00 0.00 100.00 94.93 97.45 95.30 24 0.651 8205 1158.149 1158.800 0.00 0.00 100.00 94.93 97.45 95.30 24 0.651 8212 1159.156 1159.807 0.00 0.00 100.00 94.93 97.45 95.30 24 0.651 8219 1160.179 1160.830 0.00 0.00 100.00 94.93 97.45 95.30 24 0.651 8225 1161.047 1161.697 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 可以看到,Eden区域内存占用高达100%,Old区占用高达94.9%,元数据空间区域占用高达97.4%。Young GC的次数一直是24,但是Full GC的次数却高达几千次,而且在程序运行期间,频繁发生Full GC,导致FGC的次数一直增加。 虽然FGC次数一直在增加,但是却没有回收到任何空间,导致一直在运行FGC,根据这些信息,基本可以确定是程序代码上出现了问题,可能存在内存泄漏问题,或是创建了不合理的大型对象。 基于上述分析,我们知道应该是程序的问题,要定位问题,我们需要先获取后台程序的堆转储快照,我们使用jmap工具来生成Java堆转储快照: jmap -dump:live,format=b,file=problem.bin 9499 Dumping heap to /root/problem.bin ... Heap dump file created 1 2 3 4 下面就是对Java堆转储快照进行分析了,我使用了Eclipse Memory Analyzer(MAT)来对快照进行分析,在MAT打开快照文件之前,要将其后缀名修改为hprof,打开文件之后,可以发现如下问题: 9 instances of "org.apache.tomcat.websocket.server.WsFrameServer", loaded by "java.net.URLClassLoader @ 0xc533dc70" occupy 566,312,616 (75.57%) bytes. Biggest instances: •org.apache.tomcat.websocket.server.WsFrameServer @ 0xce4ef270 - 62,923,624 (8.40%) bytes. •org.apache.tomcat.websocket.server.WsFrameServer @ 0xce4f1588 - 62,923,624 (8.40%) bytes. •org.apache.tomcat.websocket.server.WsFrameServer @ 0xcf934b10 - 62,923,624 (8.40%) bytes. •org.apache.tomcat.websocket.server.WsFrameServer @ 0xcf936e28 - 62,923,624 (8.40%) bytes. •org.apache.tomcat.websocket.server.WsFrameServer @ 0xcf9620f8 - 62,923,624 (8.40%) bytes. •org.apache.tomcat.websocket.server.WsFrameServer @ 0xd21c6158 - 62,923,624 (8.40%) bytes. •org.apache.tomcat.websocket.server.WsFrameServer @ 0xd5dc8b30 - 62,923,624 (8.40%) bytes. •org.apache.tomcat.websocket.server.WsFrameServer @ 0xd727bcf8 - 62,923,624 (8.40%) bytes. •org.apache.tomcat.websocket.server.WsFrameServer @ 0xe768bd68 - 62,923,624 (8.40%) bytes. 1 2 3 4 5 6 7 8 9 10 11 12 13 可以看到WsFrameServer的实例占用了75.57%的内存空间,而这也就是问题所在了,那WsFrameServer为什么会占用这么高的内存呢?我继续用MAT来查看WsFrameServer实例的内存分布情况: 可以看到,WsFrameServer实例中,有两个类型的变量占了WsFrameServer的绝大部分,它们分别是java.nio.HeapCharBuffer类的实例变量messageBufferText、java.nio.HeapByteBuffer类的实例变量messageBufferBinary。 WsFrameServer继承自WsFrameBase ,messageBufferText和messageBufferBinary属性就在WsFrameBase里,然后我们来debug程序,看看这两个属性是如何被赋值的。 public WsFrameBase(WsSession wsSession, Transformation transformation) { inputBuffer = ByteBuffer.allocate(Constants.DEFAULT_BUFFER_SIZE); inputBuffer.position(0).limit(0); messageBufferBinary = ByteBuffer.allocate(wsSession.getMaxBinaryMessageBufferSize()); messageBufferText = CharBuffer.allocate(wsSession.getMaxTextMessageBufferSize()); wsSession.setWsFrame(this); this.wsSession = wsSession; Transformation finalTransformation; if (isMasked()) { finalTransformation = new UnmaskTransformation(); } else { finalTransformation = new NoopTransformation(); } if (transformation == null) { this.transformation = finalTransformation; } else { transformation.setNext(finalTransformation); this.transformation = transformation; } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 我们首先看debug结果: 可以看到,这两个变量的capacity都是20971520,它们是根据WsSession返回的大小来分配大小的,我们来看WsSession的方法的返回值: private volatile int maxBinaryMessageBufferSize = Constants.DEFAULT_BUFFER_SIZE; private volatile int maxTextMessageBufferSize = Constants.DEFAULT_BUFFER_SIZE; static final int DEFAULT_BUFFER_SIZE = Integer.getInteger( "org.apache.tomcat.websocket.DEFAULT_BUFFER_SIZE", 8 * 1024) .intValue(); 1 2 3 4 5 6 7 8 这两个变量的大小默认都是8192,那如果它们只占用8K的内存大小,应该也不会出现问题啊,那这两个变量一定是在其他地方被修改了,我们继续看源代码,在WsSession的构造方法中有如下两行代码: this.maxBinaryMessageBufferSize = webSocketContainer.getDefaultMaxBinaryMessageBufferSize(); this.maxTextMessageBufferSize = webSocketContainer.getDefaultMaxTextMessageBufferSize(); 1 2 @Override public int getDefaultMaxBinaryMessageBufferSize() { return maxBinaryMessageBufferSize; } @Override public int getDefaultMaxTextMessageBufferSize() { return maxTextMessageBufferSize; } 1 2 3 4 5 6 7 8 9 webSocketContainer是在WsSession的构造方法中传入的,webSocketContainer这两个方法分别返回maxBinaryMessageBufferSize和maxTextMessageBufferSize的值,它们默认为: private int maxBinaryMessageBufferSize = Constants.DEFAULT_BUFFER_SIZE; private int maxTextMessageBufferSize = Constants.DEFAULT_BUFFER_SIZE; 1 2 即这两个方法的默认返回值仍然是Constants.DEFAULT_BUFFER_SIZE,即8192,那它们是在哪里改变成20971520了呢? WsWebSocketContainer类中还有以下几个方法: @Override public int getDefaultMaxBinaryMessageBufferSize() { return maxBinaryMessageBufferSize; } @Override public void setDefaultMaxBinaryMessageBufferSize(int max) { maxBinaryMessageBufferSize = max; } @Override public int getDefaultMaxTextMessageBufferSize() { return maxTextMessageBufferSize; } @Override public void setDefaultMaxTextMessageBufferSize(int max) { maxTextMessageBufferSize = max; } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 这几个方法分别可以获取和设置maxBinaryMessageBufferSize和maxTextMessageBufferSize的值,那是不是通过这几个方法来修改的值呢? ServletServerContainerFactoryBean类中有如下一段代码: public void afterPropertiesSet() { Assert.state(this.servletContext != null, "A ServletContext is required to access the javax.websocket.server.ServerContainer instance"); this.serverContainer = (ServerContainer) this.servletContext.getAttribute( "javax.websocket.server.ServerContainer"); Assert.state(this.serverContainer != null, "Attribute 'javax.websocket.server.ServerContainer' not found in ServletContext"); if (this.asyncSendTimeout != null) { this.serverContainer.setAsyncSendTimeout(this.asyncSendTimeout); } if (this.maxSessionIdleTimeout != null) { this.serverContainer.setDefaultMaxSessionIdleTimeout(this.maxSessionIdleTimeout); } if (this.maxTextMessageBufferSize != null) { this.serverContainer.setDefaultMaxTextMessageBufferSize(this.maxTextMessageBufferSize); } if (this.maxBinaryMessageBufferSize != null) { this.serverContainer.setDefaultMaxBinaryMessageBufferSize(this.maxBinaryMessageBufferSize); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 这个方法将在bean所有的属性被初始化后调用,其实这两个值就是在这修改的了。 为什么这么说呢,我们看着两个截图: 对比这两张图片可知,WsSession的构造方法中传入的wsWebSocketContainer与项目启动时的serverContainer是同一个实例。所以,在afterPropertiesSet()方法中设置的值就是在wsWebSocketContainer中设置的值。 ServletServerContainerFactoryBean类的相关属性如下: @Nullable private Integer maxTextMessageBufferSize; @Nullable private Integer maxBinaryMessageBufferSize; 1 2 3 4 5 这两个属性的初始值是在servlet中设置的: <bean class="org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean"> <property name="maxTextMessageBufferSize" value="20971520"/> <property name="maxBinaryMessageBufferSize" value="20971520"/> </bean> 1 2 3 4 总结 通过上述分析,也就解释了为什么WsFrameServer占用了很大的内存。那程序中为什么一开始将这两个值设置这么大呢?原因是在很久以前,我们刚测试Websocket通信时,发现只能传输小于8K的消息,大于8K的消息都不能进行传输,所以我们干脆把它调大,也就直接设置为了20M,这也就导致了现在的这个问题。 但是程序中发送的消息大小都是100K+的,那我也不能将他们设置太小,所以我们将其改小,设置为200K,然后重新测试,能够达到50并发。但是,50并发感觉还是不太行,不知道能不能有其他的解决办法~_~我再想想。
hadoop采集metrics接口 转至元数据结尾转至元数据起始 系统采用的,hadoop采集metrics接口-每分钟 namenode http://%s:50070/jmx?qry=Hadoop:service=NameNode,name=FSNamesystemState http://%s:50070/jmx?qry=Hadoop:service=NameNode,name=NameNodeActivity http://%s:50070/jmx?qry=Hadoop:service=NameNode,name=JvmMetrics http://%s:50070/jmx?qry=Hadoop:service=NameNode,name=RpcDetailedActivityForPort8020 http://%s:50070/jmx?qry=Hadoop:service=NameNode,name=FSNamesystem http://%s:50070/jmx?qry=Hadoop:service=NameNode,name=RpcActivityForPort8020 http://%s:50070/jmx?qry=Hadoop:service=NameNode,name=RpcActivityForPort8020 http://%s:50070/jmx?qry=Hadoop:service=NameNode,name=RpcDetailedActivityForPort8022 http://%s:50070/jmx?qry=Hadoop:service=NameNode,name=RpcActivityForPort8020 http://%s:50070/jmx?qry=Hadoop:service=NameNode,name=RpcActivityForPort80222 hmaster ht tp://%s:60010/jmx?qry=hadoop:service=Master,name=MasterStatistics http://%s:60010/jmx?qry=hadoop:service=HBase,name=RPCStatistics-60000 Regionserver ht tp://127.0.0.1:60030/jmx?qry=hadoop:service=RegionServer,name=RegionServerDynamicStatistics ht tp://127.0.0.1:60030/jmx?qry=hadoop:service=RegionServer,name=RegionServerStatistics ht tp://127.0.0.1:60030/jmx?qry=hadoop:service=HBase,name=RPCStatistics-60020 datanode http://%s:50075/jmx?qry=Hadoop:service=DataNode,name=DataNodeActivity-%s-%s http://%s:50075/jmx?qry=Hadoop:service=DataNode,name=JvmMetrics http://%s:50075/jmx?qry=Hadoop:service=DataNode,name=RpcActivityForPort%s http://%s:50075/jmx?qry=Hadoop:service=DataNode,name=FSDatasetState-%s resourcemanager http://%s:8088/jmx?qry=Hadoop:service=ResourceManager,name=ClusterMetrics http://%s:8088/jmx?qry=Hadoop:service=ResourceManager,name=JvmMetrics http://%s:8088/jmx?qry=Hadoop:service=ResourceManager,name=QueueMetrics,q0=root http://%s:8088/jmx?qry=Hadoop:service=ResourceManager,name=RpcDetailedActivityForPort8030 http://%s:8088/jmx?qry=Hadoop:service=ResourceManager,name=RpcDetailedActivityForPort8031 http://%s:8088/jmx?qry=Hadoop:service=ResourceManager,name=RpcDetailedActivityForPort8032 http://%s:8088/jmx?qry=Hadoop:service=ResourceManager,name=RpcDetailedActivityForPort8033 http://%s:8088/jmx?qry=java.lang:type=GarbageCollector,name=ConcurrentMarkSweep http://%s:8088/jmx?qry=Hadoop:service=ResourceManager,name=FSOpDurations http://%s:8088/ws/v1/cluster/appstatistics ht tp://%s:8088/jmx?qry=Hadoop:service=ResourceManager,name=EventMetrics nodemanager http://127.0.0.1:8042/jmx?qry=Hadoop:service=NodeManager,name=JvmMetrics http://127.0.0.1:8042/jmx?qry=Hadoop:service=NodeManager,name=NodeManagerMetrics http://127.0.0.1:8042/jmx?qry=Hadoop:service=NodeManager,name=RpcActivityForPort8040 http://127.0.0.1:8042/jmx?qry=Hadoop:service=NodeManager,name=RpcActivityForPort8041 http://127.0.0.1:8042/jmx?qry=Hadoop:service=NodeManager,name=ShuffleMetrics journalnode http://127.0.0.1:8480/jmx?qry=Hadoop:service=JournalNode,name=JournalNodeInfo http://127.0.0.1:8480/jmx?qry=Hadoop:service=JournalNode,name=JvmMetrics http://127.0.0.1:8480/jmx?qry=Hadoop:service=JournalNode,name=RpcDetailedActivityForPort8485 ht tp://127.0.0.1:8480/jmx?qry=Hadoop:service=JournalNode,name=Journal-yiran-data-ns
tomcat远程调试 1.编辑catalina.sh文件,增加JPDA_ADDRESS=8101 /root/apache-tomcat-8.5.3/bin/catalina.sh 2.启动 ./bin/catalina.sh jpda start 3.Intellij IDEA中添加remote, 配置 -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8101 设置 host 设置port -------------------------------------------------------- springboot项目远程调试 服务端启动参数 java -jar -Djava.security.egd=file:/dev/./urandom -Xdebug -Xrunjdwp:transport=dt_socket,address=8080,server=y,suspend=n -Dspring.profiles.active=test Secret-1.0-SNAPSHOT.jar Intellij IDEA配置remote设置,同上
编译通过,打包时报如下错误 : 19-Aug-2018 10:53:56.961 INFO [localhost-startStop-1] org.apache.jasper.servlet.TldScanner.scanJars At least one JAR was scanned for TLDs yet contained no TLDs. Enable debug logging for this logger for a complete list of JARs that were scanned but no TLDs were found in them. Skipping unneeded JARs during scanning can improve startup time and JSP compilation time. SLF4J: The requested version 1.7.16 by your slf4j binding is not compatible with [1.5.5, 1.5.6, 1.5.7, 1.5.8, 1.5.9, 1.5.10] SLF4J: See http://www.slf4j.org/codes.html#version_mismatch for further details. SLF4J: Class path contains multiple SLF4J bindings. SLF4J: Found binding in [jar:file:/home/baseuser/apache-tomcat-8.5.3/webapps/ROOT/WEB-INF/lib/logback-classic-1.1.11.jar!/org/slf4j/impl/StaticLoggerBinder.class] SLF4J: Found binding in [jar:file:/home/baseuser/apache-tomcat-8.5.3/webapps/ROOT/WEB-INF/lib/slf4j-log4j12-1.5.10.jar!/org/slf4j/impl/StaticLoggerBinder.class] SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation. 19-Aug-2018 10:53:57.061 SEVERE [localhost-startStop-1] org.apache.catalina.core.StandardContext.startInternal One or more listeners failed to start. Full details will be found in the appropriate container log file 19-Aug-2018 10:53:57.065 SEVERE [localhost-startStop-1] org.apache.catalina.core.StandardContext.startInternal Context [] startup failed due to previous errors 在工程目录下用 mvn dependency:tree -P profile > dependency.txt 把工程的依赖关系导出为文件, 在文件中查找对slf4J和log4j库的依赖关系,把它排除掉不打包即可。
Java内存与垃圾回收调优 ImportNew - 进林 要了解Java垃圾收集机制,先理解JVM内存模式是非常重要的。今天我们将会了解JVM内存的各个部分、如何监控以及垃圾收集调优。 Java(JVM)内存模型 正如你从上面的图片看到的,JVM内存被分成多个独立的部分。广泛地说,JVM堆内存被分为两部分——年轻代(Young Generation)和老年代(Old Generation)。 年轻代 年轻代是所有新对象产生的地方。当年轻代内存空间被用完时,就会触发垃圾回收。这个垃圾回收叫做Minor GC。年轻代被分为3个部分——Enden区和两个Survivor区。 年轻代空间的要点: 大多数新建的对象都位于Eden区。 当Eden区被对象填满时,就会执行Minor GC。并把所有存活下来的对象转移到其中一个survivor区。 Minor GC同样会检查存活下来的对象,并把它们转移到另一个survivor区。这样在一段时间内,总会有一个空的survivor区。 经过多次GC周期后,仍然存活下来的对象会被转移到年老代内存空间。通常这是在年轻代有资格提升到年老代前通过设定年龄阈值来完成的。 年老代 年老代内存里包含了长期存活的对象和经过多次Minor GC后依然存活下来的对象。通常会在老年代内存被占满时进行垃圾回收。老年代的垃圾收集叫做Major GC。Major GC会花费更多的时间。 Stop the World事件 所有的垃圾收集都是“Stop the World”事件,因为所有的应用线程都会停下来直到操作完成(所以叫“Stop the World”)。 因为年轻代里的对象都是一些临时(short-lived )对象,执行Minor GC非常快,所以应用不会受到(“Stop the World”)影响。 由于Major GC会检查所有存活的对象,因此会花费更长的时间。应该尽量减少Major GC。因为Major GC会在垃圾回收期间让你的应用反应迟钝,所以如果你有一个需要快速响应的应用发生多次Major GC,你会看到超时错误。 垃圾回收时间取决于垃圾回收策略。这就是为什么有必要去监控垃圾收集和对垃圾收集进行调优。从而避免要求快速响应的应用出现超时错误。 永久代 永久代或者“Perm Gen”包含了JVM需要的应用元数据,这些元数据描述了在应用里使用的类和方法。注意,永久代不是Java堆内存的一部分。 永久代存放JVM运行时使用的类。永久代同样包含了Java SE库的类和方法。永久代的对象在full GC时进行垃圾收集。 方法区 方法区是永久代空间的一部分,并用来存储类型信息(运行时常量和静态变量)和方法代码和构造函数代码。 内存池 如果JVM实现支持,JVM内存管理会为创建内存池,用来为不变对象创建对象池。字符串池就是内存池类型的一个很好的例子。内存池可以属于堆或者永久代,这取决于JVM内存管理的实现。 运行时常量池 运行时常量池是每个类常量池的运行时代表。它包含了类的运行时常量和静态方法。运行时常量池是方法区的一部分。 Java栈内存 Java栈内存用于运行线程。它们包含了方法里的临时数据、堆里其它对象引用的特定数据。你可以阅读栈内存和堆内存的区别。 Java 堆内存开关 Java提供了大量的内存开关(参数),我们可以用它来设置内存大小和它们的比例。下面是一些常用的开关: VM 开关 VM 开关描述 -Xms 设置JVM启动时堆的初始化大小。 -Xmx 设置堆最大值。 -Xmn 设置年轻代的空间大小,剩下的为老年代的空间大小。 -XX:PermGen 设置永久代内存的初始化大小。 -XX:MaxPermGen 设置永久代的最大值。 -XX:SurvivorRatio 提供Eden区和survivor区的空间比例。比如,如果年轻代的大小为10m并且VM开关是-XX:SurvivorRatio=2,那么将会保留5m内存给Eden区和每个Survivor区分配2.5m内存。默认比例是8。 -XX:NewRatio 提供年老代和年轻代的比例大小。默认值是2。 大多数时候,上面的选项已经足够使用了。但是如果你还想了解其他的选项,那么请查看JVM选项官方网页。 Java垃圾回收 Java垃圾回收会找出没用的对象,把它从内存中移除并释放出内存给以后创建的对象使用。Java程序语言中的一个最大优点是自动垃圾回收,不像其他的程序语言那样需要手动分配和释放内存,比如C语言。 垃圾收集器是一个后台运行程序。它管理着内存中的所有对象并找出没被引用的对象。所有的这些未引用的对象都会被删除,回收它们的空间并分配给其他对象。 一个基本的垃圾回收过程涉及三个步骤: 标记:这是第一步。在这一步,垃圾收集器会找出哪些对象正在使用和哪些对象不在使用。 正常清除:垃圾收集器清会除不在使用的对象,回收它们的空间分配给其他对象。 压缩清除:为了提升性能,压缩清除会在删除没用的对象后,把所有存活的对象移到一起。这样可以提高分配新对象的效率。 简单标记和清除方法存在两个问题: 效率很低。因为大多数新建对象都会成为“没用对象”。 经过多次垃圾回收周期的对象很有可能在以后的周期也会存活下来。 上面简单清除方法的问题在于Java垃圾收集的分代回收的,而且在堆内存里有年轻代和年老代两个区域。我已经在上面解释了Minor GC和Major GC是怎样扫描对象,以及如何把对象从一个分代空间移到另外一个分代空间。 Java垃圾回收类型 这里有五种可以在应用里使用的垃圾回收类型。仅需要使用JVM开关就可以在我们的应用里启用垃圾回收策略。让我们一起来逐一了解: Serial GC(-XX:+UseSerialGC):Serial GC使用简单的标记、清除、压缩方法对年轻代和年老代进行垃圾回收,即Minor GC和Major GC。Serial GC在client模式(客户端模式)很有用,比如在简单的独立应用和CPU配置较低的机器。这个模式对占有内存较少的应用很管用。 Parallel GC(-XX:+UseParallelGC):除了会产生N个线程来进行年轻代的垃圾收集外,Parallel GC和Serial GC几乎一样。这里的N是系统CPU的核数。我们可以使用 -XX:ParallelGCThreads=n 这个JVM选项来控制线程数量。并行垃圾收集器也叫throughput收集器。因为它使用了多CPU加快垃圾回收性能。Parallel GC在进行年老代垃圾收集时使用单线程。 Parallel Old GC(-XX:+UseParallelOldGC):和Parallel GC一样。不同之处,Parallel Old GC在年轻代垃圾收集和年老代垃圾回收时都使用多线程收集。 并发标记清除(CMS)收集器(-XX:+UseConcMarkSweepGC):CMS收集器也被称为短暂停顿并发收集器。它是对年老代进行垃圾收集的。CMS收集器通过多线程并发进行垃圾回收,尽量减少垃圾收集造成的停顿。CMS收集器对年轻代进行垃圾回收使用的算法和Parallel收集器一样。这个垃圾收集器适用于不能忍受长时间停顿要求快速响应的应用。可使用 -XX:ParallelCMSThreads=n JVM选项来限制CMS收集器的线程数量。 G1垃圾收集器(-XX:+UseG1GC) G1(Garbage First):垃圾收集器是在Java 7后才可以使用的特性,它的长远目标时代替CMS收集器。G1收集器是一个并行的、并发的和增量式压缩短暂停顿的垃圾收集器。G1收集器和其他的收集器运行方式不一样,不区分年轻代和年老代空间。它把堆空间划分为多个大小相等的区域。当进行垃圾收集时,它会优先收集存活对象较少的区域,因此叫“Garbage First”。你可以在Oracle Garbage-FIrst收集器文档找到更多详细信息。 Java垃圾收集监控 我们可以使用命令行和图形工具来监控监控应用垃圾回收。例如,我使用Java SE下载页中的一个demo来实验。 如果你想使用同样的应用,可以到Java SE下载页面下载JDK 7和JavaFX演示和示例。我使用的示例应用是Java2Demo.jar,它位于 jdk1.7.0_55/demo/jfc/Java2D 目录下。这只是一个可选步骤,你可以运行GC监控命令监控任何Java应用。 我打开演示应用使用的命令是: 1 pankaj@Pankaj:~/Downloads/jdk1.7.0_55/demo/jfc/Java2D$ java -Xmx120m -Xms30m -Xmn10m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseSerialGC -jar Java2Demo.jar jsat 可以使用jstat命令行工具监控JVM内存和垃圾回收。标准的JDK已经附带了jstat,所以不需要做任何额外的事情就可以得到它。 要运行jstat你需要知道应用的进程id,你可以使用 ps -eaf | grep java 命令获取进程id。 1 2 3 pankaj@Pankaj:~$ ps -eaf | grep Java2Demo.jar 501 9582 11579 0 9:48PM ttys000 0:21.66 /usr/bin/java -Xmx120m -Xms30m -Xmn10m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseG1GC -jar Java2Demo.jar 501 14073 14045 0 9:48PM ttys002 0:00.00 grep Java2Demo.jar 从上面知道,我的Java应用进程id是9582。现在可以运行jstat命令了,就像下面展示的一样: 1 2 3 4 5 6 7 8 9 pankaj@Pankaj:~$ jstat -gc 9582 1000 S0C S1C S0U S1U EC EU OC OU PC PU YGC YGCT FGC FGCT GCT 1024.0 1024.0 0.0 0.0 8192.0 7933.3 42108.0 23401.3 20480.0 19990.9 157 0.274 40 1.381 1.654 1024.0 1024.0 0.0 0.0 8192.0 8026.5 42108.0 23401.3 20480.0 19990.9 157 0.274 40 1.381 1.654 1024.0 1024.0 0.0 0.0 8192.0 8030.0 42108.0 23401.3 20480.0 19990.9 157 0.274 40 1.381 1.654 1024.0 1024.0 0.0 0.0 8192.0 8122.2 42108.0 23401.3 20480.0 19990.9 157 0.274 40 1.381 1.654 1024.0 1024.0 0.0 0.0 8192.0 8171.2 42108.0 23401.3 20480.0 19990.9 157 0.274 40 1.381 1.654 1024.0 1024.0 48.7 0.0 8192.0 106.7 42108.0 23401.3 20480.0 19990.9 158 0.275 40 1.381 1.656 1024.0 1024.0 48.7 0.0 8192.0 145.8 42108.0 23401.3 20480.0 19990.9 158 0.275 40 1.381 1.656 jstat命令的最后一个参数是每个输出的时间间隔。每隔一秒就会打印出内存和垃圾收集数据。 让我们一起来对每一列的意义进行逐一了解: S0C和S1C:这一列展示了Survivor0和Survivor1区的当前大小(单位KB)。 S0U和S1U:这一列展示了当前Survivor0和Survivor1区的使用情况(单位KB)。注意:无论任何时候,总会有一个Survivor区是空着的。 EC和EU:这些列展示了Eden区当前空间大小和使用情况(单位KB)。注意:EU的大小一直在增大。而且只要大小接近EC时,就会触发Minor GC并且EU将会减小。 OC和OU:这些列展示了年老代当前空间大小和当前使用情况(单位KB)。 PC和PU:这些列展示了Perm Gen(永久代)当前空间大小和当前使用情况(单位KB)。 YGC和YGCT:YGC这列显示了发生在年轻代的GC事件的数量。YGCT这列显示了在年轻代进行GC操作的累计时间。注意:在EU的值由于minor GC导致下降时,同一行的YGC和YGCT都会增加。 FGC和FGCT:FGC列显示了发生Full GC事件的次数。FGCT显示了进行Full GC操作的累计时间。注意:相对于年轻代的GC使用时间,Full GC所用的时间长很多。 GCT:这一列显示了GC操作的总累计时间。注意:总累计时间是YGCT和FGCT两列所用时间的总和(GCT=YGCT+FGCT)。 jstat的优点,我们同样可以在没有GUI的远程服务器上运行jstat。注意:我们是通过 -Xmn10m 选项来指定S0C、S1C和EC的总和为10m的。 Java VisualVM及Visual GC插件 如果你想在GUI里查看内存和GC,那么可以使用jvisualvm工具。Java VisualVM同样是JDK的一部分,所以你不需要单独去下载。 在终端运行jvisualvm命令启动Java VisualVM程序。一旦启动程序,你需要从Tools->Plugins选项安装Visual GC插件,就像下面图片展示的。 安装完Visual GC插件后,从左边栏打开应用并把视角转到Visual GC部分。你将会得到关于JVM内存和垃圾收集详情,如下图所示。 Java垃圾回收调优 Java垃圾回收调优应该是提升应用吞吐量的最后一个选择。在你发现应用由于长时间垃圾回收导致了应用性能下降、出现超时的时候,应该考虑Java垃圾收集调优。 如果你在日志里看到 java.lang.OutOfMemoryError: PermGen space错误,那么可以尝试使用 -XX:PermGen 和 -XX:MaxPermGen JVM选项去监控并增加Perm Gen内存空间。你也可以尝试使用-XX:+CMSClassUnloadingEnabled并查看使用CMS垃圾收集器的执行性能。 如果你看到了大量的Full GC操作,那么你应该尝试增大老年代的内存空间。 全面垃圾收集调优要花费大量的努力和时间,这里没有一尘不变的硬性调优规则。你需要去尝试不同的选项并且对这些选项进行对比,从而找出最适合自己应用的方案。 这就是所有的Java内存模型和垃圾回收内容。希望对你理解JVM内存和垃圾收集过程有所帮助。 原文链接: journaldev 翻译: ImportNew.com - 进林 译文链接: http://www.importnew.com/14086.html
HashMap的put怎么实现,如何解决hash冲突。 调用putval,计算相应hash码,然后初始化(默认64的capacity)或调用resize函数调整大小,判断bucket是否有值,若没有在数组初始化改值。若有则以拉链法(链表的形式)解决hash冲突,这里和ThreadLocalMap不一样,ThreadLocalMap使用的是线性探测法,接着将相应节点加入链表头部。如果超过8个元素会进化为RBtree,防止hash攻击。 RBtree是怎样的数据结构,有什么性质? 二叉树,有序的,四种性质。从而推得路径最长2n,最短n。复杂度为log2N.(此处省略n多话,感兴趣的同学请自行Google) RBtree什么时候会变色? 旋转时,共有四种旋转方式。一般是为了保持平衡,如左边太长,右边太短这样。(打哈哈过去,具体记不清了) hashmap什么时候会调整大小? 根据负载因子来搞事,默认为0.75。 什么是负载因子? 根据capacity来,举个例子,当capacity为100时,如果HashMap的ele的数量到了75就会resize,resize后的大小为原来的2倍,这样可以直接使用位运算得到原来的元素新的hash值。 扩容存在什么问题? (楞了一会,发现应该是说多线程的情况)然后说了多线程会有死循环问题。如果要解决可以使用concurrentHashMap。 为什么有死循环? 扯了半天,发现不画图,只通过电话根本扯不清。然后说就是因为1.7扩容后链表会逆序,1.8不会,所以1.8没这个问题,1.7就是两个线程同时扩容,一个扩到一半,到另一个了开始并完成扩容,之前那个再继续,就会出现。(然后说小姐姐,有机会我当面画给你看,开个玩笑) 8.你刚才提到concurrentHashMap,你知道怎么实现吗? 1.7使用分段锁,分为16个,每个segment可以视为一个hashtable,然后一次一个线程只锁一个segment,减小了锁的粒度,提高了并发。1.7使用的是Lock的实现类,可重入锁来同步的。1.8使用的是CAS和synchronized。如果已有元素,需要解决hash冲突,会使用synchronized锁住相应的bucket,然后再添加,同样元素在八个以上会转化为RBtree。 9.你提到Lock,知道哪些相应的锁? 读写锁,可重入锁 10.知道AQS吗,他的实现是怎样的?AQS可重入吗? 知道,读写锁,可重入锁都是通过AQS实现的,AQS维护一个链表,并主要提供tryacquire和tryrelease方法。默认为非公平锁,此时当一个线程需要请求锁时... 11.AQS如何实现可重入 维护一个int类型的status作为计数器,同一个线程acquire就加1,release就减1.到0就释放锁。读写说则是将status分为两部分使用。内部维护一个shift变量做位运算的变化。。。(AQS可以看占小狼的blog或者并发编程的艺术) 12.volatile、aba问题 13.volatile什么作用 指令重排序,内存可见性 14、指令重排序指什么?指令重排序的好处是什么?如何防止指令重排序。编译器重排序,cpu重排序,内存重排序。好处是流水线技术,提高并发性能等。通过禁止编译器优化,以及汇编使用Lock信号,java中的cpp加入volatile等防止。 15.内存可见性具体指什么?volatile通过什么机制防止 讲了下JMM,以及计组原理中的三级cache,buffer,缓存行等。 顺便扯了下c语言的volatile只保证防止编译器优化以及内存可见性的语义,而不能保证顺序性。然后是C11的acquire,release语义 接着回归java,扯了下内存屏障的实现与作用。(并发编程的艺术)然后扯了下#LOCK信号,包括总线锁,mesi的缓存一致性等。最后是先行发生的语义(语无伦次,不过基本点都讲到了) 16.synchronized内部分为几种锁,他们的使用场景是什么 偏向锁,轻量级锁,重量级锁(又有自旋锁等),然后详细讲了实现和使用场景(周志明的书和并发编程的艺术都有讲,此处省略)。 17.nio、bio 没有,讲了下自己准备学习netty,然后谈了下c语言的nio,包括Nginx和redis的多路复用,然后讲了下select和epoll的区别。以及epoll的优点和实现。然后设想java里的nio应该也是映射到epoll里面。 18.netty是怎么实现? 18. 操作系统调度进程有哪些算法? 优先级,时间片,FIFO,最近deadline什么的。 19.Redis有几种持久化方式? 四种,2种被废弃,比如磁盘交换。目前主要使用rdb,aof。rdb属于物理备份,aof属于逻辑日志(逐行追加)。然后又讲了aof重写。rdb和aof的配置。以及aof的rewrite机制。 18.Redis分布式的实现方式 (此处省略,Redis的设计与实现有详解) 19。 数据库特性 ACID,顺便分别提了下实现原理 20.具体讲下隔离性。 四种隔离级别和实现方式 https://www.cnblogs.com/fjdingsd/p/5273008.html 21.如何理解一致性? 说了下单个事务的一致性,以及分布式一致性。 22 一致性的三种级别 强,弱,最终一致性 23,持久性的实现方式 redo,同时使用insert buffer等方式。 24数据库mysql innodb myISAM区别
转载。 https://blog.csdn.net/goldenfish1919/article/details/50923450 1.我们以支付以后7天自动收货为例来说明下: (1)用户支付完成以后,把订单ID插入到内存的一个DelayQueue中,同时插入到Redis中。 (2)7天之内,用户点击了确认收货,则从DelayQueue中删除,从Redis中删除。 (3)超过7天,DelayQueue中的订单ID出队,查询数据库,改状态为自动收货,删除redis。 (4)如果7天之内,web服务器重启过,则web服务器启动以后,从redis中读取待收货的订单,插入到DelayQueue。 看下具体的代码: @Controller @RequestMapping(value = "") public class OrderController { @Autowired DelayService delayService; @Autowired RedisService redisServie; @Autowired ConfigService configService; //模拟数据库 private List<Long> ordeIds = new ArrayList<Long>(); private static final Logger log = Logger.getLogger(OrderController.class); @RequestMapping(value = "/order", method = RequestMethod.GET) public String order(final HttpServletRequest request, final Model model) { return "order"; } @RequestMapping(value = "/pay", method = RequestMethod.GET) @ResponseBody public Response<Void> pay(final HttpServletRequest request, final Model model) { final long orderId = Long.parseLong(request.getParameter("orderId")); ordeIds.add(orderId); log.error("订单已支付:"+orderId); //把订单插入到待收货的队列和redis ThreadPoolUtil.execute(new Runnable(){ @Override public void run() { //1 插入到待收货队列 DSHOrder dshOrder = new DSHOrder(orderId, configService.getDshTimeOut()); delayService.add(dshOrder); log.error("订单入队:"+orderId); //2插入到redis redisServie.set(Constants.RedisKey.DSH_PREFIX+orderId, dshOrder, RedisService.DB.DSH); log.error("订单入redis:"+orderId); } }); return new Response<Void>(0,"成功"); } @RequestMapping(value = "/confirm_delivery", method = RequestMethod.GET) @ResponseBody public Response<Void> confirm_delivery(final HttpServletRequest request, final Model model) { final long orderId = Long.parseLong(request.getParameter("orderId")); ordeIds.remove(orderId); log.error("订单已确认收货:"+orderId); //从delay队列删除,从redis删除 ThreadPoolUtil.execute(new Runnable(){ public void run(){ //从delay队列删除 delayService.remove(orderId); log.error("订单手动出队:"+orderId); //从redis删除 redisServie.delete(Constants.RedisKey.DSH_PREFIX+orderId, RedisService.DB.DSH); log.error("订单手动出redis:"+orderId); } }); return new Response<Void>(0,"成功"); } } @Service public class DelayService { private static final Logger log = Logger.getLogger(DelayService.class); @Autowired ConfigService configService; private boolean start ; private OnDelayedListener listener; private DelayQueue<DSHOrder> delayQueue = new DelayQueue<DSHOrder>(); public static interface OnDelayedListener{ public void onDelayedArrived(DSHOrder order); } public void start(OnDelayedListener listener){ if(start){ return; } log.error("DelayService 启动"); start = true; this.listener = listener; new Thread(new Runnable(){ public void run(){ try{ while(true){ DSHOrder order = delayQueue.take(); if(DelayService.this.listener != null){ DelayService.this.listener.onDelayedArrived(order); } } }catch(Exception e){ e.printStackTrace(); } } }).start();; } public void add(DSHOrder order){ delayQueue.put(order); } public boolean remove(DSHOrder order){ return delayQueue.remove(order); } public void add(long orderId){ delayQueue.put(new DSHOrder(orderId, configService.getDshTimeOut())); } public void remove(long orderId){ DSHOrder[] array = delayQueue.toArray(new DSHOrder[]{}); if(array == null || array.length <= 0){ return; } DSHOrder target = null; for(DSHOrder order : array){ if(order.getOrderId() == orderId){ target = order; break; } } if(target != null){ delayQueue.remove(target); } } } public class DSHOrder implements Delayed { private long orderId; private long startTime; public DSHOrder(){ } /** * orderId:订单id * timeout:自动收货的超时时间,秒 * */ public DSHOrder(long orderId, int timeout){ this.orderId = orderId; this.startTime = System.currentTimeMillis() + timeout*1000L; } @Override public int compareTo(Delayed other) { if (other == this){ return 0; } if(other instanceof DSHOrder){ DSHOrder otherRequest = (DSHOrder)other; long otherStartTime = otherRequest.getStartTime(); return (int)(this.startTime - otherStartTime); } return 0; } @Override public long getDelay(TimeUnit unit) { return unit.convert(startTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + (int) (orderId ^ (orderId >>> 32)); result = prime * result + (int) (startTime ^ (startTime >>> 32)); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; DSHOrder other = (DSHOrder) obj; if (orderId != other.orderId) return false; if (startTime != other.startTime) return false; return true; } public long getStartTime() { return startTime; } public long getOrderId() { return orderId; } public void setOrderId(long orderId) { this.orderId = orderId; } public void setStartTime(long startTime) { this.startTime = startTime; } @Override public String toString() { return "DSHOrder [orderId=" + orderId + ", startTime=" + startTime + "]"; } } @Service public class StartupListener implements ApplicationListener<ContextRefreshedEvent> { private static final Logger log = Logger.getLogger(StartupListener.class); @Autowired DelayService delayService; @Autowired RedisService redisService; @Override public void onApplicationEvent(ContextRefreshedEvent evt) { log.error(">>>>>>>>>>>>系统启动完成,onApplicationEvent()"); if (evt.getApplicationContext().getParent() == null) { return; } //自动收货 delayService.start(new OnDelayedListener(){ @Override public void onDelayedArrived(final DSHOrder order) { //异步来做 ThreadPoolUtil.execute(new Runnable(){ public void run(){ long orderId = order.getOrderId(); //查库判断是否需要自动收货 log.error("自动确认收货,onDelayedArrived():"+orderId); //从redis删除 redisService.delete(Constants.RedisKey.DSH_PREFIX+orderId, RedisService.DB.DSH); log.error("自动确认收货,删除redis:"+orderId); } }); } }); //查找需要入队的订单 ThreadPoolUtil.execute(new Runnable(){ @Override public void run() { log.error("查找需要入队的订单"); //扫描redis,找到所有可能的orderId List<String> keys = redisService.scan(RedisService.DB.DSH); if(keys == null || keys.size() <= 0){ return; } log.error("需要入队的订单keys:"+keys); //写到DelayQueue for(String key : keys){ DSHOrder order = redisService.get(key, DSHOrder.class, RedisService.DB.DSH); log.error("读redis,key:"+key); if(order != null){ delayService.add(order); log.error("订单自动入队:"+order.getOrderId()); } } } }); } } 最新的代码:https://github.com/xjs1919/util/tree/master/src/main/java/com/github/xjs/util/delay
在Linux下,默认端口1024下的是要在root下才能使用的,在其他用户下,如果尝试使用将会报错。在有的时候,我们可能考虑程序运行在root帐户下,可能会给Linux系统带来安全风险。 # netstat -lntp //查看开启了哪些端口 # netstat -r //本选项可以显示关于路由表的信息 # netstat -a //本选项显示一个所有的有效连接信息列表 # netstat -an|grep 8080 # netstat -na|grep -i listen //可以看到目前系统侦听的端口号 # netstat -antup //查看已建立的连接进程,所占用的端口。 netstat -anp|grep 1487 lsof -i:1487 查询文件大小 du 使用详解 linux查看目录大小 linux统计目录大小并排序 查看目录下所有一级子目录文件夹 du -h --max-depth=1 |grep [TG] |sort #查找上G和T的目录并排序 du -sh #统计当前目录的大小,以直观方式展现 du -h --max-depth=1 |grep 'G' |sort #查看上G目录并排序 du -sh --max-depth=1 #查看当前目录下所有一级子目录文件夹大小 du -h --max-depth=1 |sort #查看当前目录下所有一级子目录文件夹大小 并排序 du -h --max-depth=1 |grep [TG] |sort -nr #倒序排 示例: [baseuser@SHTL009035043 ~]$ sudo du -h --max-depth=1 / [root@SHTL009035043 docker]# sudo du -h --max-depth=1 ./
转载 目录 JVM简介 JVM结构 2.1 方法区 2.1.1 常量池 2.1.1.1 Class文件中的常量池 2.1.1.2 运行时常量池 2.1.1.3 常量池的好处 2.1.1.4 基本类型的包装类和常量池 2.2 堆 2.3 Java栈 2.3.1 栈帧 2.3.1.1 局部变量区 2.3.1.2 操作数栈 2.3.1.3 栈数据区 2.4 本地方法栈 2.5 PC寄存器 2.6 堆与栈 2.6.1 堆与栈里存什么 2.6.2 堆内存与栈内存的区别 JIT编译器 类加载机制 4.1 类加载的时机 4.2 类加载过程 垃圾回收 5.1 按代实现垃圾回收 5.2 怎样判断对象是否已经死亡 5.3 java中的引用 5.4 finalize方法什么作用 5.5 垃圾收集算法 5.6 Hotspot实现垃圾回收细节 5.7 垃圾收集器 5.7.1 Serial收集器 5.7.2 ParNew收集器 5.7.3 Parallel Scavenge收集器 5.7.4 Serial Old收集器 5.7.5 Parallel Old收集器 5.7.6 CMS收集器 5.7.7 G1收集器 JVM参数 6.1 典型配置 6.1.1 堆大小设置 6.1.2 回收器选择 6.1.3 辅助信息 6.2 参数详细说明 JVM性能调优 7.1 堆设置调优 7.2 GC策略调优 7.3 JIT调优 7.4 JVM线程调优 7.5 典型案例 常见问题 8.1 内存泄漏及解决方法 8.2 年老代堆空间被占满 8.3 持久代被占满 8.4 堆栈溢出 8.5 线程堆栈满 8.6 系统内存被占满 1.JVM简介 JVM是java的核心和基础,在java编译器和os平台之间的虚拟处理器。它是一种利用软件方法实现的抽象的计算机基于下层的操作系统和硬件平台,可以在上面执行java的字节码程序。 java编译器只要面向JVM,生成JVM能理解的代码或字节码文件。Java源文件经编译成字节码程序,通过JVM将每一条指令翻译成不同平台机器码,通过特定平台运行。 运行过程 Java语言写的源程序通过Java编译器,编译成与平台无关的‘字节码程序’(.class文件,也就是0,1二进制程序),然后在OS之上的Java解释器中解释执行。 C++以及Fortran这类编译型语言都会通过一个静态的编译器将程序编译成CPU相关的二进制代码。 PHP以及Perl这列语言则是解释型语言,只需要安装正确的解释器,它们就能运行在任何CPU之上。当程序被执行的时候,程序代码会被逐行解释并执行。 编译型语言的优缺点: 速度快:因为在编译的时候它们能够获取到更多的有关程序结构的信息,从而有机会对它们进行优化。 适用性差:它们编译得到的二进制代码往往是CPU相关的,在需要适配多种CPU时,可能需要编译多次。 解释型语言的优缺点: 适应性强:只需要安装正确的解释器,程序在任何CPU上都能够被运行 速度慢:因为程序需要被逐行翻译,导致速度变慢。同时因为缺乏编译这一过程,执行代码不能通过编译器进行优化。 Java的做法是找到编译型语言和解释性语言的一个中间点: Java代码会被编译:被编译成Java字节码,而不是针对某种CPU的二进制代码。 Java代码会被解释:Java字节码需要被java程序解释执行,此时,Java字节码被翻译成CPU相关的二进制代码。 JIT编译器的作用:在程序运行期间,将Java字节码编译成平台相关的二进制代码。正因为此编译行为发生在程序运行期间,所以该编译器被称为Just-In-Time编译器。 image.png image.png 2.JVM结构 image.png java是基于一门虚拟机的语言,所以了解并且熟知虚拟机运行原理非常重要。 2.1 方法区 方法区,Method Area, 对于习惯在HotSpot虚拟机上开发和部署程序的开发者来说,很多人愿意把方法区称为“永久代”(Permanent Generation),本质上两者并不等价,仅仅是因为HotSpot虚拟机的设计团队选择把GC分代收集扩展至方法区,或者说使用永久代来实现方法区而已。对于其他虚拟机(如BEA JRockit、IBM J9等)来说是不存在永久代的概念的。 主要存放已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据(比如spring 使用IOC或者AOP创建bean时,或者使用cglib,反射的形式动态生成class信息等)。 注意:JDK 6 时,String等字符串常量的信息是置于方法区中的,但是到了JDK 7 时,已经移动到了Java堆。所以,方法区也好,Java堆也罢,到底详细的保存了什么,其实没有具体定论,要结合不同的JVM版本来分析。 异常 当方法区无法满足内存分配需求时,将抛出OutOfMemoryError。 运行时常量池溢出:比如一直往常量池加入数据,就会引起OutOfMemoryError异常。 类信息 类型全限定名。 类型的直接超类的全限定名(除非这个类型是java.lang.Object,它没有超类)。 类型是类类型还是接口类型。 类型的访问修饰符(public、abstract或final的某个子集)。 任何直接超接口的全限定名的有序列表。 类型的常量池。 字段信息。 方法信息。 除了常量意外的所有类(静态)变量。 一个到类ClassLoader的引用。 一个到Class类的引用。 2.1.1 常量池 2.1.1.1 Class文件中的常量池 在Class文件结构中,最头的4个字节用于存储Megic Number,用于确定一个文件是否能被JVM接受,再接着4个字节用于存储版本号,前2个字节存储次版本号,后2个存储主版本号,再接着是用于存放常量的常量池,由于常量的数量是不固定的,所以常量池的入口放置一个U2类型的数据(constant_pool_count)存储常量池容量计数值。 常量池主要用于存放两大类常量:字面量(Literal)和符号引用量(Symbolic References),字面量相当于Java语言层面常量的概念,如文本字符串,声明为final的常量值等,符号引用则属于编译原理方面的概念,包括了如下三种类型的常量: 类和接口的全限定名 字段名称和描述符 方法名称和描述符 2.1.1.2 运行时常量池 CLass文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。 运行时常量池相对于CLass文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定只有编译期才能产生,也就是并非预置入CLass文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中,这种特性被开发人员利用比较多的就是String类的intern()方法。 2.1.1.3 常量池的好处 常量池是为了避免频繁的创建和销毁对象而影响系统性能,其实现了对象的共享。 例如字符串常量池,在编译阶段就把所有的字符串文字放到一个常量池中。 (1)节省内存空间:常量池中所有相同的字符串常量被合并,只占用一个空间。 (2)节省运行时间:比较字符串时,\==比equals()快。对于两个引用变量,只用==判断引用是否相等,也就可以判断实际值是否相等。 双等号==的含义 基本数据类型之间应用双等号,比较的是他们的数值。 复合数据类型(类)之间应用双等号,比较的是他们在内存中的存放地址。 2.1.1.4 基本类型的包装类和常量池 java中基本类型的包装类的大部分都实现了常量池技术,即Byte,Short,Integer,Long,Character,Boolean。 这5种包装类默认创建了数值[-128,127]的相应类型的缓存数据,但是超出此范围仍然会去创建新的对象。 两种浮点数类型的包装类Float,Double并没有实现常量池技术。 Integer与常量池 Integer i1 = 40;Integer i2 = 40;Integer i3 = 0;Integer i4 = new Integer(40);Integer i5 = new Integer(40);Integer i6 = new Integer(0);System.out.println("i1=i2 " + (i1 == i2));System.out.println("i1=i2+i3 " + (i1 == i2 + i3));System.out.println("i1=i4 " + (i1 == i4));System.out.println("i4=i5 " + (i4 == i5));System.out.println("i4=i5+i6 " + (i4 == i5 + i6)); System.out.println("40=i5+i6 " + (40 == i5 + i6));i1=i2 truei1=i2+i3 truei1=i4 falsei4=i5 falsei4=i5+i6 true40=i5+i6 true 解释: (1)Integer i1=40;Java在编译的时候会直接将代码封装成Integer i1=Integer.valueOf(40);,从而使用常量池中的对象。 (2)Integer i1 = new Integer(40);这种情况下会创建新的对象。 (3)语句i4 == i5 + i6,因为+这个操作符不适用于Integer对象,首先i5和i6进行自动拆箱操作,进行数值相加,即i4 == 40。然后Integer对象无法与数值进行直接比较,所以i4自动拆箱转为int值40,最终这条语句转为40 == 40进行数值比较。 String与常量池 String str1 = "abcd";String str2 = new String("abcd");System.out.println(str1==str2);//falseString str1 = "str";String str2 = "ing";String str3 = "str" + "ing";String str4 = str1 + str2;System.out.println(str3 == str4);//falseString str5 = "string";System.out.println(str3 == str5);//true 解释: (1)new String("abcd")是在常量池中拿对象,"abcd"是直接在堆内存空间创建一个新的对象。只要使用new方法,便需要创建新的对象。 (2)连接表达式 + 只有使用引号包含文本的方式创建的String对象之间使用“+”连接产生的新对象才会被加入字符串池中。 对于所有包含new方式新建对象(包括null)的“+”连接表达式,它所产生的新对象都不会被加入字符串池中。 public static final String A; // 常量Apublic static final String B; // 常量Bstatic { A = "ab"; B = "cd"; } public static void main(String[] args) { // 将两个常量用+连接对s进行初始化 String s = A + B; String t = "abcd"; if (s == t) { System.out.println("s等于t,它们是同一个对象"); } else { System.out.println("s不等于t,它们不是同一个对象"); } } 解释: s不等于t,它们不是同一个对象。 A和B虽然被定义为常量,但是它们都没有马上被赋值。在运算出s的值之前,他们何时被赋值,以及被赋予什么样的值,都是个变数。因此A和B在被赋值之前,性质类似于一个变量。那么s就不能在编译期被确定,而只能在运行时被创建了。 String s1 = new String("xyz"); //创建了几个对象? 解释: 考虑类加载阶段和实际执行时。 (1)类加载对一个类只会进行一次。”xyz”在类加载时就已经创建并驻留了(如果该类被加载之前已经有”xyz”字符串被驻留过则不需要重复创建用于驻留的”xyz”实例)。驻留的字符串是放在全局共享的字符串常量池中的。 (2)在这段代码后续被运行的时候,”xyz”字面量对应的String实例已经固定了,不会再被重复创建。所以这段代码将常量池中的对象复制一份放到heap中,并且把heap中的这个对象的引用交给s1 持有。 这条语句创建了2个对象。 public static void main(String[] args) { String s1 = new String("计算机");String s2 = s1.intern();String s3 = "计算机";System.out.println("s1 == s2? " + (s1 == s2));System.out.println("s3 == s2? " + (s3 == s2));}s1 == s2? falses3 == s2? true 解释: String的intern()方法会查找在常量池中是否存在一份equal相等的字符串,如果有则返回该字符串的引用,如果没有则添加自己的字符串进入常量池。 public class Test {public static void main(String[] args) { String hello = "Hello", lo = "lo"; System.out.println((hello == "Hello") + " "); //true System.out.println((Other.hello == hello) + " "); //true System.out.println((other.Other.hello == hello) + " "); //true System.out.println((hello == ("Hel"+"lo")) + " "); //true System.out.println((hello == ("Hel"+lo)) + " "); //false System.out.println(hello == ("Hel"+lo).intern()); //true } }class Other { static String hello = "Hello"; } package other;public class Other { public static String hello = "Hello"; } 解释: 在同包同类下,引用自同一String对象. 在同包不同类下,引用自同一String对象. 在不同包不同类下,依然引用自同一String对象. 在编译成.class时能够识别为同一字符串的,自动优化成常量,引用自同一String对象. 在运行时创建的字符串具有独立的内存地址,所以不引用自同一String对象. 2.2 堆 Heap(堆)是JVM的内存数据区。 一个虚拟机实例只对应一个堆空间,堆是线程共享的。堆空间是存放对象实例的地方,几乎所有对象实例都在这里分配。堆也是垃圾收集器管理的主要区域(也被称为GC堆)。堆可以处于物理上不连续的内存空间中,只要逻辑上相连就行。 Heap 的管理很复杂,每次分配不定长的内存空间,专门用来保存对象的实例。在Heap 中分配一定的内存来保存对象实例,实际上也只是保存对象实例的属性值,属性的类型和对象本身的类型标记等,并不保存对象的方法(方法是指令,保存在Stack中)。而对象实例在Heap中分配好以后,需要在Stack中保存一个4字节的Heap 内存地址,用来定位该对象实例在Heap 中的位置,便于找到该对象实例。 异常 堆中没有足够的内存进行对象实例分配时,并且堆也无法扩展时,会抛出OutOfMemoryError异常。 image.png 2.3 Java栈 Stack(栈)是JVM的内存指令区。 描述的是java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧,用于存放局部变量表(基本类型、对象引用)、操作数栈、方法返回、常量池指针等信息。 由编译器自动分配释放, 内存的分配是连续的。Stack的速度很快,管理很简单,并且每次操作的数据或者指令字节长度是已知的。所以Java 基本数据类型,Java 指令代码,常量都保存在Stack中。 虚拟机只会对栈进行两种操作,以帧为单位的入栈和出栈。Java栈中的每个帧都保存一个方法调用的局部变量、操作数栈、指向常量池的指针等,且每一次方法调用都会创建一个帧,并压栈。 异常 如果一个线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常, 比如递归调用。 如果线程生成数量过多,无法申请足够多的内存时,则会抛出OutOfMemoryError异常。比如tomcat请求数量非常多时,设置最大请求数。 2.3.1 栈帧 栈帧由三部分组成:局部变量区、操作数栈、帧数据区。 2.3.1.1 局部变量区 包含方法的参数和局部变量。 以一个静态方法为例 public class Demo { public static int doStaticMethod(int i, long l, float f, Object o, byte b) { return 0; } } 编译之后的具备变量表字节码如下: LOCALVARIABLEiIL0L10 LOCALVARIABLElJL0L11 LOCALVARIABLEfFL0L13 LOCALVARIABLEoLjava/lang/Object;L0L14 LOCALVARIABLEbBL0L15 MAXSTACK=1 //该方法操作栈的最大深度MAXLOCALS=6 //确定了该方法所需要分配的最大局部变量表的容量 可以认为Java栈帧里的局部变量表有很多的槽位组成,每个槽最大可以容纳32位的数据类型,故方法参数里的int i 参数占据了一个槽位,而long l 参数就占据了两个槽(1和2),Object对象类型的参数其实是一个引用,o相当于一个指针,也就是32位大小。byte类型升为int,也是32位大小。如下: 0 int int i1 long long l3 float float f4 reference Object o5 int byte b 实例方法的局部变量表和静态方法基本一样,唯一区别就是实例方法在Java栈帧的局部变量表里第一个槽位(0位置)存的是一个this引用(当前对象的引用),后面就和静态方法的一样了。 2.3.1.2 操作数栈 Java没有寄存器,故所有参数传递使用Java栈帧里的操作数栈,操作数栈被组织成一个以字长为单位的数组,它是通过标准的栈操作-入栈和出栈来进行访问,而不是通过索引访问。 看一个例子: image.png 注意,对于局部变量表的槽位,按照从0开始的顺序,依次是方法参数,之后是方法内的局部变量,局部变量0就是a,1就是b,2就是c…… 编译之后的字节码为: // access flags 0x9 public static add(II)I L0 LINENUMBER 18 L0 // 对应源代码第18行,以此类推 ICONST_0 // 把常量0 push 到Java栈帧的操作数栈里 ISTORE 2 // 将0从操作数栈pop到局部变量表槽2里(c),完成赋值 L1 LINENUMBER 19 L1 ILOAD 0 // 将局部变量槽位0(a)push 到Java栈帧的操作数栈里 ILOAD 1 // 把局部变量槽1(b)push到操作数栈 IADD // pop出a和b两个变量,求和,把结果push到操作数栈 ISTORE 2 // 把结果从操作数栈pop到局部变量2(a+b的和给c赋值) L2 LINENUMBER 21 L2 ILOAD 2 // 局部变量2(c)push 到操作数栈 IRETURN // 返回结果 L3 LOCALVARIABLE a I L0 L3 0 LOCALVARIABLE b I L0 L3 1 LOCALVARIABLE c I L1 L3 2 MAXSTACK = 2 MAXLOCALS = 3 发现,整个计算过程的参数传递和操作数栈密切相关!如图: image.png 2.3.1.3 栈数据区 存放一些用于支持常量池解析(常量池指针)、正常方法返回以及异常派发机制的信息。即将常量池的符号引用转化为直接地址引用、恢复发起调用的方法的帧进行正常返回,发生异常时转交异常表进行处理。 2.4 本地方法栈 Native Method Stack 访问本地方式时使用到的栈,为本地方法服务, 也就是调用虚拟机使用到的Native方法服务。也会抛出StackOverflowError和OutOfMemoryError异常。 2.5 PC寄存器 每个线程都拥有一个PC寄存器,线程私有的。 PC寄存器的内容总是下一条将被执行指令的"地址",这里的"地址"可以是一个本地指针,也可以是在方法字节码中相对于该方法起始指令的偏移量。如果该线程正在执行一个本地方法,则程序计数器内容为undefined,区域在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。 2.6 堆与栈 2.6.1 堆与栈里存什么 1)堆中存的是对象。栈中存的是基本数据类型和堆中对象的引用。一个对象的大小是不可估计的,或者说是可以动态变化的,但是在栈中,一个对象只对应了一个4btye的引用。 2)为什么不把基本类型放堆中呢?因为其占用的空间一般是1~8个字节——需要空间比较少,而且因为是基本类型,所以不会出现动态增长的情况——长度固定,因此栈中存储就够了,如果把他存在堆中是没有什么意义的。可以这么说,基本类型和对象的引用都是存放在栈中,而且都是几个字节的一个数,因此在程序运行时,他们的处理方式是统一的。但是基本类型、对象引用和对象本身就有所区别了,因为一个是栈中的数据一个是堆中的数据。最常见的一个问题就是,Java中参数传递时的问题。 3)Java中的参数传递时传值呢?还是传引用?程序运行永远都是在栈中进行的,因而参数传递时,只存在传递基本类型和对象引用的问题。不会直接传对象本身。 int a = 0; //全局初始化区char p1; //全局未初始化区main(){ int b; //栈 char s[] = "abc"; //栈 char p2; //栈 char p3 = "123456"; //123456\0在常量区,p3在栈上。 static int c =0; //全局(静态)初始化区 p1 = (char *)malloc(10); //堆 p2 = (char *)malloc(20); //堆} 2.6.2 堆内存与栈内存的区别 申请和回收方式不同:栈上的空间是自动分配自动回收的,所以栈上的数据的生存周期只是在函数的运行过程中,运行后就释放掉,不可以再访问。而堆上的数据只要程序员不释放空间,就一直可以访问到,不过缺点是一旦忘记释放会造成内存泄露。 碎片问题:对于栈,不会产生不连续的内存块;但是对于堆来说,不断的new、delete势必会产生上面所述的内部碎片和外部碎片。 申请大小的限制:栈是向低地址扩展的数据结构,是一块连续的内存的区域。栈顶的地址和栈的最大容量是系统预先规定好的,如果申请的空间超过栈的剩余空间,就会产生栈溢出;对于堆,是向高地址扩展的数据结构,是不连续的内存区域。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。 申请效率的比较:栈由系统自动分配,速度较快。但程序员是无法控制的;堆:是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便。 3.JIT编译器 JIT编译器是JVM的核心。它对于程序性能的影响最大。 CPU只能执行汇编代码或者二进制代码,所有程序都需要被翻译成它们,然后才能被CPU执行。 C++以及Fortran这类编译型语言都会通过一个静态的编译器将程序编译成CPU相关的二进制代码。 PHP以及Perl这列语言则是解释型语言,只需要安装正确的解释器,它们就能运行在任何CPU之上。当程序被执行的时候,程序代码会被逐行解释并执行。 编译型语言的优缺点: 速度快:因为在编译的时候它们能够获取到更多的有关程序结构的信息,从而有机会对它们进行优化。 适用性差:它们编译得到的二进制代码往往是CPU相关的,在需要适配多种CPU时,可能需要编译多次。 解释型语言的优缺点: 适应性强:只需要安装正确的解释器,程序在任何CPU上都能够被运行 速度慢:因为程序需要被逐行翻译,导致速度变慢。同时因为缺乏编译这一过程,执行代码不能通过编译器进行优化。 Java的做法是找到编译型语言和解释性语言的一个中间点: Java代码会被编译:被编译成Java字节码,而不是针对某种CPU的二进制代码。 Java代码会被解释:Java字节码需要被java程序解释执行,此时,Java字节码被翻译成CPU相关的二进制代码。 JIT编译器的作用:在程序运行期间,将Java字节码编译成平台相关的二进制代码。正因为此编译行为发生在程序运行期间,所以该编译器被称为Just-In-Time编译器。 HotSpot 编译 HotSpot VM名字也体现了JIT编译器的工作方式。在VM开始运行一段代码时,并不会立即对它们进行编译。在程序中,总有那么一些“热点”区域,该区域的代码会被反复的执行。而JIT编译器只会编译这些“热点”区域的代码。 这么做的原因在于: * 编译那些只会被运行一次的代码性价比太低,直接解释执行Java字节码反而更快。* JVM在执行这些代码的时候,能获取到这些代码的信息,一段代码被执行的次数越多,JVM也对它们愈加熟悉,因此能够在对它们进行编译的时候做出一些优化。 在HotSpot VM中内嵌有两个JIT编译器,分别为Client Compiler和Server Compiler,但大多数情况下我们简称为C1编译器和C2编译器。开发人员可以通过如下命令显式指定Java虚拟机在运行时到底使用哪一种即时编译器,如下所示: -client:指定Java虚拟机运行在Client模式下,并使用C1编译器;-server:指定Java虚拟机运行在Server模式下,并使用C2编译器。 除了可以显式指定Java虚拟机在运行时到底使用哪一种即时编译器外,默认情况下HotSpot VM则会根据操作系统版本与物理机器的硬件性能自动选择运行在哪一种模式下,以及采用哪一种即时编译器。简单来说,C1编译器会对字节码进行简单和可靠的优化,以达到更快的编译速度;而C2编译器会启动一些编译耗时更长的优化,以获取更好的编译质量。不过在Java7版本之后,一旦开发人员在程序中显式指定命令“-server”时,缺省将会开启分层编译(Tiered Compilation)策略,由C1编译器和C2编译器相互协作共同来执行编译任务。不过在早期版本中,开发人员则只能够通过命令“-XX:+TieredCompilation”手动开启分层编译策略。 总结 Java综合了编译型语言和解释性语言的优势。 Java会将类文件编译成为Java字节码,然后Java字节码会被JIT编译器选择性地编译成为CPU能够直接运行的二进制代码。 将Java字节码编译成二进制代码后,性能会被大幅度提升。 4.类加载机制 Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的加载机制。 类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括了:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(using)、和卸载(Unloading)七个阶段。其中验证、准备和解析三个部分统称为连接(Linking),这七个阶段的发生顺序如下图所示: image.png 如上图所示,加载、验证、准备、初始化和卸载这五个阶段的顺序是确定的,类的加载过程必须按照这个顺序来按部就班地开始,而解析阶段则不一定,它在某些情况下可以在初始化阶段后再开始。 类的生命周期的每一个阶段通常都是互相交叉混合式进行的,通常会在一个阶段执行的过程中调用或激活另外一个阶段。 4.1 类加载的时机 主动引用 一个类被主动引用之后会触发初始化过程(加载,验证,准备需再此之前开始) 1)遇到new、get static、put static或invoke static这4条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。生成这4条指令最常见的Java代码场景是:使用new关键字实例化对象时、读取或者设置一个类的静态字段(被final修饰、已在编译器把结果放入常量池的静态字段除外)时、以及调用一个类的静态方法的时候。 2)使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。 3)当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要触发父类的初始化。 4)当虚拟机启动时,用户需要指定一个执行的主类(包含main()方法的类),虚拟机会先初始化这个类。 5)当使用jdk7+的动态语言支持时,如果java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发器 初始化。 被动引用 一个类如果是被动引用的话,该类不会触发初始化过程 1)通过子类引用父类的静态字段,不会导致子类初始化。对于静态字段,只有直接定义该字段的类才会被初始化,因此当我们通过子类来引用父类中定义的静态字段时,只会触发父类的初始化,而不会触发子类的初始化。 2)通过数组定义来引用类,不会触发此类的初始化。 3)常量在编译阶段会存入调用类的常量池中,本质上没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化。 4.2 类加载过程 1、加载 在加载阶段,虚拟机需要完成以下三件事情: 1)通过一个类的全限定名称来获取定义此类的二进制字节流。 2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。 3)在java堆中生成一个代表这个类的java.lang.Class对象,作为方法区这些数据的访问入口。 相对于类加载过程的其他阶段,加载阶段是开发期相对来说可控性比较强,该阶段既可以使用系统提供的类加载器完成,也可以由用户自定义的类加载器来完成,开发人员可以通过定义自己的类加载器去控制字节流的获取方式。 2、验证 验证的目的是为了确保Class文件中的字节流包含的信息符合当前虚拟机的要求,而且不会危害虚拟机自身的安全。不同的虚拟机对类验证的实现可能会有所不同,但大致都会完成以下四个阶段的验证:文件格式的验证、元数据的验证、字节码验证和符号引用验证。 1)文件格式的验证:验证字节流是否符合Class文件格式的规范,并且能被当前版本的虚拟机处理,该验证的主要目的是保证输入的字节流能正确地解析并存储 于方法区之内。经过该阶段的验证后,字节流才会进入内存的方法区中进行存储,后面的三个验证都是基于方法区的存储结构进行的。 2)元数据验证:对类的元数据信息进行语义校验(其实就是对类中的各数据类型进行语法校验),保证不存在不符合Java语法规范的元数据信息。 3)字节码验证:该阶段验证的主要工作是进行数据流和控制流分析,对类的方法体进行校验分析,以保证被校验的类的方法在运行时不会做出危害虚拟机安全的行为。 4)符号引用验证:这是最后一个阶段的验证,它发生在虚拟机将符号引用转化为直接引用的时候(解析阶段中发生该转化,后面会有讲解),主要是对类自身以外的信息(常量池中的各种符号引用)进行匹配性的校验。 3、准备 准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中进行分配。 注: 1)这时候进行内存分配的仅包括类变量(static),而不包括实例变量,实例变量会在对象实例化时随着对象一块分配在Java堆中。 2)这里所设置的初始值通常情况下是数据类型默认的零值(如0、0L、、false等),而不是被在Java代码中被显式地赋予的值。 4、解析 解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程 。 符号引用(Symbolic Reference): 符号引用以一组符号来描述所引用的目标,符号引用可以是任何形式的字面量,符号引用与虚拟机实现的内存布局无关,引用的目标并不一定已经在内存中。 直接引用(Direct Reference): 直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用是与虚拟机实现的内存布局相关的,同一个符号引用在不同的虚拟机实例上翻译出来的直接引用一般都不相同,如果有了直接引用,那引用的目标必定已经在内存中存在。 1)类或接口的解析:判断所要转化成的直接引用是对数组类型,还是普通的对象类型的引用,从而进行不同的解析。 2)字段解析:对字段进行解析时,会先在本类中查找是否包含有简单名称和字段描述符都与目标相匹配的字段,如果有,则查找结束;如果没有,则会按照继承关系从上往下递归搜索该类所实现的各个接口和它们的父接口,还没有,则按照继承关系从上往下递归搜索其父类,直至查找结束。 3)类方法解析:对类方法的解析与对字段解析的搜索步骤差不多,只是多了判断该方法所处的是类还是接口的步骤,而且对类方法的匹配搜索,是先搜索父类,再搜索接口。 4)接口方法解析:与类方法解析步骤类似,只是接口不会有父类,因此,只递归向上搜索父接口就行了。 5、初始化 类初始化阶段是类加载过程的最后一步,前面的类加载过程中,除了加载(Loading)阶段用户应用程序可以通过自定义类加载器参与之外,其余动作完全由虚拟机主导和控制。到了初始化阶段,才真正开始执行类中定义的Java程序代码。 初始化阶段是执行类构造器<clinit>方法的过程。 1)<clinit>方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{}块)中的语句合并产生的,编译器收集的顺序由语句在源文件中出现的顺序所决定。 2)<clinit>方法与类的构造函数不同,它不需要显式地调用父类构造器,虚拟机会保证在子类的<clinit>方法执行之前,父类的<clinit>方法已经执行完毕,因此在虚拟机中第一个执行的<clinit>方法的类一定是java.lang.Object。 3)由于父类的<clinit>方法先执行,也就意味着父类中定义的静态语句块要优先于子类的变量赋值操作。 4)<clinit>方法对于类或者接口来说并不是必需的,如果一个类中没有静态语句块也没有对变量的赋值操作,那么编译器可以不为这个类生成<clinit>方法。 5)接口中可能会有变量赋值操作,因此接口也会生成<clinit>方法。但是接口与类不同,执行接口的<clinit>方法不需要先执行父接口的<clinit>方法。只有当父接口中定义的变量被使用时,父接口才会被初始化。另外,接口的实现类在初始化时也不会执行接口的<clinit>方法。 6)虚拟机会保证一个类的<clinit>方法在多线程环境中被正确地加锁和同步。如果有多个线程去同时初始化一个类,那么只会有一个线程去执行这个类的<clinit>方法,其它线程都需要阻塞等待,直到活动线程执行<clinit>方法完毕。如果在一个类的<clinit>方法中有耗时很长的操作,那么就可能造成多个进程阻塞。
我们今天就来了解一下锁中的乐观锁和悲观锁。 在面试中,如果是Java后天研发的工程师,很有可能会考到这一个知识点。所以今天也就来说下这个。 两者的概念 乐观锁 根据表面上来看每次去拿数据的时候认为别人都不会修改。所以不会上锁,有着更宽松的锁机制,减少了性能的开销。 在更新的时候会根据版本号进行判断是否有程序去修改这个数据,例如版本号等机制,使用版本号的机制在进行数据提交的时候,如果版本号大于对应的版本号那么进行更新,否则不进行更新。 在大多数情况下乐观锁使用在读多的应用上。在java中我们所了解的atomic包中,常用的线程安全的变量是使用的该锁机制。 乐观锁不能解决脏读问题 悲观锁 相对乐观锁来说,悲观锁具有强烈的独占和排他特性。该锁机制总是假设最坏的情况,每次去拿数据的时候都会认为别人会修改,所以在取数据的时候会进行加锁的操作。在这样的情况下,别的程序代码操作,需要进行等待操作,直到其拿到锁为止。 java中实现该两种机制的锁 在整个操作系统中,Cpu是分片操作的,在程序的执行过程中,会进行线程间的切换,也就是cpu的切换。Cpu的切换是很耗费时间,所以我们如果想减少CPU的切换,可以让某个线程一直持有该CPU,所以可以采用循环的方式来实现。 悲观锁 我们Java中使用的synchronized 就是一种典型的悲观锁的实现,该锁是拥有独占性,和排他性保证了线程 的安全,所以我们说synchronized是悲观锁。 优点:对数据处理安全起到了安全的作用。 缺点: 因为加锁 排他性,那么就会损耗性能,降低了并行性,增加了系统负载。 容易出现死锁的情况。 乐观锁 平常使用的CAS的安全操作类就属于乐观锁机制。还有我们经常说的自旋锁,轻量级锁,偏向锁这些也属于乐观锁。乐观锁为什么乐观,是因为减少了对CPU之间的切换,挂起,阻塞 ,唤醒等机制的操作造成的开销。所以在开销上,乐观锁更占一筹,减少了性能的损耗。建议对性能要求高,读请求多的使用该机制。 下面介绍下可以使用这些CAS操作一些类的使用 ``` AtomicInteger one = new AtomicInteger(); AtomicLong atomicLong = new AtomicLong(); AtomicReference student = new AtomicReference<>(); one.get() ; //获得值 one.addAndGet(2) ; //增加指定的值 one.incrementAndGet(); //增加1 one.getAndSet(0); //先得到 原先值 然后在置为0 one.longValue(); //转为 long型``` 转载: 原创: mengrui LuckQI
List和Set比较,各自的子类比较 对比一:Arraylist与LinkedList的比较 1、ArrayList是实现了基于动态数组的数据结构,因为地址连续,一旦数据存储好了,查询操作效率会比较高(在内存里是连着放的)。 2、因为地址连续, ArrayList要移动数据,所以插入和删除操作效率比较低。 3、LinkedList基于链表的数据结构,地址是任意的,所以在开辟内存空间的时候不需要等一个连续的地址,对于新增和删除操作add和remove,LinedList比较占优势。 4、因为LinkedList要移动指针,所以查询操作性能比较低。 适用场景分析: 当需要对数据进行对此访问的情况下选用ArrayList,当需要对数据进行多次增加删除修改时采用LinkedList。 对比二:ArrayList与Vector的比较 1、Vector的方法都是同步的,是线程安全的,而ArrayList的方法不是,由于线程的同步必然要影响性能。因此,ArrayList的性能比Vector好。 2、当Vector或ArrayList中的元素超过它的初始大小时,Vector会将它的容量翻倍,而ArrayList只增加50%的大小,这样。ArrayList就有利于节约内存空间。 3、大多数情况不使用Vector,因为性能不好,但是它支持线程的同步,即某一时刻只有一个线程能够写Vector,避免多线程同时写而引起的不一致性。 4、Vector可以设置增长因子,而ArrayList不可以。 适用场景分析: 1、Vector是线程同步的,所以它也是线程安全的,而ArrayList是线程异步的,是不安全的。如果不考虑到线程的安全因素,一般用ArrayList效率比较高。 2、如果集合中的元素的数目大于目前集合数组的长度时,在集合中使用数据量比较大的数据,用Vector有一定的优势。 对比三:HashSet与TreeSet的比较 1.TreeSet 是二叉树实现的,Treeset中的数据是自动排好序的,不允许放入null值 。 2.HashSet 是哈希表实现的,HashSet中的数据是无序的,可以放入null,但只能放入一个null,两者中的值都不能重复,就如数据库中唯一约束 。 3.HashSet要求放入的对象必须实现HashCode()方法,放入的对象,是以hashcode码作为标识的,而具有相同内容的String对象,hashcode是一样,所以放入的内容不能重复。但是同一个类的对象可以放入不同的实例。 适用场景分析: HashSet是基于Hash算法实现的,其性能通常都优于TreeSet。我们通常都应该使用HashSet,在我们需要排序的功能时,我们才使用TreeSet。 大体回答如上,类似文章请移驾: List,Set和Map详解及其区别和他们分别适用的场景 HashMap和ConcurrentHashMap的区别 1、HashMap不是线程安全的,而ConcurrentHashMap是线程安全的。 2、ConcurrentHashMap采用锁分段技术,将整个Hash桶进行了分段segment,也就是将这个大的数组分成了几个小的片段segment,而且每个小的片段segment上面都有锁存在,那么在插入元素的时候就需要先找到应该插入到哪一个片段segment,然后再在这个片段上面进行插入,而且这里还需要获取segment锁。 3、ConcurrentHashMap让锁的粒度更精细一些,并发性能更好。 大体回答如上,类似文章请移驾: HashMap详解 至于两者的底层实现,你如果想通过一篇文章就理解了,那就too young了,好好找些博文+看源码去吧。 HashTable和ConcurrentHashMap的区别 它们都可以用于多线程的环境,但是当Hashtable的大小增加到一定的时候,性能会急剧下降,因为迭代时需要被锁定很长的时间。因为ConcurrentHashMap引入了分割(segmentation),不论它变得多么大,仅仅需要锁定map的某个部分,而其它的线程不需要等到迭代完成才能访问map。简而言之,在迭代的过程中,ConcurrentHashMap仅仅锁定map的某个部分,而Hashtable则会锁定整个map。 大体回答如上,类似文章请移驾: HashMap和HashTable到底哪不同? String,StringBuffer和StringBuilder的区别 1、运行速度,或者说是执行速度,在这方面运行速度快慢为:StringBuilder > StringBuffer > String。 2、线程安全上,StringBuilder是线程不安全的,而StringBuffer是线程安全的。 适用场景分析: String:适用于少量的字符串操作的情况 StringBuilder:适用于单线程下在字符缓冲区进行大量操作的情况 StringBuffer:适用多线程下在字符缓冲区进行大量操作的情况 大体回答如上,类似文章请移驾: String、StringBuffer与StringBuilder介绍 wait和sleep的区别 1、sleep()方法是属于Thread类中的,而wait()方法,则是属于Object类中的。 2、sleep()方法导致了程序暂停执行指定的时间,让出cpu给其他线程,但是他的监控状态依然保持着,当指定的时间到了又会自动恢复运行状态。所以在调用sleep()方法的过程中,线程不会释放对象锁。 3、调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。 转载:阿木侠 芋道源码 https://mp.weixin.qq.com/s?__biz=MzUzMTA2NTU2Ng==&mid=2247484399&idx=1&sn=498a9f0151541d7c3bad2ebccb9aaa2f&chksm=fa497c5ecd3ef5488e6c4cddb24df96725b6147b99bbd485aa1f2c0ddb5d5604ef30026fad0d&key=de1ccb5f17d36506c921d919664438825a1235a753e24a11b1ce3a031c7b378e28090b3fdcffd4b73edbaaeb6562b6f280d4bc67a57e6cb626ac516e2eb54ccf7e5aef241c5043778c8fc78b26d0d479&ascene=0&uin=MTE1MDEyOTYwMA%3D%3D&devicetype=iMac+MacBookPro13%2C1+OSX+OSX+10.12.4+build(16E195)&version=12020810&nettype=WIFI&lang=zh_CN&fontScale=100&pass_ticket=8hfanWIrVdtZ6JsIT2zjCOessVLBtelL6wU308J%2FGjCOe3Il9eKp%2BWWn70JiqUCX
java-自定义线程池 当我们使用 线程池的时候,可以使用 newCachedThreadPool()或者 newFixedThreadPool(int)等方法,其实我们深入到这些方法里面,就可以看到它们的是实现方式是这样的。 1 public static ExecutorService newCachedThreadPool() { 2 return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 3 60L, TimeUnit.SECONDS, 4 new SynchronousQueue<Runnable>()); 5 } 1 public static ExecutorService newFixedThreadPool(int nThreads) { 2 return new ThreadPoolExecutor(nThreads, nThreads, 3 0L, TimeUnit.MILLISECONDS, 4 new LinkedBlockingQueue<Runnable>()); 5 } 包括其他几种不同类型的线程池,其实都是通过 ThreadPoolExecutor这个核心类来创建的,如果我们要自定义线程池,那么也是通过这个类来实现的。 该类有四个构造方法,查看源码可以看到,头三个构造方法,其实都是调用的第四个构造方法,所以我们就解释一下第四个构造方法的参数含义。 public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) corePoolSize:核心线程池的大小,在线程池被创建之后,其实里面是没有线程的。(当然,调用prestartAllCoreThreads()或者prestartCoreThread()方法会预创建线程,而不用等着任务的到来)。当有任务进来的时候,才会创建线程。当线程池中的线程数量达到corePoolSize之后,就把任务放到 缓存队列当中。(就是 workQueue)。 maximumPoolSize:最大线程数量是多少。它标志着这个线程池的最大线程数量。如果没有最大数量,当创建的线程数量达到了 某个极限值,到最后内存肯定就爆掉了。 keepAliveTime:当线程没有任务时,最多保持的时间,超过这个时间就被终止了。默认情况下,只有 线程池中线程数量 大于 corePoolSize时,keepAliveTime值才会起作用。也就说说,只有在线程池线程数量超出corePoolSize了。我们才会把超时的空闲线程给停止掉。否则就保持线程池中有 corePoolSize 个线程就可以了。 Unit:参数keepAliveTime的时间单位,就是 TimeUnit类当中的几个属性。 如下图: workQueue:用来存储待执行任务的队列,不同的线程池它的队列实现方式不同(因为这关系到排队策略的问题)比如有以下几种 ArrayBlockingQueue:基于数组的队列,创建时需要指定大小。 LinkedBlockingQueue:基于链表的队列,如果没有指定大小,则默认值是 Integer.MAX_VALUE。(newFixedThreadPool和newSingleThreadExecutor使用的就是这种队列)。 SynchronousQueue:这种队列比较特殊,因为不排队就直接创建新线程把任务提交了。(newCachedThreadPool使用的就是这种队列)。 threadFactory:线程工厂,用来创建线程。 Handler:拒绝执行任务时的策略,一般来讲有以下四种策略, (1) ThreadPoolExecutor.AbortPolicy 丢弃任务,并抛出 RejectedExecutionException 异常。 (2) ThreadPoolExecutor.CallerRunsPolicy:该任务被线程池拒绝,由调用 execute方法的线程执行该任务。 (3) ThreadPoolExecutor.DiscardOldestPolicy : 抛弃队列最前面的任务,然后重新尝试执行任务。 (4) ThreadPoolExecutor.DiscardPolicy,丢弃任务,不过也不抛出异常。 看一个demo ,示例代码地址:src/thread_runnable/CustomThreadPool.java 1 class CustomTask implements Runnable{ 2 private int id; 3 public CustomTask(int id) { 4 this.id = id; 5 } 6 7 @Override 8 public void run() { 9 // TODO Auto-generated method stub 10 System.out.println("#" + id + " threadId=" + Thread.currentThread().getName() ); 11 try { 12 TimeUnit.MILLISECONDS.sleep(100); 13 }catch(InterruptedException e){ 14 e.printStackTrace(); 15 } 16 } 17 18 } 19 20 public class CustomThreadPool { 21 public static void main(String[] args) { 22 // TODO Auto-generated method stub 23 BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(10); 24 ThreadPoolExecutor pool = new ThreadPoolExecutor(3, 5, 60, TimeUnit.MICROSECONDS, queue); 25 26 for (int i=0; i<7; i++){ 27 Runnable task = new CustomTask(i); 28 pool.execute(task); 29 } 30 31 pool.shutdown(); 32 } 33 34 } 输出结果: 从这个例子,可以看出,虽然我们有7个任务,但是实际上,只有三个线程在运行。 那么当我们提交任务给线程池之后,它的处理策略是什么呢? (1),如果当前线程池线程数目小于 corePoolSize(核心池还没满呢),那么就创建一个新线程去处理任务。 (2),如果核心池已经满了,来了一个新的任务后,会尝试将其添加到任务队列中,如果成功,则等待空闲线程将其从队列中取出并且执行,如果队列已经满了,则继续下一步。 (3),此时,如果线程池线程数量 小于 maximumPoolSize,则创建一个新线程执行任务,否则,那就说明线程池到了最大饱和能力了,没办法再处理了,此时就按照拒绝策略来处理。(就是构造函数当中的Handler对象)。 (4),如果线程池的线程数量大于corePoolSize,则当某个线程的空闲时间超过了keepAliveTime,那么这个线程就要被销毁了,直到线程池中线程数量不大于corePoolSize为止。 举个通俗易懂的例子,公司要设立一个项目组来处理某些任务,hr部门给的人员编制是10个人(corePoolSize)。同时给他们专门设置了一间有15个座位(maximumPoolSize)的办公室。最开始的时候来了一个任务,就招聘一个人。就这样,一个一个的招聘,招满了十个人,不断有新的任务安排给这个项目组,每个人也在不停的接任务干活。不过后来任务越来越多,十个人无法处理完了。其他的任务就只能在走廊外面排队了。后来任务越来越多,走廊的排队队伍也挤不下。然后只好找找一些临时工来帮助完成任务。因为办公室只有15个座位,所以它们最多也就只能找5个临时工。可是任务依旧越来越多,根本处理不完,那没办法,这个项目组只好拒绝再接新任务。(拒绝的方式就是 Handler),最后任务渐渐的少了,大家都比较清闲了。所以就决定看大家表现,谁表现不好,谁就被清理出这个办公室(空闲时间超过 keepAliveTime),直到 办公室只剩下10个人(corePoolSize),维持固定的人员编制为止。 关于线程池,ThreadPoolExecutor还提供了一些需要注意的方法: (1) shutdown(),平滑的关闭线程池。(如果还有未执行完的任务,就等待它们执行完)。 (2) shutdownNow()。简单粗暴的关闭线程池。(没有执行完的任务也直接关闭)。 (3) setCorePoolSize()。设置/更改核心池的大小。 (4) setMaximumPoolSize(),设置/更改线程池中最大线程的数量限制。 这几篇java多线程文章的demo代码下载地址 http://download.csdn.net/detail/yaowen369/9786452 转载作者: www.yaoxiaowen.com
k8s网络1.容器网络 pod每个POD分配单独的Ip地址,IP-pre-Pod模型(IP以Pod为单位进行分配的,一个Pod一个IP的设计模型)每个容器也可以称为pod,pod中有个pause容器,pause可以接入网络,并且共享网络给pod中其他容器,每个POD分配单独的Ip地址,在pod中每个容器共享自己的IP对于k8s来说,我们搭建的网络,每一个node有自己的子网,所有的子网形成一个大的子网,实现网络互连流向来说,通过pod暴露出的port流向外部的bridge或者说host上,通过host上的iptables或者其他方式路由到其他节点使用container network interface 标准,由cncf基金会推出,主要是统一整个容器网络API,k8s主要是使用CNI插件来组件自己的容器网络,当POD创建销毁时,k8s agent会调用CNI插件接口生成网络配置,创建container(POD)可以生成IP地址,把container接入bridge,去生成container到其他节点的路由,删除container时,会把IP地址回收,处理network namespace回收CNI也会在container里面生成虚拟网卡(NIC 全称:Network Interface Card),把它和container(POD)的namespace关联,从而container可以使用由CNI提供的IP CNI创建网络1:Kubelet runtime 也就是kubelet收到创建pod请求以后,会先生成pause的container,为pause的container创建一个network namespace,把它(network namespace)关联到pause container上面CNI创建网络2:触发CNI插件,触发插件时,把CNI的configuration load给这个CNI的插件,并且传递给其他环境变量,现在做的action是什么,已经创建的network namespace是什么,interface是什么 CNI创建网络3:收到参数后开始创建网络,使用bridge plugin CNI例子来讲,1.先创建单独的bridge出来,然后去创建虚拟以特网接口veth pair,把它和network namespace 连接起来,2.同时它(CNI)会调用ipam(IP地址的分配),获取空闲IP分配给container,3.CNI会切换到container的network namespace里,把ip地址设置在 container的network interface上 Network agent不仅需要CNI的plugin还需要daemon,不同的厂家都会实现不同的network agent,它去保证从这个IP会方便的路由到不同节点 calico:基于容器的网络,也可以openstack 用linux的内核转发机制,使用3层路由协议,实现container的互连k8s上,calico会创建agent,calico cni插件,通过BGP协议路由表 fannel:使用overloay,container连上flannel 0这个上,不同的flannel angent 通过UDP互连,有性能影响,在原来的ip包上封装 NSX-t:使用2层转发,使用openVswitch做2层转发,需要运行openVswitch的服务
centos7在yum安装etcd、kubernetes时,提示找不到源 [baseuser@SHDL009020141 ~]$ sudo yum install -y etcd kubernetes Loaded plugins: fastestmirror, langpacks Loading mirror speeds from cached hostfile No package etcd available. No package kubernetes available. Error: Nothing to do 1.备份repo文件 [baseuser@SHDL009020141 ~]$ cd /etc/yum.repos.d/ [baseuser@SHDL009020141 yum.repos.d]$ ll total 4 -rw-r--r-- 1 root root 162 Dec 9 11:49 saltup.repo [baseuser@SHDL009020141 yum.repos.d]$ sudo cp saltup.repo saltup.repo.backup [baseuser@SHDL009020141 yum.repos.d]$ ll total 8 -rw-r--r-- 1 root root 162 Dec 9 11:49 saltup.repo -rw-r--r-- 1 root root 162 Dec 26 17:44 saltup.repo.backup 2.下载163repo文件 [baseuser@SHDL009020141 yum.repos.d]$ sudo wget http://mirrors.163.com/.help/CentOS7-Base-163.repo [baseuser@SHDL009020141 yum.repos.d]$ yum makecache 3.安装成功 [baseuser@SHDL009020141 ~]$ sudo yum install -y etcd kubernetes
虚拟机内核版本: Linux SHDL009020142 2.6.32-642.el6.x86_64 #1 SMP Wed Apr 13 00:51:26 EDT 2016 x86_64 x86_64 x86_64 GNU/Linux 采用手动安装方式,安装docker-engine 直接下载文件安装 [baseuser@SHDL009020139 ~]$ wget https://yum.dockerproject.org/repo/main/centos/6/Packages/docker-engine-1.7.1-1.el6.x86_64.rpm 安装依赖 [baseuser@SHDL009020139 ~]$ sudo yum install -y libcgroup-* [baseuser@SHDL009020139 ~]$ rpm -ivh docker-engine-1.7.1-1.el6.x86_64.rpm warning: docker-engine-1.7.1-1.el6.x86_64.rpm: Header V4 RSA/SHA1 Signature, key ID 2c52609d: NOKEY error: Failed dependencies: xz is needed by docker-engine-1.7.1-1.el6.x86_64 报错提示安装依赖XZ, [baseuser@SHDL009020139 ~]$ yum search xz Loaded plugins: product-id, search-disabled-repos, subscription-manager =========================================================== N/S Matched: xz =========================================================== xz.x86_64 : LZMA compression utilities xz-devel.i686 : Devel libraries & headers for liblzma xz-devel.x86_64 : Devel libraries & headers for liblzma xz-libs.i686 : Libraries for decoding LZMA compression xz-libs.x86_64 : Libraries for decoding LZMA compression xz-lzma-compat.x86_64 : Older LZMA format compatibility binaries Name and summary matches only, use "search all" for everything. [baseuser@SHDL009020139 ~]$ sudo yum install -y xz-* [baseuser@SHDL009020139 ~]$ rpm -ivh docker-engine-1.7.1-1.el6.x86_64.rpm warning: docker-engine-1.7.1-1.el6.x86_64.rpm: Header V4 RSA/SHA1 Signature, key ID 2c52609d: NOKEY error: can't create transaction lock on /var/lib/rpm/.rpm.lock (Permission denied) [baseuser@SHDL009020139 ~]$ sudo rpm -ivh docker-engine-1.7.1-1.el6.x86_64.rpm warning: docker-engine-1.7.1-1.el6.x86_64.rpm: Header V4 RSA/SHA1 Signature, key ID 2c52609d: NOKEY Preparing... ########################################### [100%] 1:docker-engine ########################################### [100%] 查询已安装的docker [baseuser@SHDL009020139 ~]$ yum list installed | grep docker docker-engine.x86_64 1.7.1-1.el6 installed [baseuser@SHDL009020139 ~]$ [baseuser@SHDL009020139 ~]$ ps -ef|grep docker baseuser 4460 3491 0 11:43 pts/0 00:00:00 grep docker [baseuser@SHDL009020139 ~]$ sudo service docker start [sudo] password for baseuser: Starting cgconfig service: [ OK ] Starting docker: [ OK ] [baseuser@SHDL009020139 ~]$ ps -ef|grep docker root 4506 1 2 11:44 pts/0 00:00:00 /usr/bin/docker -d baseuser 4622 3491 0 11:44 pts/0 00:00:00 grep docker [baseuser@SHDL009020139 ~]$ sudo docker run hello-world Hello from Docker! This message shows that your installation appears to be working correctly. To generate this message, Docker took the following steps: 1. The Docker client contacted the Docker daemon. 2. The Docker daemon pulled the "hello-world" image from the Docker Hub. 3. The Docker daemon created a new container from that image which runs the executable that produces the output you are currently reading. 4. The Docker daemon streamed that output to the Docker client, which sent it to your terminal. To try something more ambitious, you can run an Ubuntu container with: $ docker run -it ubuntu bash Share images, automate workflows, and more with a free Docker ID: https://cloud.docker.com/ For more examples and ideas, visit: https://docs.docker.com/engine/userguide/ [baseuser@SHDL009020139 ~]$ sudo docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES e5f07cbd2647 hello-world "/hello" 33 seconds ago Exited (0) 32 seconds ago grave_turing [baseuser@SHDL009020139 ~]$ docker version Client version: 1.7.1 Client API version: 1.19 Go version (client): go1.4.2 Git commit (client): 786b29d OS/Arch (client): linux/amd64 Server version: 1.7.1 Server API version: 1.19 Go version (server): go1.4.2 Git commit (server): 786b29d OS/Arch (server): linux/amd64 [baseuser@SHDL009020139 ~]$ 卸载 [baseuser@SHDL009020139 ~]$ yum list installed | grep docker docker-engine.x86_64 1.7.1-1.el6 installed 删除安装包 [baseuser@SHDL009020139 ~]$ sudo yum -y remove docker-engine.x86_64 [baseuser@SHDL009020139 ~]$ yum list installed | grep docker [baseuser@SHDL009020139 ~]$ 以上安装过程。
docker Setup docker推荐使用version 1.13或者更高版本 docker 包含社区版和企业版 Community Edition (CE) and Enterprise Edition (EE). 支持多种平台 DeskTop:Mac、Windows Cloud:Amazon 、Microsoft Server:CentOs、OracleLinux、Ubuntu、Debian、Red Hat Enterprise Linux 安装docker CE 启动一个docker CE,需要确认前置要求,再执行安装操作。EE版本是收费版本不做介绍 前置要求 OS要求、老版本要求:docker docker-engine查看 安装 1.Docker’s repositories安装 Install required packages. yum-utils provides the yum-config-manager utility, and device-mapper-persistent-data and lvm2 are required by the devicemapper storage driver. $ sudo yum install -y yum-utils \ device-mapper-persistent-data \ lvm2 Use the following command to set up the stable repository. You always need the stable repository, even if you want to install builds from the edge or test repositories as well. $ sudo yum-config-manager \ --add-repo \ https://download.docker.com/linux/centos/docker-ce.repo Optional: Enable the edge and test repositories. These repositories are included in the docker.repo file above but are disabled by default. You can enable them alongside the stable repository. $ sudo yum-config-manager --enable docker-ce-edge $ sudo yum-config-manager --enable docker-ce-test You can disable the edge or test repository by running the yum-config-manager command with the --disableflag. To re-enable it, use the --enable flag. The following command disables the edge repository. $ sudo yum-config-manager --disable docker-ce-edge Install the latest version of Docker CE, or go to the next step to install a specific version. $ sudo yum install docker-ce Start Docker. $ sudo systemctl start docker Verify that docker is installed correctly by running the hello-world image. $ sudo docker run hello-world 2.下载rpm,手动安装 从这下载 https://download.docker.com/linux/centos/7/x86_64/stable/Packages/ Install Docker CE, changing the path below to the path where you downloaded the Docker package. $ sudo yum install /path/to/package.rpm 3.scripts脚本自动安装 4.卸载 Uninstall the Docker package: $ sudo yum remove docker-ce Images, containers, volumes, or customized configuration files on your host are not automatically removed. To delete all images, containers, and volumes: $ sudo rm -rf /var/lib/docker You must delete any edited configuration files manually.
1.使用stream的方式: List<String> userIds = appPermissionVoList.stream().map(appPermissionVo->appPermissionVo.getUserId()).collect(Collectors.toList()); 2.使用guava: List<String> usrIds = Lists.transform(appPermissionVoList,appPerm->appPerm.getUserId()); 这2种写法都是支持的,stream是jdk8支持的语法,基本普适各场景。
1.interrupt 线程中断使用,Thread有stop() 不推荐使用,建议用中断的方法来实现 Thread.interrupt(), 2.interrupted 线程中断,并且清除中断状态(参考介绍interrupted的实现:http://blog.csdn.net/hj7jay/article/details/53462553) public static boolean interrupted() { return currentThread().isInterrupted(true); } * Tests if some Thread has been interrupted. The interrupted state * is reset or not based on the value of ClearInterrupted that is * passed. */ private native boolean isInterrupted(boolean ClearInterrupted); 3.isInterrupted 检查中断状态 4.InterruptedException JDK中响应中断的方式是抛出异常,抛出InterruptedException时,会清除中断状态,参考:http://blog.csdn.net/hj7jay/article/details/53462553 这段引用:“  ” 需要在catch InterruptedException中 执行 Thread.currentThread().interrupt(); 来恢复中断状态 5.无法被中断的处理方式介绍参考:http://www.jianshu.com/p/f75b77bdf389这篇介绍能被中断和无法被中断的处理方式介绍,可以看看无法被中断的场景以及参考的资料,同时感觉这位同学的这句话蛮好,“InterruptedException 是最常见的中断表现形式。所以如何处理 InterruptedException 便成为 Java 中断知识中的必修课。” 20171016
1.rxjava,observeOn(Schedulers.io())封装多线程, Flowable.fromArray(ips) .observeOn(Schedulers.io()) .flatMap( ip -> Flowable.fromCallable( () -> { Boolean check = check(ip); checkResult.put(ip,check); return check; } ) ).blockingSubscribe();return checkResult; 2.线程池, 在线程池中执行saltStackUtil.restartJavaSync方法的同步,其返回值放入执行consummer private static final ExecutorService executorService = Executors.newFixedThreadPool(10, new ThreadFactory() {@Override public Thread newThread(Runnable r) { Thread thread = new Thread(r); thread.setName("javaOperator"); return thread; } }); public void restartJava(String ip, String projectName, String commitId, String jarName, String jvmArgs, Consumer<AppServerOperateResult> appServerOperateResultConsumer) throws SaltException {executorService.execute(() ->{ AppServerOperateResult appServerOperateResult = new AppServerOperateResult(); appServerOperateResult.setIp(ip); appServerOperateResult.setProjectName(projectName); appServerOperateResult.setCommitId(commitId); appServerOperateResult.setJarName(jarName); appServerOperateResult.setStartTime(new Date()); appServerOperateResult.setStatus(Constant.JAVA_APP_OPERATE_PROCESS); appServerOperateResult.setType(Constant.JAVA_APP_OPERATE_RESTART); appServerOperateResult.setRunParamater(jvmArgs); Map<String, Result<Map<String, State.ApplyResult>>> stringResultMap = null; try { stringResultMap = saltStackUtil.restartJavaSync(ip, projectName, commitId, jarName, jvmArgs); processResult(stringResultMap,appServerOperateResult); appServerOperateResultConsumer.accept(appServerOperateResult); } catch (Exception e) { MyExceptionHandler.handlerException(e,logger); } });} 3.stream,parallel()并行流执行foreach,consummer封装返回数据到checkResult Map中 Stream.of(ips).filter(ip -> ip!=null).parallel().forEach(new Consumer<String>() {@Override public void accept(String ip) {checkResult.put(ip, check(ip, appId, appTypeCode)); } });return checkResult;
mac下面的secureCRT默认保存不上密码 在mac下新安装了secureCRT,取代系统自带的终端工具,主要是为了方便链接服务器。mac下面的secureCRT默认保存不上密码,我们选择了保存密码后,下次登录还是提示密码错误,需要重新认证输入密码。解决办法: 因为secureCRT默认采用mac的keychain来处理密码,所以会出现这个问题。我们只需要去掉这个选项即可正常。打开secureCRT后,options -->global optins-->general 在”mac options”里面去掉”Use Keychain”的选项即可。
@RequestParam和@RequestBody是什么区别,估计很多人还是不太清楚, 因为一般用@ RequestParam就足够传入参数了,要说他们区别,就需要知道contentType是什么? Content-Type: 默认为 application/x-www-form-urlencoded编码的内容,提交方式可以是GET、POST 1.@RequestParam GET、POST:一般处理得是Content-Type: application/x-www-form-urlencoded multipart/form-data 其他格式包括application/json, application/xml等。这些格式的数据,必须使用@RequestBody来处理 PUT: 根据request header Content-Type的值来判断: application/x-www-form-urlencoded可以; multipart/form-data, 不能处理; 其他格式可以; 2.@RequestBody 这个一般处理的是在ajax请求中声明contentType: "application/json; charset=utf-8"时候。也就是json数据或者xml @RequestParam这个一般就是在ajax里面没有声明contentType的时候,为默认的。。。urlencode格式时,用这个
#$的使用,这个很少会关注, #在mybatis的Mapper.xml文件中使用很多, 1.下面一个例子看下,这段有#也有$,这2个都用到了,choose when test也是很少人使用: </sql> 2.#用于动态赋值,传入的是字符串,也可以指定jdbc内容,这个cpu就是Integer <if test="cpu != null"> </if> 3.理解mybatis中 $与# 在mybatis中的$与#都是在sql中动态的传入参数。 eg:select id,name,age from student where name=#{name} 这个name是动态的,可变的。当你传入什么样的值,就会根据你传入的值执行sql语句。 4.使用$与# #{}: 解析为一个 JDBC 预编译语句(prepared statement)的参数标记符,一个 #{ } 被解析为一个参数占位符 。 ${}: 仅仅为一个纯碎的 string 替换,在动态 SQL 解析阶段将会进行变量替换。 name-->cy eg: select id,name,age from student where name=#{name} -- name='cy' select id,name,age from student where name=${name} -- name=cy 参考:http://www.cnblogs.com/hellokitty1/p/6007801.html
mybatis中手动写sql,步骤是先在navicate中执行通过的sql, 如果是对象类型传参,需要注意jdbcType转换,比如:name = #{record.name,jdbcType=VARCHAR} 以下几种方法比较常用,欢迎补充。 1.pom.xml中mybatis依赖1.2.0 <dependencies> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.2.0</version> <exclusions> <exclusion> <groupId>org.apache.tomcat</groupId> <artifactId>tomcat-jdbc</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> </dependency> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-starter</artifactId> <version>1.1.1</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-maven-plugin</artifactId> <version>1.3.2</version> <configuration> <overwrite>true</overwrite> <verbose>true</verbose> </configuration> </plugin> </plugins> </build> 2.基本类型和对象参数,可以是这样: ServerMapper.java: int updateByExample(@Param("record") Server record, @Param("example") ServerExample example); ServerMapper.xml: <update id="updateByExample" parameterType="map"> update server set id = #{record.id,jdbcType=VARCHAR}, name = #{record.name,jdbcType=VARCHAR}, operatingsystem = #{record.operatingsystem,jdbcType=VARCHAR}, cpu = #{record.cpu,jdbcType=INTEGER}, applicant = #{record.applicant,jdbcType=VARCHAR}, status = #{record.status,jdbcType=VARCHAR} <if test="_parameter != null"> <include refid="Update_By_Example_Where_Clause" /> </if> </update> 3.仅有一个list参数,可以这样: ServerMapper.java: List<ServerDetails> selectByServerIds(List<String> serverIds); ServerMapper.xml,注意collection为list: <select id="selectByServerIds" resultMap="ServerDetailsMap"> SELECT s.id, s.`name`,s.operatingsystem,s.cpu,s.disksize,s.memorysize,s.intranetip,s.vcluster,s.`owner`,s.environment,s.applicant,s.`status`, ip.ip,ip.`status` as ipstatus, ip.segment,ip.remark,ip.line from `server` as s , ippool as ip where s.intranetip = ip.id and s.id in <foreach item="item" collection="list" open="(" separator="," close=")"> #{item} </foreach> </select> 可以传递一个 List 实例或者数组作为参数对象传给 MyBatis。 当你这么做的时,MyBatis 会自动将它包装在一个 Map 中,用名称作为key。List 实例将会以“list” 作为key,而数组实例将会以“array”作为key。 4.多个参数中包含list的情况,是这样的(excRelatedServerIds是list类型): ServerMapper.java文件中传入的是包含list的map,ServerMapper.xml中的collection为指定的excRelatedServerIds Map<String, Object> map = Maps.newHashMap(); map.put("env", env); map.put("userId", userId); map.put("excRelatedServerIds", excRelatedServerIds); PageInfo<UserServerDetails> pageInfo = PageHelper.startPage(pageNum, pageSize).doSelectPageInfo(()-> serverMapper.selectPageByEnvAndUserExcRelated(map)); ServerMapper.xml: select us.id, us.user_id, us.server_id, s.`name`, s.operatingsystem, s.cpu,s.disksize,s.memorysize, s.intranetip, s.vcluster,s.`owner`,s.environment,s.applicant,s.`status`, ip.ip,ip.`status` as ipstatus,ip.segment,ip.remark , ip.line from user_server as us,`server` as s, ippool as ip where us.server_id = s.id and s.intranetip = ip.id <if test="env != null"> and s.environment=#{env,jdbcType=INTEGER} </if> <if test="userId != null"> and us.user_id=#{userId,jdbcType=VARCHAR} </if> and s.id not in <foreach item="item" index="index" collection="excRelatedServerIds" open="(" separator="," close=")"> #{item} </foreach> </select> 5.还有一种是按照顺序传参数,这种不推荐: Mapper.java: Public User selectUser(String name,String area); Mapper.xml : <select id="selectUser" resultMap="BaseResultMap"> select * from user_user_t where user_name = #{0} and user_area=#{1} </select> 其中,#{0}代表接收的是dao层中的第一个参数,#{1}代表dao层中第二参数,更多参数一致往后加即可。 6.如果要封装自己的返回类型,需要xml文件中定义并且去引用bean对象: <resultMap id="ServerDetailsMap" type="com.ServerDetails"> <id column="id" property="id" jdbcType="VARCHAR" /> <result column="name" jdbcType="VARCHAR" property="name" /> <result column="operatingsystem" jdbcType="VARCHAR" property="operatingsystem" /> <result column="cpu" jdbcType="INTEGER" property="cpu" /> <result column="disksize" jdbcType="INTEGER" property="disksize" /> <result column="memorysize" jdbcType="INTEGER" property="memorysize" /> <result column="intranetip" jdbcType="VARCHAR" property="intranetip" /> <result column="vcluster" jdbcType="VARCHAR" property="vcluster" /> <result column="owner" jdbcType="VARCHAR" property="owner" /> <result column="environment" jdbcType="INTEGER" property="environment" /> <result column="applicant" jdbcType="VARCHAR" property="applicant" /> <result column="status" jdbcType="VARCHAR" property="status" /> </resultMap> mybatis ${}与#{}的区别(来源网络) #{} 解析的是占位符? 可以防止SQL注入,比如打印出来的语句 select * from table where id=? 然而${} 则是不能防止SQL注入打印出来的语句 select * from table where id=2 实实在在的参数。 最简单的区别就是${}解析传过来的参数值不带单引号,#{}解析传过来参数带单引号。 最后总结一下必须使用$引用参数的情况,那就是参数的int型的时候,必须使用$引用。 参考:http://blog.csdn.net/u014687389/article/details/72778664?locationNum=8&fps=1
今日初上手springboot, 想查mybatis的sql报错信息,所有想打印dao层debug日志。 于是配置了log4j.xml,但是没有生效,后发现时是springboot默认使用logback日志。 1.移除logback改用log4j,需要修改pom.xml文件: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </exclusion> </exclusions> </dependency> <!-- log4j --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-log4j</artifactId> <version>1.3.8.RELEASE</version> </dependency> 2.在log4j中,指定dao层日志输出级别: #mybatis, debug level to see sql log4j.logger.com.xx.cd.dao=debug 启动后看到输出的sql日志信息。 3.log4j.xml内容: 4. # Output pattern : date [thread] priority category - message log4j.rootCategory=debug, Console, R #Console log4j.appender.Console=org.apache.log4j.ConsoleAppender log4j.appender.Console.layout=org.apache.log4j.PatternLayout log4j.appender.Console.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n log4j.appender.R=org.apache.log4j.RollingFileAppender #/opt/apps/epp-manager/var/logs/epp-manager.log log4j.appender.R.File=/var/logs/Secret-CD/Secret-CD.log log4j.appender.R.DatePattern='.'yyyy-MM-dd log4j.appender.R.MaxFileSize=20MB log4j.appender.R.MaxBackupIndex=30 log4j.appender.R.layout=org.apache.log4j.PatternLayout log4j.appender.R.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n
springboot 项目开发中,maven配置中,dependencyManagement怎么使用,在多个子模块中,可以使用dependencyManagement中的jar吗?待解决!!! 参考:http://blog.csdn.net/mafan121/article/details/50477852
Maven项目创建mybatis generator需要注意事项: 1.参考资料中说的 <dependency> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator</artifactId> <version>1.3.2</version> </dependency> 这个没必要使用,使用了会一直下载不到jar 报错 2. mysql的mysql-connector-java的jar如果是使用6.0.5版本会报错,建议降低版本到5.1.39(本地mysql server是5.7.18) 3. generatorConfig.xml这个文件需要配对targetProject路径 4.pom文件: <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.mybatis.test</groupId><artifactId>test-mybatis-generator</artifactId><version>1.0-SNAPSHOT</version><dependencies><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.39</version></dependency></dependencies><build><finalName>xxx</finalName><plugins><plugin><groupId>org.mybatis.generator</groupId><artifactId>mybatis-generator-maven-plugin</artifactId><version>1.3.2</version><configuration><verbose>true</verbose><overwrite>true</overwrite></configuration></plugin></plugins></build> </project> 5.generatorConfig.xml文件: <?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN" "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd"><generatorConfiguration><!-- 指定数据连接驱动jar地址 --> <classPathEntry location="/Users/mysql-connector-java/5.1.39/mysql-connector-java-5.1.39.jar" /><!-- 一个数据库一个context --> <context id="context" defaultModelType="flat" targetRuntime="MyBatis3"><!-- 注释 --> <commentGenerator><property name="suppressAllComments" value="false" /><!-- 是否取消注释 --> <property name="suppressDate" value="true" /> <!-- 是否生成注释代时间戳 --> </commentGenerator><!-- jdbc连接 --> <!--jdbc:mysql://xxxxxxx:8406/CL_DEMO?useUnicode=true&amp;characterEncoding=UTF-8--> <jdbcConnection driverClass="com.mysql.jdbc.Driver" connectionURL="jdbc:mysql://localhost:3306/cms?useUnicode=true&amp;characterEncoding=UTF-8" userId="root" password="newpass" /><!-- 类型转换 --> <javaTypeResolver><!-- 是否使用bigDecimal, false可自动转化以下类型(Long, Integer, Short, etc.) --> <property name="forceBigDecimals" value="false" /></javaTypeResolver><!-- 生成实体类地址 --> <javaModelGenerator targetPackage="com.cd.dao.pojo" targetProject="src/main/java"><!-- 是否在当前路径下新加一层schema,eg:fase路径com.oop.eksp.user.model, true:com.oop.eksp.user.model.[schemaName] --> <property name="enableSubPackages" value="false" /><!-- 是否针对string类型的字段在set的时候进行trim调用 --> <property name="trimStrings" value="true" /></javaModelGenerator><!-- 生成mapxml文件 --> <sqlMapGenerator targetPackage="com.cd.dao.mapper" targetProject="src/main/java"><!-- 是否在当前路径下新加一层schema,eg:fase路径com.oop.eksp.user.model, true:com.oop.eksp.user.model.[schemaName] --> <property name="enableSubPackages" value="false" /></sqlMapGenerator><!-- 生成mapxml对应client,也就是接口dao --> <javaClientGenerator targetPackage="com.cd.dao.mapper" targetProject="src/main/java" type="XMLMAPPER"><!-- 是否在当前路径下新加一层schema,eg:fase路径com.oop.eksp.user.model, true:com.oop.eksp.user.model.[schemaName] --> <property name="enableSubPackages" value="false" /></javaClientGenerator><table schema="cms" tableName="application_permission" domainObjectName="ApplicationPermission" selectByExampleQueryId="false" enableCountByExample="false" enableDeleteByExample="false" enableSelectByExample="false" enableUpdateByExample="false" enableDeleteByPrimaryKey="false" /></context> </generatorConfiguration> 6.参考资料: http://www.tuicool.com/articles/NBnUr2E http://www.cnblogs.com/smileberry/p/4145872.html
1.这里需要输入的密码不是证书的密码执行keytool -import -keystore - file 这个命令提示需要输入密码 进入jdk的bin目录,执行以下脚本, keytool -import -alias saltapi -keystore /Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/jre/lib/security/cacerts -file /Users/Documents/cacert.crt 2.输入 changeit 输入密码需注意输入法状态 3.信任证书 4.OK
安装的系统版本:root@iZbp1eazisi1qi938qwctmZ:~# cat /proc/versionLinux version 4.4.0-79-generic (buildd@lcy01-30) (gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.4) ) #100-Ubuntu SMP Wed May 17 19:58:14 UTC 2017 1.安装supervisorUbuntu可以直接通过apt安装: apt-get install supervisor 查看版本:root@iZbp1eazisi1qi938qwctmZ:/apptest/supervisortest/shdir# supervisord -version3.2.0 2.给我们自己开发的应用程序编写一个配置文件,让supervisor来管理它。每个进程的配置文件都可以单独分拆,放在/etc/supervisor/conf.d/目录下,以.conf作为扩展名 root@iZbp1eazisi1qi938qwctmZ:~# cd /etc/supervisor/conf.d/root@iZbp1eazisi1qi938qwctmZ:/etc/supervisor/conf.d# lltotal 16drwxr-xr-x 2 root root 4096 Jun 19 19:55 ./drwxr-xr-x 3 root root 4096 Jun 19 17:41 ../-rw-r--r-- 1 root root 129 Jun 19 17:02 app.conf-rw-r--r-- 1 root root 318 Jun 19 17:15 somjob.conf 例如:somejob文件 [program:somejob]command=/usr/bin/python /apptest/supervisortest/shdir/somejob.pydirectroy=/apptest/supervisortest/shdirautostart=trueautorestart=truestartretries=3user=rootstdout_logfile=/apptest/supervisortest/shdir/%(program_name)s.logstderr_logfile=/apptest/supervisortest/shdir/%(program_name)s.logmejob command脚本文件目录结构:root@iZbp1eazisi1qi938qwctmZ:/apptest/supervisortest/shdir# lltotal 320drwxr-xr-x 2 root root 4096 Jun 19 17:20 ./drwxr-xr-x 4 root root 4096 Jun 19 15:35 ../-rw-r--r-- 1 root root 306803 Jun 20 14:04 somejob.log-rw-r--r-- 1 root root 0 Jun 19 17:16 somejob.logmejob-rwxrwxrwx 1 root root 335 Jun 19 17:05 somejob.py* command脚本:import sys, timewhile True: print "pyserver runing %s"%time.ctime() sys.stdout.flush() time.sleep(10) 3.supervisord.conf设置增加读取配置脚本[include]files = /etc/supervisor/conf.d/*.conf 还可增加web server访问端口,不过好像没生效(本地机器可以访问到index.html),分号表示注释[inet_http_server]; Web管理界面设定port=9001;username = admin;password = 123 4.启动停止进程命令:重启supervisor,让配置文件生效,然后运行命令supervisorctl启动进程: supervisorctl start somejob 停止进程: supervisorctl stop somejob 启动supervisor命令 -n -c /etc/supervisor/supervisord.conf 6.启动完成后查看进程root@iZbp1eazisi1qi938qwctmZ:~# ps -ef|grep superroot 22393 1 0 Jun19 ? 00:00:15 /usr/bin/python /usr/bin/supervisord -n -c /etc/supervisor/supervisord.confroot 22753 22393 0 Jun19 ? 00:00:01 /usr/bin/python /apptest/supervisortest/shdir/somejob.pyroot 24317 24249 0 13:51 pts/0 00:00:00 grep --color=auto superroot@iZbp1eazisi1qi938qwctmZ:~# 7.参考资料:http://blog.csdn.net/orangleliu/article/details/45057377http://supervisord.org
Java:单例模式的七种写法 转载出处:http://cantellow.javaeye.com/blog/838473 第一种(懒汉,线程不安全): 1 public class Singleton { 2 private static Singleton instance; 3 private Singleton (){} 4 public static Singleton getInstance() { 5 if (instance == null) { 6 instance = new Singleton(); 7 } 8 return instance; 9 } 10 } 11 这种写法lazy loading很明显,但是致命的是在多线程不能正常工作。第二种(懒汉,线程安全): 1 public class Singleton { 2 private static Singleton instance; 3 private Singleton (){} 4 public static synchronized Singleton getInstance() { 5 if (instance == null) { 6 instance = new Singleton(); 7 } 8 return instance; 9 } 10 } 11 这种写法能够在多线程中很好的工作,而且看起来它也具备很好的lazy loading,但是,遗憾的是,效率很低,99%情况下不需要同步。第三种(饿汉): 1 public class Singleton { 2 private static Singleton instance = new Singleton(); 3 private Singleton (){}4 public static Singleton getInstance() { 5 return instance; 6 } 7 } 8 这种方式基于classloder机制避免了多线程的同步问题,不过,instance在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用getInstance方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance显然没有达到lazy loading的效果。 第四种(饿汉,变种): 1 public class Singleton { 2 private Singleton instance = null; 3 static { 4 instance = new Singleton(); 5 } 6 private Singleton (){} 7 public static Singleton getInstance() { 8 return this.instance; 9 } 10 } 11 表面上看起来差别挺大,其实更第三种方式差不多,都是在类初始化即实例化instance。 第五种(静态内部类): 1 public class Singleton { 2 private static class SingletonHolder { 3 private static final Singleton INSTANCE = new Singleton(); 4 } 5 private Singleton (){} 6 public static final Singleton getInstance() { 7 return SingletonHolder.INSTANCE; 8 } 9 } 10 这种方式同样利用了classloder的机制来保证初始化instance时只有一个线程,它跟第三种和第四种方式不同的是(很细微的差别):第三种和第四种方式是只要Singleton类被装载了,那么instance就会被实例化(没有达到lazy loading效果),而这种方式是Singleton类被装载了,instance不一定被初始化。因为SingletonHolder类没有被主动使用,只有显示通过调用getInstance方法时,才会显示装载SingletonHolder类,从而实例化instance。想象一下,如果实例化instance很消耗资源,我想让他延迟加载,另外一方面,我不希望在Singleton类加载时就实例化,因为我不能确保Singleton类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化instance显然是不合适的。这个时候,这种方式相比第三和第四种方式就显得很合理。 第六种(枚举): 1 public enum Singleton { 2 INSTANCE; 3 public void whateverMethod() { 4 } 5 } 6 这种方式是Effective Java作者Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象,可谓是很坚强的壁垒啊,不过,个人认为由于1.5中才加入enum特性,用这种方式写不免让人感觉生疏,在实际工作中,我也很少看见有人这么写过。 第七种(双重校验锁): 1 public class Singleton { 2 private volatile static Singleton singleton; 3 private Singleton (){} 4 public static Singleton getSingleton() { 5 if (singleton == null) { 6 synchronized (Singleton.class) { 7 if (singleton == null) { 8 singleton = new Singleton(); 9 } 10 } 11 } 12 return singleton; 13 } 14 } 15 这个是第二种方式的升级版,俗称双重检查锁定,详细介绍请查看:http://www.ibm.com/developerworks/cn/java/j-dcl.html 在JDK1.5之后,双重检查锁定才能够正常达到单例效果。 总结 有两个问题需要注意: 1、如果单例由不同的类装载器装入,那便有可能存在多个单例类的实例。假定不是远端存取,例如一些servlet容器对每个servlet使用完全不同的类 装载器,这样的话如果有两个servlet访问一个单例类,它们就都会有各自的实例。 2、如果Singleton实现了java.io.Serializable接口,那么这个类的实例就可能被序列化和复原。不管怎样,如果你序列化一个单例类的对象,接下来复原多个那个对象,那你就会有多个单例类的实例。 对第一个问题修复的办法是: 1 private static Class getClass(String classname) 2 throws ClassNotFoundException { 3 ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); 4 5 if(classLoader == null) 6 classLoader = Singleton.class.getClassLoader(); 7 8 return (classLoader.loadClass(classname)); 9 } 10 } 11 对第二个问题修复的办法是: 1 public class Singleton implements java.io.Serializable { 2 public static Singleton INSTANCE = new Singleton(); 3 4 protected Singleton() { 5 6 } 7 private Object readResolve() { 8 return INSTANCE; 9 } 10 } 11 对我来说,我比较喜欢第三种和第五种方式,简单易懂,而且在JVM层实现了线程安全(如果不是多个类加载器环境),一般的情况下,我会使用第三种方式,只有在要明确实现lazy loading效果时才会使用第五种方式,另外,如果涉及到反序列化创建对象时我会试着使用枚举的方式来实现单例,不过,我一直会保证我的程序是线程安全的,而且我永远不会使用第一种和第二种方式,如果有其他特殊的需求,我可能会使用第七种方式,毕竟,JDK1.5已经没有双重检查锁定的问题了。 ======================================================================== superheizai同学总结的很到位: 不过一般来说,第一种不算单例,第四种和第三种就是一种,如果算的话,第五种也可以分开写了。所以说,一般单例都是五种写法。懒汉,恶汉,双重校验锁,枚举和静态内部类。 我很高兴有这样的读者,一起共勉。 posted on 2011-09-02 14:49 kenzhang 阅读(119640) 评论(32) 编辑 收藏 FeedBack: # re: Java:单例模式的七种写法 2013-03-15 11:03 | 工工单例和线程安全有关系吗?单例取出一个实例,全局只此一个,多线程下使用,必须加以控制才达到线程安全的目的。所以单例不是线程安全的,单例的设计也不是为线程安全考虑的,所以作者纯属误导读者。 回复 更多评论 # re: Java:单例模式的七种写法 2013-07-05 09:35 | 正仔喜欢第五种,简单安全,不过有点儿乱的是,太多选择了,不知道什么时候该用哪一种! 回复 更多评论
Guava中针对集合的 filter和过滤功能 博客分类: JAVA相关 在guava库中,自带了过滤器(filter)的功能,可以用来对collection 进行过滤,先看例子: Java代码 @Test public void whenFilterWithIterables_thenFiltered() { List<String> names = Lists.newArrayList("John", "Jane", "Adam", "Tom"); Iterable<String> result = Iterables.filter(names, Predicates.containsPattern("a")); assertThat(result, containsInAnyOrder("Jane", "Adam")); } 在这个例子中,给出一个list,过滤出含有字母a的元素 此外,可以使用Collections2.filter() 去进行过滤 Java代码 @Test public void whenFilterWithCollections2_thenFiltered() { List<String> names = Lists.newArrayList("John", "Jane", "Adam", "Tom"); Collection<String> result = Collections2.filter(names, Predicates.containsPattern("a")); assertEquals(2, result.size()); assertThat(result, containsInAnyOrder("Jane", "Adam")); result.add("anna"); assertEquals(5, names.size()); } 这里注意的是,Collections2.filter中,当在上面的result中增加了元素后,会直接影响原来的names这个list的,就是names中的集合元素是5了。 再来看下predicates判断语言, com.google.common.base. Predicate : 根据输入值得到 true 或者 false 拿Collections2中有2个函数式编程的接口:filter , transform ,例如 :在Collection<Integer>中过滤大于某数的内容: Java代码 Collection<Integer> filterList = Collections2.filter(collections , new Predicate<Integer>(){ @Override public boolean apply(Integer input) { if(input > 4) return false; else return true; } }); 把Lis<Integer>中的Integer类型转换为String , 并添加test作为后缀字符: Java代码 List<String> c2 = Lists.transform(list, new Function<Integer , String>(){ @Override public String apply(Integer input) { return String.valueOf(input) + "test"; } }); 需要说明的是每次调用返回都是新的对象,同时操作过程不是线程安全的。 再来点例子: Java代码 @Test public void whenFilterCollectionWithCustomPredicate_thenFiltered() { Predicate<String> predicate = new Predicate<String>() { @Override public boolean apply(String input) { return input.startsWith("A") || input.startsWith("J"); } }; List<String> names = Lists.newArrayList("John", "Jane", "Adam", "Tom"); Collection<String> result = Collections2.filter(names, predicate); assertEquals(3, result.size()); assertThat(result, containsInAnyOrder("John", "Jane", "Adam")); } 将多个prdicate进行组合 Java代码 @Test public void whenFilterUsingMultiplePredicates_thenFiltered() { List<String> names = Lists.newArrayList("John", "Jane", "Adam", "Tom"); Collection<String> result = Collections2.filter(names, Predicates.or(Predicates.containsPattern("J"), Predicates.not(Predicates.containsPattern("a")))); assertEquals(3, result.size()); assertThat(result, containsInAnyOrder("John", "Jane", "Tom")); } 上面的例子中找出包含J字母或不包含a的元素; 再看下如何将集合中的空元素删除: Java代码 @Test public void whenRemoveNullFromCollection_thenRemoved() { List<String> names = Lists.newArrayList("John", null, "Jane", null, "Adam", "Tom"); Collection<String> result = Collections2.filter(names, Predicates.notNull()); assertEquals(4, result.size()); assertThat(result, containsInAnyOrder("John", "Jane", "Adam", "Tom")); } 检查一个collection中的所有元素是否符合某个条件: Java代码 @Test ublic void whenCheckingIfAllElementsMatchACondition_thenCorrect() { List<String> names = Lists.newArrayList("John", "Jane", "Adam", "Tom"); boolean result = Iterables.all(names, Predicates.containsPattern("n|m")); assertTrue(result); result = Iterables.all(names, Predicates.containsPattern("a")); assertFalse(result); 下面看如何把一个list进行转换, Java代码 @Test public void whenTransformWithIterables_thenTransformed() { Function<String, Integer> function = new Function<String, Integer>() { @Override public Integer apply(String input) { return input.length(); } }; List<String> names = Lists.newArrayList("John", "Jane", "Adam", "Tom"); Iterable<Integer> result = Iterables.transform(names, function); assertThat(result, contains(4, 4, 4, 3)); } 再看结合transform和predicates结合使用的例子: Java代码 @Test public void whenCreatingAFunctionFromAPredicate_thenCorrect() { List<String> names = Lists.newArrayList("John", "Jane", "Adam", "Tom"); Collection<Boolean> result = Collections2.transform(names, Functions.forPredicate(Predicates.containsPattern("m"))); assertEquals(4, result.size()); assertThat(result, contains(false, false, true, true)); } 在这个例子中,将一个LIST中的每一个元素进行使用Predicates.containsPattern,判断是否包含m,返回的是boolean,然后再得到的boolean值一起转换为collection 下面是两个function一起结合使用的例子: Java代码 @Test public void whenTransformingUsingComposedFunction_thenTransformed() { Function<String,Integer> f1 = new Function<String,Integer>(){ @Override public Integer apply(String input) { return input.length(); } }; Function<Integer,Boolean> f2 = new Function<Integer,Boolean>(){ @Override public Boolean apply(Integer input) { return input % 2 == 0; } }; List<String> names = Lists.newArrayList("John", "Jane", "Adam", "Tom"); Collection<Boolean> result = Collections2.transform(names, Functions.compose(f2, f1)); assertEquals(4, result.size()); assertThat(result, contains(true, true, true, false)); } 在这个例子中,首先应用函数f1,求出每个元素的长度,然后再根据f1函数,分别返回 它们的boolean值,再转换为collection. 最后看下将filter和transform结合使用的例子: Java代码 @Test public void whenFilteringAndTransformingCollection_thenCorrect() { Predicate<String> predicate = new Predicate<String>() { @Override public boolean apply(String input) { return input.startsWith("A") || input.startsWith("T"); } }; Function<String, Integer> func = new Function<String,Integer>(){ @Override public Integer apply(String input) { return input.length(); } }; List<String> names = Lists.newArrayList("John", "Jane", "Adam", "Tom"); Collection<Integer> result = FluentIterable.from(names) .filter(predicate) .transform(func) .toList(); assertEquals(2, result.size()); assertThat(result, containsInAnyOrder(4, 3));
ArrayList是最常用的一种java集合,在开发中我们常常需要从ArrayList中删除特定元素。有几种常用的方法: 最朴实的方法,使用下标的方式: ArrayList<String> al = new ArrayList<String>(); al.add("a"); al.add("b"); //al.add("b"); //al.add("c"); //al.add("d"); for (int i = 0; i < al.size(); i++) { if (al.get(i) == "b") { al.remove(i); i--; } } 在代码中,删除元素后,需要把下标减一。这是因为在每次删除元素后,ArrayList会将后面部分的元素依次往上挪一个位置(就是copy),所以,下一个需要访问的下标还是当前下标,所以必须得减一才能把所有元素都遍历完 还有另外一种方式: ArrayList<String> al = new ArrayList<String>(); al.add("a"); al.add("b"); al.add("b"); al.add("c"); al.add("d"); for (String s : al) { if (s.equals("a")) { al.remove(s); } } 此处使用元素遍历的方式,当获取到的当前元素与特定元素相同时,即删除元素。从表面上看,代码没有问题,可是运行时却报异常: Exception in thread "main" java.util.ConcurrentModificationException at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:886) at java.util.ArrayList$Itr.next(ArrayList.java:836) at com.mine.collection.TestArrayList.main(TestArrayList.java:17) 从异常堆栈可以看出,是ArrayList的迭代器报出的异常,说明通过元素遍历集合时,实际上是使用迭代器进行访问的。可为什么会产生这个异常呢?打断点单步调试进去发现,是这行代码抛出的异常: final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } modCount是集合修改的次数,当remove元素的时候就会加1,初始值为集合的大小。迭代器每次取得下一个元素的时候,都会进行判断,比较集合修改的次数和期望修改的次数是否一样,如果不一样,则抛出异常。查看集合的remove方法: private void fastRemove(int index) { modCount++; int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; // clear to let GC do its work } 可以看到,删除元素时modCount已经加一,但是expectModCount并没有增加。所以在使用迭代器遍历下一个元素的时候,会抛出异常。那怎么解决这个问题呢?其实使用迭代器自身的删除方法就没有问题了 ArrayList<String> al = new ArrayList<String>(); al.add("a"); al.add("b"); al.add("b"); al.add("c"); al.add("d"); Iterator<String> iter = al.iterator(); while (iter.hasNext()) { if (iter.next().equals("a")) { iter.remove(); } } 查看迭代器自身的删除方法,果不其然,每次删除之后都会修改expectedModCount为modCount。这样的话就不会抛出异常 public void remove() { if (lastRet < 0) throw new IllegalStateException(); checkForComodification(); try { ArrayList.this.remove(lastRet); cursor = lastRet; lastRet = -1; expectedModCount = modCount; } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } } 建议以后操作集合类的元素时,尽量使用迭代器。可是还有一个地方不明白,modCount和expectedModCount这两个变量究竟是干什么用的?为什么集合在进行操作时需要修改它?为什么迭代器在获取下一个元素的时候需要判断它们是否一样?它们存在总是有道理的吧 其实从异常的类型应该是能想到原因:ConcurrentModificationException.同时修改异常。看下面一个例子 List<String> list = new ArrayList<String>(); // Insert some sample values. list.add("Value1"); list.add("Value2"); list.add("Value3"); // Get two iterators. Iterator<String> ite = list.iterator(); Iterator<String> ite2 = list.iterator(); // Point to the first object of the list and then, remove it. ite.next(); ite.remove(); /* The second iterator tries to remove the first object as well. The object does not exist and thus, a ConcurrentModificationException is thrown. */ ite2.next(); ite2.remove(); 同样的,也会报出ConcurrentModificationException。 呜哇咕噜 很好的文章,知道了为什么使用迭代器时不能删除和添加元素,对于第一种方法的判断为符合条件后,都要i--的方法可以改进一点,从集合的尾部向前遍历, for (int i = al.size()-1; i >=0 ; i--) { if ("b".equals(al.get(i))) { al.remove(i); } } 这样可以省去i--,不易出错。 刚开始学习java,不足之处望博主多指导,谢谢!
使用Gradle: com.google.guava:guava:16.0.1 jar文件 通过com.google.common.collect.Iterables和com.google.common.base.Predicate功能实现简单条件查询过滤 完整代码: /** * query monitor zknodes list * @param currentPageNum * @param pageSize * @param nodeName * @return */@RequestMapping(value = "/list", method = RequestMethod.GET)public ResponseEntity<PageableRetVO<List<MonitorNodesParam>>> getMonitorZnodeList(@RequestParam(value = "currentPageNum", required = false) Integer currentPageNum,@RequestParam(value = "pageSize", required = false) Integer pageSize,@RequestParam(value = "nodeName", required = false) String nodeName){final String nodeNameQry = nodeName; PageableRetVO<List<MonitorNodesParam>> pageableRetVO = new PageableRetVO<>();try { createConnectZk(); List<MonitorNodesParam> znodes = zkFacade.readConfigFromZk();// 1.filter List<MonitorNodesParam> filteredList = Lists.newArrayList();if(StringUtils.isNotEmpty(nodeNameQry)){ Iterable<MonitorNodesParam> filteredIter = Iterables.filter(znodes, new Predicate<MonitorNodesParam>() {@Override public boolean apply(MonitorNodesParam input) {if (StringUtils.isNotEmpty(nodeNameQry) && input.getNodeName().contains(nodeNameQry)) {return true; } else {return false; } } });for(MonitorNodesParam filtered:filteredIter){ filteredList.add(filtered); } }else{ filteredList = znodes; }// 2.page int totalCount = filteredList.size(); pageableRetVO.setTotalCount(totalCount); pageableRetVO.setPageNum((currentPageNum==null?0:currentPageNum) > 0 ? currentPageNum : 1); pageableRetVO.setPageSize((pageSize == null ? 0 : pageSize) <= 0 ? 20 : pageSize);int totalPagesNum = BaseUtils.getPagesNum(filteredList.size(), pageableRetVO.getPageSize());if(pageableRetVO.getPageNum()<=totalPagesNum){ filteredList = filteredList.subList((pageableRetVO.getPageNum()-1) * pageableRetVO.getPageSize(), Math.min(filteredList.size(),(pageableRetVO.getPageNum())*pageableRetVO.getPageSize())); pageableRetVO.setList(filteredList); }else{ pageableRetVO.setList(new ArrayList<MonitorNodesParam>()); } pageableRetVO.setRetCode(0); } catch (Exception e) {logger.error(e.getMessage()); pageableRetVO.setRetCode(-1); pageableRetVO.setErrorMsg(e.getMessage()); }return new ResponseEntity<>(pageableRetVO, HttpStatus.OK); }
http://blog.csdn.net/hfmbook/article/details/7605527 JAVA中String类的intern()方法的作用 标签: stringjavaexceptionequalsobjectc 2012-05-26 22:23 15754人阅读 评论(1) 收藏 举报 分类: JAVA(107) 版权声明:本文为博主原创文章,未经博主允许不得转载。 一般我们变成很少使用到 intern这个方法,今天我就来解释一下这个方法是干什么的,做什么用的 首先请大家看一个例子: [java] view plain copy print? public static void main(String[] args) throws Exception { String a = "b" ; String b = "b" ; System.out.print( a == b); String c = "d" ; String d = new String( "d" ).intern() ; System.out.println( c == d); } 大家能看出来这个例子打印在控制台的消息吗?在这里控制台输出的结果都是true true,原因在于 intern 这个方法返回的是 返回字符串对象的规范化表示形式,当调用 intern 方法时,如果池已经包含一个等于此 String 对象的字符串(该对象由 equals(Object) 方法确定),则返回池中的字符串。否则,将此 String 对象添加到池中,并且返回此 String 对象的引用。这时候c和d就是相等的。 下面在看一个例子: [java] view plain copy print? <span style="white-space:pre"> </span>String s1 = "ab123" ; String s2 = new String( "ab123" ) ; System.out.println( s1 == s2 ); String s3 = s2.intern() ; System.out.println( s1 == s3 ) ; 看看这里输出的是什么,我想大家应该明白这个方法的作用是什么了吧!!
http://www.cnblogs.com/hexiaochun/archive/2012/09/03/2668324.html java 算法基础之二快速排序算法 所谓的快速排序的思想就是,首先把数组的第一个数拿出来做为一个key,在前后分别设置一个i,j做为标识,然后拿这个key对这个数组从后面往前遍历,及j--,直到找到第一个小于这个key的那个数,然后交换这两个值,交换完成后,我们拿着这个key要从i往后遍历了,及i++;一直循环到i=j结束,当这里结束后,我们会发现大于这个key的值都会跑到这个key的后面,不是的话就可能你写错了,小于这个key的就会跑到这个值的前面;然后我们对这个分段的数组再时行递归调用就可以完成整个数组的排序。 用图形法表示由下: 这样就以key分为了两个段,我们把这两个段再递进去就可以解决问题了 实现代码由下: 1 package com.quick; 2 3 public class quick { 4 public void quick_sort(int[] arrays, int lenght) { 5 if (null == arrays || lenght < 1) { 6 System.out.println("input error!"); 7 return; 8 } 9 _quick_sort(arrays, 0, lenght - 1); 10 } 11 12 public void _quick_sort(int[] arrays, int start, int end) { 13 if(start>=end){ 14 return; 15 } 16 17 int i = start; 18 int j = end; 19 int value = arrays[i]; 20 boolean flag = true; 21 while (i != j) { 22 if (flag) { 23 if (value > arrays[j]) { 24 swap(arrays, i, j); 25 flag=false; 26 27 } else { 28 j--; 29 } 30 }else{ 31 if(value<arrays[i]){ 32 swap(arrays, i, j); 33 flag=true; 34 }else{ 35 i++; 36 } 37 } 38 } 39 snp(arrays); 40 _quick_sort(arrays, start, j-1); 41 _quick_sort(arrays, i+1, end); 42 43 } 44 45 public void snp(int[] arrays) { 46 for (int i = 0; i < arrays.length; i++) { 47 System.out.print(arrays[i] + " "); 48 } 49 System.out.println(); 50 } 51 52 private void swap(int[] arrays, int i, int j) { 53 int temp; 54 temp = arrays[i]; 55 arrays[i] = arrays[j]; 56 arrays[j] = temp; 57 } 58 59 public static void main(String args[]) { 60 quick q = new quick(); 61 int[] a = { 49, 38, 65,12,45,5 }; 62 q.quick_sort(a,6); 63 } 64 65 } 找一个博客做自己的女朋友,不管你跟她说什么她都帮你记录,这是多么幸福的一件事啊。如果有女生能做到这点,赶尽娶回家吧! 分类: 数据结构与算法
bash快捷建 ctrl键组合 ctrl+a:光标移到行首。 ctrl+b:光标左移一个字母 ctrl+c:杀死当前进程。 ctrl+d:退出当前 Shell。 ctrl+e:光标移到行尾。 ctrl+h:删除光标前一个字符,同 backspace 键相同。 ctrl+k:清除光标后至行尾的内容。 ctrl+l:清屏,相当于clear。 ctrl+r:搜索之前打过的命令。会有一个提示,根据你输入的关键字进行搜索bash的history ctrl+u: 清除光标前至行首间的所有内容。 ctrl+w: 移除光标前的一个单词 ctrl+t: 交换光标位置前的两个字符 ctrl+y: 粘贴或者恢复上次的删除 ctrl+d: 删除光标所在字母;注意和backspace以及ctrl+h的区别,这2个是删除光标前的字符 ctrl+f: 光标右移 ctrl+z : 把当前进程转到后台运行,使用’ fg ‘命令恢复。比如top -d1 然后ctrl+z ,到后台,然后fg,重新恢复 esc组合 esc+d: 删除光标后的一个词 esc+f: 往右跳一个词 esc+b: 往左跳一个词 esc+t: 交换光标位置前的两个单词。
SecureCRT光标丢失问题 2013-09-24 11:11 11142人阅读 评论(2) 收藏 举报 分类: 工具使用(7) 版权声明:本文为博主原创文章,未经博主允许不得转载。 SecureCRT有时候光标不显示,命令行编辑文档的时候特别麻烦,今天找出解决办法: 选项-》会话选项-》仿真:将ANSI颜色选中; 选项-》会话选项-》外观:将光标下的,使用颜色选中;闪烁选中,同时记得把颜色改成与背景色不同。
pravega产品,请教下腾总,pravega是否维护成本是什么样的,在flink场景和非flink场景有什么区别或者优势?
平台建设+问题:flink有规划任务提交平台吗?或者原有的webUI提交job的改进是否有计划?