java高频考点-再总结

反射

概念

反射其实是一种动态编译,就是在运行状态中才确定类型(这也是动态编译的概念),可以不通过new对象,就会可以动态的获取这个类的属性、接口和方法信息,而且还能创建对象和调用方法,这种动态的获取类的信息,和动态的调用对象的方法的功能就是反射机制。

优点:因为他是运行期确定类型,动态的加载类,所以可以提高程序的灵活性和扩展性,降低耦合性,提高自适应能力,避免硬编码。
缺点:反射基本上是一种解释操作,用于字段和方法接入时要远慢于直接代码。

在动态获取信息时,需要依靠Class对象,Class类对象将一个类的方法、变量信息告诉运行的程序,一般通过Class.forName(“类全限定类型”)的方式获取Class对象。

场景

  • jdbc中加载驱动类时,就用到了反射
  • baseServelet
  • Spring框架也用到很多反射机制,最经典的就是xml的配置模式。Spring 通过 XML 配置模式装载 Bean 的过程:1) 将程序内所有 XML 或 Properties 配置文件加载入内存中; 2)Java类里面解析xml或properties里面的内容,得到*对应实体类的字节码字符串以及相关的属性信息; 3)使用反射机制,根据这个字符串获得某个类的Class实例; 4)动态配置实例的属性。
  • 持久层框架中,可以通过反射机制根据实体类中的属性创建出与数据库表中的字段对应的sql语句,也可以将查询到的结果集快速的封装到一个实体类中。类似Spring中的BeanUtils.copyProperties()实现对象间相同属性的copy。
  • 动态代理中用到了反射

动态代理

  • JDK动态代理:jdk提供的是一个Proxy类,通过调用他的newProxyInstance方法,利用反射机制生成一个实现代理接口的匿名类,这个方法有3个参数分别是类加载器、类实现的接口、和InvocationHandler()对象,在调用具体方法前调用InvokeHandler来处理。(方法增强通过重写InvocationHandler接口的invoke方法实现)
    Proxy.newProxyInstance(类加载器,类接口,new InvocationHandler());
  • CGlib动态代理:利用ASM(Java 字节码操控框架,开源的Java字节码编辑库,操作字节码)开源包,将代理对象类的class文件加载进来,通过修改其字节码文件生成代理子类来处理。通过实现MethodInterceptor接口重写(唯一的)intercept方法(方法增强在intercept方法中写),然后通过创建的Enhancer对象来创建子类代理对象。
  • 区别:JDK代理只能对实现接口的类生成代理;CGlib是针对类实现代理,对指定的类生成一个子类,并覆盖其中的方法,这种通过继承类的实现方式,不能代理final修饰的类。

重定向与转发的区别

  • 请求转发是一个请求一次响应,而重定向是两次请求两次响应
  • 请求转发地址栏不变化,而重定向会显示后一个请求的地址
  • 请求转发只能转发到本项目其他Servlet,而重定向不只能重定向到本项目的其他Servlet,还能定向到其他项目
  • 请求转发是服务器端行为,只需给出转发的Servlet路径, 而重定向需要给出requestURI, 即包含项目名!
  • 请求转发和重定向效率是转发高!因为是-一个请求!
    1. 需要地址栏发生变化,那么必须使用重定向!
    2. 需要在下一个Servlet中获取request域中的数据,必须要使用转发!

hashmap

拉链法导致的链表过深问题为什么不用二叉查找树代替,而选择红黑树?为什么不一直使用红黑树?

红黑树可以解决二叉查找树的缺陷,二叉查找树在极端情况下会变成一条线性结构,和原来的链表结构就一样了查询效率很低。

红黑树在插入数据后可能会通过左旋、右旋、变色这些操作来保持平衡,红黑树就是为了查找数据快,解决链表查询深度的问题,红黑树就属于平衡二叉树。

链表树化条件为什么是这样?

  • 链表长度大于等于8:红黑树在保持树的平衡也是要付出代价的,所以在链表长度很短的<8时,引入反而会很慢
  • table数组长度大于等于64:数组容量较小时,键值对hash的碰撞概率比较高,会导致链表长度过长,这时应该先扩容而不是马上树化

解决hash冲突的方法

开放定址法:线性探查法、平方探测法、伪随机探测法
在哈希法
拉链法
建立一个公共的溢出区

