仿牌网站,太原论坛网站开发公司,北京网站建设报价表,怎么制作网站小游戏Java常见面试题及解答1 面向对象的三个特征2 this#xff0c;super关键字3 基础数据类型4 public、protected、default、private5 接口6 抽象类6.1 抽象类和接口的区别7 重载#xff08;overload#xff09;、重写#xff08;override#xff09;8 final、finalize、final…
Java常见面试题及解答1 面向对象的三个特征2 thissuper关键字3 基础数据类型4 public、protected、default、private5 接口6 抽象类6.1 抽象类和接口的区别7 重载overload、重写override8 final、finalize、finally9 final 用法10 深拷贝和浅拷贝的区别11 static用法12 int和Integer的区别13 String、StringBuffer、StringBuilder13.1 String和StringBuffer13.2 StringBuffer和StringBuilder14 垃圾回收机制14.1 垃圾回收机制的意义14.2 触发垃圾回收的条件14.3 主动请求JVM运行垃圾回收的方式14.4 垃圾回收的两个阶段标记阶段、回收阶段14.5 标记阶段14.5.1 引用计数法Reference Counting Collector14.5.2 根搜索算法可达性检测算法14.6 回收阶段14.6.1 标记-清除Mark-Sweep算法14.6.2 复制Copying算法14.6.3 标记-压缩Mark-Compact算法14.6.4 标记-清除算法、复制算法、标记-压缩算法的比较※14.6.5 分代收集Generational Collection算法14.6.5.1 区域划分14.6.5.2 分代收集算法GC 类型14.6.5.3 分代收集算法的垃圾收集器GC14.6.5.4 对象的四种引用状态15 类加载机制15.1 类加载的生命周期15.2 类加载的时机15.3 类加载过程具体的生命周期15.4 类加载器的类型15.4.1 系统提供的3种类加载器15.4.2 双亲委派模型15.4.3 自定义类加载器16 异常处理16.1 异常的介绍16.2 异常处理的机制16.3 异常处理机制中的 finally16.4 抛出异常的方法throw 和 throws16.5 自定义异常17 泛型17.1 泛型本质17.2 为什么使用泛型17.3 如何使用泛型17.4 泛型通配符17.5 泛型中KTVE的含义17.6 泛型的实现原理18 反射18.1 反射的用法19 网络IO19.1 网络IO的分类19.2 同步和异步、阻塞和非阻塞的概念19.2.1 同步和异步19.2.2 阻塞和非阻塞19.3 同步阻塞BIO19.3.1 面试题介绍一下Java中的IO流19.4 同步非阻塞NIO19.4.1 NIO和BIO的区别19.5 异步非阻塞AIO19.6 BIO、NIO、AIO适用场景20 多线程21 集合框架~~22 网络~~~~23 高并发~~~~24 高负载~~~~25 高可用性~~26 JVM原理27 注解28 线程池29 锁30 设计模式31 栈和堆能否创建一个包含可变对象的不可变对象创建对象的几种方式Object中有哪些公共方法java当中的四种引用为什么要有不同的引用类型和eqauls()的区别equals()和hashCode()的联系a.hashCode()有什么用?与a.equals(b)有什么关系有没有可能两个不相等的对象有相同的hashcodeab 与a.equals(b)有什么区别aab与ab有什么区别short s1 1; s1 s1 1; 该段代码是否有错,有的话怎么改short s1 1; s1 1; 该段代码是否有错有的话怎么改 和 的区别内部类的作用如何将byte转为String可以将int强转为byte类型么?会产生什么问题?进程、线程之间的区别循环依赖1 面向对象的三个特征
参考1java之封装继承多态 推荐看参考1。
封装是把对象的属性和操作或服务结合为一个独立的整体并尽可能隐藏对象的内部实现细节。通过提供的方法来给外部调用。比如类的属性使用private修饰符表示最小的访问权限。对成员变量的访问统一提供setXXXgetXXX方法。
封装的特点
将类的某些信息隐藏在类的内部不允许外部程序进行直接的访问调用。通过该类提供的方法来实现对隐藏信息的操作和访问。隐藏对象的信息。留出访问的对外接口。
继承 是子类继承父类的特征和行为使得子类对象实例具有父类的属性和方法或子类从父类继承方法使得子类具有父类相同的行为。但是如果在父类中拥有私有属性(private修饰)则子类是不能被继承的。
继承的特点
只支持单继承即一个子类只允许有一个父类但是可以实现多级继承即子类拥有唯一的父类而父类还可以再继承父类。子类可以拥有父类的属性和方法。子类可以拥有自己的属性和方法。子类可以重写覆盖父类的方法。
多态是同一个行为具有多个不同表现形式或形态的能力。
多态的特点
消除类型之间的耦合关系实现低耦合。灵活性。可扩充性。可替换性。
多态的体现形式
继承父类引用指向子类。重写
2 thissuper关键字
参考1java之封装继承多态
this关键字用法 1. 本类成员方法中访问本类的成员变量。 2. 本类成员方法中访问本类的另一个成员方法。 3. 本类的构造方法中访问本类的另一个构造方法。
super()关键字的用法 1. 子类的成员方法中访问父类的成员变量。 2. 子类的成员方法中访问父类的成员方法。 3. 子类的构造方法中访问父类的构造方法。
注意 this关键字同super一样必须在构造方法的第一个语句且是唯一的。 this与super不能同时存在。
3 基础数据类型
参考1java基本数据类型
java语言中有8种基本数据类型分类四大类型
逻辑类型boolean整数类型byte、short、int、long浮点类型 float、double字符类型char
记忆规律byte、short、char、int、long、float、double占用内存分别是1、2、2、4、8、4、8
4 public、protected、default、private
参考1Java中public、private、default和protected详解
publicpublic修饰的变量当前类、当前包、子类和其他类均可访问。protectedprotected修饰的变量可在当前类访问也可在当前包和子类继承父类的子类中访问。default默认情况下的变量也就是没有修饰的变量既能在当前类内访问又能在当前包package访问。privateprivate修饰词修饰的变量该变量仅能在当前类内访问其他地方如当前包、子类、其他类均无法访问。
5 接口
参考1Java 接口 Java的接口是一个抽象类型是抽象方法的集合用interface声明。一个类通过继承接口的方式从而来继承接口的抽象方法。
接口并不是类编写接口的方式和类很相似但是它们属于不同的概念。类描述对象的属性和方法。接口则包含类要实现的方法。
接口无法被实例化但是可以被实现。一个实现接口的类必须实现接口内所描述的所有方法否则就必须声明为抽象类。
接口的特点
接口不能用于实例化对象。接口没有构造方法。接口中所有的方法必须是抽象方法Java 8 之后 接口中可以使用 default 关键字修饰的非抽象方法。接口不能包含成员变量除了 static 和 final 变量。接口不是被类继承了而是要被类实现。接口支持多继承。
6 抽象类
参考1Java 抽象类
抽象类除了不能实例化对象之外类的其它功能依然存在成员变量、成员方法和构造方法的访问方式和普通类一样。
抽象类的特点
抽象类不能被实例化如果被实例化就会报错编译无法通过。只有抽象类的非抽象子类可以创建对象。由于抽象类不能实例化对象所以抽象类必须被继承才能被使用。抽象类中不一定包含抽象方法但是有抽象方法的类必定是抽象类。抽象类中的抽象方法只是声明不包含方法体就是不给出方法的具体实现也就是方法的具体功能。构造方法类方法用 static 修饰的方法不能声明为抽象方法。抽象类的子类必须给出抽象类中的抽象方法的具体实现除非该子类也是抽象类。
6.1 抽象类和接口的区别
参考1Java 接口
抽象类中的方法可以有方法体就是能实现方法的具体功能但是接口中的方法不行接口中的都是抽象方法。抽象类中的成员变量可以是各种类型的而接口中的成员变量只能是 public static final 类型的。接口中不能含有静态代码块以及静态方法(用 static 修饰的方法)而抽象类是可以有静态代码块和静态方法。一个类只能继承一个抽象类而一个类却可以实现多个接口。
7 重载overload、重写override
参考1java之封装继承多态
重载overload 是在一个类里面方法名字相同而参数不同。返回类型可以相同也可以不同。每个重载的方法或者构造函数都必须有一个独一无二的参数类型列表。最常用的地方就是构造器的重载。
重载规则
被重载的方法必须改变参数列表参数个数或者类型不一样。被重载的方法可以改变返回类型。被重载的方法可以改变访问修饰符。
重写override 是子类对父类允许访问的方法的实现过程进行重新实现 返回值和形参都不能改变。
重写规则
参数列表必须与被重写方法相同。访问权限不能比父类中被重写的方法的访问权限更低publicprotected(default)private。父类成员的方法只能被它的子类重写。被final修饰的方法不能被重写。构造方法不能重写。
8 final、finalize、finally
final 是一个修饰符可以修饰变量、方法和类。如果 final修饰变量意味着该变量的值在初始化后不能被改变。
finalize 是方法它是在对象被回收之前调用的方法给对象自己最后一个复活的机会但是什么时候调用 finalize 没有保证。
finally 是一个关键字与 try 和 catch 一起用于异常的处理。finally 块一定会被执行无论在 try 块中是否有发生异常。
9 final 用法
final修饰的类不可以被继承。final修饰的方法不可以被重写。final修饰的变量不可以被改变。如果修饰引用那么表示引用不可变引用指向的内容可变。final修饰的方法JVM会尝试将其内联以提高运行效率。final修饰的常量在编译阶段会存入常量池中。
10 深拷贝和浅拷贝的区别
浅拷贝被复制对象的所有变量都含有与原来的对象相同的值而所有的对其他对象的引用仍然指向原来的对象。换言之浅拷贝仅仅复制所考虑的对象而不复制它所引用的对象。
深拷贝被复制对象的所有变量都含有与原来的对象相同的值而那些引用其他对象的变量将指向被复制过的新对象而不再是原有的那些被引用的对象。换言之深拷贝把要复制的对象所引用的对象都复制了一遍。
11 static用法
static关键字这两个基本的用法静态变量和静态方法。也就是被static所修饰的变量/方法都属于类的静态资源类实例所共享。
除了静态变量和静态方法之外static也用于静态块多用于初始化操作。
此外static也多用于修饰内部类此时称之为静态内部类。
注意
static修饰的静态方法不能直接调用非静态的方法需要先创建类的实例再调用。非静态的方法可以直接调用static修饰的静态方法。
12 int和Integer的区别
Integer是int的包装类型在拆箱和装箱中二者自动转换。
int是基本类型直接存数值而Integer是对象用一个引用指向这个对象。
13 String、StringBuffer、StringBuilder
String是字符串常量不可变原因是有final修饰 StringBuffer字符串变量线程安全StringBuffer类里很多方法都有 synchronized同步锁实现线程安全 StringBuilder字符串变量线程不安全。
13.1 String和StringBuffer
String和StringBuffer主要区别是性能String是不可变对象每次对String类型进行操作都等同于产生了一个新的String对象然后指向新的String对象。所以在使用时尽量不要对String进行大量的拼接操作否则会产生很多临时对象导致GC开始工作影响系统性能。
StringBuffer是对对象本身操作而不是产生新的对象因此在有大量拼接的情况下建议使用StringBuffer。
13.2 StringBuffer和StringBuilder
StringBuffer和StringBuilder 都是 extends AbstractStringBuilder implements java.io.Serializable, CharSequence。
StringBuffer是线程安全的可变字符串其内部实现是可变数组。
StringBuilder是jdk 1.5新增的其功能和StringBuffer类似但是非线程安全。因此在没有多线程问题的前提下使用StringBuilder会取得更好的性能。
14 垃圾回收机制
参考1Java 垃圾回收机制整理 参考2Java超详细分析垃圾回收机制 参考3深入理解 Java 垃圾回收机制
14.1 垃圾回收机制的意义
垃圾回收可以有效的防止内存泄露有效的使用空闲的内存
内存泄露指该内存空间使用完毕后未回收在不涉及复杂数据结构的一般情况下java的内存泄露表现为一个内存对象的生命周期超出了程序需要它的时间长度我们有是也将其称为 “对象游离”
14.2 触发垃圾回收的条件
参考1JAVA垃圾回收机制 当没有线程在运行时垃圾回收会被调用。因为垃圾回收在优先级最低的线程中进行当应用忙时垃圾回收不被调用不是由程序员自己调用的但除堆内存不足外。 堆内存不足时会触发垃圾回收机制。
14.3 主动请求JVM运行垃圾回收的方式
参考1面试必问Java 垃圾回收机制
使用System.gc() 方法系统类包含静态方法gc() 用于请求 JVM 运行垃圾收集器。使用Runtime.getRuntime().gc() 方法运行时类允许应用程序与运行应用程序的 JVM 交互。因此通过使用其 gc() 方法我们可以请求 JVM 运行垃圾收集器。
注意以上两种方法中的任何一种都不能保证一定会运行垃圾收集器也就是系统是否进行垃圾回收依旧不确定因为这不是程序员控制的而是系统决定的。
14.4 垃圾回收的两个阶段标记阶段、回收阶段
14.5 标记阶段
标记阶段有1、引用计数法Reference Counting Collector2、根搜索算法可达性检测算法。
14.5.1 引用计数法Reference Counting Collector
引用计数是垃圾收集器中的早期策略。 这个方法中堆中的每个对象都会添加一个引用计数器。每当一个地方引用这个对象时计数器值 1当引用失效时计数器值 -1。任何时刻计数值为 0 的对象就是不可能再被使用的。
优点引用计数收集器可以很快的执行交织在程序运行中。对程序需要不被长时间打断的实时环境比较有利。
缺点无法解决对象之间相互引用的情况。比如对象有一个对子对象的引用子对象反过来引用父对象它们的引用计数永远不可能为 0。
14.5.2 根搜索算法可达性检测算法
相对于引用计数算法而言可达性分析算法不仅同样具备实现简单和执行高效等特点更重要的是该算法可以有效地解决在引用计数算法中循环引用的问题防止内存泄漏的发生。所以现在一般使用根搜索算法。 根搜索算法是从离散数学中的图论引入的程序把所有的引用关系看作一张图从一个节点 GC ROOT 开始寻找对应的引用节点找到这个节点以后继续寻找这个节点的引用节点当所有的引用节点寻找完毕之后剩余的节点则被认为是没有被引用到的节点即无用的节点。 如上图中的 ObjF、ObjD、ObjE通过 GC Root 是无法找到的所以它们是无用节点。
java中可作为GC Root的对象
虚拟机栈中引用的对象本地变量表。方法区中静态属性引用的对象。方法区中常量引用的对象。本地方法栈中引用的对象Native对象。
14.6 回收阶段
14.6.1 标记-清除Mark-Sweep算法
标记-清除算法分为两个阶段
标记阶段标记出需要被回收的对象。清除阶段回收被标记的可回收对象的内部空间。 标记-清除算法实现较容易不需要移动对象但是存在较严重的问题
算法过程需要暂停整个应用即GC时存在STW效率不高。标记清除后会产生大量不连续的内存碎片碎片太多可能会导致后续过程中需要为大对象分配空间时无法找到足够的空间而提前触发新的一次垃圾收集动作此时就需要一个空列表来记录这些地址。
14.6.2 复制Copying算法
为了解决标记 - 清除算法在效率方面的缺陷复制算法采用将内存按容量划分的方式划分成大小相等的两块每次只使用其中的一块。算法思想如下 1或2是一个意思 将正在使用的存活对象全部复制到另一块未被使用空间摆放整齐然后清空此空间所有对象。 当这一块内存用完了就将还存活着的对象复制到另外一块上面然后再把已经使用过的内存空间一次性清理掉。 优点实现简单不易产生内存碎片每次只需要对半个区进行内存回收。 缺点1内存空间缩减为原来的一半算法的效率和存活对象的数目有关存活对象越多效率越低。
缺点2 : 需要两倍的内存空间 , 开销较大 , 另外GC如果采用 G1 垃圾回收器的话 , 它将空间拆成了很多份, 如果采用复制算法, 还需要维护各区之间的关系。
14.6.3 标记-压缩Mark-Compact算法
为了更充分利用内存空间提出了标记-压缩算法。此算法结合了“标记-清除”和“复制”两个算法的优点。也是分为两个阶段
该算法标记阶段和“标志-清除”算法一样从根节点开始标记所有被引用对象。在完成标记之后它不是直接清理可回收对象而是将存活对象压缩到内存的一端按顺序排放然后清理掉端边界以外的内存空间。 标记-压缩算法的最终效果等同于标记-清除算法执行完成后再进行一次内存碎片整理因此也可以把它称为标记-清除-压缩Mark-Sweep-Compact算法 , 标记- 压缩是移动式的 , 将对象在内存中依次排列比维护一个空列表少了不少开销(如果对象排列整齐,当我们需要给新对象分配内存时JVM 只需要持有一个内存的起始地址即可)。
优点 : 相对于标记 -清除算法避免了内存碎片化相对于复制算法避免开辟额外的空间。
缺点 : 从效率上来说是不如复制算法的移动时如果存在对象相互引用, 则需要调整引用的位置, 另外移动过程中也会有STW。
14.6.4 标记-清除算法、复制算法、标记-压缩算法的比较
复制算法是效率最高的 , 但是花费空间最大。
标记 - 压缩算法虽然较为兼顾 但效率也变低比标记- 清除多了个整理内存的过程比复制算法多了标记的过程。
※14.6.5 分代收集Generational Collection算法
分代收集算法是目前大部分 JVM 的垃圾收集器采用的算法。
分代的垃圾回收策略是基于不同的对象的生命周期是不一样的。因此不同生命周期的对象可以采取不同的回收算法以便提高回收效率。
核心思想是根据对象存活的生命周期将内存划分为若干个不同的区域。一般情况下将堆区划分为老年代Tenured Generation和新生代Young Generation老年代的特点是每次垃圾收集时只有少量对象需要被回收而新生代的特点是每次垃圾回收时都有大量的对象需要被回收那么就可以根据不同代的特点采取最适合的收集算法。
14.6.5.1 区域划分
年轻代Young Generation
所有新生成的对象首先都是放在年轻代的。年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象。新生代内存按照811的比例分为一个 eden 区和两个 survivor(survivor0survivor1) 区。一个 Eden 区两个 Survivor 区(一般而言)。大部分对象在 Eden 区中生成。回收时先将 eden 区存活对象复制到一个 survivor0 区然后清空 eden 区当这个 survivor0 区也存放满了时则将 eden 区和 survivor0 区存活对象复制到另一个 survivor1 区然后清空 eden 和这个 survivor0 区此时 survivor0 区是空的然后将 survivor0 区和 survivor1 区交换即保持 survivor1 区为空 如此往复。当 survivor1区不足以存放 eden 和 survivor0 的存活对象时就将存活对象直接存放到老年代。若是老年代也满了就会触发一次 Full GC也就是新生代、老年代都进行回收。新生代发生的 GC 也叫做 Minor GC Minor GC 发生频率比较高(不一定等 Eden 区满了才触发)。
年老代Old Generation
在年轻代中经历了 N 次垃圾回收后仍然存活的对象就会被放到年老代中。因此可以认为年老代中存放的都是一些生命周期较长的对象。内存比新生代也大很多(大概比例是12)当老年代内存满时触发 Major GC 即 Full GCFull GC 发生频率比较低老年代对象存活时间比较长存活率标记高。
持久代Permanent Generation 用于存放静态文件如 Java 类、方法等。持久代对垃圾回收没有显著影响但是有些应用可能动态生成或者调用一些 class 例如 Hibernate 等在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。
14.6.5.2 分代收集算法GC 类型
Minor GC(新生代 GC)新生代 GC指发生在新生代的垃圾收集动作因为 Java 对象大多都具备朝生熄灭的特点所以 Minor GC 十分频繁回收速度也较快。Major GC(老年代 GC)老年代 GC指发生在老年代的垃圾收集动作当出现 Major GC 时一般也会伴有至少一次的 Minor GC并非绝对例如 Parallel Scavenge 收集器会单独直接触发 Major GC 的机制。 Major GC 的速度一般会比 Minor GC 慢十倍以上。Full GC清理整个堆空间—包括年轻代和老年代。Major GC Full GC。 参考聊聊JVM四深入理解Major GC, Full GC, CMS
产生 Full GC 可能的原因
年老代被写满。持久代被写满。System.gc() 被显式调用。上一次 GC 之后 Heap堆 的各域分配策略动态变化。
14.6.5.3 分代收集算法的垃圾收集器GC
不同虚拟机所提供的垃圾收集器可能会有很大差别下面的例子是 HotSpot。
新生代收集器使用的收集器Serial、PraNew、Parallel Scavenge。老年代收集器使用的收集器Serial Old、Parallel Old、CMS。
Serial 收集器复制算法)新生代单线程收集器标记和清理都是单线程优点是简单高效。Serial Old收集器(标记-整理算法)老年代单线程收集器Serial 收集器的老年代版本。ParNew 收集器(停止-复制算法) 新生代收集器可以认为是 Serial 收集器的多线程版本在多核 CPU 环境下有着比 Serial 更好的表现。Parallel Scavenge 收集器(停止-复制算法)并行收集器追求高吞吐量高效利用 CPU。吞吐量一般为 99% 吞吐量 用户线程时间 / (用户线程时间 GC线程时间)。适合后台应用等对交互相应要求不高的场景。Parallel Old 收集器(停止-复制算法)Parallel Scavenge 收集器的老年代版本并行收集器吞吐量优先。CMS(Concurrent Mark Sweep) 收集器标记-清理算法高并发、低停顿追求最短 GC 回收停顿时间cpu 占用比较高响应时间快停顿时间短多核 cpu 追求高响应时间的选择。
总结 根据对象的生命周期的不同将内存划分为几块然后根据各块的特点采用最适当的收集算法。大批对象死去、少量对象存活的新生代使用复制算法复制成本低对象存活率高、没有额外空间进行分配担保的老年代采用标记-清理算法或者标记-整理算法。
14.6.5.4 对象的四种引用状态
在实际开发中我们对 new 出来的对象也会根据重要程度有个等级划分。有些必须用到的对象我们希望它在其被引用的周期内能一直存在有些对象可能没那么重要当内存空间还足够时可以保留在内存中如果内存空间在进行垃圾收集后还是非常紧张则可以抛弃这些对象。
由此Java 对引用划分为四种强引用、软引用、弱引用、虚引用四种引用强度依次减弱。
强引用代码中普遍存在的类似Object obj new Object()这类的引用只要强引用还存在垃圾收集器永远不会回收掉被引用的对象。软引用描述有些还有用但并非必需的对象。在系统将要发生内存溢出异常之前将会把这些对象列进回收范围进行二次回收。如果这次回收还没有足够的内存才会抛出内存溢出异常。Java 中的类 SoftReference 表示软引用。弱引用描述非必需对象。被弱引用关联的对象只能生存到下一次垃圾回收之前垃圾收集器工作之后无论当前内存是否足够都会回收掉只被弱引用关联的对象。Java 中的类 WeakReference 表示弱引用。虚引用这个引用存在的唯一目的就是在这个对象被收集器回收时收到一个系统通知被虚引用关联的对象和其生存时间完全没关系。Java 中的类 PhantomReference 表示虚引用。
15 类加载机制
参考1java类加载机制你会了吗
类加载机制定义 java虚拟机将编译后的class文件加载到内存中进行校验、转换、解析和初始化到最终的使用。这就是java类加载机制
15.1 类加载的生命周期
加载Loading、验证Verification、准备Preparation、解析Resolution、初始化Initialization、使用Using、卸载Unloading等阶段其中验证、准备、解析3阶段也可以称为连接Lingking)如下图类的生命周期 15.2 类加载的时机
在类加载的生命周期中加载、验证、准备、初始化和卸载这5个阶段的顺序是确定的其加载过程一定是按照这个顺序执行的。而解析阶段有点特殊在某些特定的情况下它是在初始化之后开始的。
Java虚拟机规范中并没有进行强制约束类加载的第一个阶段的时机而是交给虚拟机的具体实现来自由把握。但是对于初始化阶段虚拟机规范则是严格规定了有且只有5种情况类没有初始化必须立即对类进行“初始化”而加载、验证、准备自然需要在此之前开始
遇到new、getstatic、putstatic或invokestaic这四条字节码指令时。使用java.lang.reflect包的方法对类进行反射调用的时候。当初始化一个类的时候如果发现父类还没有初始化则需要先触发其父类的初始化。当虚拟机启动时用户需要指定一个要执行的主类虚拟机会先初始化这个主类。当使用JDK1.7的动态语言支持的时候如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄并且这个句柄所对应的类没有进行过初始化则需要先触发器初始化。
15.3 类加载过程具体的生命周期
1 加载在加载阶段虚拟机主要执行以下三个操作。
通过类的全限定名来获取定义这个类的二进制字节流。将这个字节流所代表的静态存储结构转化成方法区的运行时数据结构。在内存中生成一个代表这个类的Class对象作为方法区这个类的各种数据的访问入口。
这个阶段相比其他阶段来说是开发人员可控性最强的阶段。因为这个阶段既能使用系统提供的加载器(这个加载器后面会进行介绍)加载又能通过开发人员自定义的加载器进行加载。
在加载这个阶段还有一个需要注意的地方在执行第一个操作时需要知道可以从哪里获取class文件例如
从压缩文件中读取JARWAR等从本地磁盘中获取从网络上获取Applet运行过程中动态生成动态代理其他文件生成jsp生成对应的class文件)从数据库中读取
2 验证验证阶段主要有4个阶段的验证文件格式验证、元数据验证、字节码验证和符号验证。
文件格式验证这一阶段要验证字节流是否符合Class文件格式的规范并且能被当前版本的虚拟机处理主要包括魔数、版本号、常量池等验证。元数据验证这个阶段是对字节码描述的信息进行语义分析以保证其描述的信息符合java语言规范的要求。主要包括是否有父类类中的字段、方法是否与父类冲突如果不是抽象类是否实现了其父类或接口中要求实现的所有方法等字节码验证这个阶段是在元数据验证之后对类的方法体进行校验分析保证被校验类的方法在运行时不会做出危害虚拟机的安全事件主要目的是通过数据流和控制流分析确定程序语义是合法的、符合逻辑的。也是验证过程最复杂的一个阶段。符号引用验证这个阶段的校验发生在虚拟机将符号引用转化为直接引用的时候。是对类自身以外的信息进行匹配性校验。主要目的是确保解析动作能正常执行。
3 准备准备阶段是为类变量分配内存并设置类变量初始值的阶段分配这些内存是在方法区里面进行的这个阶段有两点需要重点注意的
只有类变量被static修饰的变量会分配内存不包括实例变量实例变量是在对象实例化的时候在堆中分配内存的。设置类变量的初始值是数量类型对应的默认值而不是代码中设置的默认值。例如public static int number111这类变量number在准备阶段之后的初始值是0而不是111。而给number赋值为111是在初始化阶段。
4 解析解析阶段是虚拟机将常量池里内的符号引用转换为直接引用。注意2个概念
符号引用以一组符号来描述所有引用的目标符号可以是任何形式的字面量只要使用时能正确定义到目标即可。直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。
解析操作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符等7类符号引用进行的。
5 初始化这个阶段是类加载过程的最后一步是代码真正开始执行的时候在这个阶段开发人员可以根据自己的需求去给类变量初始化赋值。简单来说就是执行类构造器()方法的过程。
15.4 类加载器的类型
接下来看看是什么是类加载器
虚拟机设计团队将加载动作放到了Java虚拟机外部去实现以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块称之为“类加载器”。
15.4.1 系统提供的3种类加载器 启动类加载器Bootstrap ClassLoader负责将存放在JAVA_HOME\lib目录中或者被-Xbootclasspath参数所指定的路径中的并且是虚拟机识别的类库加载到虚拟机内存中。注仅按照文件名识别如rt.jar名字不符合的类库即使放在lib目录中也不会被加载 扩展类加载器Extension ClassLoader负责加载JAVA_HOME\lib\ext目录中的或被java.ext.dirs系统变量所指定的路径中的所有类库开发者可以直接使用扩展类加载器。 应用程序类加载器Application ClassLoader负责加载用户路径**ClassPath)**上所指定的类库开发者可以直接使用这个类加载器一般情况下该类加载是程序中默认的类加载器。
这三种加载器的加载顺序如下系统提供的类加载器的执行顺序
15.4.2 双亲委派模型 如上图展示的类加载器之间的这种层次关系就是双亲委派模型。双亲委派模型要求除了顶层的启动类加载器外其他的类加载器都应有自己的父类加载器。
双亲委派介绍 如果一个类加载器收到类加载的请求他首先不会自己去尝试加载这个类而是把请求委派给父类加载器去完成每一层次的类加载器都是这样因此所有的加载请求最终都应该传送到底层的启动类加载器中只有当父类加载器反馈自己无法完成这个加载请求时在它的加载路径下没有找到所需加载的Class子类加载器才会尝试去加载。
双亲委派原则的好处
避免重复加载同一个类防止用户任意修改java中的类
双亲委派原则的加载过程
15.4.3 自定义类加载器
上面讲述的是系统提供的类加载器以及它们之间的关系还有很多情况需要我们自定义类加载器。那该如何定义呢有以下两种方式
如果我们自定义的加载器不想破坏双亲委派继承 java.lang.ClassLoader 类并重写 findClass 方法。如果使用我们自定义的加载器破坏双亲委派继承 java.lang.ClassLoader 类并重写loadClass(java.lang.String) 方法。
16 异常处理
参考1Java的异常处理机制 参考2Java详解Java中的异常(Error与Exception)
16.1 异常的介绍
java中所有错误的超类为Throwable。其下有两个子类Error 和 Exception。Error的子类描述的都是系统错误比如虚拟机内存溢出。Exception的子类描述的都是程序比如空指针下标越界等。通常我们程序中处理的异常都是Exception。
Error是程序无法处理的错误表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关而表示代码运行时 JVMJava 虚拟机出现的问题。例如Java虚拟机运行错误Virtual MachineError当 JVM 不再有继续执行操作所需的内存资源时将出现 OutOfMemoryError。这些异常发生时Java虚拟机JVM一般会选择线程终止。这些错误表示故障发生于虚拟机自身、或者发生在虚拟机试图执行应用时如Java虚拟机运行错误Virtual MachineError、类定义错误NoClassDefFoundError等。这些错误是不可查的因为它们在应用程序的控制和处理能力之 外而且绝大多数是程序运行时不允许出现的状况。对于设计合理的应用程序来说即使确实发生了错误本质上也不应该试图去处理它所引起的异常状况。在 Java中错误通过Error的子类描述。
Exception异常是程序本身可以处理的异常。Exception异常分两大类运行时异常和非运行时异常(编译异常)。程序中应当尽可能去处理这些异常。
运行时异常都是RuntimeException类及其子类异常如NullPointerException(空指针异常)、ArrayIndexOutOfBoundException(数组下标越界异常)、ArithmeticException算术异常、NullPointerException空指针异常、NumberFormatException数字格式异常等这些异常是不检查异常程序中可以选择捕获处理也可以不处理。这些异常一般是由程序逻辑错误引起的程序应该尽可能避免这类异常的发生。运行时异常的特点是Java编译器不会检查它也就是说当程序中可能出现这类异常即使没有用try-catch语句捕获它也没有用throws子句声明抛出它也会编译通过。
非运行时异常 编译异常是RuntimeException以外的异常类型上都属于Exception类及其子类。这类是必须进行处理的异常如果不处理程序就不能编译通过。如IOException、SQLException等以及用户自定义的Exception异常一般情况下不自定义检查异常。
Java的异常Throwable分为可查的异常checked exceptions和不可查的异常unchecked exceptions。
可查异常编译器要求必须处置的异常正确的程序在运行中很容易出现的、情理可容的异常状况。除了Exception中的RuntimeException及RuntimeException的子类以外其他的Exception类及其子类(例如IOException和ClassNotFoundException)都属于可查异常。这种异常的特点是Java编译器会检查它当程序中可能出现这类异常要么用try-catch语句捕获它要么用throws子句声明抛出它否则编译不会通过。
不可查异常(编译器不要求强制处置的异常)包括运行时异常RuntimeException与其子类和错误Error。RuntimeException表示编译器不会检查程序是否对RuntimeException作了处理在程序中不必捕获RuntimException类型的异常也不必在方法体声明抛出RuntimeException类。RuntimeException发生的时候表示程序中出现了编程错误所以应该找出错误修改程序而不是去捕获RuntimeException。
RuntimeException 类属于非检测异常因为普通JVM操作引起的运行时异常随时可能发生此类异常一般是由特定操作引发。但这些操作在java应用程序中会频繁出现。因此它们不受编译器检查与处理或声明规则的限制。
16.2 异常处理的机制
在Java应用程序中异常处理机制为异常抛出异常捕获。 异常抛出任何Java代码都可以抛出异常如自己编写的代码、来自Java开发环境包中代码或者Java运行时系统。无论是谁都可以通过Java的throw语句抛出异常。从方法中抛出的任何异常都必须使用throws子句。
异常捕获捕捉异常通过try-catch语句或者try-catch-finally语句实现。
16.3 异常处理机制中的 finally
finally块定义在异常处理机制中的最后一块。它可以直接跟在try之后或者最后一个catch之后。finally可以保证只要程序执行到了try语句块中无论try语句块中的代码是否出现异常最终finally都必定执行。finally通常用来做释放资源这类操作。
16.4 抛出异常的方法throw 和 throws
参考1Java中throw和throws有什么区别
throw作用在方法体内使用throws 在方法声明上使用throw后面接的是异常对象只能接一个。throws 后面接的是异常类型可以接多个多个异常类型用逗号隔开throw 是在方法中出现不正确情况时手动来抛出异常结束方法的执行了 throw 语句一定会出现异常。而 throws 是用来声明当前方法有可能会出现某种异常的如果出现了相应的异常将由调用者来处理声明了异常不一定会出现异常。
16.5 自定义异常
当Java内置的异常都不能明确的说明异常情况的时候需要创建自己的异常。
定义自定义异常需要注意以下问题
异常的类名要做到见名知义。需要是Exception的子类。提供超类异常提供的所有种类构造器。
17 泛型
参考1Java泛型详解史上最全图文详解
17.1 泛型本质
Java 泛型generics是 JDK5 中引入的一个新特性泛型提供了编译时类型安全检测机制该机制允许程序员在编译时检测到非法的类型。
泛型的本质是参数化类型即给类型指定一个参数然后在使用时再指定此参数具体的值那样这个类型就可以在使用时决定了。这种参数类型可以用在类、接口和方法中分别被称为泛型类、泛型接口、泛型方法。
17.2 为什么使用泛型
泛型的好处是在编译的时候检查类型安全并且所有的强制转换都是自动和隐式的提高代码的重用率。
泛型的好处
保证了类型的安全性在没有泛型之前从集合中读取到的每一个对象都必须进行类型转换如果不小心插入了错误的类型对象在运行时的转换处理就会出错。消除强制转换泛型的一个附带好处是消除源代码中的许多强制类型转换这使得代码更加可读并且减少了出错机会。避免了不必要的装箱、拆箱操作提高程序的性能在非泛型编程中将筒单类型作为Object传递时会引起Boxing装箱和Unboxing拆箱操作这两个过程都是具有很大开销的。引入泛型后就不必进行Boxing和Unboxing操作了所以运行效率相对较高特别在对集合操作非常频繁的系统中这个特点带来的性能提升更加明显。提高了代码的重用性。
17.3 如何使用泛型
泛型有三种使用方式分别为泛型类、泛型接口和泛型方法。
泛型类把泛型定义在类上。泛型接口把泛型定义在接口上。泛型方法把泛型定义在方法上。
17.4 泛型通配符
Java泛型的通配符是用于解决泛型之间引用传递问题的特殊语法, 主要有以下三类 无边界的通配符(Unbounded Wildcards)就是?比如List?。 无边界的通配符的主要作用就是让泛型能够接受未知类型的数据。 固定上边界的通配符(Upper Bounded Wildcards)采用? extends E的形式 1使用固定上边界的通配符的泛型就能够接受指定类及其子类类型的数据。 2要声明使用该类通配符采用? extends E的形式这里的E就是该泛型的上边界。 注意这里虽然用的是extends关键字却不仅限于继承了父类E的子类也可以代指显现了接口E的类。 固定下边界的通配符(Lower Bounded Wildcards)采用? super E的形式 1使用固定下边界的通配符的泛型, 就能够接受指定类及其父类类型的数据.。 2要声明使用该类通配符采用? super E的形式这里的E就是该泛型的下边界。
注意: 你可以为一个泛型指定上边界或下边界, 但是不能同时指定上下边界。
17.5 泛型中KTVE的含义
上面这些泛型类定义中的泛型参数E、K和V都是什么意思呢其实这些参数名称是可以任意指定就想方法的参数名一样可以任意指定但是我们通常会起一个有意义的名称让别人一看就知道是什么意思。泛型参数也一样E一般是指元素用来集合类中。
常见泛型参数名称有如下 E Element (在集合中使用因为集合中存放的是元素) TTypeJava 类 K Key键 V Value值 N Number数值类型 表示不确定的java类型
17.6 泛型的实现原理
泛型本质是将数据类型参数化它通过擦除的方式来实现即编译器会在编译期间「擦除」泛型语法并相应的做出一些类型转换动作。
18 反射
参考1Java反射通俗易懂
Reflection(反射) 允许运行中的 Java 程序对自身进行检查。被private封装的资源只能类内部访问外部是不行的但反射能直接操作类私有属性。反射可以在运行时获取一个类的所有信息包括成员变量成员方法构造器等并且可以操纵类的字段、方法、构造器等部分。
要想查看一个类的具体信息必须先要获取到该类的字节码文件对象。而查看一个类的具体信息使用的就是Class类中的方法。所以先要获取到每一个字节码文件对应的Class类型的对象。
反射就是把java类中的各种成分映射成一个个的Java对象。例如一个类有成员变量、方法、构造方法、包等等信息利用反射技术可以对一个类进行解剖把一个个组成部分映射成一个个对象。其实一个类中这些成员方法、构造方法、在加入类中都有一个类来描述
加载的时候Class对象的由来是将 .class 文件读入内存并为之创建一个Class对象。
Class类 Class 类的实例表示正在运行的 Java 应用程序中的类和接口。也就是jvm中有N多的实例每个类都有该Class对象。包括基本数据类型
Class 没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的defineClass 方法自动构造的。也就是这不需要我们自己去处理创建JVM已经帮我们创建好了。
Bean Spring框架可以帮我们创建和管理对象。需要对象时我们无需自己手动new对象直接从Spring提供的容器中的Beans获取即可。Beans底层其实就是一个MapString,Object最终通过getBean(“user”)来获取。而这其中最核心的实现就是利用反射技术。
Java面向对象对象有方法和属性那么就需要对象实例来调用方法和属性即实例化凡是有方法或属性的类都需要实例化这样才能具象化去使用这些方法和属性规律凡是子类及带有方法或属性的类都要加上注册Bean到Spring IoC的注解Component Repository Controller Service Configration。把Bean理解为类的代理或代言人实际上确实是通过反射、代理来实现的这样它就能代表类拥有该拥有的东西了。在Spring中你标识一个符号那么Spring就会来看看并且从这里拿到一个Bean注册或者给出一个Bean使用。
18.1 反射的用法
看 Java反射通俗易懂 3、反射的应用
19 网络IO
参考1java的几种IO 参考2网络IO模型BIONIOAIO
参考1主要是概念参考2有更深入的分析及代码示例。
19.1 网络IO的分类
Java IO基于不同的IO模型可以分为三类
同步阻塞的BIOblocking IO在jdk1.0的时候引入的。同步非阻塞的NIOnon-blocking IO因为是在jdk1.4 引入的所以又叫New IO异步非阻塞的AIOAsynchronous IO是在jdk1.7 引入的是对NIO进一步的改进也被称为NIO2。
IO又主要可以分为文件IO和网络IO。 一般常说的java中的IO流指的就是java中BIO的具体实现 19.2 同步和异步、阻塞和非阻塞的概念
19.2.1 同步和异步
同步 就是一个任务的完成需要依赖另外一个任务时只有等待被依赖的任务完成后依赖的任务才能算完成这是一种可靠的任务序列。要么成功都成功失败都失败两个任务的状态可以保持一致。当我们进行同步操作时后续的任务是等待当前调用返回才会进行下一步
**异步**是不需要等待被依赖的任务完成只是通知被依赖的任务要完成什么工作依赖的任务也立即执行只要自己完成了整个任务就算完成了。至于被依赖的任务最终是否真正完成依赖它的任务无法确定所以它是不可靠的任务序列。其他任务不需要等待当前调用返回通常依靠事件、回调等机制来实现任务间次序关系。
异步和同步是对于请求结果的获取是客户端主动等待获取还是由服务端来通知消息结果。
19.2.2 阻塞和非阻塞
阻塞 阻塞与非阻塞主要是从 CPU 的消耗上来说的阻塞就是 CPU 停下来线程挂起等待当前一个慢的操作完成 CPU 才接着完成其它的事。阻塞期间无法从事其他任务只有当慢操作完成条件就绪才能继续。
非阻塞 非阻塞就是在这个慢的操作在执行时 CPU 去干其它别的事等这个慢的操作完成时CPU 再接着完成后续的操作。虽然表面上看非阻塞的方式可以明显的提高 CPU 的利用率但是也带了另外一种后果就是系统的线程切换增加。增加的 CPU 使用时间能不能补偿系统的切换成本需要好好评估。
阻塞和非阻塞通常是指客户端在发出请求后在服务端处理这个请求的过程中客户端本身是否直接挂起等待结果还是继续做其他的任务。
19.3 同步阻塞BIO
首先传统的java.io包是 blocking ioBIO在jdk1.0的时候引入的它提供了我们最熟知的一些IO功能比如File抽象、输入输出流等。交互方式是同步、阻塞的方式也就是在读入输入流或者写入输出流时在读写动作完成之前线程会一直阻塞在那里它们之间的调用时可靠的线性顺序。
优点代码比较简单、直观 缺点是IO效率和扩展性存在局限性容易成为应用性能的瓶颈。
同步阻塞BIO的特点
BIO不仅仅是对文件的操作网络编程中比如Socket通信都是典型的BIO操作目标。输入流、输出流InputStream/OutputStream是用于读取或写入字节的例如操作图片文件。而Reader/Writer则是用于操作字符增加了字符编解码等功能适用于类似从文件中读取或者写入文本信息。本质上计算机操作的都是字节不管是网络通信还是文件读取Reader/Writer相当于构建了应用逻辑和原始数据之间的桥梁。处理纯文本数据时使用字符流xxxReader/xxxWriter处理非纯文本时使用字节流xxxStream。最后其实不管什么类型文件都可以用字节流处理包括纯文本但会增加一些额外的工作量。所以还是按原则选择最合适的流来处理。BuferedOutputStream等带缓冲区的实现可以避免频繁的磁盘读写进而提高IO处理效率。这种设计利用了缓冲区将批量数据进行一次操作但在使用中千万别忘了结束时调用fush将未满的缓冲区数据进行写入。
19.3.1 面试题介绍一下Java中的IO流
流是Java对不同输入源输出源的抽象代表了从起源到接收的有序数据有了它程序就可以采用统一的方式来访问不同的输入源和输出源了。
按照数据的流向可以将流分为输入流和输出流。其中输入流只能读取数据、不能写入数据而输出流只能写入数据、不能读取数据。按照数据的类型可以将流分为字节流和字符流。其中字节流操作的数据单元是byte8位的字节而字符流操作的数据单元是char16位的字符。按照使用的场景可以将流分为节点流和处理流。其中节点流可以直接从/向一个特定的IO设备读/写数据也称为低级流。而处理流则是对节点流的连接或封装用于简化数据读/写功能或提高效率也成为高级流。
19.4 同步非阻塞NIO
NIO 是 Java 1.4 引入的 java.nio 包提供了 Channel通道、Buffer缓冲区、Selector选择器 等新的抽象可以构建多路复用的、同步非阻塞 IO 程序同时提供了更接近操作系统底层高性能的数据操作方式。
在Java API中提供了两套NIO一套是针对标准输入输出NIO另一套就是网络编程NIO。
NIO主要有三大核心部分Channel(通道)Buffer(缓冲区)Selector选择器。
传统IO基于字节流和字符流进行操作而NIO基于Channel(通道)和Buffer(缓冲区)进行操作数据总是从通道读取到缓冲区中或者从缓冲区写入到通道中。Selector(选择区)用于监听多个通道的事件比如连接打开数据到达。因此单个线程可以监听多个数据通道。
BIO的各种流是阻塞的。所以当一个线程调用read() 或 write()时该线程被阻塞直到有一些数据被读取或数据完全写入。该线程在此期间不能再干任何事情了。
NIO的非阻塞模式使一个线程从某通道发送请求读取数据但是它仅能得到目前可用的数据如果目前没有数据可用时就什么都不会获取。而不是保持线程阻塞所以直至数据变得可以读取之前该线程可以继续做其他的事情。 非阻塞写也是如此。一个线程请求写入一些数据到某通道但不需要等待它完全写入这个线程同时可以去做别的事情。
NIO的线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作所以一个单独的线程现在可以管理多个输入和输出通道channel。
Channel通道
Channel是一个通道可以通过它读取和写入数据。与流不同的是流是单向的而Channel是双向的。数据可以通过Channel读到Buffer里也可以通过Channel写入到Buffer里。需要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性。为了支持不同的设备Channel接口有好几种子类如FileChannel用于访问磁盘文件、SocketChannel和ServerSocketChannel用于TCP协议的网络通信、DatagramChannel用于UDP协议的网络通信。
Buffer缓冲区
NIO是面向缓冲区的在NIO中所有的数据都是通过缓冲区处理的。Buffer就是缓冲区对象无论读取还是写入数据都是先进入Buffer的。Buffer的本质是一个数组通常它是一个字节数组也可以是其他类型的数组。Buffer是一个接口它的实现类有ByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer、ShortBuffer分别对应基本数据类型: byte、char、double、float、int、long、short。Buffer对象包含三个重要的属性分别是capacity、position、limit。 capacity代表Buffer的容量就是说Buffer中最多只能写入capacity个数据。 position代表访问的位置它的初始值为0每读取/写入一个数据它就会向后移动一个位置。 limit代表访问限制就是本次操作最多能读取/写入多少个数据。这三个属性的关系是positionlimitcapacityBuffer通过不断调整position和limit的值使得自身可以不断复用。
Selector选择器 Selector是多路复用器可以通过它监听网络IO的状态。它可以不断轮询注册的Channel如果某Channel上有连接、读取、写入事件发生则这个Channel就处于就绪状态就会被Selector轮询出来。所有被轮询出来的Channel集合我们可以通过SelectionKey获取到然后进行后续的IO操作。
19.4.1 NIO和BIO的区别
NIO是以块的方式处理数据但是IO是以最基础的字节流的形式去写入和读出的。所以在效率上的话肯定是NIO效率比IO效率会高出很多。NIO不在是和IO一样用OutputStream和InputStream输入流的形式来进行处理数据的但是又是基于这种流的形式而是采用了通道和缓冲区的形式来进行处理数据的。NIO的通道是可以双向的但是IO中的流只能是单向的。NIO的缓冲区其实也就是一个字节数组还可以进行分片可以建立只读缓冲区、直接缓冲区和间接缓冲区只读缓冲区很明显就是字面意思直接缓冲区是为加快 I/O 速度而以一种特殊的方式分配其内存的缓冲区。NIO与BIO核心区别就是NIO采用的是多路复用的IO模型普通的IO用的是阻塞的IO模型两个之间的效率肯定是多路复用效率更高。
19.5 异步非阻塞AIO
AIO 是 Java 1.7 之后引入的包是对NIO进一步的改进也被称为NIO2提供了异步非堵塞的 IO 操作方式所以叫 AIOAsynchronous IO。AIO其实是一种在读写操作结束之前允许进行其他操作的I/O处理。
jdk7主要增加了三个新的异步通道
AsynchronousFileChannel: 用于文件异步读写AsynchronousSocketChannel: 客户端异步socketAsynchronousServerSocketChannel: 服务器异步socket。
因为AIO的实施需充分调用操作系统参与IO需要操作系统支持、并发也同样需要操作系统的支持所以性能方面不同操作系统差异会比较明显。
19.6 BIO、NIO、AIO适用场景
BIO方式适用于连接数目比较小且固定的架构这种方式对服务器资源要求比较高并发局限于应用中JDK1.4以前的唯一选择但程序简单易理解。
NIO方式适用于连接数目多且连接比较短轻操作的架构比如聊天服务器弹幕系统服务器间通讯等。编程比较复杂JDK1.4开始支持。
AIO方式使用于连接数目多且连接比较长重操作的架构比如相册服务器充分调用操作参与并发操作编程比较复杂JDK7开始支持。
20 多线程
参考1Java多线程超详细 参考2Java多线程【三种实现方法】 参考3Java多线程详解——一篇文章搞懂Java多线程 参考4Java多线程超级详解(看这篇就足够了) 参考5Java多线程实战精讲-带你一次搞明白Java多线程高并发 参考6java多线程详
21 集合框架
22 网络
23 高并发
24 高负载
25 高可用性
26 JVM原理
27 注解
28 线程池
29 锁
30 设计模式
31 栈和堆
参考1Java中的栈和堆
Java程序在运行时都会开辟内存空间而栈和堆就是JVM虚拟机在运行时开辟的内存空间。
栈函数中定义的基本数据类型与局部变量都是在栈内存中被分配而用完之后这些变量将会在栈内存中被释放局部变量存在于方法中一旦方法被执行局部变量也将会被执行而方法执行完后这个变量将会在栈内存中被释放掉。
堆实体对象被new出来的在堆内存中被分配而这些实体封装的数据属性如果在用完后被释放实体对象也不会被释放但是Java中垃圾回收机制在对象不被使用后会被自动当成垃圾不定时的回收。
示例
int[] a new int[2];这里首先在栈中给a变量分配地址在堆中给数组分配2个大小的内存空间并给数组中的每个默认值一个地址默认为0这个也就是两个0之后栈中的a会根据这个地址被赋值当用完后a变量会被释放掉而new对象在堆中存在。 参考1史上最全Java面试题带全部答案 参考2100道Java经典面试题面中率高 参考3Java面试题大全带答案 参考4最全java面试题及答案208道 参考5Java 教程
能否创建一个包含可变对象的不可变对象
可以创建一个包含可变对象的不可变对象的你只需要谨慎一点不要共享可变对象的引用就可以了如果需要变化时就返回原对象的一个拷贝。最常见的例子就是对象中包含一个日期对象的引用。
创建对象的几种方式
采用new通过反射采用clone通过序列化机制
Object中有哪些公共方法
equals()clone()getClass()notify(),notifyAll(),wait()toString
java当中的四种引用
强引用软引用弱引用虚引用。不同的引用类型主要体现在GC上
强引用如果一个对象具有强引用它就不会被垃圾回收器回收。即使当前内存空间不足JVM也不会回收它而是抛出 OutOfMemoryError 错误使程序异常终止。如果想中断强引用和某个对象之间的关联可以显式地将引用赋值为null这样一来的话JVM在合适的时间就会回收该对象。软引用在使用软引用时如果内存的空间足够软引用就能继续被使用而不会被垃圾回收器回收只有在内存不足时软引用才会被垃圾回收器回收。弱引用具有弱引用的对象拥有的生命周期更短暂。因为当 JVM 进行垃圾回收一旦发现弱引用对象无论当前内存空间是否充足都会将弱引用回收。不过由于垃圾回收器是一个优先级较低的线程所以并不一定能迅速发现弱引用对象。虚引用顾名思义就是形同虚设如果一个对象仅持有虚引用那么它相当于没有引用在任何时候都可能被垃圾回收器回收。
为什么要有不同的引用类型
不像C语言我们可以控制内存的申请和释放在Java中有时候我们需要适当的控制对象被回收的时机因此就诞生了不同的引用类型可以说不同的引用类型实则是对GC回收时机不可控的妥协。有以下几个使用场景可以充分的说明 利用软引用和弱引用解决OOM问题用一个HashMap来保存图片的路径和相应图片对象关联的软引用之间的映射关系在内存不足时JVM会自动回收这些缓存图片对象所占用的空间从而有效地避免了OOM的问题. 通过软引用实现Java对象的高速缓存比如我们创建了一Person的类如果每次需要查询一个人的信息哪怕是几秒中之前刚刚查询过的都要重新构建一个实例这将引起大量Person对象的消耗并且由于这些对象的生命周期相对较短会引起多次GC影响性能。此时通过软引用和 HashMap 的结合可以构建高速缓存提供性能。
和eqauls()的区别 是运算符用于比较两个变量是否相等而equals是Object类的方法用于比较两个对象是否相等。默认Object类的equals方法是比较两个对象的地址此时和 的结果一样。换句话说基本类型比较用比较的是他们的值。默认下对象用比较时比较的是内存地址如果需要比较对象内容需要重写equal方法。
equals()和hashCode()的联系
hashCode()是Object类的一个方法返回一个哈希值。如果两个对象根据equal()方法比较相等那么调用这两个对象中任意一个对象的hashCode()方法必须产生相同的哈希值。
如果两个对象根据eqaul()方法比较不相等那么产生的哈希值不一定相等(碰撞的情况下还是会相等的。
a.hashCode()有什么用?与a.equals(b)有什么关系
hashCode() 方法是相应对象整型的 hash 值。它常用于基于 hash 的集合类如 Hashtable、HashMap、LinkedHashMap等等。它与 equals() 方法关系特别紧密。根据 Java 规范使用 equal() 方法来判断两个相等的对象必须具有相同的 hashcode。
将对象放入到集合中时首先判断要放入对象的hashcode是否已经在集合中存在不存在则直接放入集合。如果hashcode相等然后通过equal()方法判断要放入对象与集合中的任意对象是否相等如果equal()判断不相等直接将该元素放入集合中否则不放入。
有没有可能两个不相等的对象有相同的hashcode
有可能两个不相等的对象可能会有相同的 hashcode 值这就是为什么在 hashmap 中会有冲突。如果两个对象相等必须有相同的hashcode 值反之不成立。
ab 与a.equals(b)有什么区别
如果a 和b 都是对象则 ab 是比较两个对象的引用只有当 a 和 b 指向的是堆中的同一个对象才会返回 true而 a.equals(b) 是进行逻辑比较所以通常需要重写该方法来提供逻辑一致性的比较。例如String 类重写 equals() 方法所以可以用于两个不同对象但是包含的字母相同的比较。
aab与ab有什么区别
操作符会进行隐式自动类型转换此处ab隐式的将加操作的结果类型强制转换为持有结果的类型而aab则不会自动进行类型转换。
short s1 1; s1 s1 1; 该段代码是否有错,有的话怎么改
有错误short类型在进行运算时会自动提升为int类型也就是说s11的运算结果是int类型。
short s1 1; s1 1; 该段代码是否有错有的话怎么改
操作符会自动对右边的表达式结果强转匹配左边的数据类型所以没错。 和 的区别
是位操作而是逻辑运算符。另外需要记住逻辑运算符具有短路特性而不具备短路特性。
内部类的作用
内部类可以有多个实例每个实例都有自己的状态信息并且与其他外围对象的信息相互独立.在单个外围类当中可以让多个内部类以不同的方式实现同一接口或者继承同一个类.创建内部类对象的时刻不依赖于外部类对象的创建。内部类并没有令人疑惑的”is-a”管系它就像是一个独立的实体。
内部类提供了更好的封装除了该外围类其他类都不能访问。
如何将byte转为String
可以使用 String 接收 byte[] 参数的构造器来进行转换需要注意的点是要使用的正确的编码否则会使用平台默认编码这个编码可能跟原来的编码相同也可能不同。
可以将int强转为byte类型么?会产生什么问题?
我们可以做强制转换但是Java中int是32位的而byte是8 位的所以,如果强制转化int类型的高24位将会被丢弃byte 类型的范围是从-128到128。导致精度损失。
进程、线程之间的区别
参考1线程和进程的区别
进程
进程是资源分配的最小单位。进程有自己的独立地址空间每启动一个进程系统就会为它分配地址空间建立数据表来维护代码段、堆栈段和数据段这种操作非常昂贵。多进程程序更健壮多线程程序只要有一个线程死掉整个进程也死掉了而一个进程死掉并不会对另外一个进程造成影响因为进程有自己独立的地址空间。.
线程
线程程序执行的最小单位资源调度的最小单位。线程是共享进程中的数据的使用相同的地址空间因此CPU切换一个线程的花费远比进程要小很多同时创建一个线程的开销也比进程要小很多。线程之间的通信更方便同一进程下的线程共享全局变量、静态变量等数据而进程之间的通信需要以通信的方式IPC)进行。
进程和线程的关系
一个线程只能属于一个进程而一个进程可以有多个线程但至少有一个线程。线程是操作系统可识别的最小执行和调度单位。资源分配给进程同一进程的所有线程共享该进程的所有资源。 同一进程中的多个线程共享代码段(代码和常量)数据段(全局变量和静态变量)扩展段(堆存储)。但是每个线程拥有自己的栈段栈段又叫运行时段用来存放所有局部变量和临时变量。处理机分给线程即真正在处理机上运行的是线程。线程在执行过程中需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。
循环依赖
参考1java 循环依赖_Java详解之Spring Bean的循环依赖解决方案【推荐】 参考2Java中的Spring循环依赖详情
什么是循环依赖 循环依赖其实就是循环引用也就是两个或则两个以上的bean互相持有对方最终形成闭环。比如A依赖于BB依赖于CC又依赖于A。 Spring中循环依赖场景有
构造器的循环依赖。field属性的循环依赖。
怎么检测是否存在循环依赖 检测循环依赖相对比较容易Bean在创建的时候可以给该Bean打标如果递归调用回来发现正在创建中的话即说明了循环依赖了。
运行之后Spring抛出了如下错误信息
Description:The dependencies of some of the beans in the application context form a cycle:解决方案 不使用基于构造函数的依赖注入。可通过下面方式解决。
在字段上使用Autowired注解让Spring决定在合适的时机注入。【推荐】用基于setter方法的依赖注射取代基于构造函数的依赖注入来解决循环依赖。