juc和jvm (剖析部分JDK源码)

it2023-03-24  86

JUC

本文档主要是记录juc和jvm的学习之路,阳哥,永远滴神!

“生死看淡,不服就干”

“基础不牢,地动山摇”

“发财的方法都写在’刑法’上”

“大学大学: 大家自己学”

“没有无所谓,只有做到位”

“树长得高是因为它的根扎得深”

“沉下来,深深的水,静静地流”

“扫帚不倒,灰尘不会自己跑掉”

“万丈高楼平地起,一切承担靠地基”

SaleTickets

并发/并行

concurrent parallel。分门别类两件事,并行。

三个包

Java.util.concurrentjava.util.concurrent.atomicjava.util.concurrent.locks

线程状态

NEWRUNNABLEBLOCKEDWAITTING 一直等待TIMED_WATTING 过时等待,过时不侯TERMINATED

java代码

Tickets tickets = new Tickets(); // Thread(Runnable target, String name) // 匿名内部类 new Thread(new Runnable() { @Override public void run() { for (int i = 1; i < 40; ++i) { tickets.sale(); try { Thread.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } } } }, "A").start();

Lambda 表达式版本:

new Thread(() -> { for (int i = 1; i < 40; ++i) tickets.sale(); }, "A").start();

Lambda 表达式

函数式编程

int age = 23; y = kx + 1; f(x) = kx + 1; //动态赋值

lambda

//指的是 函数式接口, 只能有一个接口函数,如果不写@FunctionalInterface,默认根据函数数量来判断,如果一个则是函数式接口,超过一个就是普通接口。 @FunctionalInterface public interface Foo { //public void sayHello(); public int add(int x, int y); }

口诀:拷贝小括号,写死右箭头,落地大括号

有且仅有一个函数,无返回值,无参数,方法名可以省略。 Foo foo = () -> {}; Foo foo = () -> { System.out.println("hello"); }; 有且仅有一个函数,有返回值,有参数 Foo foo = (int x, int y) -> { System.out.println("hello"); return x + y; }; 使用default后,完整定义+实现。可以有多个default方法,仍然是函数式接口。可以使哟过lambda表达式。 //指的是 函数式接口 @FunctionalInterface public interface Foo { //public void sayHello(); public int add(int x, int y); public default int mul (int x, int y) { return x * y; } } 使用static静态方法, 也可以定义多个,仍然是函数式接口,能用lambda表达式。 @FunctionalInterface public interface Foo { //public void sayHello(); public int add(int x, int y); public static int div(int x, int y) { return x / y; } }

Runnable接口

Runnable 也是 @FunctionalInterface

new Thread(() -> { System.out.println("hello"); }, "A").start();

集合类不安全

ArrayList

ArrayList 基础

New ArrayList(); 不指定使用对象类型的时候,默认是Object类型,啥都可以往里面装

java8 一开始new是空引用,第一次add 的时候创建list,初始值长度是10,hashmap初始值长度是16.

/** * Default initial capacity. ArrayList() 默认容量 */ private static final int DEFAULT_CAPACITY = 10; /** 第一个元素被添加的时候,数组从默认空容量扩容到默认容量 * The array buffer into which the elements of the ArrayList are stored. * The capacity of the ArrayList is the length of this array buffer. Any * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA * will be expanded to DEFAULT_CAPACITY when the first element is added. */ transient Object[] elementData; // non-private to simplify nested class access /** hashMap默认初始容量是 1 << 4,也就是16 * The default initial capacity - MUST be a power of two. */ static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

ArrayList扩容,扩容原值的一半 = 10 + 5 = 15. 使用arrays.copyOf 来搬家

扩容代码:

private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1); //右移实现扩容一半的容量 if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity); }

第二次扩容,15 + 7.5 = 22; hashmap扩容是原值的一倍。

//hashMap resize部分代码: else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) newThr = oldThr << 1; // double threshold

ArrayList 线程不安全

多个线程对一个ArrayList操作的时候,数据会出错。下述代码中,数据会出错;会出现报错情况,不一定每次都会报错。

public static void main(String[] args) { List<String> list = new ArrayList<String>(); for (int i = 0; i <= 3; ++i) { new Thread(() -> { list.add(UUID.randomUUID().toString().substring(0, 8)); System.out.println(list ); }, String.valueOf(i)).start(); } }

可能的报错:并发修改异常

java.util.ConcurrentModificationException

解决方法:

使用vector,vector add方法加了symchronized,而ArrayList add方法没有加锁,

//ArrayList add() public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; } //Vector public synchronized boolean add(E e) { modCount++; ensureCapacityHelper(elementCount + 1); elementData[elementCount++] = e; return true; }

vector数据一致性能保证,但是效率低;arrayList效率高,数据不能保证一致性

Collection 和 Collections

Collection是集合的顶级接口,Collections是工具类

List<String> list = Collections.synchronizedList(new ArrayList<>());

把list变成线程安全的

CopyOnWriteArrayList

List<String> list = new CopyOnWriteArrayList(); /**A thread-safe variant of {@link java.util.ArrayList} in which all mutative * operations ({@code add}, {@code set}, and so on) are implemented by * making a fresh copy of the underlying array. * 通过实现每次都copy来保证线程安全,不会出现 ConcurrentModificationException */

add 方法实现:

