Java面试4

Java面试4

一、请你讲一下Java 8的新特性

1)Lambda表达式:可将功能视为方法参数、或者将代码视为数据。使用 Lambda 表达式,可以更简洁地表达单方法接口的实例。
2)方法引用:提供了非常有用的语法,可直接引用已用 Java 类或对象(实例)的方法或者构造器。与 Lambda 联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
3)对接口进行了改造:允许在接口中定义默认方法,默认方法必须使用 default 修饰。
4)Stream API:新添加的 Stream API(java.util.stream)支持对元素流进行函数式操作。Stream API 集成在 Collections API 中,可以对集合进行批量操作。
5)Date Time API:加强对日期与时间的处理。

二、请你说说Java的四种引用方式

java中的四种引用方式分别是:强引用、软引用、弱引用、虚引用。
1,强引用,以 new 关键字创建的引用都是强引用,被强引用引用的对象永远都不会被回收。
2,软引用:以 SoftRererenc 引用对象,被弱引用引用的对象只有在内存空间不足时会被垃圾回收。
3,弱引用,以 WeakReference 引用对象,被弱引用引用的对象一定会被回收,它只能存活到下一次垃圾回收。
4,虚引用:以 PhantomReference 引用对象,一个对象被引用引用后不会有任何影响,也无法通过该引用来获取该对象,只是其再被垃圾回收时会收到一个系统通知。

三、说说你对ThreadLocal的理解

ThreadLocal 是线程变量,它将需要共享的数据复制多份,每个线程各一份。内部使用了一个 ThreadLoadMap 来存储信息,key 是当前线程,而 value 是我们需要传输的数据,我们可以在线程执行的过程中通过 get/set,操作 map 中的数据,不过需要注意的是在使用线程池的时候,线程执行完毕不会被销毁,而是进入线程池中等待,这个时候我们需要手动释放一下 map 中的数据,以免造成内存浪费。

四、说说synchronize的用法及原理

用法:
1.作用在静态方法上,则锁是当前类的Class对象。
2.作用在普通方法上,则锁是当前的实例(this)。
3.作用在代码块上,则需要在关键字后面的小括号里,显式指定一个对象作为锁对象。 能够保证同一个时刻只有一个线程执行该段代码,保证线程安全。 在执行完或者出现异常时自动释放锁。
原理:底层是采用Java对象头来存储锁信息的,并且还支持锁升级。在JVM里的实现都是 基于进入和退出Monitor对象来实现方法同步和代码块同步。

五、synchronized和Lock有什么区别

1.使用方式:synchronized 隐式锁,自动释放锁,在这种同步方式下,我们需要依赖 Monitor(同步监视器)来实现线程通信。作用在静态方法上,类的Class对象;作用在实例方法上,当前实例(this);作用在代码块上,则需要在关键字后面的小括号里显式指定一个对象作为 Monitor。
Lock 接口是显式锁,手动释放,Lock 接口具备更大的灵活性。
2.功能特性:Lock 弥补了 synchronized 的不足,它新增特性包括:可中断地获取锁,非阻塞地获取锁,可超时地获取锁。
3.实现机制:synchronized 的底层是采用 Java 对象头来存储锁信息的,lock 通过 trylock 查看是否加锁成功。

六、说说volatile的用法及原理

volatile主要用于修饰多线程中的共享变量,它可以保证变量的可见性和有序性,不能保证原子性。
可见性是指在多线程环境中若某个线程修改了该变量,那么其他线程就能察觉到变量的修改。可见性主要是通过在内存模型中修改了某值则将它同步会主内存,在读取前从主内存刷新该变量值来实现的。
有序性是指在某个线程中对该变量的操作顺序时透明的不会改变的。在java中由于寄存器和内存处理速度存在巨大差异所以java为了提升运行速度会将编译好的代码进行一定程度的重排但不影响其在独立线程环境下的运行结果,但是在多线程环境中有可能出错。volatile可以避免这种指令重排实现其在多线程的顺序一致的效果。

七、说说wt()和sleep()的区别

所属的类型不同: wt() 是 Object 类的实例方法,调用该方法的线程将进入 WTING 状态。sleep() 是 Thread 类的静态方法,调用该方法的线程将进入 TIMED_WTING 状态。
对锁的依赖不同:wt() 依赖于 synchronized 锁,它必须通过监视器进行调用,在调用后线程会释放锁。sleep() 不依赖于任何锁,所以在调用后它也不会释放锁。
返回的条件不同:调用 wt() 进入等待状态的线程,需要由 notify()/notifyAll() 唤醒,从而返回。调用 sleep() 进入超时等待的线程,需要在超时时间到达后自动返回。

