java面试题

1.什么是线程局部变量?

线程局部变量是局限于线程内部的变量,属于线程自身所有,不在多个线程间共享。
Java 提供 ThreadLocal 类来支持线程局部变量,是一种实现线程安全的方式。
但是在管理环境下(如 web 服务器)使用线程局部变量的时候要特别小心,在这种情况下,工作线程的生命周期比任何应用变量的生命周期都要长。
任何线程局部变量一旦在工作完成后没有释放,Java 应用就存在内存泄露的风险。

2.Java 中 sleep 方法和 wait 方法的区别?

虽然两者都是用来暂停当前运行的线程,但是 sleep() 实际上只是短暂停顿,因为它不会释放锁。
而 wait() 意味着条件等待,这就是为什么该方法要释放锁,因为只有这样,其他等待的线程才能在满足条件时获取到该锁。

3.我们能将 int 强制转换为 byte 类型的变量吗?如果该值大于 byte 类型的范围,将会出现什么现象?

我们可以做强制转换,但是 Java 中 int 是 32 位的,而 byte 是 8 位的。
如果强制转化是,int 类型的高 24 位将会被丢弃,byte 类型的范围是从 -128 到 128。

4.Java 中 ++ 操作符是线程安全的吗?

不是线程安全的操作。它涉及到多个指令,如读取变量值,增加,然后存储回内存,这个过程可能会出现多个线程交差。

5.能在不进行强制转换的情况下将一个 double 值赋值给 long 类型的变量吗?

不行,你不能在没有强制类型转换的前提下将一个 double 值赋值给 long 类型的变量,
因为 double 类型的范围比 long 类型更广,所以必须要进行强制转换。

6.3乘0.1 == 0.3 将会返回什么?true 还是 false?

false,因为有些浮点数不能完全精确的表示出来。

7.int 和 Integer 哪个会占用更多的内存?

Integer 对象会占用更多的内存。Integer 是一个对象,需要存储对象的元数据。
但是 int 是一个原始类型的数据,所以占用的空间更少。

8.我们能在 Switch 中使用 String 吗?

从 Java 7 开始,我们可以在 switch case 中使用字符串,但这仅仅是一个语法糖。
内部实现在 switch 中使用字符串的 hash code。

9.Java 中的构造器链是什么?

当你从一个构造器中调用另一个构造器,就是Java 中的构造器链。
这种情况只在重载了类的构造器的时候才会出现。

10.64 位 JVM 中,int 的长度是多数?

Java 中,int 类型变量的长度是一个固定值,与平台无关,都是 32 位或者 4 个字节。
意思就是说,在 32 位 和 64 位 的Java 虚拟机中,int 类型的长度是相同的。

11.Serial 与 Parallel GC之间的不同之处?

Serial 与 Parallel 在GC执行的时候都会引起 stop-the-world。
它们之间主要不同 serial 收集器是默认的复制收集器,执行 GC 的时候只有一个线程,
而 parallel 收集器使用多个 GC 线程来执行。

12.Java 中 WeakReference 与 SoftReference的区别?

虽然 WeakReference(弱引用) 与 SoftReference(软引用) 都有利于提高 GC 和 内存的效率,但是 WeakReference ,一旦失去最后一个强引用,就会被 GC 回收,而软引用虽然不能阻止被回收,但是可以延迟到 JVM 内存不足的时候。

13.WeakHashMap 是怎么工作的?

WeakHashMap的工作与正常的HashMap类似,但是使用弱引用作为 key,意思就是当 key 对象没有任何引用时,key/value 将会被回收。

14.JRE、JDK、JVM 及 JIT 之间有什么不同?

JRE 代表 Java 运行时(Java run-time),是运行 Java 引用所必须的。
JDK 代表 Java 开发工具(Java development kit),是 Java 程序的开发工具,如 Java 编译器,它也包含 JRE。
JVM 代表 Java 虚拟机(Java virtual machine),它的责任是运行 Java 应用。
JIT 代表即时编译(Just In Time compilation),当代码执行的次数超过一定的阈值时,会将 Java 字节码转换为本地代码,
    如主要的热点代码会被准换为本地代码,这样有利大幅度提高 Java 应用的性能。

15.解释 Java 堆空间及 GC?

当通过 Java 命令启动 Java 进程的时候,会为它分配内存。
内存的一部分用于创建堆空间,当程序中创建对象的时候,就从对空间中分配内存。
GC 是 JVM 内部的一个进程,回收无效对象的内存用于将来的分配。

16.你能保证 GC 执行吗?

