美文网首页
JVM实用基础 及 并发编程 笔记

JVM实用基础 及 并发编程 笔记

作者: Mr_Yzk_ | 来源:发表于2019-01-24 10:15 被阅读0次

-----【jvm基础】------------------------------------------------------------------------

无监控不调优  没有相应参考指数不调优

工具:VisualVM Eclipse插件      java bin目录下也有一个

JMeter调优Tomcat (压力测试)

不要在本机上测试  要用另外一台机器上测试  因为运行测试用例要占用很大的资源

什么是垃圾?一对垃圾  环形垃圾  与外界隔绝的叫做垃圾

GC roots对象  正向可达的,都不是垃圾

什么是OOM?

垃圾回收的算法

1.标记清除  标记可回收的内存  缺点:内存的不连续  碎片  来了大对象需要fullGC  将内存压缩

2.拷贝 拷贝的意义在于 去除碎片化 让内存连续  缺点 内存浪费  4G只有2G可用  一次GC存活量少的情况适用(如1/2  1/3)  新生代适用

3.标记压缩  标记完后 整理碎片 将内存压缩    老年代适用

强、软、弱、虚引用

什么是NIO? 什么是NIO 与AIO的差别

什么是反射?

垃圾收集器;(看oracle官方文档)

the serial collector 序列化的垃圾收集器  能处理100兆的内存 不能用多线程的硬件

the parallel collector 并行的垃圾收集器  并发大、停顿无所谓可用  吞吐量:一个时刻CPU能处理的线程、进程数量

the CMS collector 并发的垃圾收集器  停顿短一定要求短适用  内存分为很多块、区 有锁机制 一起并发地做GC

G1 现在作为默认垃圾收集器  不仅停顿短,同时并发大  1.8可能还不如CMS  平衡的选择

=====【对象分配】=============================================================

对象分配

1)栈上分配

1.线程私有小对象  对象分配在栈上面,效率极高  jvm默认开启

2.无逃逸          方法执行完 栈就清空了  方法外有引用指向对象 则逃逸了

3.支持标量替换

2)线程本地分配 Thread Local Allacation Buffer

1.占用eden 默认1%(每个线程)    其实也是使用eden区的

2.多线程的时候不用竞争eden就可以申请空间,提高效率

3.小对象  只属于线程本身的内存

3)老年代

大对象

4)eden

=====【jvm参数】=============================================================

虚拟机运行参数 run configurations

-XX:-DoEscapeAnalysis -XX:-EliminateAllocations -XX:-UseTLAB -XX:+PrintGC

不进行逃逸分析 不做标量替换(不使用栈分配对象) 不使用线程本地缓存

打印GC  (-表示不 +表示做)

-XX:+PrintGCDetails  打印细节

RunTime.getRuntime().totalMemory    .freeMemory 获取总内存、可用内存

-XX:+HeapDumpOnOutOfMemoryError  捕获内存溢出

-XX:HeapDumpPath=c:\tmp\jvm.dump 将内存溢出的信息打印到该目录

-Xms256M -Xmx256M 初始化为程序分配256M 最大256M  两者往往相等

文件可以用VisualVM打开  class选项  找哪个类占的内存最大导致溢出

栈溢出  内存溢出00M

-Xss  线程栈大小

eden:survivor:survivor  常用方案:8:1:1

new:old  即前三者:tenured  1:3  3:8  1:2

-Xmx –Xms:指定最大堆和最小堆

-Xmn

    设置新生代大小

-XX:NewRatio

    新生代(eden+2*s)和老年代(不包含永久区)的比值

        例如:4,表示新生代:老年代=1:4,即新生代占整个堆的1/5

-XX:SurvivorRatio(幸存代)

    设置两个Survivor区和eden的比值

      例如:8,表示两个Survivor:eden=2:8,即一个Survivor占年轻代的1/10

-XX:+UseSerialGC 设置串行收集器

-XX:+UseParallelGC 设置并行收集器

-XX:+UseConcMarkSweepGC 设置并发收集器

-Xloggc:filename 导出到文件

-----【Tomcat调优】------------------------------------------------------

Tomcat调优:

catalina.bat文件

set JAVA_OPTS=

-Xms4g 由几个虚拟机给定  服务多,老年代调大  new对象多,eden大点

-Xmx4g 

-Xss512k 

-XX:+AggressiveOpts  凡是虚拟机能优化的选项 全选上

