Java中高级面试题总览(二)(2)

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: Java中高级面试题总览(二)

43.字符流和字节流的区别

44.Servlet是否是线程安全?如果不安全怎么处理 ?

45.stringbuffer内部的数据结构在字符串变化时怎么操作

String 类不可变,内部维护的char[] 数组长度不可变,为final修饰,String类也是final修饰,不存在扩
容。字符串拼接,截取,都会生成一个新的对象。频繁操作字符串效率低下,因为每次都会生成新的对象。    
StringBuilder 类内部维护可变长度char[] , 初始化数组容量为16,存在扩容, 其append拼接字符串方法内
部调用System的native方法,进行数组的拷贝,不会重新生成新的StringBuilder对象。非线程安全的字符串操
作类, 其每次调用 toString方法而重新生成的String对象,不会共享StringBuilder对象内部的char[],会进
行一次char[]的copy操作。    
StringBuffer 类内部维护可变长度char[], 基本上与StringBuilder一致,但其为线程安全的字符串操作类,
大部分方法都采用了Synchronized关键字修改,以此来实现在多线程下的操作字符串的安全性。其toString方法
而重新生成的String对象,会共享StringBuffer对象中的toStringCache属性(char[]),但是每次的
StringBuffer对象修改,都会置null该属性值

46.new一个Object对象占用多少内存

如果JDK64位,8字节是引用,16字节是堆内存(对象头大小),总共是8+16=24字节,所以new一个Object对象占
用8+16=24字节。
如果JDK是32位,按如上分析方法可知new一个Object对象占用4+8=12字节

【转】Java new一个Object对象占用多少内存? - MERRU - 博客园

jvm

1.GC 如何判断对象失去引用

引用计数算法(已废弃)

可达性分析

引用计数算法简单高效,早期的Java虚拟机中使用这个方式,但是正如上面提到的不能解决“引用闭环”的问题,后来的Java虚拟机中普遍采用根集算法。从GCRoot(比如一个静态变量)开始遍历引用关系,能遍历到的,叫做引用可达,遍历不到的叫做不可达。不可达的对象就被判“死刑了”,GC的时候将被枪毙掉。

可以作为GCRoots的对象包括下面几种:

(1). 虚拟机栈(栈帧中的局部变量区,也叫做局部变量表)中引用的对象。

(2). 方法区中的类静态属性引用的对象。

(3). 方法区中常量引用的对象。

(4). 本地方法栈中JNI(Native方法)引用的对象。

2.JVM崩溃原因查找?(jvm内存镜像文件分析,常用jvm性能分析命令)

3.内存泄露、溢出的异同

1、内存泄漏是指分配出去的内存无法回收了

2、内存溢出是指程序要求的内存,超出了系统所能分配的范围,从而发生溢出。

4.如何检测内存泄露?

OOM是内存泄漏的常见指示

Java heap space 不一定意味着内存泄漏

PermGen space  此错误消息表明永久代已满。

Requested array size exceeds VM limit 此错误表示应用程序(或该应用程序使用的API)尝试分配大于堆大小的数组

Request bytes for . Out of swap space 当本机堆的分配失败并且本机堆可能将被耗尽时,HotSpot VM会抛出此异常

Native method 在JNI或本机方法中检测到Java内存分配失败,而不是在Java VM代码中检测到

Application Crash Without OOM 应用程序可能会在从本机堆分配失败后很快崩溃

Java VisualVM执行内存泄漏检测的工具

5.怎么打出线程栈信息

jstack

6如何使对象 GC 后再活一次

7.jvm内存哪些是线程共享,哪些是线程独享

线程共享:堆,非堆,本地方法栈

线程独享:栈,程序计数器,

8.什么情况下会发生栈内存溢出?

9.出现了内存溢出OOM,你怎么排错

Java.lang.OutOfMemeoryError:GC overhead limit exceeded

如上异常,即程序在垃圾回收上花费了98%的时间,却收集不回2%的空间,通常这样的异常伴随着CPU的冲高。定位方法同上(内存泄漏)

Java.lang.OutOfMemoryError: PermGen space(JAVA8引入了Metaspace区域)

永久代内存被耗尽(内存溢出)

Java.lang.OutOfMemeoryError:Unable to create new native thread