红黑树

  • 每个节点都是非红即黑
  • 根节点总是黑色的
  • 节点是红色的,则它的子节点必须是黑色的
  • 每个叶子节点都是黑色的空节点(NIL节点)
  • 从根节点到叶节点或空子节点的每条路径,必须包含相同数目的黑色节点(即相同的黑色高度)

红黑树和平衡二叉查找树的区别

  • 相同点:都是在进行插入和删除时通过特定操作保持平衡二叉查找树的平衡,从而获取较高的查找性能。二者的算法时间复杂度和AVL相同,但是统计性能比AVL更高。
  • 不同点:
    1. 红黑树放弃追求完全平衡追求局部大致平衡,与平衡二叉树的时间复杂度相差不大的情况下,保证每次插入最多只需要3次就当到平衡,并且使用颜色表示节点的高度,实现起来也比较简单
    2. 平衡二叉树追求绝对平衡,条件比较苛刻,实现起来比较麻烦,每次插入新节点都需要旋转的次数不能预知。

红黑树与B树的区别

B树又叫多路查找树,相对于二叉,B树每个内节点都有多个分支,与红黑树相似,但在降低磁盘IO操作方面要更好一些

table 数组长度永远为 2 的幂次方

HashMap通过tableSizeFor方法确保HashMap数组的长度永远是2的指数次幂,确保返回的是大于等于传入的初始化容量参数且最接近2的整数次幂例如10->16。

好处有:

  • 获取下标: index=hash&(table.length-1) 通过与运算计算下标,可以让其均匀的落在1-16个坑位中。使用位运算效率也高。
  • 扩容时:扩容时复制元素后,其位置要么在原位置,要么在原位置+oldCap的位置,因为扩容是扩容2倍,所以在计算hash时只需要判断hash新增的最高位是0还是1就好了(用e.hash & oldCap判断),是0就在原位置,是1就在原索引 +oldCap。(1.7时是重新计算hash值,效率比较低)

1.7中的4次位运算,5次异或运算(9次扰动),在1.8中,只进行了1次位运算和1次异或运算(2次扰动)

Hash冲突?

Hash:散列,将任意长度的输入通过散列算法转换为固定长度的输出(输出hash值),这种压缩映射就是将任意长度的消息压缩到某一固定长度的消息摘要的函数。从大范围映射到小范围肯定会出现相同的情况,就叫碰撞。

put步骤

根据hash&(table.length-1) 计算出该元素位于数组哪个下标i,如果table[i]为空,那么就只创建新节点添加,添加完后判断实际键值对size是否>阈值判断是否要进行扩容。如果table[i]不为空,那么判断table[i]是否和首元素key相同,是就覆盖value,不相同则然后判断table[i]是否为一个treeNode,则在树中插入该元素,如果不是树结构,那么就判断链表长度是否大于8且数组长度小于64,是就树化再插入,否则就插入到链表中。每次插入成功都会判断size释放超过阈值,进行扩容。

扩容步骤

HashMap扩容时都是建立一个新的table数组,长度变为原来的2倍,将原来的元素进行隐射到新数组中。

  • 判断table.length>0,说明已经被初始化,就直接扩容为原来的2倍,阈值也变为2倍
  • table.length<0,阈值>0,说明没有被初始化,说明调用了HashMap(initialCapacity, loadFactor) 构造方法,就把数组大小扩容为threshold。
  • table.length<0,阈值<0,说明调用的是无参构造方法,那么会把数组大小扩容设置为16,阈值为16*0.75=12
  • 扩容之后,要重新计算键值对的位置,并把它们移动到合适的位置上,如果是红黑树的话就需要进行红黑树的拆分。

HashTable与HashMap

  • 线程安全性、效率,所以hashMap不存在快速失败并发修改异常
  • HashMap 中,null可以作为键,这样的键只有一个,可以有一个或多个键所对应的值为 null。但是在 HashTable 中 put 进的键值只要有一个 null,直接抛NullPointerException。
  • HashTable默认容量为11,扩容为原来的2n+1,HashMap默认16扩容为2倍,HashTable不会将你传入的容量进行2的幂次方转换
  • HashMap会转换为红黑树,但是HashTable没有这样的机制

最简单但是却讲不出来

  • 抽象:抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面。
  • 继承:继承是从已有类得到继承信息创建新类的过程。继承让变化中的软件系统有了一定的延续性,同时继承也是封装程序中可变因素的重要手段
  • 封装:通常认为封装是把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口。多态性分为编译时的多态性和运行时的多态性。