-XX:UseBiasedLocking  优化锁 启用偏置锁

-XX:PermSize=64M  永久区大小 //1.8取消了

-XX:MaxPermSize=300M  永久区最大值

-XX:+DisableExplicitGC    //关闭显式调用GC:System.gc();往往会打乱gc节奏

拓展 较复杂:

-XX:+UseConcMarkSweepGC    使用CMS缩短响应时间,并发收集,低停顿

-XX:+UseParNewGC    并行收集新生代的垃圾

-XX:+CMSParallelRemarkEnabled    在使用UseParNewGC的情况下,尽量减少mark的时间

-XX:+UseCMSCompactAtFullConllection    使用并发收集器时,开启对老年代的压缩,使碎片减少

-XX:LargePageSizeInBytes=128m  内存分页大小对性能的提升  内存分页对性能的提升?百度

-XX:+UseFastAccessorMethods    get/set方法转成本地代码

-Djava.awt.headless=true    修复linux下的tomcat处理图表时可能会产生的一bug

可优化3倍性能

========================================================================

========================================================================

======【并发基础】========================================================================

并发:

synchronized 

如果不抛出异常  在synchronized  的代码里面  锁会被释放  可能产生脏读  所以要加try  catch 来防止

synchronized  保证可见性和原子性

使用synchronized  时候 注意粒度  不要锁太大粒度的代码 会影响代码执行的效率

synchronized 锁的是 堆内存中的对象  而不是栈内存中对象的引用  当对象改变 则这个锁的限制就无效了

不要以字符串常量作为锁定对象  同样的字符串 对象只有一个(不是 new 出来的话)

volatile  

volatile  线程之间可见  无锁同步  不加就不可见  当然也可以synchronized  但效率就低了

correct包下 的容器  用了很多的volatile  关键字  这些容器最好了解一下

java memery model  不加volatile关键字的变量  在不同的线程中 是不同的  每个线程都有自己的内存空间

其中里面包含了CPU的缓冲区  两个线程同步运行  线程从变量中取了值之后 放到CPU缓冲中  这时候 外面的内存值改变了,但线程相应空间中的CPU缓冲没变  则这个数据就出现不可重复读了  当然CPU如果有空闲 比如sleep、sysout时  可能就读了一下内存 重新刷新了缓冲 那就接受到更改了

而 加了volatile之后 变量值改变后 会通知其他线程去重新读那个被更改的值  就不会出现这个问题了

用缓冲区去理解多线程  就好理解多了  volatile不能保证原子性 别的线程修改的值可能被另外线程修改的值覆盖(在写回去的时候可能出问题)

区别:

volatile保证可见性  synchronized  可见性和原子性  volatile 效率比synchronized  高

AtomicXxx类 

AtomicXxx  比如 AtomicInteger 原子的Integer  它的每个操作都是原子性的 如incrementAndGet();

取代了  count++; 

count++;是线程不安全的  而这相当于给它加了synchronized  但是是基于操作系统底层实现的  效率比synchronized  高很多  能用Atomic类的 就用Atomic类

两个Atomic  之间如果不加 锁  则依然可能被打断  如incrementAndGet 只能保证count++的时候是原子性的

wait()  notify()

调用被锁定对象的这两个方法(只能在同步对象中调用)

对象的wait 方法,会释放锁  而当前线程进入等待状态

而notify会重新启动等待的一个线程(其他线程叫醒)

notifyAll会启动在该对象上等待的所有线程

注意 wait会释放锁  而notify不会释放锁  当前线程会继续锁住改对象  被叫醒的线程会继续等待当前线程释放该对象的锁 再被继续执行

sleep也不会释放锁

wait  和  notify  可以实现线程的交替运行  但是notify不能指定线程来继续运行

wait和notify可以替代使用 while来监测某个数据的情形  大大减少CPU性能消耗

======【锁】============================================

门闩  如CountDownLatch(1)

指定一个数 该数字减为0时  await的线程就会继续往下执行

latch.await(); 让当前线程进入wait

latch.countDown(); 可以让指定的数字减一

而且其中不需要使用 锁,不用synchronized  来实现  两个线程都一起运行 不需要等待对方来notify

手动锁  重入锁ReentrantLock  (和synchronized的区别 面试常问)

用于替代synchronized的

lock.lock();

lock.unlock();  在finally里面 释放锁

重入锁永远不会自动释放 必须手动释放  synchronized是自动释放的

