画布 1
JDK&JVM&JUC
  Jdk&Jvm&Juc
    Java入门知识点(Common)
      面向对象和面向过程的区别
      OracleJdk与OpenJdk的区别
      Java与C++的异同
        Java和C++都是基于面向对象思想的语言
        Java不提供指针来访问内存,而C++允许指针访问内
        垃圾回收机制
        Java不支持多继承，而C++支持
      JVM,JDK和JRE的区别
        JVM(java virtual machine)是java虚拟机
        JRE(java runtime environment)是java运行时环境
        JDK(java development kit)是java开发工具包,不仅包含了jre和jvm,还提供了javac编译器和javadoc等其他开发所需的工具
      Java语言的特点
        面向对象
        平台无关性,也就是跨平台(依靠JVM)
        支持多线程
        支持便捷的网络编程
        编译与解释(JIT)
        安全
      面向对象的特征
        封装
        继承
        多态
      重载和重写的区别
      接口与抽象类的区别
        接口需要被实现,而抽象类是需要被继承的
        接口里的方法都是公共抽象的，而抽象类既允许抽象也允许非抽象的方法
        一个类允许实现多个接口,但只允许继承一个抽象父类
        接口是对类的规范,规范的是行为能力。而抽象类是对类的抽象,抽象的是逻辑.
      Object类的方法有哪些
      静态属性方法和成员属性方法区别
      子类属性与父类属性初始化顺序
      自动拆箱和装箱
      String为什么不可变
      final关键字
        被 final修饰的类，不能被继承，并且这个类所有的成员方法都为final，不能被重写
        被final修饰的属性变量，不能被修改
        被final修饰的方法不能被子类重写
      StringBuilder和StringBuffer区别
      equals知识点
        ==和equals的区别
        hashCode方法返回的是对象的地址吗？
        equals重写要求
        为什么重写equals需要重写hashcode
      深拷贝与浅拷贝
      IO流
        按照流的流向,分为:输入流和输入流
        按照操作单元,分为:字节流和字符流
        使用字节流还是字符流?
      BigDecimal
      Java异常体系结构
        Exception
          RuntimeException
          CheckedException
        Error
      Comparable和Comparator
      为什么要慎用 Arrays.asList()
      Java中引用的类型
        强引用(Strong Reference)
        软引用(Soft Reference)
        弱引用(Weak Reference)
        虚引用(Phantom Reference,又称幽灵引用)
    集合(Collections)
      List,Set,Map
      ArrayList和LinkedList
      ArrayList和Vector
      TreeMap和HashMap
      HashMap
      HashTable
      Set
      ConcurrentHashMap
      ConcurrentModificationException异常解释
        为什么不允许foreach循环增删元素
      CopyOnWriteArrayList和CopyOnWriteArraySet
      ConcurrentSkipListMap和ConcurrentSkipListSet
    线程并发(JUC,AQS,CAS)
      进程与线程
      并发与并行
        并发
        并行
      多线程的利弊
        利
        弊
      什么是上下文切换
      线程的生命周期
      线程的优先级
      sleep方法和wait方法
        sleep
        wait
      stop,suspend和resume为什么被弃用
        stop
        resume和suspend
      interrupt,interrupted,isInterrupted
        interrupt
        interrupted
        isInterrupted
      join方法
      yield方法
      乐观锁和悲观锁
        乐观锁
        悲观锁
      公平锁，非公平锁，独占锁，共享锁
        公平锁
        非公平锁
        独占锁
        共享锁
      可重入锁(递归锁)
      偏向锁，轻量级锁，自旋锁，自适应自旋锁，锁粗化，锁消除
        偏向锁
        轻量级锁
        自旋锁
        自适应自旋锁
        锁粗化
        锁消除
      死锁
        互斥条件
        不可抢占条件
        占有且申请
        循环等待
        避免死锁
      synchronized
      volatile
        JMM(Java 内存模型)
        volatile保证可见性
        volatile禁止指令重排序
        volatile不保证原子性
      synchronized与ReentrantLock的异同
      lock,tryLock,lockInterruptibly
        lock
        tryLock
        lockInterruptibly
      AQS
        AQS核心思想
        AQS原理
        AQS两种资源访问方式
        AQS主要技术
        ReentrantReadWriteLock如何区分读写锁的
        CountDownLatch
        Semaphore
        CycliBarrier
      ThreadLocal
        ThreadLocal原理
        ThreadLocal内存泄露
      CAS
        CAS实现
        CAS的缺点
          循环开销过大
          只能保证一个共享变量的原子操作
          ABA问题
        解决ABA问题
      线程池的好处(池化技术的好处)
        线程池的好处
          方便资源管理
          降低资源消耗
          提高响应速度
        线程池的参数
          corePoolSize
          maximumPoolSize
          keepAliveTime / unit
          workQueue
          threadFactory
          handler
        线程池的拒绝策略
          AbortPolicy
          DiscardPolicy
          DiscardOldPolicy
          CallerRunsPolicy
        为什么阿里巴巴开发者手册规定不允许使用Executors工具类创建线程池
    BIO/NIO/AIO
      阻塞 / 非阻塞
      同步/异步
      用户态与内核态
      linux5种IO模型
        阻塞IO
          阻塞IO的优点
          阻塞IO的缺点
          阻塞IO的同步
          阻塞IO的阻塞
        非阻塞IO
          非阻塞IO的非阻塞
          非阻塞IO的优缺点
        多路复用IO
          IO多路复用的优点
        信号驱动IO
        异步IO
          异步IO的异步
    JVM
      JVM概述
      JVM内存分区(Hotspot)
        对象在内存中的布局
        程序计数器
        Java虚拟机栈与本地方法栈
          局部变量表
          操作数栈
          符号引用与直接引用
          动态连接
          方法返回地址
        堆
        方法区:永久代与元空间
      Virtual Machine Error
        StakOverflowError错误
        OutOfMemoryError错误
          Java heap space
          GC overhead limit exceed
          Direct buffer memory
          unable to create new native thread
          Metaspace
      类的生命周期
        加载
        连接
          验证
          准备
          解析
        初始化
        使用
        卸载
        双亲委派机制
        类加载器的命名空间
      常量池
        Class文件常量池(静态常量池)
        运行时常量池
        字符串常量池(全局常量池)
        包装类型缓存池
    GC
      判断对象存活(引用计数，可达性)
        引用计数
        可达性
        GC ROOT
      垃圾回收算法
        分代收集算法
        复制算法(Copying)
        标记-清除算法(Mark-Sweep)
        标记-整理算法(Mark-Compact)
      内存分配与垃圾回收策略
        一次GC的过程
        动态年龄阈值
      垃圾回收器
        Serial
          Serial Old
        Parallel Scavenge
          Parallel Old
        ParNew
        CMS(Concurrent Mark Sweep)
        G1
    JVM常用参数
      堆栈相关
        -Xss
        -Xms
        -Xmx? / -XX:MaxHeapSize=?
        -Xmn? / -XX:NewSize=?
        -XX:NewRatio=?
        -XX:SurvivorRatio=?
        -XX:MetaspaceSize=? / -XX:PermGen=?
        -XX:MaxMetaspaceSize=? / -XX:MaxPermGen=?
        -XX:+HeapDumpOnOutOfMemoryError / -XX:HeapDumpPath=?
      GC相关
        -XX:+PrintGCDetails / -Xlog:gc*
        -XX:+TraceClassUnloading / -Xlog:class+unload=info
        -XX:+UseSerialGC
        -XX:+UseParallelGC
        -XX:ParallelGCThreads=?
        -XX:+UseConcMarkSweepGC
        -XX:+UseG1GC
        -XX:MaxTenuringThreshold=?
      其他
        -server / -client
    Java常用调优工具
      jps(非常重要)
      jstat
      jinfo
      jmap
      jstack
      jconsole(非常重要)
    JDK新特性(主要内容)
      jdk8
        Lambda / 方法引用
        接口新增default方法
        Stream API
        Optional API
        新的时间API(java.time强烈推荐使用)
        内置Base64工具
      jdk9
        模块化
        JShell 交互式编程环境
        集合的工厂方法
        接口新增private方法
        响应式流(Doug Lea大师编写的，个人认为很重要，也是Webflux的基础吧)