Java应用已经达到它可以运行的线程数的上限.

解决方法

1、检查是否存在大对象的分配,最有可能的是大数组分配

2、通过jmap命令,把堆内存dump下来,使用mat工具分析一下,检查是否存在内存泄露的问题

3、如果没有找到明显的内存泄露,使用 -Xmx 加大堆内存

4、还有一点容易被忽略,检查是否有大量的自定义的 Finalizable 对象,也有可能是框架内部提供的,考虑其存在的必要性

几种OOM异常分析_sunquan291的专栏-CSDN博客_oom异常

10.JVM的工作原理(内存模型)

11.Eden和Survivor比例

12.常见JVM参数列表

13.JVM内存为什么要分成新生代,老年代,持久代。新生代中为什么要分为Eden和Survivor?

14.jvm中一次完整的 GC流程是怎样的?

15.你知道哪几种垃圾收集器,各自的优缺点,重点讲下 cms,包括原理,流程,优缺点

16.JVM为什么需要GC

多线程

1.thread类常用方法

2.Java 实现多线程的方式有哪些

3.什么是线程安全

4.volatile的原理,作用,能代替锁么

5.画一个线程的生命周期状态图

在线程的生命周期中,它要经过新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)五种状态

6.sleep和wait的区别

7.Lock与Synchronized的区别

8.ReentrantLock 获取锁定的四种方式

9.解释以下名词:重排序,自旋锁,偏向锁,轻量级锁,可重入锁,公平锁,非公平锁,乐观锁,悲观锁

重量级锁( Mutex Lock)

Synchronized 是通过对象内部的一个叫做监视器锁( monitor)来实现的。但是监视器锁本质又是依赖于底层的操作系统的 Mutex Lock 来实现的。而操作系统实现线程之间的切换这就需要从用户态转换到核心态,这个成本非常高,状态之间的转换需要相对比较长的时间,这就是为什么Synchronized 效率低的原因。因此, 这种依赖于操作系统 Mutex Lock 所实现的锁我们称之为“重量级锁” 。 JDK 中对 Synchronized 做的种种优化,其核心都是为了减少这种重量级锁的使用。JDK1.6 以后,为了减少获得锁和释放锁所带来的性能消耗,提高性能,引入了“轻量级锁”和“偏向锁”。

轻量级锁

锁的状态总共有四种:无锁状态、偏向锁、轻量级锁和重量级锁。锁升级随着锁的竞争,锁可以从偏向锁升级到轻量级锁,再升级的重量级锁(但是锁的升级是单向的,也就是说只能从低到高升级,不会出现锁的降级)。“ 轻量级” 是相对于使用操作系统互斥量来实现的传统锁而言的。但是,首先需要强调一点的是,轻量级锁并不是用来代替重量级锁的,它的本意是在没有多线程竞争的前提下,减少传统的重量级锁使用产生的性能消耗。在解释轻量级锁的执行过程之前, 先明白一点,轻量级锁所适应的场景是线程交替执行同步块的情况,如果存在同一时间访问同一锁的情况,就会导致轻量级锁膨胀为重量级锁。

偏向锁

Hotspot 的作者经过以往的研究发现大多数情况下锁不仅不存在多线程竞争,而且总是由同一线程多次获得。 偏向锁的目的是在某个线程获得锁之后,消除这个线程锁重入(CAS)的开销,看起来让这个线程得到了偏护。 引入偏向锁是为了在无多线程竞争的情况下尽量减少不必要的轻量级锁执行路径,因为轻量级锁的获取及释放依赖多次 CAS 原子指令, 而偏向锁只需要在置换ThreadID 的时候依赖一次 CAS 原子指令(由于一旦出现多线程竞争的情况就必须撤销偏向锁,所以偏向锁的撤销操作的性能损耗必须小于节省下来的 CAS 原子指令的性能消耗)。上面说过, 轻量级锁是为了在线程交替执行同步块时提高性能, 而偏向锁则是在只有一个线程执行同步块时进一步提高性能

 

自旋锁

自旋锁原理非常简单, 如果持有锁的线程能在很短时间内释放锁资源,那么那些等待竞争锁

