- 让线程等待和唤醒的方法
- AQS
- interrupt,interrupted与isInterrupted方法的区别? 如何停止一个正在运行的线程
- 什么是快速失败(fail-fast)、能举个例子吗?什么是安全失败(fail-safe)呢?
- >>和>>>的区别
- Java 中 finalize()方法的使用?
- 静/动态代理
- 多态
- Java 反射?反射有什么缺点?你是怎么理解反射的(为什么框架需要反射)?
- 获取 Class 对象的两种方式
- 内存泄露和内存溢出的场景。
- 解决措施
- GC Root 对象有哪些
- TCP滑动窗口是干什么的?TCP的可靠性体现在哪里?拥塞控制如何实现的?
- 解决死锁的策略
- Spring 框架中用到了哪些设计模式?
- JVM数据区域的划分
- String::intern()是一个本地方法,它的作用是如果字符串常量池中已经包含一个等于此String对象的字符串,则返回代表池中这个字符串的String对象的引用,否则会将此String对象包含的字符串添加到常量池中,并返回此String对象的引用。
LockSupport 用于创建锁和其他同步类的基本线程阻塞原语,
AQS是一个用来构建锁和同步器的框架。AQS核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请 求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。
- CLH队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关 联关系)。AQS是将每条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node)来实现锁的分配。
- AQS使用一个int成员变量来表示同步状态,通过内置的FIFO队列来完成获取资源线程的排队工作。AQS使用CAS对该同步状态进行原子操作实现对其值的修改
- AQS定义两种资源共享方式
- Exclusive(独占):只有一个线程能执行,如ReentrantLock。又可分为公平锁和非公平锁:
- Share(共享):多个线程可同时执行。
- AbstractQueuedSynchronizer
- 抽象队列同步器
- 与AQS有关的
- interrupt()方法的作用实际上是:在线程受到阻塞时抛出一个中断信号,这样线程就得以退出阻塞状态。
- interrupted()调用的是currentThread().isInterrupted(true)方法,即说明是返回当前 线程的是否已经中断的状态值,而且有清理中断状态的机制。测试当前线程是否已经中断,线程的中断状态由该方法清除。即如果连续两次调用该方法, 则第二次调用将返回false(在第一次调用已清除flag后以及第二次调用检查中断状态之前, 当前线程再次中断的情况除外)所以,interrupted()方法具有清除状态flag的功能
- isInterrupted()调用的是isInterrupted(false)方法,意思是返回线程是否已经中断的状态,它没有清理中断状态的机制。
- interrupt()方法用于中断线程。调用该方法的线程的状态为将被置为”中断”状态。
- 中断可以理解为线程的一个标识位属性,它表示一个运行中的线程是否被其他线程进行了中 断操作。中断好比其他线程对该线程打了个招呼,其他线程通过调用该线程的interrupt()方法对其进行中断操作。
- 线程中断仅仅是置线程的中断状态位,不会停止线程。需要用户自己去监视线程的状 态为并做处理。
- interrupted() 检测当前线程是否已经中断,是则返回true,否则false,并清除中断状态。换言之,如果该方法被连续调用两次,第二次必将返回false,除非在第一次与第二次的瞬间线程再次被中断。如果中断调用时线程已经不处于活动状态,则返回false。
- isInterrupted() 检测当前线程是否已经中断,是则返回true,否则false。中断状态不受该方法的影响。如果中断调用时线程已经不处于活动状态,则返回false。
- 使用stop方法强行终止(过期作废)
-
什么是快速失败(fail-fast)、能举个例子吗?什么是安全失败(fail-safe)呢?
快速失败(fail-fast)
快速失败(fail-fast)是 Java 集合的一种错误检测机制。在使用迭代器对集合进行遍历的时候,我们在多线程下操作非安全失败(fail-safe)的集合类可能就会触发 fail-fast 机制,导致抛出ConcurrentModificationException 异常。另外,在单线程下,如果在遍历过程中对集合对象的内容进行了修改的话也会触发 fail-fast 机制。
安全失败(fail-safe)
- 采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集 合内容,在拷贝的集合上进行遍历。所以,在遍历过程中对原集合所作的修改并不能被迭代器检测到,故不会抛 ConcurrentModificationException
>>和>>>的区别
- >>表示带符号右移:
- 如:int i=15; i>>2 的结果是 3,移出的部分将被抛弃。转为二进制的形式可能更好理解,0000 1111(15)右移 2 位的结果是 0000 0011(3),0001 1010(18)右移 3 位的结果是 0000 0011(3)。
- >>>无符号右移:
- 按二进制形式把所有的数字向右移动对应巍峨位数,低位移出(舍弃),高位的空位补零。对于正数来说和带符号右移相同,对于负数来说不同。其他结构和>>相似。
Java 中 finalize()方法的使用?
- finalize()是 Object的protected 方法,子类可以覆盖该方法以实现资源清理工作,GC 在回收对象之前调用该方法。
- finalize()方法中一般用于释放非 Java 资源(如打开的文件资源、数据库连接等)
- Java方法(native方法)时分配的内存(比如 C 语言的 malloc()系列函数)。
一个对象的finalize()方法只会被调用一次,而且finalize()被调用不意味着gc会立即回收该对象,所以有可能调用finalize()后,该对象又不需要被回收了,然后到了真正要被回收的时候,因为前面调用过一次,所以不会调用finalize(),产生问题。 所以,推荐不要使用finalize()方法,它跟析构函数不一样。
静/动态代理
为其它对象提供一种代理以控制对这个对象的访问控制,在程序运行时,通过反射机制动态生成。JDK动态代理的调用处理程序必须实现 InvocationHandler 接口,及使用 Proxy 类中的 newProxyInstance 方法动态的创建代理类。
多态
编译时多态
方法重载
- 根据实际参数的数据类型、个数和次序,Java 在编译时能够确定执行重载方法中的哪一个。
方法覆盖
通过父类对象引用变量引用子类对象来实现。当父类对象引用子类实例时。通过接口类型变量引用实现接口的类的对象来实现 。运行时多态主要是通过继承和接口实现的。
Java 反射?反射有什么缺点?你是怎么理解反射的(为什么框架需要反射)?
Java 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及 动态调用对象的方法的功能称为 Java 语言的反射机制。
- 优点:运行期类型的判断,动态加载类,提高代码灵活度。
- 缺点:
- 性能瓶颈:反射相当于一系列解释操作,通知 JVM 要做的事情,性能比直接的 Java 代码要慢很多
- 安全问题,让我们可以动态操作改变类的属性同时也增加了类的安全隐患。
在我们平时的项目开发过程中,基本上很少会直接使用到反射机制,但这不能说明反射机 制没有用,实际上有很多设计、开发都与反射机制有关。动态代理设计模式也采用了反射机制,还有我们日常使用的 Spring/Hibernate 等框架也大量使用到了反射机制。
- 我们在使用 JDBC 连接数据库时使用 Class.forName() 通过反射加载数据库的驱动程序;
- Spring 框架的 IOC(动态加载管理 Bean)创建对象以及 AOP(动态代理)功能都和反射有联系;
- 动态配置实例的属性
获取 Class 对象的两种方式
- 知道具体类的情况下可以使用:
- Class alunbarClass = TargetObject.class;
通过 Class.forName() 传入类的路径获取
内存泄漏:内存泄漏是指无用对象(不再使用的对象)持续占有内存或无用对象的内存得不 到及时释放,从而造成内存空间的浪费称为内存泄漏。
内存泄露的场景
静态集合类引起内存泄漏:静态成员的生命周期是整个程序运行期间。比如:Map 是在堆上动态分配的对象,正常情况下使用完毕后,会被 gc 回收。而如果 Map 被 static 修饰,且没有删除机制,静态成员是不会被回收的,所以会导致这个很大的 Map 一直停留在堆内存中。懒初始化 static 变量,且尽量避免使用。
- 当集合里面的对象属性被修改后,再调用 remove()方法时不起作用:修改 hashset 中对象的参数值,且参数是计算哈希值的字段。当一个对象被存储进 HashSet 集合中以后, 就不能修改这个对象中的那些参与计算哈希值的字段,否则对象修改后的哈希值与最初 存储进 HashSet 集合中时的哈希值就不同了。
- 各种连接对象( IO 流对象、数据库连接对象、网络连接对象)使用后未关闭:因为每个流 在操作系统层面都对应了打开的文件句柄,流没有关闭,会导致操作系统的文件句柄一 直处于打开状态,而jvm会消耗内存来跟踪操作系统打开的文件句柄。
- 监听器的使用:在释放对象的同时没有相应删除监听器的时候也可能导致内存泄露。
- 不正确使用单例模式是引起内存泄漏:单例对象在初始化后将在 JVM 的整个生命周期中存在(以静态变量的方式),如果单例对象持有外部的引用,那么这个对象将不能被JVM正常回收,导致内存泄漏。
解决措施
- 尽量减少使用静态变量,类的静态变量的生命周期和类同步的。
- 声明对象引用之前,明确内存对象的有效作用域,尽量减小对象的作用域,将类的成员 变量改写为方法内的局部变量;
- 减少长生命周期的对象持有短生命周期的引用;
- 使用 StringBuilder 和 StringBuffer 进行字符串连接,Sting 和 StringBuilder 以及 StringBuffer 等都可以代表字符串,其中 String 字符串代表的是不可变的字符串,后两者表示 可变的字符串。如果使用多个 String 对象进行字符串连接运算,在运行时可能产生大量临时字符串,这些字符串会保存在内存中从而导致程序性能下降。
- 对于不需要使用的对象手动设置 null 值,不管 GC 何时会开始清理,我们都应及时的将无用的对象标记为可被清理的对象;
- 各种连接(数据库连接,网络连接,IO 连接)操作,务必显示调用 close 关闭。
内存溢出场景
- JVM Heap(堆)溢出
- OutOfMemoryError: Java heap space: 发生这种问题的原因是 java 虚拟机创建的对象太多,在进行垃圾回收之间,虚拟机分配的到堆内存空间已 经用满了。JVM 在启动的时候会自动设置 JVM Heap 的值, 可以利用 JVM 提供的-Xmn -Xms -Xmx 等选项可进行设置。Heap的大小是新生代和老年代之和。
- 解决方法:
- 手动设置 JVM Heap(堆)的大小。
- 检查程序,看是否有死循环或不必要地重复创建大量对象。
Metaspace溢出
- java.lang.OutOfMemoryError: Metaspace 程序中使用了大量的 jar 或 class,使 java 虚拟机装载类的空间不够,与 metaspace 大小有关。方法区用于存放 Java 类型的相关信息。在类装载器加载 class 文件到内存的过程中,虚拟机会提取其中的类型信息,并将这些信息存储到方法区。当需要存储类信息而方法区的 内存占用又已经达到 -XX:MaxMetaspaceSize 设置的最大值,将会抛出 OutOfMemoryError 异常。对于这种情况的测试,基本的思路是运行时产生大量的类去填满方法区,直到溢出。
- 解决方法
- 通过 -XX:MetaspaceSize 和 -XX:MaxMetaspaceSize 设置永久代大小即可。
栈溢出
- java.lang.StackOverflowError : Thread Stack space:线程的方法嵌套调用层次太多(如递归调用),以致于把栈区溢出了
- 解决方法
- 解决方法
- 通过 -Xss: 来设置每个线程的 Stack 大小即可。
- 解决方法
- java.lang.StackOverflowError : Thread Stack space:线程的方法嵌套调用层次太多(如递归调用),以致于把栈区溢出了
GC Root 对象有哪些
- 方法区中的静态变量和常量引用的对象
- 虚拟机栈中引用对象
-
TCP滑动窗口是干什么的?TCP的可靠性体现在哪里?拥塞控制如何实现的?
滑动窗口
- 窗口是缓存的一部分,用来暂时存放字节流。发送方和接收方各有一个窗口,接 收方通过 TCP 报文段中的窗口字段告诉发送方自己的窗口大小,发送方根据这个值和其它信息设置自己的窗口大小。
- 发送窗口内的字节都允许被发送,接收窗口内的字节都允许被接收。接收窗口只会对窗口内 最后一个按序到达的字节进行确认。如果发送窗口内的字节已经发送并且收到了确认,那么 就将发送窗口向右滑动一定距离,直到第一个字节不是已发送并且已确认的状态;接收窗口 的滑动类似,接收窗口左部字节已经发送确认并交付主机,就向滑动接收窗口。
- 流量控制如何实现
- 流量控制是为了控制发送方发送速率,保证接收方来得及接收。
- 接收方发送的确认报文中的窗口字段可以用来控制发送方窗口大小,从而影响发送方的发送 速率。将窗口字段设置为 0,则发送方不能发送数据。
- 拥塞控制
- 如果网络出现拥塞,分组将会丢失,此时发送方会继续重传,从而导致网络拥塞 程度更高。因此当出现拥塞时,应当控制发送方的速率。TCP 主要通过四个算法来进行拥塞控制:慢开始、拥塞避免、快重传、快恢复。
- 发送方需要维护一个叫做拥塞窗口(cwnd)的状态变量。
- 慢开始与拥塞避免
- 发送的最初执行慢开始,令拥塞窗口大小为1,发送方只能发送1个报文段;当收到确认后,将拥塞窗口大小加倍。设置一个慢开始门限,当 拥塞窗口的大小大于慢开始门限 时,进入拥塞避免,每个轮次只将拥塞窗口加1。如果出现了超时,则令慢开始门限 = 拥塞窗口大小 / 2,然后重新执行慢开始。
- 快重传与快恢复
- 接收方,要求每次接收到报文段都应该对最后一个已收到的有序报文段进行确认。在 发送方,如果收到三个重复确认,那么可以知道下一个报文段丢失,此时执行快重传, 立即重传下一个报文段。在这种情况下,只是丢失个别报文段,而不是网络拥塞。因此 执行快恢复,令慢开始门限 = 拥塞窗口大小 / 2 ,拥塞窗口大小 = 慢开始门限 ,注意到此时直接进入拥塞避免。
- TCP使用超时重传来实现可靠传输
- 应用数据被分割成 TCP 认为最适合发送的数据块。
- TCP 给发送的每一个包进行编号,接收方对数据包进行排序,把有序数据传送给应用层。
- 校验和: TCP 将保持它首部和数据的检验和。这是一个端到端的检验和,目的是检测数据在传输过程中的任何变化。如果收到段的检验和有差错,TCP 将丢弃这个报文段和不确认收到此报文段。
- TCP 的接收端会丢弃重复的数据。
- 流量控制: TCP 连接的每一方都有固定大小的缓冲空间,TCP的接收端只允许发送端发送接收端缓冲区能接纳的数据。当接收方来不及处理发送方的数据,能提示发送方降低 发送的速率,防止包丢失。TCP 使用的流量控制协议是可变大小的滑动窗口协议。(TCP 利用滑动窗口实现流量控制)
- 拥塞控制:当网络拥塞时,减少数据的发送。
- ARQ协议:也是为了实现可靠传输的,它的基本原理就是每发完一个分组就停止发送,等待对方确认。在收到确认后再发下一个分组。
- 超时重传: 当 TCP 发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报文段。
解决死锁的策略
- 死锁预防
- 破坏保持和等待条件
- 一次性申请所有资源,之后不再申请资源,如果不满足资源条件则得不到资源分配。
- 破坏不可剥夺条件
- 当一个进程获得某个不可剥夺的资源时,提出新的资源申请,若不满足,则释放所有资源。
- 破坏循环等待条件
- 按某一顺序申请资源,释放资源则反序释放。
- 破坏保持和等待条件
- 死锁避免
- 进程在每次申请资源时判断这些操作是否安全。
- 死锁检测
- 判断系统是否属于死锁的状态,如果是,则执行死锁解除策略。
- 死锁解除
- 将某进程所占资源进行强制回收,然后分配给其他进程。(与死锁检测结合使用的)
Spring 框架中用到了哪些设计模式?
- 工厂设计模式: Spring使用工厂模式通过BeanFactory、ApplicationContext创建 bean 对象。
- 代理设计模式: Spring AOP 功能的实现。
- 单例设计模式: Spring 中的 Bean 默认都是单例的。
- 模板方法模式: Spring 中jdbcTemplate、hibernateTemplate等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。
- 包装器设计模式: 我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。
- 观察者模式:Spring 事件驱动模型就是观察者模式很经典的一个应用。
适配器模式:Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配Controller。
JVM数据区域的划分
Heap 存储几乎所有的实例对象
- MetaSpace 存储类元信息,字段,静态属性,方法,常量
- JVM stack 描述java方法执行的内存区域,线程私有
- 栈中的元素用于支持虚拟机进行方法调用,每个方法从开始调用到执行完成的过程就是栈帧入栈到出栈
- Native Method Stacks 为Native方法服务,调用本地方法,线程私有
- PC 程序计数器,线程私有