public boolean add(E e) { final ReentrantLock lock = this.lock; lock.lock(); //获取锁 try { Object[] elements = getArray(); int len = elements.length; Object[] newElements = Arrays.copyOf(elements, len + 1);//拷贝数组 newElements[len] = e; //赋值新元素 setArray(newElements); return true; } finally { lock.unlock(); //释放锁 } }

CopyOnWriteArrayList 和 Vector 和 ArrayList 平级

CopyOnWrite并发容器用于读多写少的并发场景。比如白名单,黑名单,商品类目的访问和更新场景,假如我们有一个搜索网站,用户在这个网站的搜索框中,输入关键字搜索内容,但是某些关键字不允许被搜索。这些不能被搜索的关键字会被放在一个黑名单当中,黑名单每天晚上更新一次。当用户搜索时,会检查当前关键字在不在黑名单当中,如果在,则提示不能搜索。

CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。所以如果你希望写入的的数据,马上能读到,请不要使用CopyOnWrite容器。Vector是增删改查方法都加了synchronized,保证同步,但是每个方法执行的时候都要去获得锁,性能就会大大下降,而CopyOnWriteArrayList 只是在增删改上加锁,但是读不加锁,在读方面的性能就好于Vector,CopyOnWriteArrayList支持读多写少的并发情况。

Set

如下代码仍会报 ConcurrentModificationException 异常,所以是线程不安全的。HashSet内部结构是数组+链表(红黑树),内部本质是HashMap。key还是key,value是一个object

public static void main(String[] args) { Set<String> set = new HashSet<>(); for (int i = 0; i <= 30; ++i) { new Thread(() -> { set.add(UUID.randomUUID().toString().substring(0, 8)); System.out.println(set ); }, String.valueOf(i)).start(); } }

hashset源码:

public HashSet() { map = new HashMap<>(); //表明它是一个hashmap 本质上 } // Dummy value to associate with an Object in the backing Map private static final Object PRESENT = new Object(); //这个Object会被当作value处理 public boolean add(E e) { return map.put(e, PRESENT) == null; //放入key 和 value,value是一个Object }

可以使用 CopyOnWriteSet

Set<String> set = new CopyOnWriteArraySet<>(); //构造函数 本质上来说,他是个CopyOnWriteArrayList public CopyOnWriteArraySet() { al = new CopyOnWriteArrayList<E>(); } //add 方法 public boolean add(E e) { return al.addIfAbsent(e); }

Map

HashMap如上所述,就是不安全的,JUC包中有 ConcurrentHashMap类,这是线程安全的。使用的是分段锁,锁住数组中的某个segment,这样读写其他segments的时候,仍然是线程安全的(因为没有同时对同一个segment进行操作)。

ConcurrentHashMap的put方法主要是调用了putVal方法,putVal方法源码简洁代码如下:

final V putVal(K key, V value, boolean onlyIfAbsent) { for (Node<K,V>[] tab = table;;) { Node<K,V> f; int n, i, fh; V oldVal = null; synchronized (f) { if (tabAt(tab, i) == f) { //do something 在这里锁住数组某个segment,然后对其进行操作 } } } }

锁理论

Sleep 并不会释放资源,所以会阻塞直到发送完邮件后才会发送短信。

class Phone { public synchronized void sendEmail() throws Exception { TimeUnit.SECONDS.sleep(4); System.out.println("*****sendEmail"); } public synchronized void sendSMS() throws Exception { System.out.println("*****sendSMS"); } } public class lock { public static void main(String[] args) throws InterruptedException { Phone phone = new Phone(); new Thread(() -> { try { phone.sendEmail(); } catch (Exception e) { e.printStackTrace(); } }).start(); new Thread(() -> { try { phone.sendSMS(); } catch (Exception e) { e.printStackTrace(); } }).start(); } }

synchronized 加在方法上,同一个资源类内不管有多少同步方法,这个synchronized锁在当前对象身上。简称:对象锁。

添加了static的同步方法,这个synchronized就加在了类上,俗称类锁。两个对象调用了同一个类锁的方法,就会阻塞。如果一个对象调用对象锁方法,另一个对象访问类锁方法,两者相互不干涉,能各自正常访问。

同步代码块锁住的是同步代码块中的东西

synchronized修饰同步方法 等价于 synchronized(this)

public synchronized void test() { } public void test() { synchronized (this) { } }

静态同步方法,锁住当前class类,等价于

synchronized (SyncThread.class) { //todoSomething }

生产者消费者

Demo1

两个线程对变量轮流操作:

class Aircondition { private int num = 0; public void increment() throws Exception { ++num; } public void decrement() throws Exception { --num; } }

对于上述案例,实现一个线程对该变量加1,一个线程对该变量减1,交替执行。

高内聚低耦合前提下,线程操作资源类判断/干活/通知防止多线程的虚假唤醒

使用wait() 和 notifyAll() 方法:(该方法不太正确)

class Aircondition { private int num = 0; public synchronized void increment() throws Exception { if (num != 0) this.wait(); ++num; System.out.println(Thread.currentThread().getName() + num); this.notifyAll(); } public synchronized void decrement() throws Exception { if (num != 1) this.wait(); --num; System.out.println(Thread.currentThread().getName() + num); this.notifyAll(); } }

把上述案例换成两个生产者和两个消费者:那么唤醒的时候会唤醒多个消费者,这会导致数据出错。java API文档中说:多线程的唤醒(notifyAll() 方法)必须用 while 而不是 if 来判断。举个例子:两个消费者在生产者生产前,都会进入阻塞状态,生产者生产后,其中一个消费者获得锁,然后执行操作,执行完毕后,另一个消费者获取锁,然后继续执行。正确代码示例如下:

class Aircondition { private int num = 0; public synchronized void increment() throws Exception { while (num != 0) this.wait(); ++num; System.out.println(Thread.currentThread().getName() + num); this.notifyAll(); } public synchronized void decrement() throws Exception { while (num != 1) this.wait(); --num; System.out.println(Thread.currentThread().getName() + num); this.notifyAll(); } }

使用JUC包的改进版本:使用 condition.await() 和 conditon.signalAll() 替代 wait() 和 notifyAll()

class Aircondition { private int num = 0; private Lock lock = new ReentrantLock(); private final Condition condition = lock.newCondition(); public void increment() throws Exception { lock.lock(); try { while (num != 0) condition.await(); ++num; System.out.println(Thread.currentThread().getName() + num); condition.signalAll(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public void decrement() throws Exception { lock.lock(); try { while (num != 1) condition.await(); --num; System.out.println(Thread.currentThread().getName() + num); condition.signalAll(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } }

新版本的好处是:定点通知,不再疯抢

Demo2

这里要求多线程按照 A - B - C 三个线程顺序操作,这里使用 notifyAll 这种群通知就不行了。可以针对设置三个condition,分别调用 condition.signal() 方法

class test { /** * A : 1; B : 2; C : 3 */ private int num = 1; private Lock lock = new ReentrantLock(); /** * 一把锁配多把备用钥匙 */ private Condition c1 = lock.newCondition(); private Condition c2 = lock.newCondition(); private Condition c3 = lock.newCondition(); public void print5() throws Exception { lock.lock(); try { while (num != 1) c1.await(); for (int i = 0; i < 5; ++i) System.out.println(Thread.currentThread().getName() + i); num = 2; c2.signal(); } finally { lock.unlock(); } } public void print10() throws Exception { lock.lock(); try { while (num != 2) c2.await(); for (int i = 0; i < 10; ++i) System.out.println(Thread.currentThread().getName() + i); num = 3; c3.signal(); } finally { lock.unlock(); } } public void print15() throws Exception { lock.lock(); try { while (num != 3) c3.await(); for (int i = 0; i < 15; ++i) System.out.println(Thread.currentThread().getName() + i); num = 1; c1.signal(); } finally { lock.unlock(); } } }

Callable接口

class MyThread implements Runnable { @Override public void run() { } } class MyThread2 implements Callable<Integer> { @Override public Integer call() throws Exception { return 1024; } }

三个区别:

有无异常有无返回值有无run方法

Callable接口是函数式表达式,1.8版本才出现,可以写lambda表达式。但是不能写如下模式,因为没有 new Thread(Callable<>) 接口

new Thread(() -> {}, "A").start();

Java 接口中,经常传入接口,我们可以实现Runnable 和 Callable接口,然后传入。

class MyThread2 implements Callable<Integer> { @Override public Integer call() throws Exception { System.out.println("call function"); return 1024; } } public class Callable222 { public static void main(String[] args) { FutureTask futureTask = new FutureTask(new MyThread2()); new Thread(futureTask).start(); } }

在这里FutureTask实现了Runnable, 同时内置一个Callable对象,这样就体现了适配器模式,在实现Runnable的同时,能够帮助使用Callable对象。(变相实现了Runnable和Callable)。Future对象来异步获取之后计算的结果

public class FutureTask<V> implements RunnableFuture<V> { /** The underlying callable; nulled out after running */ private Callable<V> callable; /** other codes */ } public interface RunnableFuture<V> extends Runnable, Future<V> {

总结

有两种创建线程的方法-一种是通过创建Thread类,另一种是通过使用Runnable创建线程。但是,Runnable缺少的一项功能是,当线程终止时(即 run() 完成时),我们无法使线程返回结果。为了支持此功能,Java中提供了Callable接口。

java多线程获得的第三种方法:

get方法一般放在最后一行 class MyThread2 implements Callable<Integer> { @Override public Integer call() throws Exception { System.out.println("coming in Callable..."); Thread.sleep(2000); return 1024; } } public class Callable222 { public static void main(String[] args) throws ExecutionException, InterruptedException { FutureTask futureTask = new FutureTask(new MyThread2()); new Thread(futureTask, "A").start(); new Thread(futureTask, "B").start(); System.out.println("main stop..."); futureTask.get(); } }

一个FutureTask只会调用一次。get方法会阻塞直到计算出所需返回值。

CountDownLatch

public static CountDownLatch countDownLatch = new CountDownLatch(10); public static void main(String[] args) throws InterruptedException { for (int i = 0; i < 10; ++i) { new Thread(() -> { countDownLatch.countDown(); System.out.println("玩家准备完毕"); }, String.valueOf(i)).start(); } countDownLatch.await(); System.out.println("所有玩家准备完毕"); }

CyclicBarrier

CyclicBarrier参看这里

https://blog.csdn.net/weixin_40037938/article/details/106504674

Semaphore

Semaphore参看这里

https://blog.csdn.net/weixin_40037938/article/details/106504674

ReadWriteLock

读-读能共存读-写不能共存写-写不能共存 class MyCache { private volatile HashMap<String, Object> map = new HashMap<>(); private java.util.concurrent.locks.ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); public void put(String key, Object value) { readWriteLock.writeLock().lock(); System.out.println(Thread.currentThread().getName() + " 写入数据"); map.put(key, value); System.out.println(Thread.currentThread().getName() + " 写入完成"); readWriteLock.writeLock().unlock(); } public void get(String key) { readWriteLock.readLock().lock(); System.out.println(Thread.currentThread().getName() + " 读取数据"); Object result = map.get(key); System.out.println(Thread.currentThread().getName() + " 读取完成 " + result); readWriteLock.readLock().unlock(); } }

BlockingQueue

举例:海底捞吃饭的时候,队列满了就在候客区等着,对应阻塞队列。

阻塞队列是个接口,子类实现:ArrayBlockingQueue,PriorityBlockingQueue,LinkedBlockingQueue (有边界 Integer.MAX_VALUE),LinkedBlockingDeque 等

方法类型抛出异常特殊值阻塞超时插入add(e)offer(e)put(e)offer(e, time, unit)移除remove()poll()take()poll(time, unit)检查element()peek()NoneNone 队列满了之后,抛出 java.lang.IllegalStateException: Queue full,队列没有元素后,java.util.NoSuchElementExceptionoffer() 函数 返回布尔值put() 函数 会一直阻塞程序

线程池

线程池优势

线程池主要工作是控制运行时的线程数量,处理过程中将任务放入队列中,然后在线程启动后启动这些任务,如果线程数量超过了最大数量,超出数量的线程排队等候,其他线程执行完毕后,再从队列中取出任务来执行。

它的主要特点:线程复用,控制最大并发数,管理线程

降低创建销毁线程资源消耗提高响应速度(任务不需要等线程创建就能执行)提高线程的可管理

Executor

Executor接口可以执行提交的Runnable任务

ExecutorService

Executor的子接口,实现类是 Executors(类似于Collections, Arrays)

newFixedThreadPool()

ExecutorService threadPool = Executors.newFixedThreadPool(5); try { for (int i = 0; i < 10; ++i) { threadPool.execute(() -> { System.out.println(Thread.currentThread().getName() + " 办理业务"); }); } } catch (Exception e) { e.printStackTrace(); } finally { }

输出结果

pool-1-thread-1 办理业务 pool-1-thread-3 办理业务 pool-1-thread-2 办理业务 pool-1-thread-4 办理业务 pool-1-thread-5 办理业务 pool-1-thread-2 办理业务 pool-1-thread-4 办理业务 pool-1-thread-3 办理业务 pool-1-thread-1 办理业务 pool-1-thread-4 办理业务

newSingleThreadExecutor()

只有一个线程

newCachedThreadPool()

缓存表示数据可以复用,一池N线程。当当前线程够用时,会减少线程数,不够用时会增加线程数。

阿里巴巴开发手册说:Executors可能会导致 OOM。线程池不许使用Executors去创建,而是通过ThreadPoolExecutor创建。FixedThreadPool和SingleThreadPool,允许的请求队列长度为Integer.MAX_VALUE,会堆积请求,导致OOM。CachedThreadPool和ScheduledThreadThreadPool,允许创建线程数为Integer.MAX_VALUE,导致OOM

ThreadPoolExecutor 原理

ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlokcingQueue());

线程池7大参数

源码上的注解就包含了7大参数解释:

/** * @param corePoolSize 池中的常驻线程数 * @param maximumPoolSize the maximum number of threads to allow in the * pool * @param keepAliveTime when the number of threads is greater than * the core, this is the maximum time that excess idle threads * will wait for new tasks before terminating. * @param unit the time unit for the {@code keepAliveTime} argument 时间单位,秒 毫秒 等等,和 keepAliveTime 一起使用。多久没收到业务请求后,停止扩容,缩容 * @param workQueue the queue to use for holding tasks before they are * executed. This queue will hold only the {@code Runnable} * tasks submitted by the {@code execute} method. * @param threadFactory the factory to use when the executor * creates a new thread * @param handler the handler to use when execution is blocked * because the thread bounds and queue capacities are reached * @throws IllegalArgumentException if one of the following holds:<br> * {@code corePoolSize < 0}<br> * {@code keepAliveTime < 0}<br> * {@code maximumPoolSize <= 0}<br> * {@code maximumPoolSize < corePoolSize} * @throws NullPointerException if {@code workQueue} * or {@code threadFactory} or {@code handler} is null */ public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0) throw new IllegalArgumentException(); if (workQueue == null || threadFactory == null || handler == null) throw new NullPointerException(); this.acc = System.getSecurityManager() == null ? null : AccessController.getContext(); this.corePoolSize = corePoolSize; this.maximumPoolSize = maximumPoolSize; this.workQueue = workQueue; this.keepAliveTime = unit.toNanos(keepAliveTime); this.threadFactory = threadFactory; this.handler = handler; }

如果核心线程数满了,放入等候阻塞队列等待;如果队列满了,线程池扩容;如果线程池最大线程数满了,则直接放弃处理。

线程池拒绝策略

最大线程数到了,同时等待队列也满了。

AbortPolicy(默认):直接抛出RejectedExecutionException异常阻止系统运行

CallerRunsPolicy:“调用者运行”一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者,从而降低新任务的流量。

ExecutorService threadPool = new ThreadPoolExecutor(2, 5, 2L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy()); try { for (int i = 0; i <= 10; ++i) { threadPool.execute(() -> { System.out.println(Thread.currentThread().getName() + "业务办理"); }); } } catch (Exception e) { e.printStackTrace(); } finally { threadPool.shutdown(); }

输出:

pool-1-thread-1业务办理 pool-1-thread-4业务办理 pool-1-thread-3业务办理 pool-1-thread-3业务办理 pool-1-thread-2业务办理 pool-1-thread-5业务办理 pool-1-thread-4业务办理 pool-1-thread-1业务办理 main业务办理 pool-1-thread-3业务办理 pool-1-thread-2业务办理

DiscardPolicy:不会报错,会丢失消息,只处理部分业务,这是最好的一种处理

DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入到队列中,尝试再次提交当前任务

手写线程池

list.stream().count() = select count(*) from book;

总结

CPU密集型工作:CPU核数 + 1 = 最大线程数

IO密集型工作:CPU核数 / 阻塞系数

流式计算

@AllArgsConstructor @Data @NoArgsConstructor @Accessors(chain = true) public class Book { private int id; private String bookName; private double price; public static void main(String[] args) { Book book = new Book(); book.setId(11); book.setBookName("java"); book.setPrice(33.5d); List<String> list = new ArrayList<>(); Book book2 = new Book(); book2.setId(12).setPrice(46.5d); } } @Data 使用这个注解,就不用再去手写Getter,Setter,equals,canEqual,hasCode,toString等方法了,注解后在编译时会自动加进去。 @AllArgsConstructor 使用后添加一个构造函数,该构造函数含有所有已声明字段属性参数 @NoArgsConstructor 使用后创建一个无参构造函数 @Builder 关于Builder较为复杂一些,Builder的作用之一是为了解决在某个类有很多构造函数的情况,也省去写很多构造函数的麻烦,在设计模式中的思想是:用一个内部类去实例化一个对象,避免一个类出现过多构造函数

四大函数接口

Function<T, R>

Function<String, Integer> function = new Function<String, Integer>() { @Override public Integer apply(String s) { System.out.println(2); return 1; } }; function.apply("S"); //lambda expression Function<String, Integer> function = s -> {return s.length();}; System.out.println(function.apply("S"));

Predicate

Predicate<String> predicate = new Predicate<String>() { @Override public boolean test(String s) { return false; } }; System.out.println(predicate.test("S")); // lambda expression Predicate<String> predicate = s -> {return s.isEmpty();}; System.out.println(predicate.test("S"));

Comsumer

Consumer<String> consumer = new Consumer<String>() { @Override public void accept(String s) { } }; Consumer<String> consumer1 = s -> {};

Supplier

Supplier<String> stringSupplier = new Supplier<String>() { @Override public String get() { return null; } }; Supplier<String> stringSupp = () -> {return null};

Stream定义

集合讲的是数据,流讲的是计算

Stream会延迟执行,需要结果的时候计算

创建一个Stream:一个数据源(数组,集合)中间操作:一个中间操作,处理数据源数据终止操作:一个终止操作,执行中间操作链,产生结果

filter

这里的filter中传入的是 predicate,本来是(Book book) -> {return true;},可以省略Book类型,省略return语句和;。过滤出Book中 id 能整除2的book并且书本价格为2.3d。

public static void main(String[] args) { Book b1 = new Book(1, "id1", 2.3d); Book b2 = new Book(2, "id2", 2.3d); Book b3 = new Book(3, "id3", 2.3d); Book b4 = new Book(4, "id4", 2.5d); List<Book> list = Arrays.asList(new Object[]{b1, b2, b3, b4}); list.stream().filter(book -> book.getId() % 2 == 0).filter(book -> book.getPrice() == 2.3d).forEach(System.out::println); }

map

List<Integer> ls = Arrays.asList(new Object[]{1,2,3}); ls.stream().map(x -> x * 2).collect(Collectors.toList()).forEach(System.out::print); Book b1 = new Book(1, "id1", 2.3d); Book b2 = new Book(2, "id2", 2.3d); Book b3 = new Book(3, "id3", 2.3d); Book b4 = new Book(4, "id4", 2.5d); List<Book> list = Arrays.asList(new Object[]{b1, b2, b3, b4}); list.stream().map(book -> book.getBookName().toUpperCase()).forEach(System.out::println); // 排序 逆序 List<Integer> ls = Arrays.asList(new Object[]{1,2,3}); ls.stream().sorted((i, j) -> j - i).collect(Collectors.toList()).forEach(System.out::println);

分支合并框架

类似分治 divide and conquer,只不过是多个线程跑结果,然后汇总

ForkJoinPool

public class ForkJoin { static class MyTask extends RecursiveTask<Integer> { private static final Integer ADJUST_VALUE = 10; private int begin; private int end; private int result; public MyTask(int begin, int end) { this.begin = begin; this.end = end; } @Override protected Integer compute() { if (end - begin <= 10) { for (int i = begin; i <= end; ++i) { result += i; } } else { int mid = (end + begin) / 2; MyTask task = new MyTask(begin, mid); MyTask task1 = new MyTask(mid + 1, end); task.fork(); task1.fork(); result = task.join() + task.join(); } return result; } } public static void main(String[] args) throws ExecutionException, InterruptedException { MyTask myTask = new MyTask(0, 10); ForkJoinPool pool = new ForkJoinPool(); ForkJoinTask<Integer> result = pool.submit(myTask); System.out.println(result.get()); pool.shutdown(); } }

异步回调

Demo:

CompletableFuture<Object> objectCompletableFuture = CompletableFuture.supplyAsync(() -> { System.out.println("有返回值,这是Supplier接口"); int i = 10/0; return "s"; }); //System.out.println(objectCompletableFuture.get()); objectCompletableFuture.whenComplete((t, u) -> { System.out.println("t " + t + " u " + u); }).exceptionally(f -> { System.out.println("function interface Execption"); return 4444; }).get();

上述whenComplete中t表示计算完成后的正常结果,u表示异常内容,并且会执行exceptionalyy中的函数内容。

JVM

ClassLoader

jvm运行在操作系统上,没有直接与硬件交互。

类装载器 ClassLoader

负责加载class文件,class文件在文件开头有特定的文件标示(cafe babe),将class文件字节码内容加载到内存中,并将这些内容转换成方法区中的运行时数据结构,并且classloader只负责加载,不负责运行。运行由Execution Engine决定。class文件在开头有特定表示:

cafe babe

BootStrap 启动类加载器,是最底层类加载器,cpp写的,jdk自带的都是用bootstrap。

扩展类加载器 Extension,javax开头的包,就是Extension。在基础java包上,进行拓展。

如果是自己写的类,会用AppClassLoader加载器,也叫做系统类加载器(应用程序加载器),加载当前classpath所有类

public static void main(String[] args) { Object object = new Object(); System.out.println(object.getClass().getClassLoader()); }

这个是cpp写的,java获取不到,所以输出为 null

public static void main(String[] args) { Object object = new Object(); System.out.println(object.getClass().getClassLoader()); classLoader classLoaders = new classLoader(); System.out.println(classLoaders.getClass().getClassLoader()); }

这里输出是: sun.misc.Launcher$AppClassLoader@18b4aac2

sun.misc.Launcher jvm相关调用入口程序

public static void main(String[] args) { classLoader classLoaders = new classLoader(); System.out.println(classLoaders.getClass().getClassLoader().getParent().getParent()); System.out.println(classLoaders.getClass().getClassLoader().getParent()); System.out.println(classLoaders.getClass().getClassLoader()); }

输出如下:

null sun.misc.Launcher$ExtClassLoader@448139f0 sun.misc.Launcher$AppClassLoader@18b4aac2

说明AppClassLoader父类是ExtClassLoader,ExtClassLoader父类是BootStrap

双亲委派模型

当一个类收到了类加载请求,他首先不尝试自己去加载这个类,而是把请求委派给父类去完成,每一个层次类加载器都是如此,因此所有的类加载请求都会到bootstrap中,只有当父类无法加载后,才会让子类尝试自己加载。真能保证整个java环境会获取同一个类,比如自己写了一个String类,jvm会加载java.lang.String,这样就不会污染源代码。

Native

线程私有。

//当java中出现native中,这个函数会调用本地方法,和jvm无关了 private native void start0();

native方法在本地方法栈里执行。

PC计数器

Program Counter Register:程序计数寄存器。在cpu中,指向下一行执行的代码。几kb大小,不存在内存回收机制。

每个线程都有一个程序计数器,是线程私有的,它是当前线程所执行代码的字节码的行号指示器,如果是Native方法,这个计数器是空的。用来完成分支,循环,跳转,异常处理,线程恢复等基础功能,不会发生OOM错误。

方法区

所有线程共享。供各线程共享的运行时内存区域,它存储了每一个类的结构信息,例如运行时的常量池(Runtime Constant Pool)、字段和方法数据、构造函数和普通方法的字节码内容。上述讲的是规范,在不同虚拟机里实现不同,最典型的就是永久代(PermGen space)和元空间(Metaspace)。

举例子:空调 k1 = new 格力(); 方法区 = new 永久代();

说白了,保存的是类的模版,

Java 栈

栈管理运行,堆管理存储。

栈也叫栈内存,主管Java程序的运行,是在线程创建的时候创建,它的生命周期和线程一样,线程结束也就释放栈内存,对于栈来说不存在垃圾回收机制,是线程私有的。8种基本类型的变量 + 对象的引用变量 + 实例方法都是在函数的栈内存中分配。

java 方法 = 栈帧

栈主要保存三种数据:

本地变量:输入参数和输出参数以及方法内的变量栈操作:记录出栈,入栈的操作栈帧数据:包括类文件,方法等

栈中的数据都是以栈帧的格式存在,是一个内存区块,是一个有关方法和数据的数据集。当方法A被调用,就产生了一个栈帧Fa,当方法B被调用,就产生了一个栈帧Fb。然后先探出Fb,再弹出Fa。

StackOverFlow 是错误,不是异常。属于java.lang.Error

堆、栈、方法区关系

栈中的reference就是直接存储对象的地址(但是都来自方法区的对象类型数据),HotSpot是使用指针的方式来访问对象,Java堆中会存放访问类元数据的地址。

heap 堆

类加载器读取了类文件后,就要把类、方法和常变量放到堆内存中,保存所有引用类型的真实信息,方便执行器执行。

新生代 Young / New

伊甸园区 (Eden Space):new 的对象放在这里,GC = YGC后,绝大部分Eden空间都被清空。幸存者0区 (Survivor 0 Space):把YGC幸存的,放入幸存者0区,简称S0。S0和S1会交换。 Eden :S0 : S1 = 8 :1 :1幸存者1区 (Survivor 1 Space):

老年代 Old / Tenure (Tenure Generation Space)

养老区如果满了则启动 Full GC。如果多次Full GC还是内存不够,OOM。

元空间 / 永久代 (Permanent Space)

如果内存不够,可以通过修改参数 -Xms -Xmx来调整堆内存大小。

TransferValue 传值

Java中基本类型传复印件,子函数中无法修改父函数中的int型变量值

public void change(String s) { s = "xxx"; } public static void main(String[] args) { String s = "abc"; change(s); System.out.println(s); }

上述代码结果为 “abc”,如果String在常量池中没有,那么会新建一个。上述s是引用类型,传的是内存地址

对象生命周期和GC

当第一次Eden满的时候,触发GC,把活着的对象拷贝到SurvivorFrom区,当Eden区再次触发GC的时候会扫描到Dden和From区,对这两个区域进行垃圾回收,仍然存活的对象会放入To区域(如果有对象到了老年标准,则进入老年区),同时这些对象年龄+1。清空Eden和SurvivorFrom中的对象,即复制后也有交换,谁空谁是ToSurvivorTo和SurvivorFrom交换,部分对象会在From和To中来回复制,交换满15次,就进入老年代(由JVM参数 MaxTenuringThreshold决定,默认15)

Eden : Old = 1 :2

永久代(7) / 元空间(8)

元空间对应 Method Area,实际而言,方法区和堆一样,是各个线程共享的内存区域,它用于存储虚拟机加载的:类信息 + 普通常量 + 静态常量 + 编译器编译后的代码等等,虽然JVM规范将方法区描述成堆堆一个逻辑部分,但它却还有一个别名叫做非堆(Non-heap),目的就是要和堆分开。

对于HotSpot虚拟机,jdk1.7已经把原本放在永久代的字符串常量池移走。

永久代基本没有垃圾回收,里面加载的jdk自带的类和元数据,例如rt.jar(runtime包,包含Object等)。

堆参数调优和调参数

堆逻辑上包含:Eden - Old Memory - Perm,物理上包含:Eden - Old Memory

-Xms:jvm堆初始化大小,默认为物理内存的 1/64-Xmx:jvm堆最大值,默认为物理内存的 1/4-Xmn:调Eden 和 Old比例-XX:PermSize:永久代初始值–XX:MaxPermSize:永久代最大值

永久代使用JVM的堆内存,但是java8之后的元空间并不在虚拟机中,而是使用本机物理内存,因此元空间的大小仅受本地内存限制,类的元数据放入native mrmory中,字符串池和类的静态变量放入java堆中,这样大小就不受MaxPermSize中了。

生产上堆初始化大小和堆大小设置成一样,避免GC高频出现,GC和堆内存初始化值有关和堆最大内存无关。

在Idea中的VM option配置上:-Xms1024m -Xmx1024m -XX:+PrintGCDetails,输出结果打印如下:

981.5MB 981.5MB Heap PSYoungGen total 305664K, used 15729K [0x00000007aab00000, 0x00000007c0000000, 0x00000007c0000000) eden space 262144K, 6% used [0x00000007aab00000,0x00000007aba5c420,0x00000007bab00000) from space 43520K, 0% used [0x00000007bd580000,0x00000007bd580000,0x00000007c0000000) to space 43520K, 0% used [0x00000007bab00000,0x00000007bab00000,0x00000007bd580000) ParOldGen total 699392K, used 0K [0x0000000780000000, 0x00000007aab00000, 0x00000007aab00000) object space 699392K, 0% used [0x0000000780000000,0x0000000780000000,0x00000007aab00000) Metaspace used 3235K, capacity 4496K, committed 4864K, reserved 1056768K class space used 348K, capacity 388K, committed 512K, reserved 1048576K

上述Young和Old内存和等于JVM内存,所以元空间是放在物理内存中的

[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(294400K)] [ParOldGen: 464159K->464106K(699392K)] 464159K->464106K(993792K), [Metaspace: 3881K->3881K(1056768K)], 0.0577660 secs] [Times: user=0.18 sys=0.00, real=0.05 secs] // Full GC 失败后,直接报错 Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

GC 日志分析

[GC (Allocation Failure) [PSYoungGen: 5242K->448K(283136K)] 623429K->618642K(982528K), 0.0065821 secs] [Times: user=0.03 sys=0.00, real=0.01 secs]

上述代码意思:GC(由于分配失败),GC前Young区5242k,GC后448k。后面的是堆内存GC前后变化。user表示YoungGC耗时,sys表示YoungGC时候系统耗时。

[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(294400K)] [ParOldGen: 464159K->464106K(699392K)] 464159K->464106K(993792K), [Metaspace: 3881K->3881K(1056768K)], 0.0577660 secs] [Times: user=0.18 sys=0.00, real=0.05 secs]

上述例子意思:

YoungGC没内存了,Old区几乎没有回收内存,导致Full GC失败。

GC 垃圾回收算法

三大特点:

频繁收集Eden偶尔收集Old几乎不动Meta Space

mimor GC or Full GC(major GC),Major GC的速度一般要比Minor GC慢10倍以上

空的main函数跑起来的时候,后台有两个线程,一个是main线程,一个是GC线程。System.gc()不是立刻调用。

引用计数法:引用计数为0则回收,缺点:循环引用无法被回收。复制算法(Copying):年轻代中使用的是Minor GC,这种GC算法使用的是复制算法。把存活的对象放到Survivor中,不会产生内存碎片,从根集合 GC Root 开始。缺点:比较浪费内存标记清除(Mark—Sweep):先标记出要回收的对象,然后把这些对象统一回收。能够减少内存浪费。缺点:内存碎片化,某种程度上会浪费内存;考虑到标记+回收,两次扫描内存区域,速度比复制算法慢。标记压缩(Mark—Compact):老年代一般是由标记清除或者是标记清除和标记整理混合而成。分为两步,首先标记,然后再次扫描,并往一端滑动存活对象,使得碎片化内存变得紧凑。缺点:效率低,耗时严重(标记,清除,压缩)三次耗时。

标记-清除-压缩(Mark-Sweep-Compact):多次GC后,才Compact

分代收集算法:不同代使用不同收集算法,年轻代使用复制算法,老年代使用标记清除和标记压缩混合算法。

JMM java内存模型

应用线程会从主内存复制一份到工作内存(线程私有),写完后,写入主内存。

volatile

Volatile 是java虚拟机提供的轻量级同步机制

保证可见性不保证原子性不保证指令重排序

JMM理解

可见性:原子性:volatile的++i,这不是原子性有序性: public static void main(String[] args) { Number myNumber = new Number(); new Thread(() -> { try { Thread.sleep(3000); myNumber.add(); } catch (InterruptedException e) { e.printStackTrace(); } },"A").start(); while (myNumber.number == 10) { // main线程内的副本还是10 } System.out.println("finish"); }

上述代码永远都不会打印出 finish,因为没有线程通知main中的number副本值。可以很好的体现可见性。加了volatile就能保证number变量的可见性。

GC Root

Class - 由系统类加载器(system class loader)加载的对象,这些类是不能够被回收的,他们可以以静态字段的方式保存持有其它对象。我们需要注意的一点就是,通过用户自定义的类加载器加载的类,除非相应的java.lang.Class实例以其它的某种(或多种)方式成为roots,否则它们并不是rootsThread - 活着的线程Stack Local - java方法的local变量或参数JNI Local - 全局JNI引用(Native方法)引用的方法JNI Global - 全局JNI引用Monitor Used - 用于同步的监控对象Hedl By JVM - 用于JVM特殊目的由GC保留的对象,但实际上这个与JVM的实现是有关的。可能已知的一些类型是:系统类加载器、一些JVM知道的重要的异常类、一些用于处理异常的预分配对象以及一些自定义的类加载器等。然而,JVM并没有为这些对象提供其它的信息,因此就只有留给分析分员去确定哪些是属于"JVM持有"的了

ClassLoader,rt.jar包加载的类,异常对象(OOM),JNI对象,所有synchronized锁住的对象引用,等都是GC Root

《深入理解Java虚拟机》一书中是这么说的,一下几种对象可以作为GC Root:

虚拟机栈中的引用对象,;new 的对象方法区中类静态属性引用的对象方法区中常量引用对象本地方法栈中JNI引用对象

更权威的GC Root解释:

1.System Class ----------Class loaded by bootstrap/system class loader. For example, everything from the rt.jar like java.util.* . 2.JNI Local ----------Local variable in native code, such as user defined JNI code or JVM internal code. 3.JNI Global ----------Global variable in native code, such as user defined JNI code or JVM internal code. 4.Thread Block ----------Object referred to from a currently active thread block. Thread ----------A started, but not stopped, thread. 5.Busy Monitor ----------Everything that has called wait() or notify() or that is synchronized. For example, by calling synchronized(Object) or by entering a synchronized method. Static method means class, non-static method means object. 6.Java Local ----------Local variable. For example, input parameters or locally created objects of methods that are still in the stack of a thread. 7.Native Stack ----------In or out parameters in native code, such as user defined JNI code or JVM internal code. This is often the case as many methods have native parts and the objects handled as method parameters become GC roots. For example, parameters used for file/network I/O methods or reflection. 7.Finalizable ----------An object which is in a queue awaiting its finalizer to be run. 8.Unfinalized ----------An object which has a finalize method, but has not been finalized and is not yet on the finalizer queue. 9.Unreachable ----------An object which is unreachable from any other root, but has been marked as a root by MAT to retain objects which otherwise would not be included in the analysis. 10.Java Stack Frame ----------A Java stack frame, holding local variables. Only generated when the dump is parsed with the preference set to treat Java stack frames as objects. 11.Unknown ----------An object of unknown root type. Some dumps, such as IBM Portable Heap Dump files, do not have root information. For these dumps the MAT parser marks objects which are have no inbound references or are unreachable from any other root as roots of this type. This ensures that MAT retains all the objects in the dump.
最新回复(0)