的线程就不需要做内核态和用户态之间的切换进入阻塞挂起状态,它们只需要等一等(自旋),等持有锁的线程释放锁后即可立即获取锁,这样就避免用户线程和内核的切换的消耗。线程自旋是需要消耗 cup 的,说白了就是让 cup 在做无用功,如果一直获取不到锁,那线程也不能一直占用 cup 自旋做无用功,所以需要设定一个自旋等待的最大时间。如果持有锁的线程执行的时间超过自旋等待的最大时间扔没有释放锁,就会导致其它争用锁的线程在最大等待时间内还是获取不到锁,这时争用线程会停止自旋进入阻塞状态。

自旋锁的优缺点

自旋锁尽可能的减少线程的阻塞,这对于锁的竞争不激烈,且占用锁时间非常短的代码块来

说性能能大幅度的提升,因为自旋的消耗会小于线程阻塞挂起再唤醒的操作的消耗,这些操作会导致线程发生两次上下文切换!

但是如果锁的竞争激烈,或者持有锁的线程需要长时间占用锁执行同步块,这时候就不适合使用自旋锁了,因为自旋锁在获取锁前一直都是占用 cpu 做无用功,占着 XX 不 XX,同时有大量线程在竞争一个锁,会导致获取锁的时间很长,线程自旋的消耗大于线程阻塞挂起操作的消耗,其它需要 cup 的线程又不能获取到 cpu,造成 cpu 的浪费。所以这种情况下我们要关闭自旋锁;

公平锁( Fair)

加锁前检查是否有排队等待的线程,优先排队等待的线程,先来先得

非公平锁( Nonfair)

加锁时不考虑排队等待问题,直接尝试获取锁,获取不到自动到队尾等待

1. 非公平锁性能比公平锁高 5~10 倍,因为公平锁需要在多核的情况下维护一个队列

2. Java 中的 synchronized 是非公平锁, ReentrantLock 默认的 lock()方法采用的是非公锁。

 

可重入锁(递归锁)

本文里面讲的是广义上的可重入锁,而不是单指 JAVA 下的 ReentrantLock。 可重入锁,也叫做递归锁,指的是同一线程 外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响。在 JAVA 环境下 ReentrantLock 和 synchronized 都是 可重入锁

 

乐观锁

乐观锁是一种乐观思想,即认为读多写少,遇到并发写的可能性低,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,采取在写时先读出当前版本号,然后加锁操作(比较跟上一次的版本号,如果一样则更新),如果失败则要重复读-比较-写的操作。java 中的乐观锁基本都是通过 CAS 操作实现的, CAS 是一种更新的原子操作, 比较当前值跟传入值是否一样,一样则更新,否则失败。

悲观锁

悲观锁是就是悲观思想,即认为写多,遇到并发写的可能性高,每次去拿数据的时候都认为别人会修改,所以每次在读写数据的时候都会上锁,这样别人想读写这个数据就会 block 直到拿到锁。java中的悲观锁就是 Synchronized,AQS框架下的锁则是先尝试 cas乐观锁去获取锁,获取不到,才会转换为悲观锁,如 RetreenLock。

10.重排序

11.用过哪些原子类,他们的原理是什么

12.线程池原理,并说说newCache和newFixed有什么区别,构造函数的各个参数的含义是什么

13.线程池的关闭方式有几种,各自的区别是什么

shutdown()调用后,不可以再 submit 新的 task,已经 submit 的将继续执行 shutdownNow()调用后,试图停止当前正在执行的 task,并返回尚未执行的 task 的 list

14.假如有一个第三方接口,有很多个线程去调用获取数据,现在规定每秒钟最多有10个线程同时调用它,如何做到

Semaphore类是一个计数信号量,必须由获取它的线程释放, 通常用于限制可以访问某些资源(物理或逻辑的)线程数目。

15.spring的controller是单例还是多例,怎么保证并发的安全

16.用三个线程按顺序循环打印 abc 三个字母,比如 abcabcabc

17.ThreadLocal原理是什么,用的时候要注意什么

18.AQS同步器的实现原理(AbstractQueuedSynchronizer)

19.countdowlatch内部原理和用法(比如countdownlatch的await方法是怎么实现的)

CountDownLatch是基于AQS的共享锁来实现的,由于内部类继承了AQS,所以它内部也是FIFO队列,同时也一样是

前驱节点唤醒后继节点,不能像CyclicBarrier那样使用完毕后还可以复用;