lock.trylock();尝试锁定  拿到了返回true

lock.trylock(5, TimeUnit.SECONDS);

unlock时也要根据之前的这个返回值来确定是否unlock

//lock.lock();

lock.lockInterruptibly(); //可以对interrupt()方法做出响应 打断对锁的等待

并抛出InterruptedException异常

可以指定为公平锁 即谁等的时间久,谁获得锁

private static ReentrantLock lock = new ReentrantLock(true);//true表示为公平锁

synchronized为非公平锁 不用线程调度器去计算谁等的时间久 效率也会更高

======【杂话】==========================================

生产者消费者  看c_021  (经常被问到)

用while 搭配 wait 

因为wait被叫醒之后

while在出程序的时候 还会再去条件里检查一遍

而if不会 这就是区别 防止叫醒后出现不符合判断条件的情况下 依旧执行了while外面的代码

使用notifyAll 为了防止应当叫醒消费者的情况下  却叫醒了生产者 

这个程序整个是一个紧致的 收紧的 各线程间紧密相连的程序

effective java里说 永远只使用notifyAll,别使用notify

c_021 MyContainer2 使用Condition和Lock来实现 (代码暂时未上传)

数据库高并发

索引、分库、分点、读写分离、主从结构

读的非常多 写的非常少

不用加锁 使用copyOnWrite 效率非常高

消息的可靠性投递和幂等 架构师常问

======【最好的单例】==========================================

线程安全的单例模式:

可看网页

更好的是采用下面的方式,既不用加锁,也能实现懒加载

public class Singleton {

private Singleton() {

System.out.println("single");

}

private static class Inner {

private static Singleton s = new Singleton();

}

public static Singleton getSingle() {

return Inner.s;

}

public static void main(String[] args) {

Thread[] ths = new Thread[200];

for(int i=0; i<ths.length; i++) {

ths[i] = new Thread(()->{

Singleton.getSingle();

});

}

Arrays.asList(ths).forEach(o->o.start());

}

}

======【并发底层】=====实际使用可能是 netty 等封装好的框架=============================

======【队列】==========================================

ConcurrentLinkedQueue  链表/无界队列(加到内存爆了为止)

它的poll方法是原子性的  从队列中拿一个元素

拿完之后 对该元素进行操作,即使有判断  也和容器无关了。

因此不需要考虑判断和操作的原子性,因为它们是异步的。

且poll的底层也不是加锁的实现  所以速度会非常快

加值 offer()方法  返回boolean 表示是否加成功

    add()方法 如果加失败会抛异常

取值 peek() 取值但不移除值

    poll() 取值且移除

支付订单的时候可以使用  消息推送使用  LinkedTransferQueue

------【阻塞式队列】---非常常用--------------------------------------

BlockingQueue  阻塞式队列  使用非常多,种类也非常多

LinkedBlockingQueue  可以指定最大值  也可以无界

put(e) 如果满了  就等待

take() 从头部取一个值 如果空了 就等待

poll(long timeout, TimeUnit unit):从BlockingQueue取出一个队首的对象,如果在指定时间内,队列一旦有数据可取,则立即返回队列中的数据。否则知道时间

超时还没有数据可取,返回失败。

ArrayBlockingQueue  有界队列  必须指定最大值

new的时候 指定一个值 表示最大容量  满了add会报错 offer返回false

offer(E e, long timeout, TimeUnit unit)可以指定等待时间 满了 时间过了 就不往里面加了

put(e) 会一直阻塞  offer 可以指定阻塞时间

DelayQueue  也是无界队列 也实现BlockingQueue

每一个元素记录还有多长时间可以被消费者 拿走  过了一定时间后 任务才可以被执行

元素必须实现 Delayed接口  需要实现 compareTo(o) 实现队列元素的排序逻辑  getDelay(TimeUnit unit) 化为指定时间单位逻辑

放入DelayQueue 里面时,按照compareTo自动进行排序 -1表示优先级低 放入尾部  ==使用put 和 take方法

可以用来做定时任务  例子 c_025

LinkedTransferQueue  实时消息处理  有更高的并发要求时使用  ==转发消息的时候可以使用

transfer方法放入元素  如果有消费者等待消费  则直接给消费者 而不放入队列  take方法取元素

如果没有消费者 则一直阻塞 等待消费者来临  注意:用put、add、offer 都不会阻塞

实时消息处理 这个在netty中大量使用