八、请你说说内存溢出

内存溢出:指的是程序运行过程中申请的内存大于系统能够提供的内存,导致无法申请到足够的内存,于是就发生了内存溢出。
引起内存溢出的原因有:1)内存加载的数据量过于庞大,如一次从数据库取出过多的数据。2)代码中存在死循环或者死循环中产生大量的对象实体。3)启动内存值设定过小。
解决内存溢出的方案:1)修改 JVM 启动参数,直接增加内存。2)检查错误日志,查看 “OutOfMemory” 错误之前是否存在异常。3)对代码进行 debug 分析。4)使用内存工具动态查看内存使用情况。
常见的内存溢出出现在:1)堆,对象创建过多。2)栈溢出。3)方法区和运行时常量池,创建大量动态类。

九、请你说说内存泄漏

内存泄漏,是指不再使用的对象仍然被引用,导致垃圾收集器无法回收它们的内存。由于不再使用的对象仍然无法清理,甚至这种情况可能会越积越多,最终导致致命的OutOfMemoryError。
可以按照如下的思路来分析和解决内存泄漏问题:
1.启用分析器,使用分析器,我们可以比较不同的方法并找到可以最佳利用资源的方式。
2.启用详细垃圾收集日志,通过启用详细垃圾收集日志,我们可以跟踪GC的详细进度。
3.使用引用对象,我们还可以借助java.lang.ref包内置的Java引用对象来规避问题。
4.Eclipse内存泄漏警告,Eclipse会在遇到明显的内存泄漏情况时显示警告和错误。
5. 基准测试,我们可以通过执行基准测试来衡量和分析Java代码的性能。
6.代码审查 最后,我们总是采用经典的老方式来进行简单的代码演练

十、请你讲一下Java NIO

NIO 就是有3个核心组件,分别是 Buffer,Channel,Selector。
Buffer:缓冲区,读写数据都要进过缓冲区,而不是经过流。
Channel:通道,全双工双向通道。用来读取和写入数据的。
Selector:多路复用器,不断的轮询注册在 Selector 上的 Channel,如果有新的 TCP 接入,就会被轮询出来。Channel 变成就绪状态,SelectionKey 获取 Channel 的集合,进行后续 IO 操作。JDK 使用 epoll(),没有了最大连接句柄 1024/2048 的限制,一个线程负责 Selector 的轮询,就能接入成千上万的客户端。

十一、请你说说JUC

JUC 是 java.util.concurrent 的缩写,它是jdk5提供的并发包,包内主要提供了并发操作的各种工具,大致可以分为5类:原子类、锁、线程池、并发容器、同步工具。
1)原子类:并发包下提供了 atomic 子包,这个包下的原子操作类提供了一种用法简答、性能高效、线程安全的更新一个变量的方式。在atomic包里一共提供了17个类,属于原子更新基本类型、原子更新引用类型、原子更新属性、原子更新数组4种类型的原子更新方式。
2)锁:jdk5开始,并发包中新增了 Lock 接口以及相关实现类,用来实现锁功能,它提供了与synchronize关键字类似的同步功能,只是在使用时需要显式的获取和释放锁。虽然它缺少了隐式释放锁的便捷性,但是却拥有了多种synchronize关键字所不具备的同步特性,包括:可中断的获取锁、非阻塞的获取锁、可超时的获取锁。
3)线程池:从jdk5开始,并发包下新增了内置的线程池。其中,ThreadPoolExecutor 类代表常规的线程池,而它的子类ScheduledThreadPoolExecutor 对定时任务提供了支持,在子类中我们可以周期性的执行某个任务,也可以延迟若干时间再执行某个任务。
4)并发容器从 jdk5 开始,并发包下新增了大量高效的并发容器,安装实现机制可以分为三类:第一类是以降低锁粒度来提高并发性能的容器,类名以 Concurrent 开头,如 ConcurrentHashMap。第二类是采用写时复制技术实现的并发容器,类名以 CopyOnWrite 开头,如 CopyOnWriteArrayList。第三类是采用 Lock 实现的阻塞队列,内部创建两个 Condition 分别用于生产者和消费者的等待,这些类都实现了 BlockingQueue 接口,如 ArrayBlockQueue。
5)同步工具:从 jdk5 开始,并发包下新增了几个有用的并发工具类,一样可以保证线程安全。其中 Semaphore 类代表信号量,可以控制同时访问特定资源的线程数量,Countdownlatch 类允许一个或多个线程等待其他线程完成操作,Cyclicbarrier 类可以让一组线程达到一个屏障时被阻塞,直到最后一个线程到达屏障时,屏障才会打开,所有被屏障拦截的线程才会继续运行。