不能,虽然你可以调用 System.gc() 或者 Runtime.gc(),但是没有办法保证 GC 的执行。

17.怎么获取 Java 程序使用的内存?堆使用的百分比?

通过 java.lang.Runtime 类中与内存相关方法来获取剩余的内存,总内存及最大堆内存。
通过这些方法你也可以获取到堆使用的百分比及堆内存的剩余空间。

Runtime.freeMemory() 方法返回剩余空间的字节数,
Runtime.totalMemory() 方法总内存的字节数,
Runtime.maxMemory() 返回最大内存的字节数。

18.Java 中堆和栈有什么区别?

JVM 中堆和栈属于不同的内存区域,使用目的也不同。
栈常用于保存方法帧和局部变量,而对象总是在堆上分配。
栈通常都比堆小,也不会在多个线程之间共享,而堆被整个 JVM 的所有线程共享。

19.a==b和a.equals(b)有什么区别?

如果 a 和 b 都是对象,则 a==b 是比较两个对象的引用,只有当 a 和 b 指向的是堆中的同一个对象才会返回 true,
而 a.equals(b) 是进行逻辑比较,所以通常需要重写该方法来提供逻辑一致性的比较。
例如,String 类重写 equals() 方法,所以可以用于两个不同对象,但是包含的字母相同的比较。

20.a.hashCode() 有什么用?与 a.equals(b) 有什么关系?

hashCode() 方法是相应对象整型的 hash 值。
它常用于基于 hash 的集合类,如 Hashtable、HashMap、LinkedHashMap等等。它与 equals() 方法关系特别紧密。
根据 Java 规范,两个使用 equal() 方法来判断相等的对象,必须具有相同的 hash code。

21.final、finalize 和 finally 的不同之处?

final 是一个修饰符,可以修饰变量、方法和类。如果 final 修饰变量,意味着该变量的值在初始化后不能被改变。
finalize 方法是在对象被回收之前调用的方法,给对象自己最后一个复活的机会,但是什么时候调用 finalize 没有保证。
    finalize只会被调用一次,并且不一定被调用。
finally 是一个关键字,与 try 和 catch 一起用于异常的处理。finally 块一定会被执行,无论在 try 块中是否有发生异常。

22.Java 中的编译期常量是什么?使用它又什么风险?

公共静态不可变(public static final )变量也就是我们所说的编译期常量,这里的 public 可选的。
实际上这些变量在编译时会被替换掉,因为编译器知道这些变量的值,并且知道这些变量在运行时不能改变。
这种方式存在的一个问题是你使用了一个内部的或第三方库中的公有编译时常量,但是这个值后面被其他人改变了,但是你的客户端仍然在使用老的值,甚至你已经部署了一个新的jar。
为了避免这种情况,当你在更新依赖 JAR 文件时,确保重新编译你的程序。

23.poll() 方法和 remove() 方法的区别?

poll() 和 remove() 都是从队列中取出一个元素;
poll() 在获取元素失败的时候会返回空。
remove() 失败的时候会抛出异常。

24.Java 中怎么打印数组?

可以使用 Arrays.toString() 和 Arrays.deepToString() 方法来打印数组。
由于数组没有实现 toString() 方法,所以如果将数组传递给 System.out.println() 方法,将无法打印出数组的内容,但是 Arrays.toString() 可以打印每个元素。

25.能自己写一个容器类,然后使用 for-each 循环码?

可以,你可以写一个自己的容器类。
如果你想使用 Java 中增强的循环来遍历,你只需要实现 Iterable 接口。
如果你实现 Collection 接口,默认就具有该属性。

26.java当中的四种引用?

强引用,软引用,弱引用,虚引用

强引用:如果一个对象具有强引用,它就不会被垃圾回收器回收。
       即使当前内存空间不足,JVM也不会回收它,而是抛出 OutOfMemoryError 错误,使程序异常终止。
       如果想中断强引用和某个对象之间的关联,可以显式地将引用赋值为null,这样一来的话,JVM在合适的时间就会回收该对象。

软引用:在使用软引用时,如果内存的空间足够,软引用就能继续被使用,而不会被垃圾回收器回收,只有在内存不足时,软引用才会被垃圾回收器回收。

弱引用:具有弱引用的对象拥有的生命周期更短暂。
       因为当 JVM 进行垃圾回收,一旦发现弱引用对象,无论当前内存空间是否充足,都会将弱引用回收。
       不过由于垃圾回收器是一个优先级较低的线程,所以并不一定能迅速发现弱引用对象。