1任务分为N个任子线程去执行,state也初始化为N(注意N要要与线程个数一致)

2这N个线程也是并行执行的,每个子线程执行完后后countDown()一次,state会CAS减1

3等到所有子线程都执行完成之后即(state==0),会调用LockSupport.unpark(s.thread)

4然后主调用会从await()函数返回(之前是通过LockSupport.park(this);阻塞),继续后余动作

20.cyclicbarrier 的内部原理和用法

CyclicBarrier实现主要基于ReentrantLock
默认的构造方法是CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用await方法告
诉CyclicBarrier已经到达屏障位置,线程被阻塞。
另外一个构造方法CyclicBarrier(int parties, Runnable barrierAction),其中barrierAction任务会在
所有线程到达屏障后执行。

深入浅出java CyclicBarrier - 简书

21.Lock锁的实现原理

 需要实现锁的功能,两个必备元素:
一个是表示(锁)状态的变量(我们假设0表示没有线程获取锁,1表示已有线程占有锁),该变量必须声明为
voaltile类型;
另一个是队列,队列中的节点表示因未能获取锁而阻塞的线程。

22Synchronized对象锁和类锁的区别

23.synchronized的原理是什么

24.进程和线程的区别

25.如果让你实现一个并发安全的链表,你会怎么做

26.多线程如果线程挂住了怎么办

27.简述ConcurrentLinkedQueue和LinkedBlockingQueue 的用处和不同之处

28.Queue添加数据方法add()put()offer()不同之处

add方法在添加元素的时候,若超出了度列的长度会直接抛出异常

offer方法在添加元素时,如果发现队列已满无法添加的话,会直接返回false。

put方法,若向队尾添加元素的时候发现队列已经满了会发生阻塞一直等待空间,以加入元素。

29.导致线程死锁的原因?怎么解除线程死锁

30编写一段死锁代码

//首先我们先定义两个final的对象锁.可以看做是共有的资源.
 final Object lockA = new Object();
 final Object lockB = new Object();
//生产者A
 class ProductThreadA implements Runnable{
   @Override
   public void run() {
//这里一定要让线程睡一会儿来模拟处理数据 ,要不然的话死锁的现象不会那么的明显.这里就是同步语句块里面,首先获得对象锁lockA,然后执行一些代码,随后我们需要对象锁lockB去执行另外一些代码.
     synchronized (lockA){
     //这里一个log日志
       Log.e("CHAO","ThreadA lock lockA");
       try {
         Thread.sleep(2000);
       } catch (InterruptedException e) {
         e.printStackTrace();
       }
       synchronized (lockB){
        //这里一个log日志
         Log.e("CHAO","ThreadA lock lockB");
         try {
           Thread.sleep(2000);
         } catch (InterruptedException e) {
           e.printStackTrace();
         }
       }
     }
   }
 }
 //生产者B
 class ProductThreadB implements Runnable{
 //我们生产的顺序真好好生产者A相反,我们首先需要对象锁lockB,然后需要对象锁lockA.
   @Override
   public void run() {
     synchronized (lockB){
      //这里一个log日志
       Log.e("CHAO","ThreadB lock lockB");
       try {
         Thread.sleep(2000);
       } catch (InterruptedException e) {
         e.printStackTrace();
       }
       synchronized (lockA){
        //这里一个log日志
         Log.e("CHAO","ThreadB lock lockA");
         try {
           Thread.sleep(2000);
         } catch (InterruptedException e) {
           e.printStackTrace();
         }
       }
     }
   }
 }
 //这里运行线程
ProductThreadA productThreadA = new ProductThreadA();
ProductThreadB productThreadB = new ProductThreadB();
   Thread threadA = new Thread(productThreadA);
   Thread threadB = new Thread(productThreadB);
   threadA.start();
   threadB.start();

当threadA开始执行run方法的时候,它会先持有对象锁localA,然后睡眠2秒,这时候threadB也开始执行run方法,它持有的是localB对象锁.当threadA运行到第二个同步方法的时候,发现localB的对象锁不能使用(threadB未释放localB锁),threadA就停在这里等待localB锁.随后threadB也执行到第二个同步方法,去访问localA对象锁的时候发现localA还没有被释放(threadA未释放localA锁),threadB也停在这里等待localA锁释放.就这样两个线程都没办法继续执行下去,进入死锁的状态.  

