本文档主要是记录juc和jvm的学习之路,阳哥,永远滴神!
“生死看淡,不服就干”
“基础不牢,地动山摇”
“发财的方法都写在’刑法’上”
“大学大学: 大家自己学”
“没有无所谓,只有做到位”
“树长得高是因为它的根扎得深”
“沉下来,深深的水,静静地流”
“扫帚不倒,灰尘不会自己跑掉”
“万丈高楼平地起,一切承担靠地基”
concurrent parallel。分门别类两件事,并行。
Lambda 表达式版本:
new Thread(() -> { for (int i = 1; i < 40; ++i) tickets.sale(); }, "A").start();口诀:拷贝小括号,写死右箭头,落地大括号
有且仅有一个函数,无返回值,无参数,方法名可以省略。 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 也是 @FunctionalInterface
new Thread(() -> { System.out.println("hello"); }, "A").start();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 16ArrayList扩容,扩容原值的一半 = 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操作的时候,数据会出错。下述代码中,数据会出错;会出现报错情况,不一定每次都会报错。
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是工具类
List<String> list = Collections.synchronizedList(new ArrayList<>());把list变成线程安全的
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支持读多写少的并发情况。
如下代码仍会报 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); }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 }两个线程对变量轮流操作:
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(); } } }新版本的好处是:定点通知,不再疯抢
这里要求多线程按照 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(); } } }三个区别:
有无异常有无返回值有无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方法会阻塞直到计算出所需返回值。
CyclicBarrier参看这里
https://blog.csdn.net/weixin_40037938/article/details/106504674
Semaphore参看这里
https://blog.csdn.net/weixin_40037938/article/details/106504674
举例:海底捞吃饭的时候,队列满了就在候客区等着,对应阻塞队列。
阻塞队列是个接口,子类实现: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接口可以执行提交的Runnable任务
Executor的子接口,实现类是 Executors(类似于Collections, Arrays)
输出结果
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 办理业务只有一个线程
缓存表示数据可以复用,一池N线程。当当前线程够用时,会减少线程数,不够用时会增加线程数。
阿里巴巴开发手册说:Executors可能会导致 OOM。线程池不许使用Executors去创建,而是通过ThreadPoolExecutor创建。FixedThreadPool和SingleThreadPool,允许的请求队列长度为Integer.MAX_VALUE,会堆积请求,导致OOM。CachedThreadPool和ScheduledThreadThreadPool,允许创建线程数为Integer.MAX_VALUE,导致OOM
ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlokcingQueue());
源码上的注解就包含了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:抛弃队列中等待最久的任务,然后把当前任务加入到队列中,尝试再次提交当前任务
CPU密集型工作:CPU核数 + 1 = 最大线程数
IO密集型工作:CPU核数 / 阻塞系数
集合讲的是数据,流讲的是计算
Stream会延迟执行,需要结果的时候计算
创建一个Stream:一个数据源(数组,集合)中间操作:一个中间操作,处理数据源数据终止操作:一个终止操作,执行中间操作链,产生结果这里的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); }类似分治 divide and conquer,只不过是多个线程跑结果,然后汇总
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运行在操作系统上,没有直接与硬件交互。
负责加载class文件,class文件在文件开头有特定的文件标示(cafe babe),将class文件字节码内容加载到内存中,并将这些内容转换成方法区中的运行时数据结构,并且classloader只负责加载,不负责运行。运行由Execution Engine决定。class文件在开头有特定表示:
cafe babeBootStrap 启动类加载器,是最底层类加载器,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,这样就不会污染源代码。
线程私有。
//当java中出现native中,这个函数会调用本地方法,和jvm无关了 private native void start0();native方法在本地方法栈里执行。
Program Counter Register:程序计数寄存器。在cpu中,指向下一行执行的代码。几kb大小,不存在内存回收机制。
每个线程都有一个程序计数器,是线程私有的,它是当前线程所执行代码的字节码的行号指示器,如果是Native方法,这个计数器是空的。用来完成分支,循环,跳转,异常处理,线程恢复等基础功能,不会发生OOM错误。
所有线程共享。供各线程共享的运行时内存区域,它存储了每一个类的结构信息,例如运行时的常量池(Runtime Constant Pool)、字段和方法数据、构造函数和普通方法的字节码内容。上述讲的是规范,在不同虚拟机里实现不同,最典型的就是永久代(PermGen space)和元空间(Metaspace)。
举例子:空调 k1 = new 格力(); 方法区 = new 永久代();
说白了,保存的是类的模版,
栈管理运行,堆管理存储。
栈也叫栈内存,主管Java程序的运行,是在线程创建的时候创建,它的生命周期和线程一样,线程结束也就释放栈内存,对于栈来说不存在垃圾回收机制,是线程私有的。8种基本类型的变量 + 对象的引用变量 + 实例方法都是在函数的栈内存中分配。
java 方法 = 栈帧
栈主要保存三种数据:
本地变量:输入参数和输出参数以及方法内的变量栈操作:记录出栈,入栈的操作栈帧数据:包括类文件,方法等栈中的数据都是以栈帧的格式存在,是一个内存区块,是一个有关方法和数据的数据集。当方法A被调用,就产生了一个栈帧Fa,当方法B被调用,就产生了一个栈帧Fb。然后先探出Fb,再弹出Fa。
StackOverFlow 是错误,不是异常。属于java.lang.Error
栈中的reference就是直接存储对象的地址(但是都来自方法区的对象类型数据),HotSpot是使用指针的方式来访问对象,Java堆中会存放访问类元数据的地址。
类加载器读取了类文件后,就要把类、方法和常变量放到堆内存中,保存所有引用类型的真实信息,方便执行器执行。
新生代 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来调整堆内存大小。
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是引用类型,传的是内存地址
Eden : Old = 1 :2
元空间对应 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前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失败。
三大特点:
频繁收集Eden偶尔收集Old几乎不动Meta Spacemimor 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
分代收集算法:不同代使用不同收集算法,年轻代使用复制算法,老年代使用标记清除和标记压缩混合算法。
应用线程会从主内存复制一份到工作内存(线程私有),写完后,写入主内存。
Volatile 是java虚拟机提供的轻量级同步机制
保证可见性不保证原子性不保证指令重排序上述代码永远都不会打印出 finish,因为没有线程通知main中的number副本值。可以很好的体现可见性。加了volatile就能保证number变量的可见性。
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.