十二、请你说说BIO、NIO、O

BIO:同步并阻塞,服务实现模式为一个连接对应一个线程,即客户端发送一个连接,服务端要有一个线程来处理,如果连接多了,线程数量不够,就只能等待,即会发生阻塞。
NIO:同步非阻塞,服务实现模式为一个线程可以处理多个连接,即客户端发送的连接都会注册到多路复用器上,然后进行轮询连接,有IO请求就处理。
AIO:异步非阻塞,引入了异步通信,采用的是 proactor 模式,特点是:有效的请求才启动线程,先由操作系统完成再通知服务端。

十三、说说你对AQS的理解

AQS 是队列同步器,是用来构建锁的基础框架。Lock 实现类都是基于 AQS 实现的。
AQS是基于模板设计模式,所以锁的实现要继承 AQS 并实现指定的方法。AQS 内部定义了一个先进先出的队列来实现线程的同步,同时还定义了同步状态来记录锁的信息。同步状态是一个 int类型整数,在表示状态的时候还能表示数量。0表示无锁,大于0表示锁的重入次数。
AQS模板方法将将管理同步状态的逻辑提炼出来形成标准流程。主要包括:独占获取同步状态,独占释放同步状态,共享获取同步状态,共享释放同步状态。

十四、Java哪些地方使用了CAS

原子类、AQS、并发容器。
原子类中以 AtomicInteger 为例,内部提供了诸多原子操作方法。如原子替换整数值、增加指定的值、加一。
对于AQS,向队列中添加节点时会以CAS方式尝试一次,如果失败进入自旋状态反复尝试。共享方式释放同步锁时也是以CAS方式对同步状态进行同步修改的。
对于并发容器以 ConcurrentHashMap 为例内部多次使用了CAS。以CAS修改初始化状态。put方法初始化头结点以CAS方式初始化好的头结点设置到槽首位。get是以CAS获取指定槽的头节点避免其他线程修改。

十五、请你说说IO多路复用(select、poll、epoll)

IO多路复用指的是单个进程或者线程能同时处理多个IO请求,select,epoll,poll是LinuxAPI提供的复用方式。本质上由操作系统内核缓冲IO数据,使得单个进程线程能监视多个文件描述符。select是将装有文件描述符的集合从用户空间拷贝到内核空间,底层是数组,poll和select差距不大,但是底层是链表,这就代表没有上限,而select有数量限制。epoll则是回调的形式,底层是红黑树,避免轮询,时间复杂度从O(n)变为O(1)。

十六、epoll原理

epoll是一种高效地IO多路复用技术。调
用epoll_create()会创建一个结构体数据,里面包含一个用于遍历扫描文件描述符状态的红黑树和一个就绪列表。
调用epoll_ctr()可以进行增删改要监听的文件描述符及事件。
调用epoll_wt()就会让内核检测就绪事件,将就绪事件到该表列表返回。
epoll有两种触发机制。
水平触发:当文件描述符状态改变时就立即进行IO操作,如果不进行处理将继续通知。
边沿触发:是高速工作方式。该机制默认你已经知道了状态描述符改变,再你改变IO状态后描述状态改变后不会通知。该种方式减少了epoll的重复触发次数,提升了效率。必须使用非阻塞接口防止因一个文件描述符阻塞读写其他任务饿死

十七、简单说下你对JVM的了解

JVM是Java语言跨平台的关键,Java在虚拟机层面隐藏了底层技术的复杂性以及机器与操作系统的差异性。运行程序的物理机千差万别,而JVM则在千差万别的物理机上面建立了统一的运行平台,实现了在任意一台JVM上编译的程序,都能在任何其他JVM正常运行。
JVM由三部分组成:类加载子系统、执行引擎和执行时数据区。 1)类加载子系统:可以根据指定的全限定名来载入类或接口。 2)执行引擎:负责执行那些包含在被载入类的方法中的指令。 3)当程序运行时,JVM需要内存来存储许多内容,例如:字节码、对象、参数、返回值、局部变量、运算的中间结果等,JVM 会把这些东西都存储到运行时数据区中,以便于管理。而运行时数据区又可以分为方法区、堆、虚拟机栈、本地方法栈、程序计数器。

十八、说说JVM的双亲委派模型