注意 消费者先启动!  扔了东西你必须给我处理掉  其实效率高就高在 不用放入队列了  而是实时处理

SynchronousQueue  容量为0    特殊的TransferQueue

put(e) 方法如果没有消费者  则直接阻塞  内部调用 transfer(e) 方法

add(e) 如果没有消费者等待  则直接抛异常 IllegalStateException:Queue full

阻塞、等待获取锁  程序是活着的 但是 wait是不占用CPU的  这就是差别  底层实现可以进行优化

-----【双向队列】------------------------------------------

双向队列  Deque    (主体还是使用Queue)

ConcurrentLinkedDeque 

它的方法  addLast addFirst(相当于 栈的push()) offerLast  removeFirst(相当于 栈的pop())  pollFirst  getFirst  peekFirst(相当于 栈的peek())

===================================================

注意:异步方案 队列解耦合  这是并发容器中 最重要的

===================================================

=====【并发Map容器】==========================================

ConcurrentHashMap

和HashTable的比较  HashTable对每一个元素都加锁 效率很低

而ConcurrentHashMap  将容器分为16段  对不同部分修改时 也只对该段加锁 

不同段没有并发问题

ConcurrentHashMap  也比Collections.synchronizedMap(HashMap map) 要高

(Collections.synchronizedXxx 是把容器变为支持并发的容器,支持不是很高并发的情形)

ConcurrentSkipListMap  跳表map

高并发且排序的map

效率比 TreeMap(都实现SortedMap)高

ConcurrentHashMap 和 ConcurrentSkipListMap 

比 HashTable 和 Collections.synchronizedXxx 效率要高些

所有和Map相关的  都可以替换为Set  如:HashSet  ConcurrentSkipListSet

注:并发量大小问题  先用简单的实现  如果服务器撑不住了  需要优化  再使用更高级的实现

=====【排序容器 补充知识】==========================================

SortedMap

在插入的时候效率都比较低 (因为要排序)

将插入的数据根据键值进行排序,可以指定比较器。便利的时候,是排好序的。

按自然顺序或自定义顺序遍历键选择使用。

1.需要在定义容器的时候指定Comparator 

2.不指定的话需要调用key.compareTo() 进行比较  这就要求key必须实现Comparable接口

LinkedHashMap 会记录插入的顺序,遍历的时候 会根据插入的顺序进行读取。输出的顺序和输入的相同

=====【写时复制 List】==========================================

CopyOnWriteArrayList  写时修改  读多写少使用  (==并发包下 唯一的List)

写的时候很慢  因为要复制  但是读的时候 就不需要加锁了(因为写和读并发时 读的是新复制的容器 不会有脏读了)

因此写少读多的情形 非常适用  但是写的时候 效率非常非常低

事件监听器的队列 向里面加监听器的机会很少  但是每次使用的时候 都需要查看放了哪些【监听器】

=====【线程调度器】===========================================================

Executor  执行器

只有一个方法

execute(Runnable command)  指定一个命令  然后在实现类里定义它的执行

ExecutorService  继承 Executor  接口

execute()  只能执行  run()  没有返回值

submit()  可以执行  run() 也可以执行 call()  返回一个Future<T>

Callable<T>  call()

Runnable  run()    非常相像 

但run() 方法没有返回值 void 

而call()方法可以指定泛型返回

且call()可以抛出异常 run()不行

Executors  相当于 Arrays、Collections 等工具类  用来操作Executor

ExecutorService service = Executors.newFixedThreadPool(5);

会在第一次使用的时候起线程,随后就是循环利用,效率很高了。

如果线程数达到上限(5)则会把任务放到线程池维护的任务队列中  (使用的BlockingQueue)

completed task 已完成的任务队列,任务完成后放入。  【一个线程池共维护两个队列。】

启动和关闭线程,都会耗费很大的内存,因此线程池的意义比较大。

service.shutdown();  等待所有的任务执行完毕,再正常关闭线程池。

isShutdown()

          如果此执行程序已关闭,则返回 true。

isTerminated()

          如果关闭后所有任务都已完成,则返回 true。

shutdownNow()

          试图停止所有正在执行的活动任务,暂停处理正在等待的任务,并返回等待执行的任务列表。

【数据库连接池的底层,肯定也是线程池】

【关于所起的线程池个数:一般CPU有多少核 那就起多少个线程,可以自己进行调节,获取一个最优数目。

这个数目往往大于CPU核数】