java8新特性

接口默认方法default ,可以有方法体
Lambda表达式
函数式接口
方法引用构造函数引用可以使用 ::
Stream流

集合存null值

  • List集合可以存储null,添加几个存储几个;
  • Set集合也可以存储null,但是只能存储一个,即使添加多个也只存一个
  • HashMap可以存储null键值对,但是只能有一个,建相同会覆盖
  • HashTable不能存null,会报空指针

重载与重写

方法重写:

子类中出现了和父类中方法声明一模一样的方法。

  1. 发生在父类与子类之间
  2. 方法名,参数列表,返回类型(除过子类中方法的返回类型是父类中返回类型的子类)必须相同
  3. 访问修饰符的限制一定要大于被重写方法的访问修饰符(public> protected>default> private)
  4. 重写方法一定不能抛出新的检查异常或者比被重写方法申明更加宽泛的检查型异常

重写方法得满足一大两小两同,所谓的一大两小两同是:

一大:访问修饰符比(被重写的)父类的大(或- -样)
两小:返回值类型、声明异常比(被重写的)父类的小(或一样)
两同:方法名和参数列表(方法签名)必须相同

方法重载:

本类中出现的方法名一样,参数列表不同的方法。与返回值无关。

  1. 重载Overload是一个类中多态性的一种表现
  2. 重载要求同名方法的参数列表不同(参数类型,参数个数甚至是参数顺序)
  3. 重载的时候,返回值类型可以相同也可以不相同。无法以返回型别作为重载函数的区分标准

区别

方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。
重载发生在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)则视为重载;重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的参数列表,有兼容的返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常(里氏代换原则)。重载对返回类型没有特殊的要求,不能根据返回类型进行区分。

JVM再看看

jvm7与8做了什么改变 ?

  • 1.8用元数据取代了1.7中的永久代。(永久代和元空间本质上都是方法区的实现,但是元空间并不在虚拟机中,而是使用本地内存)
  • 在1.6及之前静态变量存放在永久代上,1.7已经开始逐渐开始去永久代化,字符串常量池、静态变量被移动到了堆中,1.8彻底去除永久代,将类型信息、字段、方法常量都保存到本地内存的元空间,但是字符串常量池、静态变量仍在堆中。

说一下类加载的过程

  1. 加载:类加载器会在双亲委派机制下以类的全限定类名,把一个类通过二进制流的形式进行加载,创建Class对象,即:将字节流的静态存储结构转化为方法区的运行时数据结构
  2. 验证:验证Class文件是否满足jvm的安全性(魔术cafebabe开头)
  3. 准备:为静态字段进行赋初识零值,显示初始化final修饰的静态字段
  4. 解析:将常量池中的符号引用转化为直接引用,解析动作主要针对类或接口、字段、类方法、接口方法、方法类型等。
  5. 初始化:执行类的构造器clinit方法,对静态字段的显示初始化,静态代码块的初始化

双亲委派:当一个类收到了类加载请求时,不会自己先去加载这个类,而是将其委派给父类,由父类去加载,如果此时父类不能加载,反馈给子类,由子类去完成类的加载。
引导(启动)类加载器:由C、C++实现,嵌自jvm内部,这个类加载器我们获取不到,用来加载java核心类库,例如java、javax、sun等开头的类(用于提供jvm自身需要的类)
拓展类加载器:java语言表现,用来加载java.ext.dirs系统属性所指定的目录中的类
应用程序类加载器:父类为拓展类加载器,程序中默认的类加载器,负责加载classpath下的类。

创建对象的过程

(一共分为6步)

  1. jvm遇见new指令时,首先会检查此类是否已经加载,如果没有加载,会在双亲委派机制下以ClassLoader和类的全限定类名去寻找.class文件,并对类进行加载、准备、验证、解析、初始化的过程,并创建Class对象。
  2. 计算对象的内存空间大小,然后划分一块内存给该对象(分配方式有“指针碰撞”和“空闲列表”两种方式)
  3. 处理线程安全问题,保证new的过程是安全的(“CAS失败重试、区域加锁”和“本地线程分配缓冲区(TLAB ,Thread Local Allocation Buffer)”两种方式)
  4. 初始化,也就是给对象初始化默认零值
  5. 设置对象头,对象头(分代年龄、锁状态、hash值等)
  6. 对象的显示初始化(成员代码块、成员变量的初始化)