作者: qsjzwithguang19forever
作者微信: yg1446125917
作者邮箱: 2196927727@qq.com
作者github: github.com/guang19
本xmind归属于qsjzwithguang19forever的开源项目:framework-learning。
framework-learning旨在为Java方向的开发者提供较为全面，通俗的知识梳理。
项目地址: github.com/guang19/framework-learning
如若转载，请声明来源。
Java基础是Java方向的程序员必备的知识，本部分对常见Java基础问题做了一个较为全面的总结，其中涉及到JUC,JVM，GC等部分较为难理解的知识，我也尽力做了较为通俗的标注。
首先面向过程和面向对象的语言没有具体的性能高下之分,要依据每种语言的设计来做参考.
个人认为面向过程与面向对象的最大区别在于: 面向过程的语言是结构化的,面向对象的语言是模块化的.
模块化比结构化的代码更易于维护,复用与扩展.
OpenJdk是基于Sum捐赠的源代码HotPot的,是开源的.
OracleJdk是Oracle对Jdk的商业化版本,由Oracle发布并维护.
因此OracleJdk比OpenJdk更可靠.
Java不提供指针来访问内存,而C++允许指针访问内存。因此,Java相比C++更加安全,但也因此失去了一定的灵活性.
Java无需开发者手动释放内存,因为Java有垃圾回收机制,而C++则需要开发者手动释放内存,因此Java比C++在内存管理上相对安全。
个人认为所有语言写出来的代码的安全性是开发者决定的,而不是语言本身决定的,语言能决定的只是提供方便或不便的安全的API.
面向对象三大特征:封装,继承,多态.
封装是隐藏对象属性和的实现细节,只对外提供可访问或修改的接口.
封装的目的是为了简化编程和增加程序的安全性,使得使用者无需了解对象的具体实现细节。
继承是 在已存在的类上定义新的类的技术.
在Java中,已存在的类被称为基类(父类),新的类叫做派生类(子类).子类拥有父类的所有属性,方法.但是子类对于父类中私有的方法或属性只是拥有,并不能访问和使用.
继承的目的主要是为了代码的复用.
多态指的是相同类型的对象,调用其相同的方法,参数也相同,但是它的表现形式也就是结果不同.
多态的目的是为了程序的可扩展性和维护性.
在Java中可以使用继承与接口2大特性实现多态.
个人认为重载和重写完全没有可比性,不知道为啥老有人喜欢拿它们做比较.
重载是描述一个类中的多个方法函数的名字是一样的,但是参数,类型,返回值,顺序都可能不同的.
重写是描述子类对父类的某个方法的逻辑进行了重新编写,但变的只是方法的内容,方法名,参数,类型,顺序,返回值都是不变的.
在jdk8中,接口被允许定义default方法,jdk9中还允许定义private私有方法.
1. getClass
2. equals
3. hashCode
4. toString
5. wait
6. wait(long) : 让当前对象进入TIMED_WATING状态
7. wait(long,int):让当前对象进入TIMED_WATING状态
8. notify
9. nofifyAll
10. clone
11. finalize
静态属性和方法属于类Class,而成员属性和方法属于实例化的对象。
静态方法只能使用静态方法和静态属性,不能使用成员属性和方法,因为静态属性和方法在对象还没被实例化的时候就存在了.简单理解就是不允许一个已存在的事物使用一个不存在的事物。
1. 无论如何,静态数据首先加载,所以先初始化父类静态变量和静态初始化块,再初始化子类静态变量和静态初始化块.
2. 普通初始化块优先于构造方法,所以接下来加载父类的普通代码块,再调用父类构造方法
3. 调用子类普通代码块和构造方法.
自动拆箱和装箱实际上是Java编译器的一个语法糖。
自动装箱是指:将基本数据类型转为对应的包装类对象的过程.
自动拆箱是指:将包装类转为对应的基本数据类型.
自动装箱实际上是调用了包装类对象的valueof方法:
Integer.valueof(1)
自动拆箱实际上是调用了包装类的xxxValue方法:
Integer.intValue()
在自动装箱的时候,如果包装类允许缓存并且值在缓存的范围内,那么装箱生成的对象会被缓存到常量池中.
Integer,Byte,Short,Long,Character包装类型具有缓存池,而其他三种:Float,Double,Boolean不具有缓存池.
包装类的缓存池缓存的范围基本都为: -128 - 127之间，
除了Character的缓存范围为 0 - 127。
先说下我的看法:String是Java中最常使用的类没有之一,如果String是可变的,那么会发生非常多数不清的问题.
如ip地址,人名,邮箱非常多的敏感数据,如果String是可变的,那么就会发生安全问题.
还有如果String是可变的,那么字符串常量池也就无从谈起了.
String是不可变的,那么它本质上也是线程安全的.
不可变类的缺点就是每个不同的值需要创建一个对象
String 是用final修饰的，保证了String类不能被扩展。
String内部的字段是用final修饰的(我的是jdk11,String由byte[]实现)，并且没有对外提供修改字段的方法。
这也是为什么String不可变的原理.
被final修饰的属性变量，不能被修改。
如果该变量是基本数据类型的，那么其值在初始化后不能被修改。
如果该变量是引用类型的，那么该引用不能再指向其他对象。
StringBuilder和StringBuffer都是可变的字符串,但是StringBuilder是线程不安全的。
StringBuffer是安全的,因此单线程情况下考虑使用StringBuilder,多线层情况下考虑使用StringBuffer.他们之间的关系就好比HashMap和HashTable的关系。
在hotspot中，hashcode返回的不完全是地址:
源代码：openjdk的hotspot的/src/share/vm/runtime/synchronizer.cpp下
可以看到hashcode有多种返回策略:随机数，自增长序列，关联地址等多种方式。
1. 自反性:
x.equals(x) == true 永远成立
2. 非空性:
x.equals(null) == false 永远成立
3. 对称性:
如果 x.equals(y) == true , 那 y.equals(x)== true
4. 传递性:
如果 x.equals(y) == true,并且 y.equals(z) == true,那么一定满足x.equals(z) == true
5. 一致性:
如果x.equals(y) == true , 那么只要x和y的值不变,那么x.equals(y) == true　永远成立
在普通环境下(不涉及hash表),equals方法和hashcode方法一毛钱关系没有的,
此时的equals方法是会按照我们的程序去执行的。
但当使用map,set这些散列表时,
它们会根据对象的hashcode来计算对象在散列表中的位置的。
试想下,如果2个对象的值相等,但是由于它们是2个对象,hashcode却不相等。
那么即使放入set(map),set(map)仍会存在重复数据。
深拷贝,听名字就知道,拷贝所有的内容,除了基本数据类型的变量复制一份,连引用类型的的对象也复制一份.
浅拷贝,只是复制基本数据类型的变量,对于引用类型的变量,直接返回这个引用本身.
考虑通用性,应该使用字节流。
如果只是文本文件的操作,可以考虑使用字符流。
BigDecimal是Java中表示大浮点数的类型。
在Java中,如果遇到浮点数的判断,可以使用BigDecimal来做计算,因为如果使用普通数据类型很可能会发生精度丢失的情况,这个时候的结果可能会出乎意料之外.
Exception:异常是程序本身可以处理的.
Exception 分为 运行时异常(RuntimeException),又称可不检查的异常和非运行时异常,也是不检查的异常.
RuntimeException是在程序运行时可能会发生的异常,如NullPointException,这类异常往往是不可预料的,编译器也不会要求你手动try catch或throws
非运行时异常: 非运行时异常是RuntimeException以外的异常,如IOException，这类异常要求必须显示的try catch或throws ， 如果不处理,那么编译就不会通过.
Error错误是程序无法处理的,表示程序出现了无法解决的问题。
Comparable：自然排序接口。实现了它的类意味着就支持排序。
Comparator是外部比较器。无需让需要排序的对象实现排序逻辑，而是根据Comparator定义的逻辑来排序。
Comparator相较于Comparable更加的灵活。
因为Arrays.asList这个方法返回的根本就不是我们期盼的ArrayList,而是Arrays类内部实现的ArrayList,这个内部类只支持访问和set操作,并不支持remove,add,clear等修改操作。
Java程序中绝大部分都是强引用，一般使用new关键字创建的对象就是强引用。
只要强引用存在，就不会被回收，除非不可达(参考jvm部分)
SoftReference,软引用一般不会回收，但是当堆内存不够的时候，比如几乎快要发生OOM的时候,就会发生回收掉软引用
WearkReference,只要垃圾回收开始，就会回收掉弱引用的对象
PhantomReference,和其他几种引用不同，虚引用不决定对象的生命周期，它在任何时候都可能被回收掉.
List:有序可重复的集合,这里的有序是指输出时按输入的顺序输出的.
Set:不允许重复元素的集合.
Map:key->value的字典映射集.
1. ArrayList底层使用Object数组实现,LinkedList底层使用双向链表实现.
ArrayList的容量默认为0
2. 由于ArrayList采用数组实现,它的容量是固定的,所以当添加新元素的时候,如果超出了数组的容量,那么此时add操作的时间复杂度将会是O(n-1).相反,LinkedList的add操作只需要改变尾节点的引用就行了,但是如果需要在指定位置进行add操作的话，那么时间复杂度也是比较高的,为O(n)，因为需要从头节点或尾节点遍历到需要操作的节点.
3. ArrayList实现了RandomAccess接口，该接口没有具体的规范，只是一个标记，这代表ArrayList支持快速的随机访问。而LinkedList在这点上就不如ArrayList了。
4. ArrayList在内存空间利用率上肯定是不如LinkedList的，因为数组是一片固定的连续的内存空间，一旦分配就无法改变，所以难免会有空间不足或空间使用率很低的情况。
而LinkedList的空间利用率虽然很高，但是它的每个Node可以说也是占用了较大空间的，因为每个Node需要保存它的前继和后继节点.
ps: 双向链表与双向循环链表的区别:
双向链表:每个Node都保存了前后2个节点的引用，双向链表的first节点的前一个节点为null, last节点的后一个节点为null
双向循环链表: 每个Node都保存了前后2个节点的引用，双向循环链表的first节点的前一个节点指向last节点，last节点的最后一个节点指向first节点.
1. ArrayList是线程不安全的，Vector是线程安全的，但是Vector实现线程安全的方式是给每个方法都加synchronized关键字，包括get方法，这就造成了Vector效率不如人意的效果.
2. ArrayList默认容量为 0 ， Vector默认容量为 10.只有当第一次进行add操作的时候，ArrayList才会初始化容量为10.
3.ArrayList扩容1.5倍;而Vector是当它的扩容增量大于0时,扩容为原容量 + 扩容增量,否则就扩容为原来的2倍,这点可以看看Vector的源码,虽然不建议使用Vector，但是大师们的设计总还是令人信服的.
HashMap在Jdk8之前使用拉链法实现,jdk8之后使用拉链法+红黑树实现.
HashMap是线程不安全的,并允许null key 和 null value
HashMap在我当前的版本(11)的默认容量为0.在第一次添加元素的时候才初始化容量为 16,
之后才扩容为原来的2倍.
HashMap的扩容是根据 threshold决定的,threshold = loadfactory * capacity,
当 size 大于 threshold 时,扩容.
当每个桶的元素数量达到默认的阈值TREEIFY_THRESHOLD(8)时,那么这个桶的链表将会转为红黑树,当红黑树节点的数量低于默认的阈值UNTREEIFY_THRSHOLD(6)时，那么这个桶的红黑树将转为链表
HashMap的长度为什么要设计成2的幂？
这就不得不佩服大师们的设计。
想想看，一个对象的hashcode是很大的，当HashMap的容量仅为16,32时，如何根据hashcode来确定key在数组中的下标。一个好的办法就是取余. hashcode % length,这样就能确保，key的下表是永远不会超过数组的长度的。但是想想，除了取余有没有更好的办法，当然有。
hash % length == hash & (length - 1)
为什么上面这个性能超高的等式成立，当然是有条件的，只当length为2的幂的时候这样的等式才成立.
这就明白了为什么使用2的幂来定义HashMap的长度.
HashTable就像Vector一样,也是jdk1就存在的很古老的一个类，它是线程安全的，实现线程安全的手段是使用synchronized，HashTable的默认容量为16，每次扩容为原来的2倍+1.
HashTable底层使用拉链法实现.
HashTable不允许null key 和 null value
为啥不单独说HashSet，我目前看到的JDK所有的Set,都是使用Map实现的,除了CopyOnWriteArraySet(底层是CopyOnWriteArrayList)。
TreeSet --> TreeMap
LinkedHashSet --> LinkedHashMap
HashSet --> HashMap
ConcurrentSkipListSet --> ConcurrentSkipListMap
Set是如何保证元素不会重复,这个得看各自Map的实现。
拿HashMap来讲，它就是先判断key的hashcode是否相等，然后才使用equals判断
2个对象是否相等
ConcurrentHashMap应该是最常用的并发HashMap了.
ConcurrentHashMap使用数组+链表/红黑树实现,其扩容等机制与HashMap一样,但是控制并发的方法改为了CAS+synchronized
synchronized锁的只是链表的首节点或红黑树的首节点,
ConcurrentModificationException可以从名字看出是并发修改的异常。但我要说的是这个异常并不是在修改的时候会抛出的，而是在调用迭代器遍历集合的时候才会抛出，而集合类的大部分toString方法，都是使用迭代器遍历的。所以如果多线程修改集合后，接着就遍历集合，那么很有可能会抛出ConcurrentModificationException。
在ArrayList，HashMap等非线程安全的集合内部都有一个modCount变量，这个变量是在集合被修改时(删除，新增)，都会被修改，如果是多线程对同一个集合做出修改操作,那么最终在调用集合的迭代方法时，modCount与预期expectedModeCount比较，expectedModcount是在迭代器初始化时使用modCount赋值的，如果发现modCount与expectedModeCount不一致，就说明在使用迭代器遍历集合期间，有其他线程对集合进行了修改,所以就会抛出ConcurrentModificationException异常。
首先要明白一点:foreach只是个语法糖，它底层会被编译成Iterator迭代器，而迭代器在初始化时会初始化expectedModCount的值，如果在foreach里进行增删操作，会修改modCount的值，导致expectedModeCount与ModeCount不一致。
CopyOnWriteArraySet由CopyOnWriteArrayList实现，所以看CopyOnWriteArrayList就好了.
CopyOnWriteArrayList是线程安全的ArrayList，可以被称为 写时复制的ArrayList，它底层仍然使用数组实现，但是它的修改操作(增删改)采用synchronized关键字保证并发的安全性，然后在进行修改的时候复制原来的数组到一个新副本，对新副本进行修改，修改完后再设置原数组。这样就不会让写操作影响读操作了。
但是CopyOnWriteArrayList不容忽视的缺点就是修改操作比较消耗内存空间，所以它适用于读多写少的环境。
ConcurrentSkipListSet由ConcurrentSkipListMap实现，所以看ConcurrentSkipListMap就好了.
ConcurrentSkipListMap是基于跳表这种数据结构实现的。
跳表比较特殊，它由多个层次的链表组成，每层链表又有多个索引节点连接，每层链表的元素也都是有序的。处于上层索引的链表都是下层链表的子集。
跳表与普通链表相比查找元素的效率更高。
进程与线程最主要的区别是它们是操作系统管理资源的不同方式的体现。
准确来说进程与线程属于衍生关系。
进程是操作系统执行程序的一次过程,在这个过程中可能会产生多个线程。比如在使用QQ时，有窗口线程，文字发送的线程，语音输入的线程，可能不是很恰当，但是就是这个意思。
在Java中，一个进程产生的多个线程共享这个进程内的堆，metaspace等共享资源，每个线程又有自己的程序计数器，虚拟机栈，本地方法栈。
由于系统在线程之间的切换比进程之间的切换更加轻量，所以线程也被成为轻量级进程。
并发:
多个线程被一个cpu轮流执行，注意，这里并不是规定一个cpu，多个cpu也是可以的，并发主要强调的是cpu有处理多个任务的能力。
并行:
多个线程被多个cpu同时执行，这里也并不是规定要多个cpu，一个cpu也是可以的，只要你的cpu能在同一时刻处理多任务，并行强调的是拥有同时处理多任务的能力。
线程可以比作轻量级的进程，现在是多核cpu时代，意味着多个线程可以被通过cpu同时运行(并行)，
这就减少了上下文的开销，如果可以利用好多线程，那么就可以编写出高并发的程序。
虽然线程带来的好处很多，但是并发编程并不容易，如果控制不好线程，那么就可能造成死锁，资源闲置，内存泄露等问题.
cpu是采用时间片的轮转制度，在多个线程之间来回切换运行的。
当cpu切换到另一个线程的时候，它会先保存当前线程执行的状态，以便在下次切换回来执行时，可以重新加载状态，继续运行。
从保存状态再到重新加载回状态的这个过程就叫做上下文切换。
NEW ， RUNNABLE ， BLOCKED，WATING，TIMED_WAITING, TERMINATED
在Java中可以通过Thread类的setPriority来设置线程的优先级，虽然可以通过这样的方式来设置线程的优先级，但是线程执行的先后顺序
并不依赖与线程的优先级，换句话说就是，线程的优先级不保证线程执行的顺序。
sleep方法是Thread类的方法。
sleep方法会使当前线程让出cpu的调度资源，从而让其他线程有获得被执行的机会，但是并不会让当前线程释放锁。
wait方法是Object类的方法。
而wait方法是让当前线程释放锁并进入wait状态，不参与获取锁的争夺，从而让其他等待资源的线程有机会获取锁，只有当其他线程调用notify或notifyAll方法是，被wait的线程才能重新与其他线程一起争夺资源。
stop:
stop方法被弃用很好理解，因为stop方法是强行终止线程的执行，不算线程的run方法是否执行完，资源是否释放完，它都会终止线程的运行，并释放锁。这在设计上就不合理，不说资源浪费等问题，它都有可能造成数据的不一致，假设A线程被用于a用户向b用户转账，那么钱刚从a用户的账户里扣除，线程就被stop了，b用户却没收到钱，然后其他线程又继续执行其它的操作，这根本上就是不被允许的。
suspend方法用于阻塞一个线程,但并不释放锁，而resume方法的作用只是为了恢复被suspend的线程。
假设A，B线程都争抢同一把锁，A线程成功的获得了锁，然后被suspend阻塞了，却并没有释放锁，它需要其他线程来唤醒，但此时B线程需要获得这把锁才能唤醒A，所以此时就陷入了死锁.
这也是它们为什么被弃用的原因。
interrupt:
这个方法并不是中断当前线程，而是给当前线程设置一个中断状态。
isInterrupted:
当线程调用interrupt方法后，线程就有了一个中断状态，而使用isInterrupted方法就可以检测线程的中断状态。
interrupted：
这个方法用于清除interrupt方法设置的中断状态。如果一个线程之前调用了interrupt方法设置了中断状态，那么interrupted方法就可以清除这个中断状态。
join方法的作用是让指定线程加入当线程执行。
假如在main方法里面创建一个线程A执行，并调用A的join方法，那么当前线程就是main，指定的A线程就会在main之前执行，等A执行完后，才会继续执行main。
join方法的底层是wait方法，调用A线程(子线程)的join方法实际上是让main线程wait，等A线程执行完后，才能继续执行后面的代码。
yield属于Thread的静态方法，它的作用是让当前线程让出cpu调度资源。
yield方法其实就和线程的优先级一样，你虽然指定了，但是最后的结果不由得你说了算，
即使调用yield方法，最后仍然可能是这个线程先执行，只不过说别的线程可能先执行的机会稍大一些。
锁从宏观上讲分为乐观锁和悲观锁。
乐观锁:
乐观锁一般是用户实现的一种锁机制，乐观锁机制对与共享数据很乐观，认为任何时候都不会发生线程安全问题，所以不会给数据加锁。
乐观锁适用于读多写少的应用。典型的例子就是mysql的更新使用version控制。
CAS也是乐观锁。
悲观锁:
数据库锁基本都属于悲观锁。悲观锁认为在任何环境下，数据都有可能发生共享一致性问题，所以每次读写数据的时候都会加锁。synchronized就是悲观锁。
公平锁:
根据线程在队列中的优先级获取锁，比如线程优先加入阻塞队列，那么优先获取锁。
非公平锁:
每次在获取锁的时候，每个线程都有机会获得锁，不考虑队列优先级的问题。因此，非公平锁的效率比公平所的效率高。
独占锁:
独占锁保证了在任何时候，只能有一个线程获取锁，其他线程只能等待获取锁的线程释放锁。synchonized，reentrantlock，ReentrantReadWriteLock的WriteLock都是独占锁。
共享锁:
共享锁允许多个线程同时访问同一份共享数据。.ReentrantReadWriteLock的ReadLock是共享锁，它允许多个线程同时读取数据。
可重入锁(也叫递归锁):
一个线程获取锁后，如果遇到被相同锁修饰的方法或资源时，可以继续获取该锁。
偏向锁:
线程首先会判断对象是否为可偏向的,也就是判断对象的MarkWord的偏向线程的ID是否为当前线程id。如果是，那么表示当前对象锁是偏向的，就无需再次获得这个锁。如果对象锁已经被其他线程持有，就说明有竞争了，会升级为轻量级锁。
轻量级锁:
线程会使用CAS修改对象的MarkWord为00，如果修改成功，那么当前线程就获取锁成功。如果修改失败，就说明有竞争，那么当前线程会自旋争夺，如果自旋成功，那么仍然处于轻量级锁，如果失败，那么将膨胀为重量级锁。
自旋锁:
在争夺锁的过程中，线程不会阻塞，而是
会不断的争抢。
自适应自旋锁:
自旋锁意味着线程会不断的消耗cpu资源，短时间还行，长时间就意味着而资源的浪费，所以自适应自旋锁就是给自旋设定一个时间,
网上有文章说这个时间依据前一次在同一个锁上的自旋时间以及锁的拥有者状态来决定的,如果前一次自旋成功了,那么当前自旋也会成功，因此会花费教程时间来自旋，如果前一次自旋失败了，那么说明当前自旋也可能会失败，自旋时间就会更短。
锁粗化:
当虚拟机检测出一系列连续的操作都对同一个连续加锁，那么它会把加锁的返回扩大至整个操作的序列的外部，保证只加锁一次。
锁消除:
锁消除是当虚拟机检测出一些已经加锁的代码，不可能存在共享数据竞争的问题，会消除这样的锁.
锁消除的依据来源于逃逸分析算法。
如果判断到一段代码，在堆上的数据不会逃逸出去被其他线程访问到，那么就把它们当做栈上的数据，为线程私有的，自然无需同步加锁。
死锁是指多个线程在执行的过程中，因为循环等待彼此占有的资源而造成程序的无限期阻塞。
发生死锁的条件:
1.互斥条件
2. 不可抢占条件
3. 占有且申请
4.循环等待
互斥条件:
一份资源在一个时间段内，只能被一个线程所持有。
不可抢占条件:
资源被一个线程所持有后，其他线程不能强行抢占资源，只能等待持有资源的线程主动释放资源。
占有且申请:
线程已持有其他线程所需的资源，仍然申请其他已被其他线程占有资源。
循环等待:
线程循环等待彼此释放资源。
只要打包上述4个条件中的一个条件就可以避免死锁发生。
但是只有第4个条件可以被打破，因为其他三个都是锁的必要条件。
唯有第四个条件可以被打破:
我们可以实现资源的有序分配。
synchronized是jdk提供的jvm层面的同步机制。
它解决的是多线程之间访问共享资源的同步问题,它保证了
在被它修饰的方法或代码块同一时间只有一个线程执行。
java6之前的synchronized属于重量锁,性能较差,
它的原理是基于操作系统的Mutex Lock互斥量实现的。
因为java线程是映射到操作系统的线程之上的,
所以暂停或唤醒线程都需要Java程序从用户态转换为内核态,这段转换时间消耗较长。
而jdk6以后，jvm团队对synchronized进行了很大的优化，包括:锁消除，锁粗化，自旋锁，自适应自旋锁，偏向锁，轻量级锁等技术来减少synchronized的开销.
当synchronized修饰实例方法的时候,锁为当前实例对象。
当synchronized修饰静态方法的时候,锁为当前Class
当synchronized修饰代码块的时候，需要指定锁对象。
volatile是JVM提供的轻量级的线程同步机制。
1. volatile保证内存的可见性
2. volatile禁止指令重排序
指令冲排序是编译器和cpu为了程序的高效运行的一种优化手段，它只能保证程序执行的结果是正确的，但是无法保证程序指令运行的顺序是否与代码的顺序一致，比如:
比如:
1. int a = 1;
2. int b = 3;
3 . int c = a + b;
上面的代码可能执行的顺序就有:
1,2,3
2,1,3
这样程序实际执行的顺序可能与代码的顺序不符,但并不会影响程序最终的结果。
3. volatile如何禁止指令重排序的?
volatile通过提供 内存屏障 来防止指令重排序.
java内存模型会在每个volatile写操作前后都会插入store指令，将工作内存中的变量同步回主内存。
在每个volatile读操作前后都会插入load指令，从主内存中读取变量。
4. volatile不保证原子性
比如: i++
如果是多线程环境下，一个线程读取到i的值到工作内存，然后对i做出自增操作，然后写回主内存，其它内存才可见。
可以看到这个过程本身就不是一个原子的.
所以不能拿volatile来带替synchronized,如果是多线程环境下，仍然需要使用synchronized保证线程同步.
jvm的内存模型是: 线程总是从主内存读取变量到工作内存，然后在工作内存中进行修改，在修改完后再把数据同步到主内存中。
线程:线程栈(私有)
主内存:从某种程度上来说，主内存是线程共享的，可以看做是包含了方法区和堆内存。
可见性是指一个线程的修改对其他线程是可见的。
在Java内存模型中，如果多个线程同时读取了一个变量到各自的内存中，其中一个线程对变量进行了修改，并同步回了主内存，但其它线程仍然使用的是原来的旧值，这就造成了数据的不一致。解决这个问题的办法就是给变量加上volatile关键字修饰，volatile使得线程如果要使用这个变量，那么每次都需要从主内存中读取，保证了变量的可见性。
1. synchronized是基于JVM层面的同步机制，
而ReentrantLock是基于Java API层面提供的同步机制。
2. synchronized和reentrantlock都属于可重入锁。
3. ReentrantLock提供了比synchroinzed更高级的功能:
1. 公平锁
2. 更方便的线程间的通信(Condition)
3. 等待可中断(在线程等待获取锁的时候可以被中断)
lock:
线程不获取到锁不罢休。如果线程获取到了锁，那么线程的锁的计数设置为1，如果线程已经持有了该锁，那么锁的计数加1,，否则线程就阻塞。
tryLock:
尝试获取锁，并立刻返回，如果获取到锁返回true，并设置锁计数为1，并返回true，如果线程已经持有了该锁，那么设置锁计数加1并返回true，否则返回false
lockInterruptibly:
尝试获取锁，如果获取不到就阻塞，但在阻塞期间响应中断，也就是说在阻塞期间可以被打断，不只在阻塞期间可以被打断，而且如果线程在获取锁之前就已经被调用了interrupt方法，那么在阻塞时也会被打断。
与lock相似，但是lockInterruptibly优先响应中断，而不是优先获取锁。
AQS是什么?
AQS是Doug Lea大师为JDK编写的一套基于API层面的抽象队列同步器。
AbstractQueuedSynchronizer,抽象队列同步器。
AQS的核心思想是如果被请求的资源空闲，那么就将当前请求资源的线程设置为有效的工作线程；如果请求的资源被其他线程所占有， 那么就使用CLH线程阻塞队列来提供阻塞线程并唤线程分配资源的机制。
在CLH队列中，每个请求资源的线程都会被封装成队列中的一个节点。
在AQS内部有一个int类型的state表示线程同步状态，当线程lock获取到锁后，该state计数就加1,unlock就减1，这就是为什么解锁要对应加锁的次数。
AQS定义了2种访问资源的方式:独占式和共享式。
独占式规定同一时间资源只能被一个线程所持有。(写锁)
共享式允许同一时间资源被多个线程共享。(读锁)
AQS主要实现技术为:CLH队列，自旋CAS，Park(阻塞线程)以及unparkSuccessor(唤醒阻塞队列中的后继线程)
Sync既有写锁，又有读锁，因此一个state不够用，所以使用state的高16为表示读锁，低位16表示写锁.
CountDownLatch和Semaphore都是基于AQS的同步器。
CountDownLatch允许count个线程阻塞在一个地方，直至所有的线程都执行完毕。
CountDownLatch是共享锁的一种实现,它默认构造AQS的state为count，
当线程使用countDown方法时,其实使用了tryReleaseShared方法以CAS的操作来减少state,
直至state为0就代表所有的线程都调用了countDown方法。当调用await方法的时候，如果state不为0，
就代表仍然有线程没有调用countDown方法，那么就把已经调用过countDown的线程都放入阻塞队列Park,并自旋CAS判断state == 0，
直至最后一个线程调用了countDown，使得state == 0，于是阻塞的线程便判断成功，全部往下执行。
Semaphore允许一次性最多(不是同时)permits个线程执行任务。
Semaphore与CountDownLatch一样，也是共享锁的一种实现。
它默认构造AQS的state为permits。
当执行任务的线程数量超出permits,那么多余的线程将会被放入阻塞队列Park,并自旋判断state是否大于0，
只有当state大于0的时候，阻塞的线程才能继续执行,此时先前执行任务的线程继续执行release方法，
release方法使得state的变量会加1，那么自旋的线程便会判断成功。
如此，每次只有不超过permits的线程能自旋成功，便限制了执行任务线程的数量。
所以这也是我为什么说它可能不是permits个线程同时执行，
因为只要state>0,线程就有机会执行.
CycliBarrier的功能与CountDownLatch相似，但是CountDownLatch的实现是基于AQS的，而CycliBarrier是基于ReentrantLock(ReentrantLock也属于AQS同步器)和Condition的.
CountDownLatch虽然可以令线程阻塞，但是CountDownLatch只能await一次就不能使用了，而CycliBarrier有Generation代的概念，一个代，就代表CycliBarrier的一个循环，这也是CycliBarrier支持重复await的原因。
ThreadLocal为每个线程都提供了一份相同的变量的副本，每个线程都可以修改这个副本，但不用担心与其他线程发生数据冲突，实现了线程之间的数据隔离。
ThreadLocal的原理还得从Thread线程类说起，每个Thread类内部都有一个ThreadLocalMap，当使用ThreadLocal的get和remove操作的时候，就是使用每个线程的ThreadLocalMap的get和remove。
ThreadLocal引发的内存泄露:
在ThreadLocalMap中，key是使用弱引用的ThreadLocal存储的，在[Java常见知识点(Common)] 中我已经介绍了java中4种引用类型。
弱引用是只要垃圾回收器开始回收，无论内存是否充足，都会回收掉弱引用对象，如此一来，当ThreadLocal被回收掉,那么ThreadLocalMap将可能出现Null Key 的 value。但是也不必太过担心，因为设计者已经想到了这点，所以ThreadLocal会自动处理key 为 null的 value.
CAS: Compare And Swap
比较成功并交换。
CAS体现的是一种乐观锁的机制。
CAS涉及到3个元素: 指定的内存地址,期盼值和目标值。
它将指定内存地址的值与期盼值相比较，如果比较成功就将内存地址的值修改为目标值。
CAS在Java中的实现是 juc的atomic包下的Atomicxx原子类。
而这些Atomic原子类的核心是: <Unsafe>类
Unsafe类是个final类，它的核心方法都是native的，因为Java无法像C/C++一样使用指针来操作内存,Unsafe类就解决了这个问题。
拿incrementAndGet方法来说，Unsafe首先调用getAndAddInt方法,它会根据当前Atomic的value在内存中地址获取到当前对象的值,然后再重复一遍此操作，把之前获得的值与第二遍获得的值进行比较，如果成功，就把内存地址的值更新为新值，否则就do while循环.
并且有个重要的细节就是,Atomic原子类内部的value值都是由volatile修饰的，这就使得Atomic的值是对其他线程可见的。
我在看源码的时候，发现Atomic的CAS操作并没有进行CAS失败的退出处理，只是单纯的循环比较并交换，这就让我很担心它的性能问题，如果长时间不成功，那会是很可怕的一件事请，至少cpu的负荷会很大。
Atomic原子类只能保证一个变量的原子操作，如果是多数据的话，还是考虑用互斥锁来实现数据的同步吧。
ABA问题是指如果一个线程进行CAS操作并成功了，却不代表这个过程就是没有问题的。
假设2个线程读取了同一份数据，线程1修改了这个值并把它改回了原值，并同步到主内存中，另一个线程准备进行CAS操作,当它发现原值和期盼的值是一样的，那么CAS仍然成功。
如何解决ABA问题?
在juc的atomic包中提供了 AtomicStampedReference 类,这个类较普通的原子类新增了一个stamp字段，它的作用相当于version，每次修改这个引用的值，也都会修改stamp的值，当发现stamp的值与期盼的stamp不一样，也会修改失败，这就类似于以version实现乐观锁一样。
http连接池，数据库连接池，线程池等都是利用了池化技术。
如果一个资源需要多次使用并且很昂贵，那么使用new创建的对象或资源，可能会带来较大的消耗。
方便资源的管理，无需显示的使用new创建。
降低了资源的消耗，在线程池里的资源可以重复利用
提供了任务的响应速度，任务可以很快的被分配资源进行处理。
线程池的核心线程数，是线程池最小的线程数量，这部分线程不会被回收。
线程池最大运行的线程数量。
当线程池中的线程数量超过corePoolSize，但此时没有任务执行，那么空闲的线程会保持keepAliveTime才会被回收，corePoolSize的线程不会被回收。
keepAliveTime的单位。
当线程池中的线程达到了corePoolSize的线程数量，并仍然有新任务，那么新任务就会被放入workQueue。
创建工作线程的工厂。
拒绝策略，当线程池中的工作线程达到了最大数量，并且阻塞队列也已经满了，那么拒绝策略会决定如何处理新的任务。
当池中的工作线程数达到了最大，并且阻塞队列已满，但仍有新的任务需要处理，那么将采用一下拒绝策略处理新任务:
1. AborPolicy
2. DiscardPolicy
3. DiscardOldPolicy
4. CallerRunsPolicy
AbortPolicy:
如果使用此拒绝策略，那么将对新的任务抛出RejectedExecutionException异常，来拒绝任务。
DiscardPolicy:
如果使用此策略，那么会拒绝执行新的任务，但不会抛出异常。
DiscardOldestPolicy:
如果使用此策略，那么不会拒绝新的任务，但会抛弃阻塞队列中等待最久的那个线程。
CallerRunsPolicy:
如果使用此策略，不会拒绝新的任务，但会让调用者执行线程。也就是说哪个线程发出的任务，哪个线程执行。
newFixedThreadPool和newSIngleThreadPoolExecutor都是创建固定线程的线程池,尽管它们的线程数是固定的，但是它们的阻塞队列的长度却是Integer.MAX_VALUE的,所以，队列的任务很可能过多，导致OOM。
newCacheThreadPool和newScheduledThreadPool创建出来的线程池的线程数量却是Integer.MAX_VALUE的，如果任务数量过多,也很可能发生OOM.
同步与阻塞没有任何关系,异步与非阻塞也没有任何关系!!!
阻塞:
一个线程调用一个方法计算1-100的和。如果该方法一直计算，没有返回，那么调用者线程就必须等待方法执行完成。
非阻塞:
一个线程调用一个方法计算 1 - 100的和，如果该方法没有返回，调用者线程也无需一直等待该方法返回，可以去做其他的事情，但是线程仍然需要轮询检查方法是否返回。
结论:阻塞和非阻塞是针对调用方的立场而言的。
同步与阻塞没有任何关系,异步与非阻塞也没有任何关系!!!
同步:
一个线程调用一个方法计算 1 - 100 的和，如果方法没有计算完，就不返回。
异步:
一个线程调用一个方法计算 1 - 100 的和，该方法立刻返回，但是由于方法没有返回结果，所以就需要被调用的这个
方法来通知调用线程 1 - 100的结果，或者线程在调用方法的时候指定一个回调函数来告诉被调用的方法执行完后就执行回调函数。
结论:同步和异步是针对被调用方的立场而言的。
用户态与内核态是操作系统对执行权限进行分级的不同的运行模式。
1. 内核态(核心态,特权态)：
内核态是操作系统的内核的运行模式，内核态控制计算机的硬件资源，为上层应用程序提供基本运行环境。
2. 用户态
用户态是应用程序运行的活动空间。应用程序的执行必须依托于内核态，因此用户态的执行权限要比内核态低。如cpu，磁盘等，访问都是受限的。
3. 系统调用
系统调用是内核为上层应用程序提供的能够访问到内核态的资源的应用接口。
1. 阻塞IO
2. 非阻塞IO
3. 多路复用IO
4. 信号驱动IO
5. 异步IO
在阻塞IO模型中，用户态应用程序发起一个系统调用，检测内核是否准备好数据。如果内核准备好数据，就把数据复制到用户应用进程。如果没有准备好数据，用户应用进程就阻塞，直到内核准备好数据并把数据复制到应用进程内，最后用户应用进程再处理数据。
阻塞IO是同步阻塞的。
阻塞IO的优点是能够确保数据是无延时的，因为应用程序会一直阻塞直到IO完成。
应用程序的阻塞就意味着应用程序进程无法去做其他事情，这会大大降低程序性能。一个不太可行的办法是为每个客户端socket都分配一个线程，这样就会提升server处理请求的能力。
不过操作系统的线程资源是有限的，如果请求过多，可能造成线程资源耗尽，系统卡死。。
阻塞IO的同步体现在:
内核只有准备好数据并把数据复制到用户应用进程才会返回。
阻塞IO的阻塞体现在:
用户应用进程等待内核准备数据和把数据拷贝到用户应用进程的这2段时间。
如果是本地磁盘IO，可能内核准备数据的时间就比较短。但如果是网络IO，那么服务端不知道客户端何时发送数据，内核就可能一直等待客户端数据，时间就会很长。
在非阻塞IO模型中，用户应用程序也会发起一个系统调用，检查内核是否准备好数据，但内核会立刻返回结果。
如果内核没有准备好数据，用户应用进程也不会阻塞等待内核准备数据，
可以执行其他任务，但仍需要不断发起系统调用。这个过程就叫轮询。轮询直到内核准备好数据，然后把数据复制到用户应用进程，再进行数据的处理。
非阻塞IO的非阻塞体现在用户应用进程不用阻塞在对内核的系统调用上。
非阻塞IO的优点在于用户应用进程在轮询阶段可以执行其它任务。但这也是它的缺点，轮询就代表着用户应用进程不是时刻都会发起系统调用。可能数据准备好了，而用户应用进程可能等待其它任务执行完毕才会发起系统调用，
这就意味着数据的延时获取。
在多路复用IO模型中，用户应用进程会调用操作系统的select/poll/epoll函数,它会使内核轮询指定的socket(可以允许多socket)，直至监听的socket可读或可写，select函数才会返回。用户应用进程也会一直阻塞等待select/poll/epoll函数返回。当select/poll/epoll函数返回后，即某个socket有数据了，用户应用进程就发起系统调用将数据从内核复制到用户应用进程，最后处理数据。
多路复用IO模型是同步阻塞的。
多路复用IO模型的同步体现在:
select函数只有监听到某个socket有数据才会返回。
多路复用IO模型的阻塞体现在:
用户应用进程会阻塞在对select函数上的调用上。
多路复用IO的优点在于内核可以处理多个socket，相当于一个用户进程(线程)就可以处理多个socket连接，这样不仅降低了系统的开销，并且对于需要高并发的应用是非常有利的。而非阻塞IO和阻塞IO的一个用户应用进程只能处理一个socket，要想处理多socket，只能新开进程或线程，但这样很消耗系统资源。
在信号驱动IO模型中，用户态应用进程发起一个sigaction的系统调用，内核收到并立即返回。
用户应用进程可以继续执行其他任务，不会阻塞。
当内核准备好数据后向用户应用进程发送SIGIO信号。用户应用进程收到信号后发起系统调用，将数据从内核拷贝到用户应用进程，最后处理数据。
信号驱动模型已经有些像异步IO模型了。。
在异步IO模型中，用户应用进程发起一个aio_read的系统调用，内核收到并立即返回。
用户应用进程不用阻塞，可以继续执行其他任务。当内核准备好数据，就将数据拷贝到用户应用进程内，最后内核会通知用户应用进程IO完成。
异步IO的异步体现在:
内核不用准备好数据就可以返回，所以内核后续肯定需要在IO完成后通知用户应用进程。
Java Virtual Machine
JVM是Java语言的核心部分，是java语言能够跨平台的根本原因是JVM,所以必须对JVM有一个较为全面的了解。
以hotspot为例:
jdk8之前:
线程私有:
java虚拟机栈，本地方法栈，程序计数器。
线程公有:
堆区,永久代(永久代是方法区的实现)
jdk8之后，hotspot的永久代改为了metaspace元空间
ps: metaspace和永久代都是方法区的实现。
对象在内存中归档布局由对象头,实例数据,对齐填充组成.
对象头:
对象头主要由:ClassPointer类型指针，MarkWord组成，如果对象是数组，那么对象头还需要保存数组的指针。
MarkWord:
markword由对象的哈希码，GC分代年龄，锁状态标识，偏向锁的线程ID，偏向锁时间戳，持有当前对象锁的线程ID
ClassPointer:
用于判断对象属于哪个类,也就是指向类的指针。
实例数据:
存储对象的各个字段的值，包括继承自父类的属性值，这部分是有可能为空的，因为一个类没有属性，就没有值了。
对齐填充:
jvm要求对象的大小必须是8bit的倍数，如果对象的大小不足或超出都需要对齐填充为8bit的倍数。
ps:锁标志位：
01 ：代表无锁或偏向锁
00 : 轻量级锁
10 : 重量级锁
程序计数器是一块较小的内存空间，它的作用是作为当前线程执行的字节码的行号指示器。当字节码解释器工作时，通过改变行号计数器的值来选取下一条要执行的字节码指令。分支，循环，跳转，异常处理，线程恢复等功能都需要依赖程序计数器完成。
程序计数器是属于线程私有的部分。当cpu在多个线程之间切换执行时，需要记录下当前线程执行的字节码的位置，以便下次切换回当前线程时，能够继续执行字节码指令，所以每个线程都需要有自己的程序计数器。
程序计数器的特点:
1. 如果当前线程执行的是java方法，那么程序计数器记录的是字节码指令的地址。
2. 如果当前线程执行的native方法，那么程序计数器记录的值为空(实际上是未定以的undefined)。
3. 程序计数器这部分内存区域是JVM中唯一不会出现OOM错误的区域
4. 程序计数器的生命周期与线程相同,即程序计数器随着线程创建而创建，随着线程的销毁而销毁。
Java虚拟机栈与程序计数器一样，都是线程私有的部分，生命周期也跟线程一样。
Java虚拟机栈描述的是Java方法运行时的内存模型。
栈帧:
栈帧是用于支持Java方法运行时的数据结构。
栈帧包含了局部变量表，操作数栈，动态链接，方法出口等信息。
每个方法执行时，都会在java虚拟机栈中创建一个栈帧。方法的调用和返回，就对应着栈帧的入栈和出栈的过程。
本地方法栈与虚拟机栈的作用是相似的，不过虚拟机栈是为执行Java方法提供服务的，本地方法栈视为执行native方法提供服务的。
在本地方法执行的时候，也会在本地方法栈中创建栈帧，用于存放该本地方法的局部变量表，操作数栈，动态连接和方法返回地址等信息。
局部变量表用于存储方法参数和方法内定义的局部变量。
局部变量表存放了各种已知的数据类型的变量。一个局部变量的类型可以是基本数据类型(int,short,float,double,boolean,long,byte,
char) ,引用类型(reference)。
在Java代码被编译成class字节码后，方法Code属性的locals就确定了方法的局部变量表的大小
局部变量表以 slot为最小单位，一个slot代表4个字节，也就是32位度的大小。
操作数栈是一个后进先出(LIFO)的数据结构。它存储的是方法在进行数据运算时的元素。和局部变量表一样，操作数栈的每个元素的类型也可以是基本数据类型和引用类型。
操作数栈的深度不会超过 Code属性的stack值。
符号引用可以理解为字符串，可以通过字符串找到指定数据的位置。
符号引用存于Class文件常量池
符号引用有 类的全限定名，方法名，字段名
直接引用是数据的内存地址。如方法的内存地址。
在虚拟机栈中，每个栈帧都包含了一个该栈帧所属方法的符号引用，持有这个符号引用的目的是为了支持方法调用过程中的动态连接。
这些符号引用有的一部分会在JVM类解析阶段就会转为直接引用，这部分转换成为静态解析。还有一部分会在运行时转为直接引用，这部分称为动态连接。
当方法执行时，有2种方式可以退出该方法。
1. 正常退出
当方法执行时，执行到return指令，该方法就会正常退出。
2. 异常退出
在方法执行过程中遇到了异常，并且方法内部没有处理这个异常，就会导致方法退出。
无论何种退出方式，在方法退出后，都需要回到方法被调用的位置，程序才能继续执行。
一般来说，方法正常退出时，调用者的程序计数器的值可以作为方法返回的地址，栈帧中可能会保存这个计数器的值。
方法异常退出时，返回地址需要通过异常处理器表来确定的，栈帧中不会保存这部分值。
堆是JVM中内存占用最大的一块区域，它是所有线程共享的一块区域。
堆的作用是为对象分配内存并存储和回收它们。堆是垃圾回收的主要区域，所以堆区也被成为GC堆。
堆区可以划分为 新生代(Young Generation),老年代(Old Generation)和永久代(Permanent Generation),永久代已被元空间代替,而元空间存储的是类的元信息，几乎不可能发生GC。
新生代再细分可以分为: eden空间，From Survivor空间和 To Survivor空间。
缺省情况下新生代占堆区的 1/3,老年代占堆区的2/3.(通过-XX:+NewRatio参数可以调整新生代和老年代的空间占比)
eden空间占新生代的80%,FromSurvivor和ToSurvivor的空间占比为1:1.(通过-XX:+SurvivorRatio参数可以调整eden和survivor的空间占比)
发生在新生代的GC叫做Young GC或Minor GC,
发生在老年代的GC叫做Old GC或Major GC。
PS:
有的文章说 Full GC与Major GC一样是属于对老年代的GC，
也有的文章说 Full GC 是对整个堆区的GC，所以这点需要各位同学自行
分辨Full GC语义。。
方法区在JVM规范里是各个线程共享的一部分区域，它用于存储已被jvm加载的类信息，常量，静态变量等数据。方法区在逻辑上是属于堆区的。
HotSpot虚拟机对于方法区的实现在jdk8之前为永久代，在jdk8之后，HotSpot移除了永久代，新增了元空间。
元空间使用的是本地内存，所以元空间仅受本地物理内存的限制。
元空间存储着已被加载的类的元信息，而常亮，静态变量等信息被转移到了堆内存之中。
当前线程执行或请求栈的大小超过了Java虚拟机栈允许的最大空间(比如递归嵌套太深)，就可能出现SOF错误
可能出现OOM的情况主要有一下几种:
1 . java heap space
2. GC overhead limit exceed
3. Direct buffer memory
4. unable to create new native thread
5. Metaspace
当需要为对象分配内存时，堆空间占用已经达到最大值，无法继续为对象分配内存，可能会出现OOM: java heap space错误。
当垃圾回收机花费很长时间进行GC，但是回收的内存非常少的时候，可能会出现OOM: GC overhead limit exceed。
当程序分配了超额的本地物理内存(native memory/ direct buffer)，minor gc(young gc)并不会回收这部分内存，只有 full gc才会回收直接内存，如果不发生full gc，但直接内存却被使用完了，那么可能会发生 OOM: Direct buffer memory。
操作系统的线程资源是有限的，如果程序创建的线程资源太多(无需超过平台限制的线程资源上限)，就可能发生 OOM: unable to create new native thread 错误。
当加载到元空间中的类的信息太多，就有可能导致 OOM : Metaspace。
使用cglib的库，可以动态生成class，所以可以使用cglib测试此错误
类的生命周期:
加载 -->连接-->初始化-->使用-->卸载
其中连接又可细分为: 验证，准备，解析三个阶段。
加载:
在加载阶段，类加载器将类的class文件的二进制数据读取到方法区，并在堆区生成该类的Class对象。
通常有多种方式可以获取类的二进制数据:
1). 从磁盘读取，这也是最常见的一种方式。当java源文件被编译成class文件后，jvm就会从磁盘读取class文件。
2). 从jar，zip等归档文件中读取class文件。
3). 通过网络读取类的字节流
4). 通过动态生成字节码的技术(如:动态代理，cglib等)来动态生成class。
PS:
数组由数组元素的类性的类加载器加载，这是ClassLoader类的部分注释:
连接分为:验证，准备，解析3个阶段。
验证:
验证阶段，jvm会对类的字节流进行验证，以确保符合虚拟机规范，并且不会对虚拟机造成恶意损害。
jvm会验证class文件的格式。如是否以0×CAFEBABE开头，常量池中的常量是否是jvm支持的类型，注册版本号是在jvm规定的范围内。
jvm会验证class元数据。jvm会对类的元信息进行语义分析，确保符合java语法规范。
jvm会验证字节码。jvm会通过分析控制流和数据流，确保类的方法体的程序语义是合法的，符合逻辑的。
jvm会验证符号引用。jvm会验证符号引用，确保符号引用能在解析阶段被正常解析。
准备:
在准备阶段，jvm会为类的静态变量初始化零值。(如，0,0L，false,null)
解析:
在解析阶段，jvm会将class文件中的常量池中的符号引用解析为直接引用。
符号引用包括类的全限定名，方法名和描述信息，字段名和描述信息。
直接引用可以理解为指向目标的指针，即目标的内存地址。
初始化阶段是类加载过程的最后一个阶段。在这个阶段，只有主动使用类才会初始化类，总共有8中情况涉及到主动使用类:
1. 当jvm执行new指令时，会初始化类。即程序创建类的实例对象时。
2. 当jvm执行getstatic指令时，会初始化类。即程序获取类的静态变量时(不是静态常量，静态常亮归属于运行时常量池)。
3. 当jvm执行putstatic指令时，会初始化类。
即程序为类的静态变量赋值时。
4.当jvm执行invokestatic指令时，会初始化类。即程序调用类的静态方法时。
5. 当通过反射主动调用类时，会初始化类。
6. 当初始化一个子类时，会先初始化这个子类的所有父类，然后才会初始化这个子类。
7. 当一个类是启动类时，即这个类包含main方法，会初始化这个类。
8. MethodHandle和VarHandle可以看作是轻量级的反射调用机制，而要想使用这2个调用，
就必须先使用findStaticVarHandle来初始化要调用的类。
初始化完类后，就可以使用类了。
卸载:
卸载类即该类的Class对象被GC。
卸载类需要满足3个要求:
1.该类的所有的实例对象都已被GC，也就是说堆不存在该类的实例对象。
2. 该类没有在其他任何地方被引用
3. 该类的类加载器的实例已被GC
所以，在JVM生命周期类，由jvm自带的类加载器加载的类是不会被卸载的。但是由我们自定义的类加载器加载的类是可能被卸载的。
只要想通一点就好了，jdk自带的BootstrapClassLoader,PlatformClassLoader,AppClassLoader负责加载jdk提供的类，所以它们(类加载器的实例)肯定不会被回收。而我们自定义的类加载器的实例是可以被回收的，所以使用我们自定义加载器加载的类是可以被卸载掉的。
双亲委派机制是指在加载一个类的时候，会判断这个类被其类加载器是否加载过了。如果加载过了，就直接返回这个类。如果没有被加载，就调用这个类的父类加载器判断是否被加载，一层一层，最终可能由BootstrapClassLoader判断。如果BootstrapClassLoader判断都没有加载过，那么也由BootstrapClassLoader尝试加载这个类，如果BootstrapClassLoader加载失败，就使用它的子类加载器们尝试加载。
在jdk9之后，由于模块化的到来，双亲委派机制也变化了一点:如果类没有被加载，那么会根据类名找到这个类的模块，如果找到了这个类的模块，就由这个类的模块加载，否则仍然使用父类加载器加载。
由此可见:在加载一个类的时候是自下而上的判断类是否被加载。如果类没有被加载，那么就自上而下的尝试加载这个类。
双亲委派机制防止了类的重复加载，避免类被重写，Java的核心API被恶意破坏。
如Object类，它由BootstrapClassLoader在JVM启动时加载。如果没有双亲委派机制，那么Object类就可以被重写，其带来的后果将无法想象。
每个类加载器都有自己的命名空间，命名空间由该类加载器和其父类加载器加载的所有的类组成。
在同一个命名空间内，不会出现相同全限定名(包括包名)的2个类。
在不同的命名空间内，可能出现相同全限定名(包括包名)的2个类。
Jvm常量池分为 Class文件常量池，运行时常量池，字符串常量池和包装类型常量池。
Class文件常量池:
当Java源文件被编译后，就会生成Class字节码文件。Class文件常量池就存在于Class文件的Constant Pool中。
Class文件常量池主要存放两大常量:
字面量和符号引用。
1). 字面量
字面量分为文本字符串(如: "abc")和用final修饰的成员变量(实例变量和静态变量)
2). 符号引用
符号引用包括三种：类的全限定名，方法名和描述符，字段名和描述符。
运行时常量池:
运行是常量池是在类加载阶段后，将class常量池中的符号引用存放到运行时常量池中，所以运行时常量池存于元空间(方法区)，
然后在解析阶段会将符号引用替换成直接引用。
也就是说一个Class常量池对应一个运行时常量池。
字符串常量池:
字符串常量池在jdk7之前都是存于方法区之中(永久代),jdk7以后存于堆区之中。
包装类型缓存池:
包装类缓存池并不是所有的包装类都有，并且缓存池缓存的是一定范围内的
数据。
拥有包装类型缓存池的类有:Integer,Byte,Character,Long,Short，
而Float，Double，Boolean都不具有缓存池。
包装类的缓存池缓存的范围基本都为: -128 - 127之间，
除了Character的缓存范围为 0 - 127。
在垃圾回收器对堆内存回收的时候，首先需要判断对象是否存活。判断对象存活的方法主要有引用计数法和可达性算法
引用计数算法:
给每个对象添加一个引用计数器,每当对象被引用,
对象的引用计数器就加1,当引用失效时,引用计数器就减1,
直到引用计数器为0,就代表对象不再被引用。
引用计数的主要缺陷是很难解决循环引用的问题:
也就是当2个对象互相引用的时候,除了彼此,
没有其他地方引用这2个对象,那么他们的引用计数都为1,就无法被回收。
可达性算法:
通过一系列GC ROOT的对象节点往下搜索，节点走过的路径叫做引用链，如果一个对象不处于被任何引用链，那么此对象就是不可达的。
什么样的对象可以作为GC ROOT 对象?
1. 虚拟机栈和本地方法栈中引用的对象。
2 .方法区中静态属性引用用的对象。
3 .方法区中常量引用的对象
常见垃圾回收算法有以下4种:
1. 复制算法
2. 标记-清除算法
3. 标记-整理算法
4. 分代收集算法
分代收集算法并不是指某一种具体的垃圾收集算法，而是将复制，标记-清除，标记-整理等算法合理运用到堆区的不同空间，比如新生代使用复制算法，老年代使用标记清除或标记整理算法。
现代的几乎所有的JVM都使用分代收集，毕竟每种算法都有优缺点，结合它们的特点，对不同的环境采用不同的算法是非常明智的选择。
复制算法:
将堆内存分为2块大小相等的内存空间，每次只使用其中的一块内存，另一块则空闲。当其中一块内存使用完后，就将仍然存活的对象复制到另一块空闲内存空间，再清理已使用的内存。
复制算法的优点是不会产生连续的内存碎片，速度也很高效。
但是缺点更明显:每次只使用内存的一半，就代表可使用的内存减少了1/2，代价比较高昂。
复制算法一般用于新生代。因为新生代的GC非常频繁，每次GC的对象较多，存活的对象较少。所以采用复制算法效率更高，复制时只需要复制少量存活的对象。
标记-清除算法:
标记-清除算法分为2个步骤：标记和清除。
首先标记出所有可达(存活)的对象，在标记完成后，统一回收所有未被标记(不可达)的对象。
标记-清除算法的缺点主要有2个:
1). 标记和清除2个阶段的耗时都比较长，可以总结为效率较低。
2).对象在内存中的分布可能是不连续的，分散的，标记-清除后可能造成不连续的内存碎片。当内存碎片过多后，后续想要分配较大的对象时，无法找到足够大的内存碎片，可能又需要触发GC。
标记-清除算法和标记-整理算法一样，一般用于老年代。因为老年代中的对象存活率较高，几乎很少被回收，所以标记-清除和标记-整理算法GC的时间不会太长，GC的对象较新生代更少。
标记-整理算法:
标记-整理算法是对标记-清除算法的一种改进。
标记-整理算法与标记-清除算法的在标记阶段是相同的，
都是首先标记出所有可达(存活)的对象。
但标记之后并不直接清理未被标记(不可达)的对象，
而是使被标记(存活)的对象向内存一端移动，然后清理掉这一端外的内存。
标记-整理算法的优点是:几乎不会如标记-清除算法那样产生不连续的内存碎片。
但，所谓慢工出细活。标记-整理的效率大概率上都是比标记-清除要低的。
标记-整理算法和标记-清除算法一样，一般用于老年代。
1. 对象优先在eden区域分配
2. 大对象将直接进入老年代
3. 长时间存活的对象将进入老年代
对象优先在eden区被分配，当eden区内存不足时，JVM发起Minor GC，Minor GC的范围包括eden和From Survivor。
首先JVM会根据可达性算法标记出所有存活的对象。
如果存活的对象中，有的对象的年龄已经达到晋升阈值(阈值是动态变化的，第一次默认为15，可以通过-XX:MaxTenuringThreshold设置最大的阈值)，
那么将已经达到阈值的对象复制到老年代中。
如果To Survivor空间不足以存放剩余存活对象，则直接将存活的对象提前复制到老年代。
如果老年代也没有足够的空间存放存活的对象，那么将触发Full GC(GC整个堆，包括新生代和老年代)。
如果To Survivor可以存放存活的对象，那么将对象复制到To Survivor空间，并清理eden和From Survivor。
此时From Survivor为空，那么From Survivor就成为了下一次的To Survivor，此时To Survivor存放着存活的对象，就成为了下一次的From Survivor。
这样From Survivor与To Survivor就是不断交替复制的使用。
同时可以明白，老年代中的对象很多都是不易被回收的对象。
老年代的空间比新生代的空间要大，所以老年代的Major GC要比Minor GC耗时更长。
根据垃圾回收器的不同，老年代的GC算法也不同。
JVM并不要求对象的年龄一定要达到 MaxTenuringThreshold 才会晋升到老年代,这个阈值是动态计算的。
如果在Survivor中某个年龄阶段的所有对象的大小之和大于等于Survivor的一半，那么只要大于这个年龄的所有对象，就可以进入老年代。
如果说垃圾回收算法是JVM对GC算法的方法论，那么垃圾回收器就是对GC的算法的实现。
串行垃圾回收器:
它为单线程环境设计，只使用一个线程进行回收。
当Serial回收器回收时，需要暂停用户线程(Stop The World),所以并不使用于多线程环境。
但是Serial收集器在单线程环境下的效率是很高效的，他没有多线程带来的开销。
Serial收集器使用的是复制算法.
PS: 目前，只有Serial和ParNew收集器能与CMS收集器搭配使用。
Serial Old:
Serial Old收集器是Serial收集器的老年代版本，同样是一个单线程的回收器。
Serial Old采用的是标记-整理算法。
Parallel Scavenge:
Parallel Scavenge收集器是并行收集器,它使用多线程进行垃圾回收，但回收时，仍需要暂停用户线程。
吞吐量 = 应用程序代码运行时间 / (应用程序代码运行时间 + GC时间)
Parallel Scavenge 收集器采用复制算法。
Parallel Old:
Parallel Old 收集器是 Parallel Scavenge 收集器的老年代版本，同样是一个并行多线程的收集器。
Parallel Old 收集器采用标记-整理算法。
ParNew:
ParNew 收集器可以看做是多线程版本的Serial收集器。
除了多线程外，ParNew与Serial几乎没啥区别。
ParNew收集器采用复制算法。
PS: 目前，只有Serial和ParNew收集器能与CMS收集器搭配使用。
CMS:
CMS收集器是一款老年代的收集器，是以达到最短停顿时间为目标的收集器。
CMS在垃圾回收过程中，用户线程可以一起运行，无需暂停。
因为CMS收集器采用的是标记-清除算法，所以回收时可能产生不连续的内存碎片。
CMS回收主要分为以下阶段:
* 初始标记
(Stop The World，此过程会暂停用户线程)
初始标记只标记与GC ROOT直接关联的对象。
* 并发标记
并发标记是对在初始标记阶段标记的对象进行Tracing，标记所有可达的对象。
* 重新标记
(Stop The World,此过程会暂停用户线程)
在并发标记阶段，可能由于用户线程的执行，被标记对象之间的引用关系可能发生变化，所以此阶段对已标记的对象重新标记调整。
* 并发清除
清除所有未被标记的对象。
G1收集器是目前最前沿的一款垃圾收集器，它是一款面向服务端的服务器。G1收集器无需搭配其他收集器就可以独自管理整个堆内存。jdk9开始 ， G1成为了jdk默认的收集器。
G1收集器的主要特点有:
1) . 并行与并发
G1收集器能够充分利用多核CPU的优势，使垃圾回收与用户线程同时执行。
2). 可预测的停顿时间
降低GC停顿时间是G1与CMS的共同目标，但除了降低停顿时间，G1收集器还能建立可预测的停顿时间模型。
3). 空间整合
虽然G1收集器保留了新生代和老年代的概念，但已经不再是物理上的分隔了。
在G1收集器的堆内存模型中，内存被分割成了一块一块的大小相等的Region，这些Region的类型也不同，有eden，survivor，old,humongous之分。当大小过大，会被分配到Humongous Region之中。
G1回收主要分为以下阶段:
* 初始标记:
(Stop The World,此过程会暂停用户线程)
只标记与GC ROOT直接关联的对象。
* 并发标记
并发标记是对在初始标记阶段标记的对象进行Tracing，标记所有可达对象。
* 最终标记
(Stop The World，此过程会暂停用户线程)
在并发标记阶段，由于用户程序的执行，可能造成被标记对象之间的引用关系发生变化，最终标记就是对这些对象进行重新标记调整。
* 筛选回收
不同与CMS，G1收集器并不会直接清除未被标记的对象，而是根据Region的回收价值和回收成本进行排序，然后再进行回收。这样可以保证在有限的时间内获得最大的回收率。
设置线程栈大小
设置堆内存的初始化大小
设置堆内存最大值
-Xmn10m与-XX:NewSize=10m相同，都是设置新生代的大小。
设置老年代和新生代的空间占比。
如设置为 -XX:NewRatio=2
那么 老年代:新生代=为2:1
eden与survivor的空间占比:
eden/from survivor = eden / to survivor
如: -XX: SurvivorRatio=2,那么:
eden : from survivor : to survivor= 2 : 1 : 1
-XX:PermGen=9m 设置永久代初始大小，只对jdk8之前的版本生效。
-XX:MetaspaceSize=9m设置元空间初始大小，只对jdk8及之后的版本生效。
-XX:MaxMetaspaceSize=50m设置元空间最大值。
-XX:MaxPermGen=50m 设置永久代最大值
-XX:+HeapDumpOnOutOfMemoryError: 在发生OOM时，dump错误堆栈。
-XX:HeapDumpPath=/home/logs:在发生OOM时，将堆栈信息dump到指定目录或文件。此参数只有开启 -XX:+DumpHeapOnOutOfMemoryError 才有效
打印GC日志详情。
-Xlog:gc*在我这个版本是推荐使用的(jdk11)
打印类卸载的日志信息。 -Xlog:class+unload=info是我使用的版本(jdk11)所推荐的。
使用Serial串行回收器
使用Parallel Scavenge并行回收器
设置并行收集线程的数量。如:
-XX:ParallelGCThreads=5
使用CMS并发清除回收器，默认新生代使用的是ParNew 回收器，可以通过加参数 -XX:+UseSerialGC 来改变新生代的收集器为Serial
使用G1收集器，jdk9默认为此收集器。
设置新生代对象晋升老年代所需的最大年龄阈值。
-client:以客户端模式运行应用，client模式适用于客户端桌面应用程序。JVM在此模式下会对客户端程序运行做很大优化。
-server:以服务端模式运行应用，server模式适用于服务端应用程序。JVM在此模式下会对服务端的运行效率做很大优化。
jps 命令类似于 linux的 ps 命令，不过ps命令是用于查看系统进程的，而jps用于查看当前系统运行的java进程。
jps -q 只输出java进程id
jps -l 输出java进程main函数的详细路径
jps -v 输出java进程时指定的jvm参数
jps -m 输出java进程执行时main函数的参数
jstat用于查看java进程的运行状态.
jstat -class pid 用于查看java进程类的情况
jstat -compiler pid 用于查看java进程编译的情况
jstat -gc pid 用于查看java进程gc的情况
...
jinfo 查看正在运行的java进程的jvm参数
jinfo -flag MetaspaceSize pid : 查看java进程的jvm的元空间大小
jinfo -flag MaxHeapSize pid : 查看java进程的jvm的最大堆的大小
...
jmap 既可以dump java程序的快照，也可以查看对象的统计信息。
jmap -heap pid 查看java进程堆的信息
jmap -histo pid 查看java进程对象的信息
jmap -dump:file=filename pid 生成 java进程jvm的堆快照到指定文件
jstack用于分析java线程栈信息
jstack pid
jconsole 是jdk提供的对java程序进行分析的GUI界面工具。