当我们使用一个类时会先对类进行加载,双亲委派模型就是java提供的一种类加载机制。java中默认存在三种类加载器:
1,启动类加载器(BootstrapClassLoader),主要用于加载<java_home>\lib中的类库。
2,扩展类加载器(ExtClassLoader),主要用于加载<java_home>\lib\ext文件夹下的类库。
3,应用程序类加载器(Application ClassLoader),用于classpath下的类,即用户自定义的类。
启动类加载器存在于jvm层面,使用c++语言编写,用户无法调用。双亲委派模型建立了一个逻辑上的从上到下的父子关系(启动类加载器,扩展类加载器,应用程序类加载器,这里的父子类不是继承关系),向上委派:当一个类加载器收到一个类加载请求后他不会立即执行类加载而时将请求向上转发交给父类加载器去执行。向下查找:当一个父类加载器无法加载某个类时便会将类加载的请求向下转发交给子类实现。双亲委派模型最大的优点是可以避免类的覆盖。</java_home></java_home>

十九、说说类的实例化过程

在JVM中,对象的创建遵循如下过程:
当JVM遇到一条字节码new指令时,首先会去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,那必须先执行相应的类加载过程。在类加载检查通过后,
接下来,虚拟机将为新生对象分配内存。对象所需内存的大小在类加载完成后便可完全确定,为对象分配空间的任务实际上便等同于把一块确定大小的内存块从Java堆中划分出来。内存分配完成之后,虚拟机必须将分配到的内存空间都初始化为零值,如果使用了TLAB的话,这一项工作也可以提前直TLAB分配时顺便进行。这步操作保证了对象的实例字段在Java代码中可以不赋初始值就直接使用,使程序能访问到这些字段的数据类型所对应的零值。
接下来,虚拟机还要对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息。这些信息存放在对象的对象头之中。根据虚拟机当前运行状态的不同,如是否启动偏向锁等,对象头会有不同的设置方式。
在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但是从Java程序的视角来看,对象创建才刚刚开始——构造函数,即Class文件中的init()方法还没执行,所有的字段都为默认的零值,对象需要的其他资源和状态信息也还没有按照预订的意图构造好。一般来说,new指令之后会接着执行init()方法,按照程序猿的意愿对对象进行初始化,这样一个真正可用的对象才能完全被构造出来。

二十、请你讲下CMS垃圾回收器

CMS垃圾收集器采用标记清除算法,使用多线程实现,所以它的应用场景一般为服务端系统的老年代。它是一个以达到在垃圾回收期间用户线程低停顿为目标的垃圾收集器。
CMS垃圾收集器垃圾回收分为四个阶段:1,初始标记:只对与 GCRoots 有直接关键的对象进行可达性分析的标记。2,并发标记:标记整个 GCRoots 引用链中的对象,与用户线程并发执行。3,重新标记:用于更新在并发标记过程中被复活的对象。4,并发清除:清除标记阶段判断的已死亡的对象。该流程与用户线程并发执行。
缺点:1,它使用标记清除算法导致内存碎片化,2,会产生在并发清除阶段的浮动垃圾,只有到下一次垃圾回收时才会被清除。

二十一、请你讲下G1垃圾回收器

G1 收集器是一个多线程的采用标记清除算法的,面向混合收集(同时收集新生带和老年带)的一个垃圾收集器。G1收集器将整个堆内存区域划分为多个大小相等的 region,以 region 为单位进行垃圾收集并获取每个 region 的收集效率和收集收益,通过一张优先级表对其进行维护。同时每个 region 中维护了一个 remember set 用来存储该分区中的对象所引用的对象在其他分区的位置来避免在做可达性分析算法时全堆扫描。
G1 垃圾收集器垃圾回收主要包括四个流程:1,初始标记,2,并发标记(类似于CMS),3,最终标记在并发标记中维护了一个 remember set log 用来记录在该阶段发生变化的对象引用关系,在该阶段就是将该信息同步到最终标记信息中。4,并发筛选回收:根据优先级表选择分区进行垃圾回收,用户线程不停顿。

二十二、说说GC的可达性分析

可达性分析算法用于判断对象是否可以被回收,程序通过 GCRoots 中的对象为起点以类之间的引用关系建立引用链,最终形成一个类似于数据结构中森林的一个结果,不存在与森林中的对象便是需要被回收的对象。这里的GcRoots主要包括线程栈中引用的变量,本地方法栈中引用的变量,方法区中的静态引用对像,常量池中的常量引用对象和被锁引用的对象。对一个对象真正的宣告回收需要经历两次标记过程,如果一个对象不在引用链上就会对他进行第一次标记,并判断它是否重新了finalize方法,若未重写或finalize方法已经被执行过了则会直接回收对象,否则会创建一个F-queue队列来存储这些对象,并启动一个低优先级的Finalizer线程区执行它们的finalize方法。第二次标记,稍后收集器会对队列中的对象进行可达性分析并标记,若任然存在标记则表明该对象没有通过finalize方法实现自救则直接回收,否则对象复活。任何对象的finalize方法都只会被调用一次。