-----【Future】-----------------------------------------------------

Future

FutureTask  实现接口 Runnable, Future

未来会有一个指定类型的返回值 (匿名内部类 Callable 的call()方法返回值)

FutureTask<Integer> task = new FutureTask<>(()->{

TimeUnit.MILLISECONDS.sleep(500);

return 1000;

}); //new Callable () { Integer call();}

new Thread(task).start();

System.out.println(task.get()); //阻塞  get的时候 会一直阻塞等待结果

注意 以上程序  最内部逻辑 为 Callable,外面封装一层 FutureTask,然后Task要放到Thread 里面去跑

FutureTask就像一个转换器

FutureTask提供一个 get()方法,去阻塞程序。

Callable没有阻塞的get方法,且不能直接放到Thread中去运行。

其中 get()方法其实是线程间通讯。新起一个线程去运行程序,即进行【】异步操作【】。

可以把以上 new Thread(task).start();

改为线程池操作 service.submit(task/Runnable/Callable)

c_026 T07 计算质素,就是非常典型的 【多个线程进行并发计算 (使用线程池)】 来提高效率的例子

=====【6种 线程池】=====================================================================

Executors.newFixedThreadPool(int num); 固定个数的线程池

初始化是num 最大num LinkedBlockingQueue

Executors.newCachedThreadPool(); 如果没有空闲 那就起一个 最多起到系统支撑的最大数(几万个) 缓存的线程 如果线程空闲时间达到指定时间  则缓存失效  默认60秒  AliveTime

初始化0 最大Integer.MAX_VALUE  SynchronizedQueue

Executors.newSingleThreadPool(); 永远只有一个线程,可以保证业务一定是前后顺序执行的。

初始化1 最大1  LinkedBlockingQueue

Executors.newScheduledThreadPool(int corePoolSize);定时的线程

初始化corePoolSize  最大Integer.MAX_VALUE  DalayedWorkQueue

频率在service.scheduleAtFixedRate()指定 共四个参数 第一个为Runnable 第二个为第一个任务执行需要等待多长时间  第三个为后面的每个任务执行之间的等待时间  第四个为时间单位

其执行会重复地、定时地执行指定的Runnable 的run方法

corePoolSize为指定的最大线程数

它返回的ScheduledExecutorService可以用来替代Timer  Timer每次执行都会new一个新的线程 而该线程调度的线程,可重复使用,可能连续由同一个线程执行,优先保障任务先完成。

Executors.newWorkStealingPool(); 工作窃取的线程池  每个线程都会维护一个自己的队列

CPU是几核的默认就会起几个线程 (也可以指定)  底层由ForkJoinPool来实现(只是做了一个抽象本质是一样的) 

背后有一个精灵线程(守护线程)来执行  精灵线程伴随着jvm 只要jvm不停止 就一直在后台运行 不停地寻找工作来做  为了防止池中进程任务分配不均匀  会相互间撮合来提高运行效率

ForkJoinPool  递归使用子线程来细分任务进行执行

Fork分开  Join合并  类似于 MapReduce 任务大则切分为多个小的任务来分别执行 最后汇总

ForkJoinPool  里面执行的任务必须是 ForkJoinTask<V>  使用的是精灵线程

ForkJoinTask<V> 常用子类 RecursiveAction 无返回值  RecursiveTask  有返回值  可递归切分的  通常会使用者两个子类 而不是直接用ForkJoinTask<V>

重写 V compute()方法  使用fork() 来拆小  使用 join() 来合并  返回值就是join的结果

forkJoinPool.execute(RecursiveTask/RecursiveAction的实现子类)  调用join来阻塞等待结果

可以用递归 fork、join来排序

-----【自定义 线程池】----------------------------

ThreadPoolExecutor  自定义线程池/底层线程池

是以上除了ForkJoinPool 线程池的底层实行

(ForkJoinPool是直接从 ExecutorService 继承的 或者说它并不是一个池 而是一个使用多个线程解决一个问题的调度器)

可以自定义什么时候死  自定义高级的 CachedThreadPool (默认是60秒死)

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue);

5个参数  1.初始化的线程数量  2.线程最大数目 3.空闲多久之后消失 4.3的单位  5.指定的 BlockingQueue

参数3 如果是0 则表示永久不消失

相关文章

网友评论

      本文标题:JVM实用基础 及 并发编程 笔记

      本文链接:https://www.haomeiwen.com/subject/ljqyjqtx.html