做网站的服务器cpu异常免费销售管理系统软件

当前位置: 首页 > news >正文

做网站的服务器cpu异常,免费销售管理系统软件,上海设计网站设计,金阊seo网站优化软件文章目录 基础基础数据类型内部类Java IOIO多路复用重要概念 Channel 通道重要概念 Buffer 数据缓存区重要概念 Selector 选择器 关键字final 元注解常用接口异常处理ErrorException JVM与虚拟机JVM内存模型本地方法栈虚拟机栈 Stack堆 Heap方法区 Method Area (JD… 文章目录 基础基础数据类型内部类Java IOIO多路复用重要概念 Channel 通道重要概念 Buffer 数据缓存区重要概念 Selector 选择器 关键字final 元注解常用接口异常处理ErrorException JVM与虚拟机JVM内存模型本地方法栈虚拟机栈 Stack堆 Heap方法区 Method Area (JDK8以后被元空间Metaspace替代)程序计数器TLAB*Thread Local Allocation Buffer内存间操作 JVM的GC类加载机制类加载器引用类型 Java类加载器Tomcat类加载器FAQ 集合Collections快速失败fail-fast VS 安全失败fail-safeFAQ HashSetEnumeration VS IteratorCopyOnWriteArrayListArrayListArrayList VS Vector 区别ArrayList VS Array(即数组非类)* 区别 MapHashtableHashtable VS HashMap 区别 Hashtable VS ConcurrentHashMap 区别 ConcurrentMapJDK 7JDK 8 HashMap工作原理重要参数重要方法与JDK7对比细节总结结构体系 序列化序列化允许重构序列化并不安全序列化的数据可以被签名和密封序列化允许将代理放在流中信任但要验证参考链接 Java特性列表Java5Java6Java7Java8 (LTS)Java9Java10Java11 (LTS)Java12Java13Java14Java16Java12Java13Java14Java16 基础 基础数据类型 类型数据大小(位)数据范围int32-2^31 - 2^31-1long64-2^63 - 2^63-1float32^double64booleanshort16-2^15 - 2^15-1byte8-128 - 127char16存储unicode码 内部类 成员内部类 定义在一个类的内部可以认为是外部类的一个成员变量它可以无条件访问成员内部类的所有成员属性和方法(包括私有和静态)HashMap的EntrySet, KeySet等需要注意的是当成员内部类拥有和外部类同名的成员变量或者方法时会发生隐藏现象。即默认情况下访问的是成员内部类的成员。 静态内部类 HashMap的Node类 局部内部类 匿名内部类 使用匿名内部类实现的多线程实例
public class ThreadTest extends Thread {public static void main(String[] args) {new ThreadTest() {Overridepublic void run() {// …}}.start();} }Java IO IO多路复用 目前流行的多路复用IO实现主要包括四种select, poll, epoll, kqueue IO模型相对性能关键思路操作系统JAVA支持情况select较高Reactorwindows/Linux支持,Reactor模式(反应器设计模式)。Linux操作系统的 kernels 2.4内核版本之前默认使用select而目前windows下对同步IO的支持都是select模型poll较高ReactorLinuxLinux下的JAVA NIO框架Linux kernels 2.6内核版本之前使用poll进行支持。也是使用的Reactor模式epoll高Reactor/ProactorLinuxLinux kernels 2.6内核版本及以后使用epoll进行支持Linux kernels 2.6内核版本之前使用poll进行支持另外一定注意由于Linux下没有Windows下的IOCP技术提供真正的 异步IO 支持所以Linux下使用epoll模拟异步IOkqueue高ProactorLinux目前JAVA的版本不支持 重要概念 Channel 通道 被建立的一个应用程序和操作系统交互事件、传递内容的渠道 (注意是连接到操作系统)。一个通道会有一个专属的文件状态描述符。那么既然是和操作系统进行内容的传递那么说明应用程序可以通过通道读取数据也可以通过通道向操作系统写数据。 重要概念 Buffer 数据缓存区 为了保证每个通道的数据读写速度Java NIO框架为每一种需要支持数据读写的通道集成了Buffer的支持 重要概念 Selector 选择器 关键字 final 使用final修饰的方法无法被重写类无法被继承。 元注解 Retention保留的范围 SOURCE注解将被编译器丢弃该类型的注解信息只会保留在源码里源码经过编译后注解信息会被丢弃不会保留在编译好的class文件里如*Override*CLASS注解在class文件中可用但会被VM丢弃该类型的注解信息会保留在源码里和class文件里在执行的时候不会加载到虚拟机中请注意当注解未定义Retention值时默认是CLASSRUNTIME注解信息将在运行期JVM也保留因此可以通过反射机制读取注解的信息源码、class文件和执行的时候都有注解的信息如Deprecated Target可以用来修饰哪些程序元素如TYPE、METHOD、CONSTRUCTOR、FIELD、PARAMETER等未标注则表示可修饰所有Inherited是否可以被继承默认为falseDocumented是否会保存到Javadoc文档中 常用接口 Comparable和Comparator接口 Comparable 只包含一个compareTo方法可以给两个对象排序。具体来说它返回负数、0、正数来表示输入对象对于、等于、大于已经存在的对象 Comparator 包含equals、compare两个方法。compare方法用来给两个输入参数排序返回负数、0、正数表示第一个参数是小于、等于、大于第二个参数。equals方法需要一个对象作为参数它用来决定输入参数是否和comparator相等。只有当输入参数也是一个comparator并且输入参数和当前comparator的排序结果是相同的时候这个方法才返回true
异常处理 Error类对象由Java虚拟机生成并抛出不可捕捉不管有没有异常finally中的代码都会执行当try、catch中有return时finally中的代码依然会继续执行 Error OutOfMemoryErrorStackOverflowErrorNoClassDeffoundError Exception 非检查性异常 ArithmeticExceptionArrayIndexOutOfBoundsExceptionClassCastExceptionIllegalArgumentExceptionIndexOutOfBoundsExceptionNullPointerException 检查性异常 IOExceptionCloneNotSupportedExceptionIllegalAccessExceptionNoSuchFieldExceptionNoSuchMethodException
JVM与虚拟机 JVM内存模型 本地方法栈 虚拟机栈 Stack 线程私有的内存区域每个方法执行的同时都会创建一个栈帧 (Stack Frame)用于储存局部变量表 (基本数据类型、对象引用)、操作数栈、动态链接、方法出口等信息。 堆 Heap 线程共享。储存几乎所有的实例对象由垃圾收集器自动回收堆区由各子线程共享使用 Java中的堆是用来存储对象本身的以及数组当然数组引用是存放在Java栈中的堆是被所有线程共享的在JVM中只有一个堆。所有对象实例以及数组都要在堆上分配内存。 随着JIT发展栈上分配标量替换优化技术在堆上分配变得不那么绝对只能在server模式下才能启用逃逸分析栈上分配 逃逸分析 目的是判断对象的作用域是否有可能逃逸出函数体 标量替换 允许将对象打散分配在栈上 比如若一个对象拥有两个字段会将这两个字段视作局部变量进行分配 新生代 Eden minorGC后未被回收的到from区每一次到fromage1FromSurvivorToSurvivor 老年代
方法区 Method Area (JDK8以后被元空间Metaspace替代) 被所有线程共享的内存区域用来储存已被虚拟机加载的类信息、常量、静态变量、JIT编译后的代码。运行时常量池是方法区的一部分用于存放编译期间生成的各种字面常量和符号引用运行时常量池是每一个类或接口的常量池的运行时表示形式在类和接口被加载到JVM后对应的运行时常量池就会被创建出来。当然并非Class文件常量池中的内容才能进入运行时常量池在运行期间也可将新的常量放入运行时常量池比如String的intern方法 程序计数器 指向当前线程所执行的字节码指令的(地址)行号 TLABThread Local Allocation Buffer 线程专用的内存分配区域由于对象一般会分配在堆上而堆是全局共享的。因此在同一时间可能会有多个线程在堆上申请空间。因此每次对象分配都必须要进行同步虚拟机采用CAS配上失败重试的方式保证更新操作的原子性而在竞争激烈的场合分配的效率又会进一步下降。JVM使用TLAM来避免多线程冲突在给对象分配内存时每个线程使用自己的TLAB这样可以避免线程同步提高了对象分配的效率 内存间操作 lock和unlock 把一个变量标识为一条线程独占的状态把一个处于锁定状态的变量释放出来释放之后的变量才能被其它线程锁定 read和write 把一个变量值从主内存传输到线程的工作内存以便load把store操作从工作内存得到的变量的值放入主内存的变量中 load和store 把read操作从主内存得到的变量值放入工作内存的变量副本中把工作内存的变量值传送到主内存以便write use和assign 把工作内存变量值传递给执行引擎将执行引擎值传递给工作内存变量值 volatile的实现基于这8种内存间操作保证了一个线程对某个volatile变量的修改一定会被另一个线程看见即保证了可见性 JVM的GC 分代垃圾回收算法 试想在不进行对象存活时间区分的情况下每次垃圾回收都是对整个堆空间进行回收花费时间相对会长同时因为每次回收都需要遍历所有存活对象但实际上对于生命周期长的对象而言这种遍历是没有效果的因为可能进行了很多次遍历但是它们依旧存在。因此分代垃圾回收采用分治的思想进行代的划分把不同生命周期的对象放在不同代上不同代上采用最适合它的垃圾回收方式进行回收年轻代Young Generation 所有新生成的对象首先都是放在年轻代的目标就是尽快收集生命周期短的对象分三个区 Eden区 大部分对象在这里生成当Eden区满时还存活的对象将被复制到Servivor区当这个Survivor区满时此区的存活对象将被复制到另外一个Survivor区当所有Survivor区都满时从第一个Survivor区复制过来的且此时还存活的对象将被复制到年老区Tenured两个Survivor区是对称的没先后关系 FromSurvivor区ToSurvivor区 年老代Old Generation 在年轻代中经历了N次垃圾回收后仍然存活的对象就会被放到年老代中。因此可以认为年老代中存放的都是一些生命周期较长的对象 持久代Permanent Generation 主要存放Java类的类信息、方法与垃圾收集要收集的Java对象关系不大 新生代Eden内存快超出时触发minor gc对象内存从Eden到from对象回收次数age1 老年代内存快超出时触发full gc Minor GC和Major GC Java内存分配与回收策略 对象优先在堆的Eden区分配大对象直接进入老年代长期存活的对象将直接进入老年代 当Eden区没有足够的空间进行分配时虚拟机会执行一次Minor GC。Minor GC通常发生在新生代的Eden区在这个区的对象生存期短往往发生GC的频率较高回收速度比较快。Full GC/Major GC发生在老年代一般情况下触发老年代GC的时候不会触发Minor GC但是通过配置可以在Full GC之前进行一次Minor GC这样加快老年代的回收速度垃圾回收不会发生在永久代如果永久代满了或者是超过了临界值会触发完全垃圾回收Full GC Java 8已经移除了永久代新加了一个叫元数据区的native内存区 垃圾分析算法 判断一个对象是否可被回收 引用计数算法 为对象添加一个引用计数器当对象增加一个引用时计数器加1引用失效时计数器减1。引用计数为0的对象可被回收。在两个对象出现循环引用的情况下此时引用计数器永远不为0导致无法对他们进行回收。正是因为循环引用的存在因此Java虚拟机不再使用计数算法可达性算法 主流 以GC Roots为起始点进行搜索可达的对象都是存活的不可达的对象可被回收。Java虚拟机使用该算法来判断对象是否可回收GC Roots一般包含以下内容 虚拟机栈中局部变量表中引用的对象本地方法栈中JNI引用的对象方法区中类静态属性引用的对象方法区中常量引用的对象 虽然可达性算法可以判定一个对象是否能够被回收但是当满足上述条件时一个对象不一定会被回收。当一个对象不可达GC Root时这个对象并不会马上被回收而是处于一个死缓的阶段若要被真正的回收需要经历两次标记 如果对象在可达性分析中没有与GC Root的引用链那么此时就会被第一次标记并且进行一次筛选筛选的条件是是否有必要执行finalize()方法。当对象没有覆盖finalize()方法或者已被虚拟机调用过那么就认为是没必要的。如果对象有必要执行finalize()方法那么这个对象将会放在一个称为F-Queue的队列中虚拟机会触发一个Finalize()线程去执行此线程是低优先级的并且虚拟机不会承诺一直等待它运行完这是因为如果finalize()执行缓慢或者发生了死锁那么就会造成F-Queue队列一直等待造成了内存回收系统的崩溃。GC对处于F-Queue中的对象进行第二次标记这时该对象将被移除即将回收集合等待回收 垃圾收集算法 标记-清除算法标记哪些要被回收的对象然后统一回收 优点简单缺点垃圾回收后内存变得不连续造成很大零散的内存区域 复制算法 大多数GC使用将可用内存按容量划分为相等的两部分然后每次只使用其中的一块当一块内存用完时就将还存活的对象复制到第二块内存上然后一次性清除完第一块内存再将第二块上的对象复制到第一块 这种方式内存的代价太高每次都要浪费一半的内存。于是将算法进行了改进内存区域不再是按照11去划分而是将内存划分为811三部分较大的那份内存交Eden区其余是两块较小的内存叫Survivor区。每次都会优先使用Eden区若Eden区满就将对象复制到第二块内存区上然后清除Eden区如果此时存活的对象太多以至于Survivor不够时会将这些对象通过分配担保机制复制到老年代中优点GC后的内存空间是连续的缺点真正存放新对象的内存空间会变少 标记-整理算法为了解决标记-清除算法产生大量内存碎片的问题当对象存活率较高时也解决了复制算法的效率问题。它的不同之处就是在清除对象的时候先将可回收对象移动到一端然后清除掉端边界以外的对象这样就不会产生内存碎片 缺点效率低下 finalize() 用于关闭外部资源但是try-finally等方式可以做的更好并且该方法运行代价很高不确定性大无法保证各个对象的调用顺序因此最好不要使用 如果内存总是充足的那么垃圾回收可能永远不会进行也就是说finalize()可能永远不会被执行 有一种JNIJava Native Interface 调用non-java程序C或Cfinalize()的工作就是回收这部分内存 当一个对象可被回收时如果需要执行该对象的finalize方法那么就有可能在该方法中让对象重新被引用从而实现自救。自救只能进行一次如果回收的对象之前调用了finalize方法自救后面回收时不会再调用该方法。 类加载机制 加载Loading 通过类的完全限定名称获取定义该类的二进制字节流将该字节流表示的静态存储结构转换为方法区的运行时储存结构在内存中生成一个代表该类的Class对象作为方法区中该类各种数据的访问入口 连接Linking 验证Verification 确保Class文件的字节流中包含的信息符合当前虚拟机的要求并且不会危害虚拟机自身的安全从整体上看大致上会完成下面4个阶段的检验动作 文件格式验证验证字节流是否符合Class文件的规范如主次版本号是否在当前虚拟机范围内、常量池中的常量是否有不被支持的类型元数据验证对字节码描述的信息进行语义分析如这个类是否有父类、是否集成了不被继承的类等字节码验证是整个验证过程中最复杂的一个阶段通过验证数据流和控制流的分析确定程序语义是否正确主要针对方法体的验证。如方法中的类型转换是否正确跳转指令是否正确等符号引用验证这个动作在后面的解析过程中发生主要是为了确保解析动作能正确执行 准备Preparation 类变量是被static修饰的变量准备阶段为类变量分配内存并设置初始值使用的是方法区的内存实例变量不会在这阶段分配内存 实例化不是类加载的一个过程类加载发生在所有实例化操作之前并且类加载只进行一次实例化可以进行多次 解析Resolution 将常量池的符号引用替换为直接引用的过程 解析过程在某些情况下可以在初始化阶段之后再开始这是为了支持Java的动态绑定 初始化Initialization 初始化阶段才真正开始执行类中定义的Java程序代码虚拟机规范严格规定了有且只有5种情况必须立即对类进行初始化而加载、验证、准备自然需要在此之前开始 遇到new、getstatic、putstatic、invokestatic这4条字节码指令时如果类没有进行过初始化则需要先触发其初始化。生成这4条指令的最常见的Java代码场景是使用new关键字实例化对象的时候、读取或设置一个类的静态字段被final修饰、已在编译器把结果放入常量池的静态字段除外的时候以及调用一个类的静态方法的时候使用java.lang.reflect包的方法对类进行反射调用的时候如果类没有进行过初始化则需要先触发其初始化当初始化一个类的时候如果发现其父类还没有进行过初始化则需要先触发其父类的初始化当虚拟机启动时用户需要指定一个要执行的主类包含main方法的那个类虚拟机会先初始化这个主类当使用JDK1.7的动态语言支持时如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄并且这个方法句柄所对应的类没有进行过初始化则需要先触发其初始化 对于静态字段只有直接定义这个字段的类才会被初始化因此通过其子类来引用父类中定义的静态字段只会触发父类的初始化而不会触发子类的初始化 使用Using 卸载Unloading
类加载器 启动类加载器Bootstrap ClassLoader 加载Java核心类库无法被Java程序直接引用 扩展类加载器Extensions ClassLoader 加载Java的扩展库。Java虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载Java类 系统类加载器System ClassLoader 根据Java应用的类路径CLASSPATH来加载Java类。一般来说Java应用的类都是由它来完成加载的。可以通过*ClassLoader.getSystemClassLoader()来获取它 用户自定义类加载器 通过继承java.lang.ClassLoader类的方式实现
引用类型 强引用 被强引用关联的对象不会被回收。使用new一个新对象的方式来创建强引用 非静态内部类会在其整个生命周期中持有对它外部类的强引用 软引用 被软引用关联的对象只有在内存不够的情况下才会被回收。使用SoftReference类来创建软引用。
Object obj new Object(); SoftReferenceObject sf new SoftReferenceObject(obj); obj null; // 使对象只被软引用关联弱引用 被弱引用关联的对象一定会被回收也就是说它只能存活到下一次垃圾回收发生之前。使用WeakReference类来创建弱引用。
Object obj new Object(); WeakReferenceObject wf new WeakReferenceObject(obj); obj null;虚引用 一个对象是否有虚引用的存在不会对其生存时间造成影响也无法通过虚引用得到一个对象。为一个对象设置虚引用的唯一目的是能在这个对象被回收时收到系统的一个通知。使用PhantomReference来创建虚引用只能通过是否被加入到ReferenceQueue来判断是否被GC这也是唯一判断对象是否被GC的途径
Object obj new Object(); PhantomReference pf new PhantomReferenceObject(obj, null); obj null;Java类加载器 对于任意一个类都需要由加载它的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性每一个类加载器都拥有一个独立的类名称空间 比较两个类是否相等只有在这两个类是由同一个类加载器加载的前提下才有意义否则即使这两个类来源于同一个Class文件被同一个虚拟机加载只要加载它们的类加载器不同那这两个类就必定不相等 这里的相等包括代表类的Class对象的equals、isAssignableFrom、isInstance方法返回的结果也包括使用instanceof关键字做对象所属关系判定等情况 在自定义ClassLoader的子类的时候我们常见的会有两种做法一种是重写loadClass方法另一种是重写findClass。其实这两种方法本质上差不多毕竟loadClass也会调用findClass但是从逻辑上讲我们最好不要直接修改loadClass的内部逻辑最好是只在findClass里重写自定义类的加载方法 loadClass这个方法是实现双亲委托模型逻辑的地方擅自修改这个方法会导致模型被破坏容易造成问题 双亲委派模型该机制可以避免重复加载当父亲已经加载了该类的时候就没有必要子ClassLoader再加载一次。JVM根据类名包名ClassLoader实例ID来判定两个类是否相同是否已经加载过。(可以通过创建不同的ClassLoader实例来实现类的热部署) BootStrapClassLoader 最顶层的类加载器由C编写而成已经内嵌到JVM中。在JVM启动时会初始化该ClassLoader它主要用来读取Java的核心类库JRE/lib/rt.jar中所有的class文件这个jar文件中包含了java规范定义的所有接口及实现。 ExtensionClassLoader 用来读取Java的一些扩展类库如读取JRE/lib/ext/
.jar中的包 AppClassLoader 用来读取classpath下指定的所有jar包或目录的类文件一般情况下这个就是程序默认的类加载器 CustomClassLoader 用户自定义编写的用来读取指定类文件。基于自定义的ClassLoader可用于加载非classpath中如从网络上下载的jar或二进制的jar及目录、还可以在加载前对class文件优化一些动作如解密、编码等 ExtensionClassLoader的父类加载器是BootStrapClassLoader其实这里省掉了一句话容易造成很多新手的迷惑。严格说ExtClassLoader的父类加载器是null只不过是在默认的ClassLoader 的loadClass方法中当parent为null时是交给BootStrapClassLoader来处理的而且ExtClassLoader没有重写默认的loadClass方法所有ExtClassLoader也会调用BootStrapClassLoader类加载器来加载。 Tomcat类加载器 Common ClassLoader作为Catalina ClassLoader和Shared ClassLoader的parent而Shared ClassLoader又可能存在多个children类加载器WebApp ClassLoader一个WebApp ClassLoader实际上就对应一个Web应用那Web应用可能存在Jsp页面最终会转成class类被加载因此也需要一个Jsp的类加载器在代码层面Catalina ClassLoader、Shared ClassLoader、Common ClassLoader对应的实体类实际上都是URLClassLoader或者SecureClassLoader一般我们只是根据加载内容的不同和加载父子顺序的关系在逻辑上划分为这三个类加载器当tomcat启动时会创建几种类加载器 Bootstrap 引导类加载器 加载JVM启动所需的类以及标准扩展类位于 jre/lib/ext 下 System 系统类加载器 加载tocmat启动的类比如bootstrap.jar通常在 Catalina.bat 中指定。位于 CATALINA_HOME/bin 下 Common 通用类加载器 加载tomcat使用以及应用通用的一些类位于 CATALINA_HOME/lib 下比如 servlet-api.jar webapp 应用类加载器 每个应用在部署后都会创建唯一的类加载器。该类加载器会加载位于 WEB-INF/lib 下的jar文件中的class和 WEB-INF/classes 下的class文件
FAQ Java中的内存泄漏情况 长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄漏尽管短生命周期对象已经不再需要但因为长生命周期持有它的引用而导致不能被回收 如果一个外部类的实例对象的方法返回了一个内部类的实例对象这个内部类 虚拟机栈和本地方法栈区别 虚拟机栈为虚拟机执行Java方法服务本地方法栈为虚拟机使用到的Native方法服务服务 JVM内存区域哪些会发生OOM 堆 抛出OutOfMemoryError: Java heap space通过控制-Xmx和-Xms解决 Java虚拟机栈和本地方法栈 如果线程请求的栈大于所分配的栈大小则抛出StackOverFlowError错误比如不会停止的递归调用如果虚拟机栈是可以动态拓展的拓展时无法申请到足够的内存则抛出OutOfMemoryError错误 方法区 抛出OutOfMemoryError: Metaspace 永久代 旧版本的JDK因JVM对永久代的垃圾回收并不积极。抛出OutOfMemoryError: PermGen space
内存区域是否线程私有是否会发生OOM程序计数器是否虚拟机栈是是本地方法栈是是方法区否是直接内存否是堆否是 *System.gc()会做什么事情 提示JVM要进行垃圾回收但实际是否进行回收取决于JVM 如果对象的引用被置为null垃圾收集器是否会立即释放对象占用的内存 不会在下一个垃圾回收周期中这个对象将是可被回收的 GC为什么要分代 减少stw次数即减少full gc次数 新生代为什么要替换to和from区 重载和重写的区别 重载发生在同一个类中方法名必须相同返回值和访问修饰符可以不同参数类型和个数顺序不同重写Override发生在父子类中方法名、参数列表必须相同返回值范围小于等于父类抛出的异常范围小于等于父类访问修饰符范围大于等于父类如果父类方法访问修饰符为private则子类不能重写 Java面向对象编程三大特性封装、继承、多态 封装 把一个对象的属性私有化同时提供一些可以被外界访问的属性的方法 继承 使用已存在的类的定义作为基础简历新类 多态 变量指向的具体类型和该变量发出的方法调用在编程时并不确定而是在程序运行期间才确定。 Java中通过两种形式实现多态 继承 多个子类对同一方法的重写接口 实现接口并覆盖接口中同一方法
集合 包含Map接口和Collection接口及其所有实现类 Collection接口 Set接口 HashSet类TreeSet类LinkedHashSet类 List接口 ArrayList类LinkedList类Stack类Vector类 Map接口 HashMap类TreeMap类Hashtable类ConcurrentHashMap类Properties类
Collections 快速失败fail-fast VS 安全失败fail-safe Iterator的安全失败是基于对底层集合做拷贝因此它不受源集合上修改的影响。java.util包下面的所有的集合类都是快速失败的而java.util.concurrent包下面的所有的类都是安全失败的。快速失败的迭代器会抛出ConcurrentModificationException异常而安全失败的迭代器永远不会抛出这样的异常。 fail-fast: Java集合中的一种错误机制在用迭代器遍历一个集合对象时如果遍历过程中对集合对象的结构进行了修改(增加删除)则会抛出ConcurrentModificationException 并发修改异常防止继续遍历这就是快速失败机制。 JDK官方注释当Iterator这个迭代器被创建后除了迭代器本身的方法(remove)可以改变集合的结构外其它因素如若改变了集合的结构都被抛出ConcurrentModificationException异常 JDK官方注释迭代器的快速失败行为是不一定能够得到保证的一般来说存在非同步的并发修改时不可能做出任何坚决的保证的。但是快速失败迭代器会做出最大的努力来抛出ConcurrentModificationException。因此编写依赖于此异常的程序的做法是不正确的。正确的做法应该是迭代器的快速失败行为应该仅用于检测程序中的bug。 fail-safe: 不会抛出异常 复制时需要额外的空间和时间上的开销不能保证遍历的是最新内容 FAQ 如何避免fail-fast抛异常 在遍历时修改集合使用迭代器的remove等方法不用集合的remove等方法并发的环境需要对Iterator对象加锁也可以直接使用Collections.synchronizedList使用CopyOnWriteArrayList (采用fail-safe) #mermaid-svg-rp5m1uBh1rN1TNnu {font-family:“trebuchet ms”,verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-rp5m1uBh1rN1TNnu .error-icon{fill:#552222;}#mermaid-svg-rp5m1uBh1rN1TNnu .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-rp5m1uBh1rN1TNnu .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-rp5m1uBh1rN1TNnu .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-rp5m1uBh1rN1TNnu .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-rp5m1uBh1rN1TNnu .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-rp5m1uBh1rN1TNnu .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-rp5m1uBh1rN1TNnu .marker{fill:#333333;stroke:#333333;}#mermaid-svg-rp5m1uBh1rN1TNnu .marker.cross{stroke:#333333;}#mermaid-svg-rp5m1uBh1rN1TNnu svg{font-family:“trebuchet ms”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-rp5m1uBh1rN1TNnu .label{font-family:“trebuchet ms”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-rp5m1uBh1rN1TNnu .cluster-label text{fill:#333;}#mermaid-svg-rp5m1uBh1rN1TNnu .cluster-label span{color:#333;}#mermaid-svg-rp5m1uBh1rN1TNnu .label text,#mermaid-svg-rp5m1uBh1rN1TNnu span{fill:#333;color:#333;}#mermaid-svg-rp5m1uBh1rN1TNnu .node rect,#mermaid-svg-rp5m1uBh1rN1TNnu .node circle,#mermaid-svg-rp5m1uBh1rN1TNnu .node ellipse,#mermaid-svg-rp5m1uBh1rN1TNnu .node polygon,#mermaid-svg-rp5m1uBh1rN1TNnu .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-rp5m1uBh1rN1TNnu .node .label{text-align:center;}#mermaid-svg-rp5m1uBh1rN1TNnu .node.clickable{cursor:pointer;}#mermaid-svg-rp5m1uBh1rN1TNnu .arrowheadPath{fill:#333333;}#mermaid-svg-rp5m1uBh1rN1TNnu .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-rp5m1uBh1rN1TNnu .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-rp5m1uBh1rN1TNnu .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-rp5m1uBh1rN1TNnu .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-rp5m1uBh1rN1TNnu .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-rp5m1uBh1rN1TNnu .cluster text{fill:#333;}#mermaid-svg-rp5m1uBh1rN1TNnu .cluster span{color:#333;}#mermaid-svg-rp5m1uBh1rN1TNnu div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:“trebuchet ms”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-rp5m1uBh1rN1TNnu :root{–mermaid-font-family:“trebuchet ms”,verdana,arial,sans-serif;} 如果在迭代器遍历元素的时候modCount值发生了改变. 再次遍历就会抛出异常 当对集合的元素的个数做出改变的时候. modCount的值就会被改变 可以在涉及到影响modCount值变化的地方加上同步锁synchronized. 或直接使用Collections.synchronizedList HashSet HashSet如何保证数据不可重复 底层是HashMapHashSet是实现了Set接口并且把数据作为K值而V值一直使用一个相同的虚值来保存
private static final Object PERSENT new Object();public boolean add(E e) {return map.put(e, PERSENT) null; }Enumeration VS Iterator 两者都是接口 Iterator boolean hasNext()E next()default void remove()default void forEachRemaining(Consumer? super E action) Enumeration boolean hasMoreElements()E nextElement() Iterator支持fail-fast机制, Enumeration不支持Enumeration遍历效率较高 CopyOnWriteArrayList 写时复制往一个容器添加元素的时候先将当前容器复制出一个新的容器然后新的容器里添加元素添加完元素之后再将原容器的引用指向新的容器。这样做的好处是可以对CopyOnWriteArrayList进行并发的读而无需加锁。所以CopyOnWriteArrayList也是一种读写分离的思想读和写不同的容器。 添加元素的时候需要加锁否则多线程会复制N个副本多个线程在添加数据时读到的是旧数据。因为写的时候不会锁住旧的ArrayList 应用场景: 用于读多写少的并发场景。比如白名单、黑名单、商品类目的访问和更新场景 ArrayList 第一次添加元素时数组大小将变化为DEFAULT_CAPACITY10不断添加元素后会进行扩容。删除元素时会按照位置关系把数组元素整体复制移动一遍 ArrayList VS Vector 区别 Vector线程安全ArrayList线程不安全数据增长。Vector在数据满时(加载因子为1)增长为原来的两倍。ArrayList在数据量达到容量的一半时(加载因子为0.5)增长为原容量的0.5倍 1个空间。 ArrayList VS Array
(即数组非类)* 区别 数据可以包含基本类型和对象类型ArrayList只能包含对象类型数据大小固定ArrayList的大小不固定ArrayList提供更多方法 Map 永远不要将可变对象类型用作HashMap中的键因为hashCode()可能会随着变动导致get出来为null Hashtable Hashtable VS HashMap 区别 HashMap没有考虑同步线程不安全Hashtable使用了synchronized关键字(直接锁方法)线程安全HashMap允许K/V都为null后者K/V都不允许为null (HashMap存null时hash值为0)HashMap继承自AbstractMap类而Hashtable继承自Dictionary类HashMap的迭代器(Iterator)是快速失败的迭代器Hashtable的迭代器(Enumeration)不是。如果有其它线程对HashMap进行增删操作将会抛出ConcurrentModificationException但迭代器本身的remove方法移除元素不会抛出异常。这也是Enumeration和Iterator的区别。 Hashtable VS ConcurrentHashMap 区别 都是线程安全Hashtable锁住整个方法ConcurrentHashMap锁级别更细粒度 ConcurrentMap JDK 7 当一个Map被多个线程访问时通常使用containsKey()或者get()来查看给定键是否在存储键值对之前出现。但是即使有一个同步的Map线程还是可以在这个过程中潜入然后夺取对Map的控制权。问题是在对put()的调用中锁在get()开始时获取然后在可以再次获取锁之前释放。它的结果是个竞争条件这是两个线程之间的竞争结果也会因谁先运行而不同 ConcurrentHashMap 最外层不是一个大的数组而是一个Segment的数组。每个Segment包含一个与HashMap数据结构差不多的链表数组 在读写某个key时先取该key的哈希值并将哈希值的高N位对Segment个数取模从而得到该key应该属于哪个Segment接着如同操作HashMap一样操作这个Segment。Segment继承自ReentrantLock可以很方便的对每一个Segment上锁 读操作 获取key所在的Segment时需要保证可见性。具体实现上可以使用volatile关键字也可使用锁。但使用锁开销太大而使用volatile时每次写操作都会让所有CPU内缓存无效也有一定开销 ConcurrentHashMap使用如下方法保证可见性取得最新的Segment SegmentK,V s (SegmentK,V) UNSAFE.getObjectVolatile(segments, u)获取Segment中的HashEntry时也使用了类似方法 HashEntryK,V e (HashEntryK,V) UNSAFE.getObjectVolatile(tab, ((long)(((tab.length - 1) h)) TSHIFT) TBASE)写操作 不要求同时获取所有Segment的锁因为那样相当于锁住了整个Map。它会先获取该Key-Value对所在的Segment的锁获取成功后就可以像操作一个普通的HashMap一样操作该Segment并保证该Segment的安全性同时由于其它Segment的锁并未被获取因此理论上可支持concurrencyLevel等于Segment的个数个线程安全的并发读写获取锁时并不直接使用lock来获取因为该方法获取锁在失败时会挂起。事实上它使用了自旋锁如果tryLock获取锁失败说明锁被其它线程占用此时通过循环再次以tryLock的方式申请锁。如果在循环过程中该Key所对应的链表头被修改则重置retry次数。如果retry次数超过一定值则使用lock方法申请锁 这里使用自旋锁是因为自旋锁的效率比较高但是它消耗CPU资源比较多因此在自旋次数超过阈值时切换为互斥锁
JDK 8 JDK 7已经解决了并发问题并且能支持N个Segment这么多次的并发但依然存在HashMap在JDK 7中的问题查询遍历链表效率太低因此JDK 8做了一些数据结构上的调整抛弃了原有的Segment分段锁采用了CAS synchronized来保证并发安全性 HashMap工作原理 重要参数 loadFactor 负载因子会影响到HashMap的扩容 负载因子越大HashMap的碰撞越剧烈但是resize越少负载因子越少HashMap碰撞概率越小但是resize越多。 threshold 要扩容的阈值。当容量大小等于这个阈值的时候HashMap就会扩容
重要方法 put 取Key的hash值判断HashMap里面的槽是不是空的如果是空的就需要初始化HashMap里面槽的数量判断这个Kay所对应的槽是否被占用如果没有被占用就将Key-Value生成一个新的节点放到对应的槽中如果这个槽被占用了(调用hash方法返回值一致hash方法里面会调用hashCode方法)分成三步判断 判断Key是否相同(调用equals方法返回一致)如果相同则返回原有的Node如果不相同则进行下面的判断判断节点是否属于树节点TreeNode如果属于则添加到红黑树中如果不是树节点则一定是链表的Node遍历链表如果链表长度大于等于8了则将链表转换成为红黑树 (转红黑树条件有二)否则添加到链表中 链表长度达到8数组长度大于等于64 最后判断HashMap的大小是否大于阈值如果大于则进行扩容 resize 判断当前的table里面的size是否大于0如果大于0的话就会判断当前的table里面的size是否超过了最大值如果超过最大值就不会再扩容如果没有超过的话就会将原有的size扩大到原来的两倍并且判断扩容之后的size是否大于最大值如果超过最大值就按照最大值来扩容。如果当前table里面的size等于0的话并且当前table里面的阈值大于0的时候就会将原有的阈值设置为新的table的size大小如果两个条件都不满足的话则会对新的table设置默认的size(16)和默认的阈值(16 * 0.75)如果这个时候新的table的阈值为空则会重新计算新的阈值 HashMap的容量始终为2的幂次。在扩容的时候元素的位置要么是在原位置要么是否原位置再移动2次幂的位置 get 判断现在HashMap里面的table是否为空以及要获取的key对应的槽是否为空如果为空就直接返回null如果都不为空就判断槽里面的第一个node是不是想要找的key如果是直接返回如果第一个不是就判断node节点是不是树节点如果是就直接去红黑树里面查找如果也不是树节点那就在链表里面循环查找
与JDK7对比 插入链表的时候在JDK7的时候HashMap插入链表是采用头插法而在JDK8使用的是尾插法之所以这么改变的原因是因为头插法的链表在HashMap的resize()过程中可能因为多线程导致的逆序让链表形成死循环。在JDK7的HashMap中HashMap的数据结构是数组单向链表在JDK8的HashMap中采用的是数组单链表红黑树的数据结构。JDK7中可能存在链表过长时间复杂度为O(n)导致查询时间变长的情况。在resiez过程中JDK7和JDK8的差别主要是在迁移时计算新的索引的位置。JDK7是重新计算Key的hash值然后用size-1 hash得到新的索引位置而JDK8时是采用判断高一个bit位的位值如果高一位的位值是0那么索引位置就不变如果是1那么就用原来的HashMap的size大小加上原有的索引位置原索引oldCap这么改变是为了降低rehash带来的开销 细节总结 为什么JDK8的阈值要选择8 链表特点是插入快查询慢。红黑树是插入慢查询快。理想情况下使用随机的哈希码导致冲突的概率符合泊松分布按照泊松分布的计算公式计算出了桶中元素个数和hash冲突概率的对照表可以看到链表中元素个数为8时概率已经非常小再多的就更少了。红黑树本身就有维护成本避免频繁维护红黑树红黑树变为链表。 红黑树的根节点不一定是索引位置的头节点 HashMap通过moveRootToFront方法来维持红黑树的根结点就是索引位置的头结点但是在removeTreeNode方法中当movable为false时不会调用moveRootToFront方法此时红黑树的根节点不一定是索引位置的头节点该场景发生在HashIterator的remove方法中。 转为红黑树节点后链表的结构还存在通过next属性维持红黑树节点在进行操作时都会维护链表的结构 为链表结构时是单向链表。为红黑树结构时成为双向链表。双向链表主要是为了红黑树相关链表操作方便 在HashMap中链表什么时候会转换成红黑树 链表在大于等于8个的时候 (源码验证)数组长度大于等于64
// 链表长度大于等于8 static final int TREEIFY_THREESHOLD 8; if (binCount TREEIFY_THRESHOLD - 1) {treeifyBin(tab, hash) }// 数组长度大于等于64 if (tab null || (n tab.length) MIN_TREEIFY_CAPACITY)resize(); else if ((e tab[index (n - 1) hash]) ! null) {… }HashMap什么时候扩容 当前容量大于等于阈值在树化之前当前数组的长度小于64链表长度大于等于8每次扩容阈值旧阈值1容量旧容量1 HashMap为什么选择红黑树 AVL(自平衡二叉搜索树)插入节点/删除节点整体性能不如红黑树。AVL每个左右节点的高度差不能大于1维持这种结构比较消耗性能。红黑树好在改变节点颜色即可二分查找树左右节点不平衡一开始就固定root极端情况下会成为链表结构 怎么解决哈希冲突 链地址法链接拥有相同hash值的数据使用2次扰动函数(hash函数)降低哈希冲突概率使数据分布更平均引入红黑树降低遍历的时间复杂度 HashMap为什么不直接使用hashCode方法值用作table的下标 hashCode返回值是int数据类型范围是-(2^31) ~ 2^31 - 1而HashMap的容量范围是16 ~ 2^30导致通过hashCode计算出的哈希值可能不在数组大小范围内进而无法匹配存储位置。 HashMap中怎么计算key的hash值
static final int(Object key) {int h;return (key null) ? 0 : (h key.hashCode()) ^ (h 16); }为什么数组长度要保证为2的幂次方 只有当数组长度为2的幂次方时h (length-1)才等价h % length。 为什么String、Integer这样的类适合作为K 这些类能够保证Hash值的不可更改性和计算准确性都是final类型即不可变性保证key的不可更改性不会存在获取hash值不同的情况内部已重写equals、hashCode等方法 (想让自己的类作为Key同理要重写这两个方法)
结构体系 JDK8以后HashMap结构实际上是由数组链表红黑树组成 序列化 java对象序列化是JDK1.1中引入的一组开创性特性之一用于作为一种将Java对象的状态转换为字节数组以便存储或传输的机制以后仍可以将字节数组转换回Java对象原有的状态。 序列化允许重构 序列化允许一定数量的变种泪甚至重构之后也是如此新旧类保持同一个序列化版本hash即private static final serialVersionUID字段。ObjectInputStream仍可以很好的将其读出来。Java Object Serialization规范可以自动管理的关键任务是 将新字段添加到类将字段从static改为非static将字段从transient改为非transient 序列化并不安全 序列化二进制格式完全编写在文档中并且完全可逆。实际上只需将二进制序列化流的内容转储到控制台就足以看清类是什么样子。好在序列化允许hook序列化过程。可以通过在Serializable对象上提供一个writeObject方法来做到这一点。 public class Person implements java.io.Serializable {private int age;// 这两个方法不是override要求方法和参数与下面要求一致private void writeObject(java.io.ObjectOutputStream stream) throws java.io.IOException {age age 2;stream.defaultWriteObject();}private void readObject(java.io.ObjectInputStream stream) throws java.io.IOException, ClassNotFoundException {stream.defaultReadObject();age age 2;} }序列化的数据可以被签名和密封 可以通过writeObject和readObject实现密码加密和签名管理但其实还有更好的方式。如果需要对整个对象进行加密和签名最简单的是将它放在一个javax.crypto.SealedObject和java.security.SignedObject包装器中。两者都是可序列化的所以将对象包装在SealedObject中可以围绕原对象创建一种“包装盒”。必须有对称密钥才能解密而且密钥必须单独管理。同样也可以将SignedObject用于数据验证并且对称密钥也必须单独管理。 序列化允许将代理放在流中 很多情况下类中包含一个核心数据元素通过它可以派生或找到类中的其他字段。在此情况下没有必要序列化整个对象。可以将字段标记为transient但是每当有方法访问一个字段时类仍然必须显式地产生代码来检查它是否被初始化。 如果首要问题是序列化那么最好指定一个flyweight或代理放在流中。为原始person提供一个writeReplace方法可以序列化不同类型的对象来代替它。类似地如果反序列化期间发现一个readResolve方法那么将调用方法将替代对象提供给调用者。 class PersonProxy implements java.io.Serializable {public String data;private Object readResolve() throws java.io.ObjectStreamException {String[] pieces data.split(,);Person result new Person(…);// do somethingreturn result;} }public class Person implements java.io.Serializable {// }注意PersonProxy必须跟踪Person的所有数据。这通常意味着代理需要是Person的一个内部类以便能访问private字段。有时候代理还需要追踪其他对象引用并手动序列化他们。 这种技巧是少数几种不需要读/写平衡的技巧之一。例如一个类被重构成另一种类型后的版本可以提供一个readResolve方法以便静默地将序列化的对象转换成新类型。类似地它可以采用writeReplace方法将旧类序列化成新版本。 信任但要验证 对于序列化的对象这意味着验证字段以确保在反序列化之后它们仍具有正确的值。为此可以实现ObjectInputValidation接口并覆盖validateObject()方法如果调用该方法时发现某处有错误则抛出一个InvalidObjectException。 参考链接 Java 对象序列化 Java特性列表 Java5 自动装箱拆箱泛型枚举变长参数注解foreach循环静态导入新的线程模型和并发库 java.util.concurrent Java6 脚本引擎Java Compiler APIJDBC 4.0规范 Java7 字面常量数字的下划线switch支持String泛型实例化类型自动推断 // java7之前 MapString,ListString map new HashMapString,ListString(); // java7之后 MapString,ListString map new HashMap();try-with-resources单个catch中捕获多个异常类型NIO 2.0 (AIO) ByteBuffer buffer ByteBuffer.allocate(32); buffer.put(new byte[16]); buffer.position();Java8 (LTS) Lambda表达式新Date Time API接口的默认方法和静态方法 public interface Test {default String defaultFunction() {return default;}static String staticFunction() {return static;} }方法引用集合的stream操作Optional处理空指针异常 Java9 REPL工具 jShell模块系统增强Stream Java10 局部变量类型推断 var url new URL(http://www.oracle.com/)Optional新增orElseThrow方法 Java11 (LTS) 字符串、集合、Stream、Optional、InputStream增强java直接编译运行

before

javac Javastack.java; java Javastack# after java Javastack.javaJava12 switch增强 // 之前 switch (day) {case MONDAY:// XXX }// 之后 switch (day) {case MONDAY - monday; }其他增强 Java13 Java14 增强instanceof // 无需自己进行类型转换 public boolean isBadRequestError(Exception ex) {return (ex instanceof HttpClientErrorException rce) HttpStatus.BAD_REQUEST rce.getStatusCode(); }JDK12引入的switch在JDK14变为正式版本。增强switch提供yield在块中返回值但不同于return String quantityString switch (n) {case 1 : yield one;case 2 - two; }长文本字符串 String name whats your name;##Java15 关键字sealed、permits、non-sealed限定实现类限定父类的使用。 Java16 ##Java17 (LTS) m/) Optional新增orElseThrow方法## Java11 (LTS) 字符串、集合、Stream、Optional、InputStream增强java直接编译运行shell

before

javac Javastack.java; java Javastack# after java Javastack.javaJava12 switch增强 // 之前 switch (day) {case MONDAY:// XXX }// 之后 switch (day) {case MONDAY - monday; }其他增强 Java13 Java14 增强instanceof // 无需自己进行类型转换 public boolean isBadRequestError(Exception ex) {return (ex instanceof HttpClientErrorException rce) HttpStatus.BAD_REQUEST rce.getStatusCode(); }JDK12引入的switch在JDK14变为正式版本。增强switch提供yield在块中返回值但不同于return String quantityString switch (n) {case 1 : yield one;case 2 - two; }长文本字符串 String name whats your name;##Java15 关键字sealed、permits、non-sealed限定实现类限定父类的使用。 Java16 ##Java17 (LTS)