二十三、说说垃圾收集器

Serial(新生代)、Serial Old(老年代):适用于单核小CPU,单核工作,回收时会暂停其他工作stop the world。
PawNew(新生代)、CMS(老年代):适用于多核CPU,最求短暂停时间,多核工作,使用标记清除算法,最短的暂停时间。
Parallel Scavenge(新生代-标记复制算法)、Parallel Old(老年代-标记整理算法):使用与多核CPU,追求最大吞吐量。
G1:适用于大内存多核CPU服务器,它不按整个新生代或老年代去回收,而是开辟了面向局部收集,实现了较小的收集暂停时间和高吞吐量。

二十四、说说你了解的JVM内存模型

JVM由三部分组成:类加载子系统、执行引擎、运行时数据区
1、类加载子系统:可以根据指定的全限定名来载入类或接口。
2、执行引擎:负责执行那些包含在被载入类的方法中的指令。
3、运行时数据区:分为方法区、堆、虚拟机栈、本地方法栈、程序计数器。当程序运行时,JVM需要内存来存储许多内容,例如:字节码、对象、参数、返回值、局部变量、运算的中间结果等,把这些东西都存储到运行时数据区中,以便于管理。

二十五、说说JVM的垃圾回收机制

JVM的垃圾回收机制是遵循分代收集理论进行设计的,主要分为四种收集方式:
1.新生代收集,目标为新生代的垃圾收集。
2.老年代收集:目标为老年代的垃圾收集,目前只有CMS收集器会有这种行为。
3.混合收集:目标为整个新生代及部分老年代的垃圾收集,目前只有G1收集器会有这种行为。
4.整堆收集:目标为整个方法区和堆的垃圾收集。
常见的垃圾回收算法包括:
标记清除算法:缺点:内存碎片化,优点:速度快。
标记复制算法:缺点:占用内存大,优点:内存连续。
标记整理算法:优点:内存连续,缺点:整理效率低。

二十六、说说JVM的垃圾回收算法

1.引用计数法,每次赋值时均要维护引用计数器且计数器本身也有一定的消耗,较难处理循环引用,一般不采用这种方式;
2.复制算法,将内存分为两块,每次只使用其中一块,当这块内存用完,就将还活着的对象复制到另外一块上面,效率高且没有碎片,但是需要双倍的空间,年轻代中使用复制算法;
3.标记-清除法,先标记要清除的对象,然后统一回收这些对象,不需要额外的空间,但是需要两次扫描耗时严重并且会产生内存碎片;
4.标记-整理法,标记存活对象,然后将标记的存活对象按内存地址依次排序,清除边界外未标记的对象,没有内存碎片,但是需要移动对象。老年代一般用标记-清除和标记-整理的混合实现。

二十七、说说Java运行时数据区

当程序运行时,JVM 需要内存来存储许多内容,例如:字节码、对象、参数、返回值、局部变量、运算的中间结果等,JVM 会把这些东西都存储到运行时数据区中,以便于管理,而运行时数据区又可分为程序计数器、虚拟机栈、本地方法栈、堆和方法区。
1)程序计数器:程序计数器是一块很小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。在Java虚拟机的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,它是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。由于Java虚拟机的多线程是通过线程轮流切换、分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器都只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各个线程之间的计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。
2)虚拟机栈:虚拟机栈也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是 Java 方法执行的线程内存模型:每个方法被执行的时候,Java 虚拟机都会同步创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
3)本地方法栈:本地方法栈与虚拟机栈所发挥的作用是非常相似的,其区别只是虚拟机栈为虚拟机执行 Java 方法服务,而本地方法栈则是为了虚拟机使用到的本地方法服务。
4)堆:堆是虚拟机所管理的最大的一块内存空间。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建,此内存区域的唯一目的就是存放对象实例,Java世界里“几乎”所有的对象实例都是在这里分配内存的。堆是垃圾收集器管理的存储区域,因此也称为“GC堆”。
5)方法区:方法区与堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。运行时常量池是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息时常量池表,用于存放编译期生成的各种字面量与符号。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值