31用过读写锁吗,原理是什么,一般在什么场景下用

ReentrantReadWriteLock是一个解决单线程写和多线程读的理想方法。它采用类似于读写分离的思路设定了读锁和写锁。对于这两个锁的访问保证尽可能大的读并行和写互斥。另外,在一定的条件下写锁可以转换成读锁,而读锁却不能转换成写锁。

ReentrantReadWriteLock却可以有锁的机制保证互斥。它同时也尽可能保证了足够大的并行性。


相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
7天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
24 2
|
12天前
|
存储 算法 Java
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
本文详解自旋锁的概念、优缺点、使用场景及Java实现。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
|
17天前
|
存储 缓存 Oracle
Java I/O流面试之道
NIO的出现在于提高IO的速度,它相比传统的输入/输出流速度更快。NIO通过管道Channel和缓冲器Buffer来处理数据,可以把管道当成一个矿藏,缓冲器就是矿藏里的卡车。程序通过管道里的缓冲器进行数据交互,而不直接处理数据。程序要么从缓冲器获取数据,要么输入数据到缓冲器。
Java I/O流面试之道
|
14天前
|
存储 缓存 Java
大厂面试必看!Java基本数据类型和包装类的那些坑
本文介绍了Java中的基本数据类型和包装类,包括整数类型、浮点数类型、字符类型和布尔类型。详细讲解了每种类型的特性和应用场景,并探讨了包装类的引入原因、装箱与拆箱机制以及缓存机制。最后总结了面试中常见的相关考点,帮助读者更好地理解和应对面试中的问题。
39 4
|
14天前
|
存储 Java 程序员
Java基础的灵魂——Object类方法详解(社招面试不踩坑)
本文介绍了Java中`Object`类的几个重要方法,包括`toString`、`equals`、`hashCode`、`finalize`、`clone`、`getClass`、`notify`和`wait`。这些方法是面试中的常考点,掌握它们有助于理解Java对象的行为和实现多线程编程。作者通过具体示例和应用场景,详细解析了每个方法的作用和重写技巧,帮助读者更好地应对面试和技术开发。
55 4
|
1月前
|
存储 安全 算法
Java面试题之Java集合面试题 50道(带答案)
这篇文章提供了50道Java集合框架的面试题及其答案,涵盖了集合的基础知识、底层数据结构、不同集合类的特点和用法,以及一些高级主题如并发集合的使用。
90 1
Java面试题之Java集合面试题 50道(带答案)
|
27天前
|
存储 Java 程序员
Java面试加分点!一文读懂HashMap底层实现与扩容机制
本文详细解析了Java中经典的HashMap数据结构,包括其底层实现、扩容机制、put和查找过程、哈希函数以及JDK 1.7与1.8的差异。通过数组、链表和红黑树的组合,HashMap实现了高效的键值对存储与检索。文章还介绍了HashMap在不同版本中的优化,帮助读者更好地理解和应用这一重要工具。
54 5
|
26天前
|
存储 Java
[Java]面试官:你对异常处理了解多少,例如,finally中可以有return吗?
本文介绍了Java中`try...catch...finally`语句的使用细节及返回值问题,并探讨了JDK1.7引入的`try...with...resources`新特性,强调了异常处理机制及资源自动关闭的优势。
20 1
|
1月前
|
Java 程序员
Java 面试高频考点:static 和 final 深度剖析
本文介绍了 Java 中的 `static` 和 `final` 关键字。`static` 修饰的属性和方法属于类而非对象,所有实例共享;`final` 用于变量、方法和类,确保其不可修改或继承。两者结合可用于定义常量。文章通过具体示例详细解析了它们的用法和应用场景。
28 3
|
2月前
|
设计模式 Java 关系型数据库
【Java笔记+踩坑汇总】Java基础+JavaWeb+SSM+SpringBoot+SpringCloud+瑞吉外卖/谷粒商城/学成在线+设计模式+面试题汇总+性能调优/架构设计+源码解析
本文是“Java学习路线”专栏的导航文章,目标是为Java初学者和初中高级工程师提供一套完整的Java学习路线。
417 37
下一篇
无影云桌面