对象的内存布局,一个对象中有

实例数据:对象真正存储有效的信息,如成员变量、包括继承父类的字段信息

对其填充:不是必须的,理解为占位信息

对象头:包含运行时元数据和类型指针,运行时元数据Markworld包含哈希码、GC分代年龄、锁状态标志、线程持有锁的、偏向线程id、偏向时间戳。类型指针是指向元数据中类对象,用来确定对象所属的类型,

回收机制

一个对象被创建后首先在Eden区,当Eden区满时,还存活的对象将被复制到两个Survivor区(中的一个)。当这个Survivor区满时,此区的存活且不满足“晋升”条件的对象将被复制到另外一个Survivor区。对象每经历一次Minor GC,年龄加1,达到“晋升年龄阈值”后,被放到老年代,这个过程也称为“晋升”。当老年代满时会触发MajorGC,之后如果内存还是不足时,会OOM。显然,“晋升年龄阈值”的大小直接影响着对象在新生代中的停留时间。

对象如何晋升到老年代

  • 经历一定Minor次数依然存活的对象
  • Survivor区中存放不下的对象
  • 新生成的大对象( -XX:+ PretenuerSizeThreshold设置大对象的阈值 )

触发fullGC的条件

  • 老年代空间不足
  • 7以下永久代空间不足(8中改为元空间就可减少fullGC的几率)。
  • MinorGC晋升到老年代的平均大小大于老年代的剩余空间
  • 调用System.gc()。显示调用

常见的GC参数

-Xms:初始化堆内存
-Xmx:最大堆内存
-Xss:线程堆栈大小
-Xmn:设置年轻代大小
-XX:PermSize:设置永久代空间大小
-XX:MetaspaceSize:元空间大小
-XX:NewRatio:设置新生代大小
-XX:SurvivorRatio:设置2个Survivor和Eden的比例
-XX:MaxTenuringThreshold:新生代晋升为老年代的年龄阈值默认15
-XX:+PrintGC:开启打印gc信息
-XX:+PrintGCdetails:打印gc详细信息
(默认:堆中的新生代和老年代1:2,新生代和2个S区8:1:1)

深拷贝和浅拷贝

  • 浅拷贝(shallowCopy)只是增加了一个指针指向已存在的内存地址,仅仅是指向被复制的内存地址,如果原地址发生改变,那么浅复制出来的对象也会相应的改变。
  • 深拷贝(deepCopy)是增加了一个指针并且申请了一个新的内存,使这个增加的指针指向这个新的内存,使用深拷贝的情况下,释放内存的时候不会因为出现浅拷贝时释放同一个内存的错误。在计算机中开辟一块新的内存地址用于存放复制的对象。

java内存模型

java内存模型规定了所有的变量都存储在主内存中,但是每个线程会有自己的工作内存,线程的工作内存保存了该线程中使用了的变量(从主内存中拷贝的), 线程对变量的操作都必须在工作内存中进行,不同线程之间无法直接访问对方工作内存中的变量,线程间变量值从传递都要经过主内存完成。

垃圾回收算法了解那些

垃圾标记阶段

  • 引用计数算法:
    对每个对象都设置一个整形的引用计数器属性,用于记录对象的引用情况。每次对对象的引用有变化时,都维护这个引用计数器,值为0表示不再被引用可以回收。
    特点:实现简单,判断效率高,但是每个对象均要开辟空间去维护这个变量,而且每次操作引用都伴随这个这个计数器的加减操作,也增加了时间开销,最重要的是它不能解决循环引用问题。
  • 可达性分析算法
    思想是维护了一系列根对象,即GC ROOT(常见的:虚拟机栈中引用的对象也就是栈帧中本地变量表的对象,方法区中的常量引用,方法区中的静态属性引用对象,本地方法栈中的引用对象,活跃线程的引用对象),只要是被这个些对象直接或间接的引用的对象就是可达对象,不会被回收。
    特点:相对于引用计数算法而言,可达性分析算法不仅同样具备实现简单和执行高效等特点,更重要的是该算法可以有效地解决在引用计数算法中循环引用的问题,防止内存泄漏的发生。