虚引用:顾名思义,就是形同虚设,如果一个对象仅持有虚引用,那么它相当于没有引用,在任何时候都可能被垃圾回收器回收。

27.深拷贝和浅拷贝的区别是什么?

浅拷贝:被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。
       换言之,浅拷贝仅仅复制所考虑的对象,而不复制它所引用的对象。

深拷贝:被复制对象的所有变量都含有与原来的对象相同的值,而那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。
       换言之,深拷贝把要复制的对象所引用的对象都复制了一遍。

28.synchronized和ReentrantLock的区别?

synchronized是和if、else、for、while一样的关键字,ReentrantLock是类,这是二者的本质区别。
既然ReentrantLock是类,那么它就提供了比synchronized更多更灵活的特性,可以被继承、可以有方法、可以有各种各样的类变量,ReentrantLock比synchronized的扩展性体现在几点上: 
(1)ReentrantLock可以对获取锁的等待时间进行设置,这样就避免了死锁 
(2)ReentrantLock可以获取各种锁的信息 
(3)ReentrantLock可以灵活地实现多路通知 
另外,二者的锁机制其实也是不一样的:ReentrantLock底层调用的是Unsafe的park方法加锁,synchronized操作的应该是对象头中mark word。

29.FutureTask是什么?

FutureTask表示一个异步运算的任务。FutureTask里面可以传入一个Callable的具体实现类,可以对这个异步运算的任务的结果进行等待获取、判断是否已经完成、取消任务等操作。
当然,由于FutureTask也是Runnable接口的实现类,所以FutureTask也可以放入线程池中。

30.一个线程如果出现了运行时异常怎么办?

如果这个异常没有被捕获的话,这个线程就停止执行了。
另外重要的一点是:如果这个线程持有某个某个对象的监视器,那么这个对象监视器会被立即释放。

31.Java当中有哪几种锁?

自旋锁: 自旋锁在JDK1.6之后就默认开启了。
       基于之前的观察,共享数据的锁定状态只会持续很短的时间,为了这一小段时间而去挂起和恢复线程有点浪费,
       所以这里就做了一个处理,让后面请求锁的那个线程在稍等一会,但是不放弃处理器的执行时间,看看持有锁的线程能否快速释放。
       为了让线程等待,所以需要让线程执行一个忙循环也就是自旋操作。
       在jdk6之后,引入了自适应的自旋锁,也就是等待的时间不再固定了,而是由上一次在同一个锁上的自旋时间及锁的拥有者状态来决定。

偏向锁: 在JDK1.之后引入的一项锁优化,目的是消除数据在无竞争情况下的同步原语。进一步提升程序的运行性能。 
       偏向锁就是偏心的偏,意思是这个锁会偏向第一个获得他的线程,如果接下来的执行过程中,改锁没有被其他线程获取,则持有偏向锁的线程将永远不需要再进行同步。
       偏向锁可以提高带有同步但无竞争的程序性能,也就是说他并不一定总是对程序运行有利,如果程序中大多数的锁都是被多个不同的线程访问,那偏向模式就是多余的,在具体问题具体分析的前提下,可以考虑是否使用偏向锁。

轻量级锁: 为了减少获得锁和释放锁所带来的性能消耗,引入了“偏向锁”和“轻量级锁”,
        所以在Java SE1.6里锁一共有四种状态,无锁状态,偏向锁状态,轻量级锁状态和重量级锁状态,它会随着竞争情况逐渐升级。
        锁可以升级但不能降级,意味着偏向锁升级成轻量级锁后不能降级成偏向锁。
乐观锁:乐观锁认为竞争不总是会发生,因此它不需要持有锁,将比较-替换这两个动作作为一个原子操作尝试去修改内存中的变量,如果失败则表示发生冲突,那么就应该有相应的重试逻辑。

悲观锁:悲观锁认为竞争总是会发生,因此每次对某资源进行操作时,都会持有一个独占的锁,就像synchronized,不管三七二十一,直接上了锁就操作资源了。

32.如果你提交任务时,线程池队列已满,这时会发生什么?

1.如果你使用的LinkedBlockingQueue,也就是无界队列的话,没关系,继续添加任务到阻塞队列中等待执行,
因为LinkedBlockingQueue可以近乎认为是一个无穷大的队列,可以无限存放任务;
2.如果你使用的是有界队列比方说ArrayBlockingQueue的话,任务首先会被添加到ArrayBlockingQueue中,
ArrayBlockingQueue满了,则会使用拒绝策略RejectedExecutionHandler处理满了的任务,默认是AbortPolicy。

微信公众号,欢迎扫码关注