Java面试题

1. 创建线程的方式

  1. 继承Thread类
    没有把要并行运行的任务与运行机制解耦合;
    不能继承其他类。
  2. 实现Runnable接口
  3. 实现Callable接口
    参考
  4. 使用线程池
    如果有多个任务,为每个任务创建一个单独的线程开销会太大,可能会限制吞吐量并造成性能降低。
    线程可以重用,当前任务已经完成的情况下,可以去执行另外一个任务。

2. HashMap和HashTable

  • 都是用散列实现的数据结构
  • HashMap线程不安全,效率高,可储存null键、null值,实现Map接口
  • HashTable线程安全,效率低,不可储存null键、null值,继承Dictionary类
  • HashTable已被弃用

3. HashTable和ConcurrentHashMap的区别

4. String、StringBuilder和StringBuffer

  • 三者都是final类,不能被继承

  • StringBuilder线程不安全,StringBuffer线程安全

  • String对象的值不能被改变

    • String是不可改变类(记:基本类型的包装类都是不可改变的)的典型代表,也是Immutable设计模式的典型应用,String对象一旦初始化后就不能更改,禁止改变对象的状态,从而增加共享对象的坚固性、减少对象访问的错误,同时还避免了在多线程共享时进行同步的需要。
    • Immutable模式的实现主要有以下两个要点:
      • 除了构造函数之外,不应该有其它任何函数(至少是任何public函数)修改任何成员变量。
      • 任何使成员变量获得新值的函数都应该将新的值保存在新的对象中,而保持原来的对象不被修改。
  • 性能比较:StringBuilder > StringBuffer > String

    参考

5. equlas()和hashcode()

  • equals()相等的两个对象,hashcode()一定相等;

  • equals()不相等的两个对象,hashcode()有可能相等。

  • hashcode()不等,equals()也不等;

  • hashcode()相等,equals()可能相等,也可能不等。

  • 哈希码主要用于确定在散列结构中对象的存储地址

  • 重写equals()必须重写hashcode()

  • 哈希存储结构中,添加元素重复性校验的标准就是先检查hashCode值,后判断equals()。(由于hashcode()效率高于equals())

6. String对象的两种创建方式

String s1 = “abc”;

  • step1: 查看字符串常量池中是否已经有“abc”这个字符串,如果已经被创建,则直接返回一个引用,否则先创建再返回引用

String s2 = new String(“abc”);

  • step1:查看字符串常量池中是否已经有“abc”这个字符串,如果已经被创建,则直接返回一个引用,否则先创建再返回引用
  • step2:在堆中创建一个String对象,并将引用赋给引用变量s2

(1) 字符串常量池——保存字符串对象的引用

  • 当我们在程序中使用双引号来表示一个字符串时,这个字符串就会进入到 String Pool 中
  • JDK7以后,字符串常量池从永久代移到了Java堆中

(2) intern()

  • 本地方法
  • 如果字符串常量池中已经包含一个等于(equals)此String对象的字符串,则返回代表常量池中这个字符串的String对象的引用;
    否则,会将此String对象包含的字符串添加到常量池中,并返回此String对象的引用。(由于常量池在Java堆,只需要在常量池里记录一下首次出现的实例引用即可,不需要再拷贝字符串的实例)

(3) 测试

String s6 = new String("good");
String s7 = s6.intern();
String s8 = "good";
System.out.println(s6 == s7);//false
System.out.println(s7 == s8);//true
System.out.println(s6 == s8);//false

String s0 = new String("fi") + new String("sh");
String s4 = s0.intern();
String s5 = "fish";
System.out.println(s0 == s4);//true
System.out.println(s4 == s5);//true
System.out.println(s0 == s5);//true

String s1 = "jialei";
String s2 = new String("jialei");
String s3 = s2.intern();
System.out.println(s1 == s2);//false
System.out.println(s2 == s3);//false
System.out.println(s1 == s3);//true

String s6 = "bl" + "ack";
//用 + 拼接两个字符串字面量时,JVM会自动把这两个字面量的合并值作为一个完整的字符串常量值
String s7 = s6.intern();
String s8 = "black";
System.out.println(s6 == s7);//true
System.out.println(s7 == s8);//true
System.out.println(s6 == s8);//true

讲解1
讲解2
讲解3
讲解4
讲解5
讲解6
讲解7

7. 内存泄露

参考1
参考2
参考3

  • Java内存回收方式

Java判断对象是否可以回收使用的而是可达性分析算法。

在主流的商用程序语言中(Java和C#),都是使用可达性分析算法判断对象是否存活的。这个算法的基本思路就是通过一系列名为"GC Roots"的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的,下图对象object5, object6, object7虽然有互相判断,但它们到GC Roots是不可达的,所以它们将会判定为是可回收对象。

  • 在Java中,内存泄漏就是存在一些被分配的对象,这些对象有下面两个特点,首先,这些对象是可达的;其次,这些对象是无用的,即程序以后不会再使用这些对象。

  • (1)静态集合类引起内存泄漏
    像HashMap、Vector等的使用最容易出现内存泄露,这些静态变量的生命周期和应用程序一致,他们所引用的所有的对象Object也不能被释放,因为他们也将一直被Vector等引用着。
    (2)当集合里面的对象属性被修改后,再调用remove()方法时不起作用。
    (3)监听器
    在java 编程中,我们都需要和监听器打交道,通常一个应用当中会用到很多监听器,我们会调用一个控件的诸如addXXXListener()等方法来增加监听器,但往往在释放对象的时候却没有记住去删除这些监听器,从而增加了内存泄漏的机会。
    (4)各种连接
    比如数据库连接(dataSourse.getConnection()),网络连接(socket)和io连接,除非其显式的调用了其close()方法将其连接关闭,否则是不会自动被GC 回收的。
    (5)内部类和外部模块的引用
    内部类的引用是比较容易遗忘的一种,而且一旦没释放可能导致一系列的后继类对象没有释放。
    (6)单例模式
    不正确使用单例模式是引起内存泄漏的一个常见问题,单例对象在初始化后将在JVM的整个生命周期中存在(以静态变量的方式),如果单例对象持有外部的引用,那么这个对象将不能被JVM正常回收
    由于单例的静态特性使得其生命周期跟应用的生命周期一样长,所以如果使用不恰当的话,很容易造成内存泄漏。

  • 排查

    • 用工具生成java应用程序的heap dump(如jmap)
    • 使用Java heap分析工具(如MAT),找出内存占用超出预期的嫌疑对象
    • 根据情况,分析嫌疑对象和其他对象的引用关系。
    • 分析程序的源代码,找出嫌疑对象数量过多的原因。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值