垃圾清除阶段

  • 标记清除算法
    垃圾收集器从引用根节点开始遍历,标记被引用的对象(可达对象),然后收集器会清除那些没有被标记的不可达对象。
    特点:基础常见、易于理解、但是效率不高,最主要的是清理的内存是不连续的,可能会产生内存碎片,需要维护一个空闲列表。(空闲列表:这里所谓的清除并不是真的置空,而是把需要清除的对象地址保存在空闲的地址列表里。下次有新对象需要加载时,判断垃圾的位置空间是否够,如果够就存放。)
  • 复制算法
    将活着的内存空间分为两块,每次只使用其中一块,在垃圾回收时将正在使用的内存中的存活对象复制到未被使用的内存块中,之后清除正在使用的内存块中的所有对象,交换两个内存的角色,最后完成垃圾回收。
    特点:没有标记和清除过程,实现简单,运行高效因为只需移动堆顶指针即可。复制过去以后保证空间的连续性,不会出现“碎片”问题。此算法的缺点也是很明显的,就是需要两倍的内存空间。对于G1这种分拆成为大量region的GC,复制而不是移动,意味着GC需要维护region之间对象引用关系,不管是内存占用或者时间开销也不小。
    适用于对象存活率低的场景,如年轻代。对于对象存活率高的老年代,就不适合,因为清除后复制的工作量太大,效率就低了。
  • 标记压缩算法
    从根节点开始标记所有被引用对象,将所有的存活对象压缩到内存的一端,按顺序排放。之后,清理边界外所有的空间。
    特点:消除了标记-清除算法当中,内存区域分散的缺点,消除了复制算法当中,内存减半的高额代价。从效率上来说,标记-整理算法要低于复制算法,,更低于标记-清除算法,因为移动对象时要调整引用地址,
    关系:标记-压缩算法的最终效果等同于标记-清除算法执行完成后,再进行一次内存碎所整理,二者的本质差异在于标记-清除算法是一种非移动式的回收算法,标记-压缩是移动式的。是否移动回收后的存活对象是一项优缺点并存的风险决策。
    适用于老年代的垃圾回收,对象存活率高
  • 分代收集算法
    垃圾回收算法的组合拳,按照对象生命该周期的不同划分区域以采用不同的垃圾回收算法。目的就是提供jvm的回收效率。
    增量收集算法、分区算法

safepoint

在可达性分析中要分析那个对象没有引用的时候,必须在一个快照的点进行,这个点中,所有的线程都被冻结了,该点需具备确定性,这个点就是安全点。

  • 分析过程中对象弓|用关系不会发生变化的点
  • 产生Safepoint的地方:方法调用;循环跳转;异常跳转等
  • 安全点数量得适中

垃圾收集器

年轻代–Serial收集器( -XX:+UseSerialGC ,复制算法)

  • 复制算法单线程收集,进行垃圾收集时,必须暂停所有工作线程
  • 简单高效, Client模式下默认的年轻代收集器

年轻代–parNew收集器( -XX:+UseParNewGC ,复制算法)

  • 复制算法多线程收集,其余的行为、特点和Serial收集器一 样
  • 单核执行效率不如Serial ,在多核下执行才有优势

年轻代–Parallel Scavenge收集器( -XX: +UseParallelGC ,复制算法)

  • 比起关注用户线程停顿时间,更关注系统的吞吐量
  • 在多核下执行才有优势, Server模式下默认的年轻代收集器

老年代–Serial Old收集器( -XX:+UseSerialOldGC ,标记-整理算法)

  • 单线程收集,进行垃圾收集时,必须暂停所有工作线程
  • 简单高效,Client模式下默认的老年代收集器

老年代–Parallel Old收集器( -XX : +UseParallelOldGC ,标记整理算法)

  • 多线程,吞吐量优先

老年代–CMS收集器( -XX:+UseConcMarkSweepGC ,标记-清除算法)

  • 初始标记: stop-the-world
  • 并发标记:并发追溯标记,程序不会停顿
  • 并发预清理:查找执行并发标记阶段从年轻代晋升到老年代的对象
  • 重新标记:暂停虚拟机,扫描CMS堆中的剩余对象(短暂stw)
  • 并发清理:清理垃圾对象,程序不会停顿
  • 并发重置:重置CMS收集器的数据结构

年轻老年均可–G1收集器( -XX:+UseG1GC,复制+标记整理算法)
特点:并行和并发、分代收集、空间整合、可预测的停顿

  • 将整个Java堆内存划分成多个大小相等的Region
  • 年轻代和老年代不再物理隔离
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值