todo【面经】牛客网java相关面经题

1. final相关,抽象类可以用final修饰吗

使用 final 关键字声明类、变量和方法需要注意以下几点:

  • final 用在变量的前面表示变量的值不可以改变,此时该变量可以被称为常量。

表示该变量一旦被初始化便不可改变,这里不可改变的意思对基本类型变量来说是其值不可变,而对对象引用类型变量来说其引用不可再变。其初始化可以在两个地方:一是其定义处,也就是说在 final 变量定义时直接给其赋值;二是在构造方法中。这两个地方只能选其一,要么在定义时给值,要么在构造方法中给值,不能同时既在定义时赋值,又在构造方法中赋予另外的值。

  • final 用在方法的前面表示方法不可以被重写。

说明这种方法提供的功能已经满足当前要求,不需要进行扩展,并且也不允许任何从此类继承的类来重写这种方法,但是继承仍然可以继承这个方法,也就是说可以直接使用。在声明类中,一个 final 方法只被实现一次。

  • final 用在类的前面表示该类不能有子类,即该类不可以被继承。

表示该类是无法被任何其他类继承的,意味着此类在一个继承树中是一个叶子类,并且此类的设计已被认为很完美而不需要进行修改或扩展。
【todo】

2. ==和equals

  • 首先 object 类的 “==” 和 equals 没有区别,指的都是:A和B是同一个东西时返回true。比较的是引用类型的变量所指向的对象的地址;
  • 很多类继承了object类之后,都会重写object类的 equals() 和 toString()方法。
    ==通常表明引用的是同一个东西(引用的地址相同),equals通常表明两个相同种类对象的内容相同(值相同)。
  • 对于基本数据类型的变量,==直接比较其存储的 “值”是否相等;equals方法不能作用于基本数据类型的变量!

3. hashmap和hashtable区别

  1. HashMap几乎可以等价于Hashtable,除了HashMap是非synchronized的,并可以接受null(HashMap可以接受为null的键值(key)和值(value),而Hashtable则不行)。
  2. HashMap是非synchronized,而Hashtable是synchronized,这意味着Hashtable是线程安全的,多个线程可以共享一个Hashtable;而如果没有正确的同步的话,多个线程是不能共享HashMap的。Java 5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的扩展性更好。
  3. 另一个区别是HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。所以当有其它线程改变了HashMap的结构(增加或者移除元素),将会抛出ConcurrentModificationException,但迭代器本身的remove()方法移除元素则不会抛出ConcurrentModificationException异常。但这并不是一个一定发生的行为,要看JVM。这条同样也是Enumeration和Iterator的区别。
  4. 由于Hashtable是线程安全的也是synchronized,所以在单线程环境下它比HashMap要慢。如果你不需要同步,只需要单一线程,那么使用HashMap性能要好过Hashtable。
  5. HashMap不能保证随着时间的推移Map中的元素次序是不变的。

4. 还知道哪些线程安全的容器

同步容器类:使用了synchronized
1.Vector
2.HashTable

并发容器:
3.ConcurrentHashMap:分段
4.CopyOnWriteArrayList:写时复制
5.CopyOnWriteArraySet:写时复制

Queue:
6.ConcurrentLinkedQueue:是使用非阻塞的方式实现的基于链接节点的无界的线程安全队列,性能非常好。
(java.util.concurrent.BlockingQueue 接口代表了线程安全的队列。)
7.ArrayBlockingQueue:基于数组的有界阻塞队列
8.LinkedBlockingQueue:基于链表的有界阻塞队列。
9.PriorityBlockingQueue:支持优先级的无界阻塞队列,即该阻塞队列中的元素可自动排序。默认情况下,元素采取自然升序排列
10.DelayQueue:一种延时获取元素的无界阻塞队列。
11.SynchronousQueue:不存储元素的阻塞队列。每个put操作必须等待一个take操作,否则不能继续添加元素。内部其实没有任何一个元素,容量是0

Deque:
(Deque接口定义了双向队列。双向队列允许在队列头和尾部进行入队出队操作。)
12.ArrayDeque:基于数组的双向非阻塞队列。
13.LinkedBlockingDeque:基于链表的双向阻塞队列。

Sorted容器:
14.ConcurrentSkipListMap:是TreeMap的线程安全版本
15.ConcurrentSkipListSet:是TreeSet的线程安全版本

5. String和StringBuffer,StringBuilder

  1. 运行速度:StringBuilder > StringBuffer > String
  2. 线程安全:StringBuilder是线程不安全的,而StringBuffer是线程安全的
    如果一个StringBuffer对象在字符串缓冲区被多个线程使用时,StringBuffer中很多方法可以带有synchronized关键字,所以可以保证线程是安全的,但StringBuilder的方法则没有该关键字,所以不能保证线程安全,有可能会出现一些错误的操作。所以如果要进行的操作是多线程的,那么就要使用StringBuffer,但是在单线程的情况下,还是建议使用速度比较快的StringBuilder。
  3. 字符串连接运算符 (+) 在内部使用 StringBuilder 类。

6. 面向对象的特性,分别解释一下

面向对象的三个基本特征是:封装、继承、多态

  • 封装
    封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。
  • 继承
    继承一种类与类间的关系:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。通过继承创建的新类称为“子类”或“派生类”,被继承的类称为“基类”、“父类”或“超类”。
  • 多态
    多态是同一个行为具有多个不同表现形式或形态的能力。继承是多态的基础。
  1. 引用多态:
    父类的引用可以指向本类的对象;
    父类的引用可以指向子类的对象;
  2. 方法多态:
    方法重载:在同一个类中处理不同数据的多个相同方法名的多态手段。
    方法重写:相对继承而言,子类中对父类已经存在的方法进行区别化的修改。
  • 有时还会有抽象这一特性:抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面,抽象只关注对象的哪些属性和行为,并不关注这此行为的细节是什么

7. 设计模式

设计模式

8. 迭代器Iterator的原理

  • 迭代器(Iterator)模式,又叫做游标(Cursor)模式。GOF给出的定义为:提供一种方法访问一个容器(container)对象中各个元素,而又不需暴露该对象的内部细节。
  • util包中的迭代器实现是fast-fail迭代器,说白了就是一旦由修改就抛异常,在current包中迭代器是弱一致性迭代器,可以在遍历时增删。
  • 弱一致性迭代器的原理太复杂了,还是算了。。。
  • 快速失败(fail-fast)
    快速失败(fail-fast)是 Java 集合的一种错误检测机制。在使用迭代器对集合进行遍历的时候,我们在多线程下操作非安全失败(fail-safe)的集合类可能就会触发 fail-fast 机制,导致抛出ConcurrentModificationException 异常。另外,在单线程下,如果在遍历过程中对集合对象的内容进行了修改的话也会触发 fail-fast 机制。
    举个例子:多线程下,如果线程 1 正在对集合进行遍历,此时线程 2 对集合进行修改(增加、删除、修改),或者线程 1 在遍历过程中对集合进行修改,都会导致线程 1 抛出异常。
  • 安全失败(fail-safe)
    采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。所以,在遍历过程中对原集合所作的修改并不能被迭代器检测到,故不会抛 ConcurrentModificationException

9. java static(原题是针对C++里的static修饰的类,但是java里面static只能修饰嵌套类)

  1. 静态变量:
  • 运行时,Java 虚拟机只为静态变量分配一次内存,在加载类的过程中完成静态变量的内存分配。
  • 在类的内部,可以在任何方法内直接访问静态变量。
  • 在其他类中,可以通过类名访问该类中的静态变量。
  1. 静态方法:
  • 静态方法不需要通过它所属的类的任何实例就可以被调用,因此在静态方法中不能使用 this 关键字,也不能直接访问所属类的实例变量和实例方法,但是可以直接访问所属类的静态变量和静态方法。另外,和 this 关键字一样,super 关键字也与类的特定实例相关,所以在静态方法中也不能使用 super 关键字。
  • 在实例方法中可以直接访问所属类的静态变量、静态方法、实例变量和实例方法。
  1. 静态代码块:
  • Java 类中的 static{ } 代码块,主要用于初始化类,为类的静态变量赋初始值,提升程序性能。
  • 静态代码块类似于一个方法,但它不可以存在于任何方法体中。
  • 静态代码块可以置于类中的任何地方,类中可以有多个静态初始化块。
  • Java 虚拟机在加载类时执行静态代码块,所以很多时候会将一些只需要进行一次的初始化操作都放在 static 代码块中进行。
  • 如果类中包含多个静态代码块,则 Java 虚拟机将按它们在类中出现的顺序依次执行它们,每个静态代码块只会被执行一次。
  • 静态代码块与静态方法一样,不能直接访问类的实例变量和实例方法,而需要通过类的实例对象来访问。
  • static 修饰的成员变量和方法,从属于类。
  • 普通变量和方法从属于对象。
  • 静态方法不能调用非静态成员,编译会报错。

10. 抽象类(原题是虚函数和纯虚函数,针对c++,java只有抽象类)

  1. 抽象方法没有方法体
  2. 抽象方法必须存在于抽象类中
  3. 子类重写父类时,必须重写父类所有的抽象方法

11. 反射机制

  • Java的反射机制的核心是在程序运行时进行动态加载类并获取类的详细信息,从而操作类或对象的属性和方法。本质是JVM得到class对象后,再通过class对象进行反编译,从而获取对象的各种信息。
  • 程序的JVM将使用类加载器子系统,类加载器首先检查这个类的class对象是否已经加载,如果尚未加载,默认的类加载器就会根据类名在本地磁盘查找.class文件,并将其载入,一旦某个类的class对象被载入内存,它就被用来创建这个类的所有对象。

12. 重写与重载

  1. 重写:重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。

重写规则:

  • 参数列表与被重写方法的参数列表必须完全相同。
  • 返回类型与被重写方法的返回类型可以不相同,但是必须是父类返回值的派生类(java5 及更早版本返回类型要一样,java7 及更高版本可以不同)。
  • 访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为 public,那么在子类中重写该方法就不能声明为 protected。
  • 父类的成员方法只能被它的子类重写。
  • 声明为 final 的方法不能被重写。
  • 声明为 static 的方法不能被重写,但是能够被再次声明。
  • 子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为 private 和 final 的方法。
  • 子类和父类不在同一个包中,那么子类只能够重写父类的声明为 public 和 protected 的非 final 方法。
  • 重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以。
  • 构造方法不能被重写。
  • 如果不能继承一个类,则不能重写该类的方法。
  1. 重载:在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。

重载规则:

  • 被重载的方法必须改变参数列表(参数个数或类型不一样);
  • 被重载的方法可以改变返回类型;
  • 被重载的方法可以改变访问修饰符;
  • 被重载的方法可以声明新的或更广的检查异常;
  • 方法能够在同一个类中或者在一个子类中被重载。
  • 无法以返回值类型作为重载函数的区分标准。

13. Java实现单例模式

  1. 单例(Singleton)模式的定义:指一个类只有一个实例,且该类能自行创建这个实例的一种模式。
  2. 3 个特点:
  • 单例类只有一个实例对象;
  • 该单例对象必须由单例类自行创建;
  • 单例类对外提供一个访问该单例的全局访问点。
  1. 实现:
  • 懒汉式单例模式:类加载时没有生成单例,只有当第一次调用 getlnstance 方法时才去创建这个单例
/* 
1、懒汉式,线程不安全
是否 Lazy 初始化:是
是否多线程安全:否
实现难度:易
描述:这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁 >synchronized,所以严格意义上它并不算单例模式。
这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工作。
*/
public class Singleton {  
   private static Singleton instance;  
   private Singleton (){}  
 
   public static Singleton getInstance() {  
   if (instance == null) {  
       instance = new Singleton();  
   }  
   return instance;  
   }  
}

//接下来介绍的几种实现方式都支持多线程,但是在性能上有所差异。

/* 
2、懒汉式,线程安全 是否 Lazy 初始化:是 
是否多线程安全:是 
实现难度:易 
描述:这种方式具备很好的 lazy loading,能够在多线程中很好的工作,但是,效率很低,99% 情况下不需要同步。 
优点:第一次调用才初始化,避免内存浪费。
缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率。 getInstance()的性能对应用程序不是很关键(该方法使用不太频繁)。
*/
public class Singleton {  
   private static Singleton instance;  
   private Singleton (){}  
   public static synchronized Singleton getInstance() {  
	    if (instance == null) {  
	        instance = new Singleton();  
	    }  
	    return instance;  
   }  
}

/*
3、饿汉式
是否 Lazy 初始化:否
是否多线程安全:是
实现难度:易
描述:这种方式比较常用,但容易产生垃圾对象。
优点:没有加锁,执行效率会提高。
缺点:类加载时就初始化,浪费内存。
它基于 classloader 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用 getInstance 方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化 instance 显然没有达到 lazy loading 的效果。
*/
public class Singleton {  
   private static Singleton instance = new Singleton();  
   private Singleton (){}  
   public static Singleton getInstance() {  
	    return instance;  
   }  
}

/*
4、双检锁/双重校验锁(DCL,即 double-checked locking)
JDK 版本:JDK1.5 起
是否 Lazy 初始化:是
是否多线程安全:是
实现难度:较复杂
描述:这种方式采用双锁机制,安全且在多线程情况下能保持高性能。
getInstance() 的性能对应用程序很关键。
*/
public class Singleton {  
   private volatile static Singleton singleton;  
   private Singleton (){}  
   public static Singleton getSingleton() {  
   	if (singleton == null) {  
       synchronized (Singleton.class) {  
       	if (singleton == null) {  
         	  singleton = new Singleton();  
       	}  
       }  
   	}  
   	return singleton;  
   }  
}

/*
5、登记式/静态内部类
是否 Lazy 初始化:是
是否多线程安全:是
实现难度:一般
描述:这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。
这种方式同样利用了 classloader 机制来保证初始化 instance 时只有一个线程,它跟第 3 种方式不同的是:第 3 种方式只要 Singleton 类被装载了,那么 instance 就会被实例化(没有达到 lazy loading 效果),而这种方式是 Singleton 类被装载了,instance 不一定被初始化。因为 SingletonHolder 类没有被主动使用,只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance。想象一下,如果实例化 instance 很消耗资源,所以想让它延迟加载,另外一方面,又不希望在 Singleton 类加载时就实例化,因为不能确保 Singleton 类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化 instance 显然是不合适的。这个时候,这种方式相比第 3 种方式就显得很合理。
*/
public class Singleton {  
   private static class SingletonHolder {  
	    private static final Singleton INSTANCE = new Singleton();  
   }  
   private Singleton (){}  
   public static final Singleton getInstance() {  
   		return SingletonHolder.INSTANCE;  
   }  
}
/*
6、枚举
JDK 版本:JDK1.5 起
是否 Lazy 初始化:否
是否多线程安全:是
实现难度:易
描述:这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。
这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。
不能通过 reflection attack 来调用私有构造方法。
*/
public enum Singleton {  
   INSTANCE;  
   public void whateverMethod() {  
   }  
} 

14. 常用的java注解有哪些

【todo】

15. jvm区域

  • 线程私有:
  1. 程序计数器
  2. JVM栈:每个方法被执行的时候,JVM会同步创建一个栈帧,用于存储局部变量表、操作数栈、动态连接、方法出口等信息。每一个方法被调用直至执行完毕的过程,就对应一个栈帧在JVM栈中从入栈到出栈的过程。
  3. 本地方法栈:和JVM栈发挥的作用类似,区别是JVM栈为JVM执行Java方法服务,本地方法栈为JVM使用到的本地方法服务。有的JVM将JVM栈和本地方法栈合二为一(如HotSpot虚拟机)
  • 线程共享:
  1. 方法区:用于存储已被JVM加载的类型信息、常量、静态变量、即时编译后的代码缓存等数据。
  2. Java堆:只存放对象实例,不存放基本类型和对象引用。是垃圾收集器管理的内存区域,也被成为GC堆 。所有线程共享的Java堆里可以划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer TLAB),以提升对象分配时的效率和更好地回收内存,同时可以解决创建对象时的线程安全问题。

16. gc算法

  • 所有gc算法的前提分代假说:
  1. 弱分代假说:绝大多数对象都是朝生夕灭的。
  2. 强分代假说:熬过越多次垃圾收集过程的对象就越难以消亡。

收集器应该将Java堆划分出不同的区域,然后将回收对象依据其年龄(年龄即对象熬过垃圾收集过程的次数)分配到不同的区域之中存储。垃圾收集器得以每次只回收其中某一个或者某些部分的区域。

  1. 跨代引用假说:跨代引用相对于同代引用来说仅占极少数。

依据假说3,只需在新生代上建立一个全局的数据结构(记忆集Remembered Set),这个结构把老年代划分成若干小块,标识出老年代的哪一块内存会存在跨代引用。此后当发生Minor GC(新生代收集)时,只有包含了跨代引用的小块内存里的对象才会被加入GC Roots进行扫描。这个方法需要在对象改变引用关系时维护记录数据的正确性,会增加一些运行时的开销,但比起收集时扫描整个老年代来说仍然是划算的。

  • 标记-清除算法
    标记出所有需要回收/存活的对象。在标记完成后,统一回收掉所有被标记/未标记的对象。
  • 标记-复制算法
    将可用内存按容量划分成大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。被用于回收新生代。
    改进:将新生代分为一块较大的Eden空间和两块较小的Survivor空间,每次分配内存只使用Eden和其中一块Survivor空间。发生垃圾收集时,将Eden和Survivor中仍然存活的对象一次性复制到另外一块Survivor空间上,然后直接清理掉Eden和已用过的那块Survivor空间。HotSpotJVM默认Eden和Survivor的大小比例是8:1。
  • 标记-整理算法
    该算法针对老年代对象的存亡特征。
  1. 标记所有需要清理的对象。
  2. 让所有存活的对象都向内存空间的一端移动。
  3. 直接清理掉边界以外的内存。

17. 内存区域划分

【jvm见15题,面向对象见第26题】

18. 线程池拒绝策略

  1. ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
  2. ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。
  3. ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务
  4. ThreadPoolExecutor.CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务

19. 当线程队列满了之后事项

【见第18题】
线程池工作原理

20. 多态

【见第6题】

21. 什么是装饰器

【todo,这是python的】

22. 函数入参顺序

函数调用过程中,第一个进栈的是(主函数中的) 调用处的下一条指令(即函数调用语句的下一条可执行语句) 的地址;然后是函数的各个参数,而在大多数C/C++编译器中,在函数调用的过程中,函数的参数是 由右向左入栈的;然后是函数内部的 局部变量(注意static变量是不入栈的);在函数调用结束(函数运行结束)后,局部变量最先出栈,然后是参数,最后栈顶指针指向最开始存的指令地址,程序由该点继续运行。

23. hashmap往里put两个数据,如果它们的hashcode是一样的,那怎么区分查找出他们的值来

就是在哈希表中,针对相同的hash值使用链表的方式来存放。JDK8之后,如果链表长度超过8将会将链表转化为红黑树以便提高在hash冲突严重情况下的查询效率。不直接使用红黑树,是因为链表实现比红黑树简单。

24. java如何处理异常,有哪些异常

  1. 异常处理:
    在Java语言的错误处理结构由try,catch,finally三个块组成。其中try块存放将可能发生异常的Java语言,并管理相关的异常指针;catch块紧跟在try块后面,用来激发被捕获的异常;finally块包含清除程序没有释放的资源,句柄等。
    不管try块中的代码如何退出,都将执行finally块。
  2. 抛出异常:
    Java语言可以不在方法中直接捕获,而用throw语句将异常抛给上层的调用者。
  3. 种类:
    检查性异常:最具代表的检查性异常是用户错误或问题引起的异常,这是程序员无法预见的。例如要打开一个不存在的文件时,一个异常就发生了,这些异常在编译时不能被简单的忽略。javac强制要求程序员为这样的异常做预备处理工作(使用try…catch…finally或者throws)。在方法中要么用try-catch语句捕获它并处理,要么用throws子句声明抛出它,否则编译不会通过。这样的异常一般是由程序的运行环境导致的。因为程序可能被运行在各种未知的环境下,而程序员无法干预用户如何使用他编写的程序
    运行时异常:运行时异常是可能被程序员避免的异常。与检查型异常相反,运行时异常可以在编译时被忽略。对于这些异常,我们应该修正代码,而不是去通过异常处理器处理 。这样的异常发生的原因多半是代码写的有问题。如除0错误ArithmeticException,错误的强制类型转换错误ClassCastException,数组索引越界ArrayIndexOutOfBoundsException,使用了空对象NullPointerException等等。
    错误:错误不是一场,而是脱离程序员控制的问题。

25. jvm垃圾检测机制

  • 引用计数算法
  1. 基本思路:在对象中添加一个引用计数器,每当又一个地方引用它时,计数器值加一;当引用失效时,计数器值减一。任何时刻计数器为0的对象就是不可能再被使用的。
  2. 优点:引用计数算法虽然占用了一些额外的内存空间来进行计数,但它的原理简单,判定效率也很高。
  3. 缺点:这个算法有很多例外情况需要考虑,必须配合大量的额外处理才能保证正确地工作。比如单纯的引用计算很难解决两个不可能再被访问的对象之间相互循环引用的问题。
  • 可达性分析算法
  1. 基本思路:通过一系列被称为“GC Roots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为“引用链”,如果某个对象到GC Roots间没有任何引用链相连,即从GC Roots到这个对象不可达时,证明此对象是不可能再被使用的。
  2. 固定可作为GC Roots的对象包括以下几种:
    • 在JVM栈(栈帧中的本地变量表)中引用的对象,如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等。
    • 在方法区中类静态属性引用的对象,比如Java类的引用类型静态变量。
    • 在方法区中常量引用的对象,比如字符串常量池里的引用。
    • 在本地方法栈中JNI(Native方法)引用的对象
    • JVM内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象等。
    • 所有被同步锁(Synchronized 关键字)持有的对象。
    • 反映JVM内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等。

26. 面向对象程序的内存分配方式

  • 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量。
  • 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
  • 从堆上分配,亦称动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。

27. Runnable和Callable的区别

返回结果并且可能抛出异常的任务。实现者定义了一个不带任何参数的叫做 call 的方法。
Callable 接口类似于 Runnable,两者都是为那些其实例可能被另一个线程执行的类设计的。但是 Runnable 不会返回结果,并且无法抛出经过检查的异常。

28. 线程的创建方式

  1. 继承Thread类创建线程

  2. 实现Runnable接口创建线程

  3. 使用Callable和Future创建线程

  4. 使用线程池例如用Executor框架

29. 抽象类和接口的区别

  1. 默认的方法实现:抽象类可以有默认的方法实现。接口是所有抽象方法的集合,根本不存在方法的实现。
  2. 实现:抽象类使用extends关键字来继承抽象类。如果子类不是抽象类的话,它需要提供抽象类中所有声明的方法的实现。子类使用关键字implements来实现接口。它需要提供接口中所有声明的方法的实现。
  3. 构造器:抽象类可以有构造器,而接口不能有构造器
  4. 修饰符:抽象方法可以有public、protected和default这些修饰符;接口方法默认修饰符是public。你不可以使用其它修饰符。
  5. 父子关系:抽象类在java语言中所表示的是一种继承关系,一个子类只能存在一个父类,但是可以存在多个接口。
  6. 速度:抽象方法比接口速度要快;接口是稍微有点慢的,因为它需要时间去寻找在类中实现的方法。
  7. 维护代价:如果你往抽象类中添加新的方法,你可以给它提供默认的实现。因此你不需要改变你现在的代码。 如果你往接口中添加方法,那么你必须改变实现该接口的类。

30. Array和ArrayList

ArrayList内部由Array实现

  • 空间大小:
  1. Array的空间大小是固定的,空间不够时也不能再次申请,所以需要事前确定合适的空间大小。
  2. ArrayList的空间是动态增长的,如果空间不够,它会创建一个空间比原空间大0.5倍的新数组,然后将所有元素复制到新数组中,接着抛弃旧数组。而且,每次添加新的元素的时候都会检查内部数组的空间是否足够。
  • 存储内容:
  1. Array数组可以包含基本类型和对象类型。
  2. ArrayList却只能包含对象类型。
  • 方法: Array只提供一个数组长度属性。ArrayList有许多可以调用的方法

31. 面向对象的五大原则

  1. 单一职责原则(Single-Resposibility Principle):一个类,最好只做一件事,只有一个引起它的变化。单一职责原则可以看做是低耦合、高内聚在面向对象原则上的引申,将职责定义为引起变化的原因,以提高内聚性来减少引起变化的原因。
  2. 开放封闭原则(Open-Closed principle):软件实体应该是可扩展的,而不可修改的。也就是,对扩展开放,对修改封闭的。
  3. Liskov替换原则(Liskov-Substituion Principle):子类必须能够替换其基类。这一思想体现为对继承机制的约束规范,只有子类能够替换基类时,才能保证系统在运行期内识别子类,这是保证继承复用的基础。
  4. 依赖倒置原则(Dependecy-Inversion Principle):依赖于抽象。具体而言就是高层模块不依赖于底层模块,二者都同依赖于抽象;抽象不依赖于具体,具体依赖于抽象。
  5. 接口隔离原则(Interface-Segregation Principle):使用多个小的专门的接口,而不要使用一个大的总接口

32. 弱引用和虚引用的区别

  1. 强引用:指在程序代码中普遍存在的引用赋值,即类似“Object obj=new Object()”这种引用关系。任何情况下,只要强引用关系存在,垃圾收集器就永远不会回收掉被引用的对象。
  2. 软引用:用来描述一些还有用但非必须的对象。只被软引用关联着的对象,在系统将要发生内存溢出异常前,会把这些对象列进回收范围之中进行第二次回收,如果这次回收还没有足够的内存,才会抛出内存溢出异常。
  3. 弱引用:也用来描述非必须对象,但强度比软引用更弱一些,只被弱引用关联的对象只能生存到下一次垃圾收集发生为止。当垃圾收集器开始工作,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。
  4. 虚引用:最弱的一种引用关系。一个对象是否虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的只是为了能在这个对象被收集器回收时收到一个系统通知。

33. 线程池是什么

【todo】

34. 新老生代垃圾回收为什么要设置8:1:1

【todo】

35. 垃圾回收后为什么还会内存泄漏

【todo】

36.多线程start和run的区别

【todo】

37.++i和i++的区别,为什么要引出这两种,只用一个不行吗,举出特定场景

【todo】

38.volatile

首先是每个cpu都读取主内存中的数据,写入自己的本地内存中去。然后每个处理器会通过嗅探器来监控总线(使用缓存一致性协议)上的数据来检查自己缓存内的数据是否过期,如果发现自己缓存行对应的地址被修改了,就会将此缓存行置为无效。当处理器对此数据进行操作时,就会重新从主内存中读取数据到缓存行。

【todo】

39. 双亲委派机制

【todo】

40. HashMap中链表发生死锁

【todo】

41. java中的集合说一下

【todo】

42. ArrayList和LinkedList的区别

【todo】

43. HashMap默认的数据结构,扩容方式

HashMap是基于拉链法实现的一个散列表,内部由数组和链表和红黑树实现。

  1. 数组的初始容量为16,而容量是以2的次方扩充的,一是为了提高性能使用足够大的数组,二是为了能使用位运算代替取模预算。
  2. 数组是否需要扩充是通过负载因子判断的,如果当前元素个数为数组容量的0.75时,就会扩充数组。这个0.75就是默认的负载因子,可由构造传入。
  3. 为了解决碰撞,数组中的元素是单向链表类型。当链表长度到达一个阈值时(>=8), 会将链表转换成红黑树提高性能。而当链表长度缩小到另一个阈值时(<=6),又会将红黑树转换回单向链表提高性能,这里是一个平衡点。
  4. 当个数不多的时候,直接链表遍历更方便,实现起来也简单。而红黑树的实现要复杂的多。

44. jvm jre jdk关系 他们是什么

【todo】

45.基本数据类型、包装类区别

  1. 包装类是对象,拥有方法和字段,对象的调用都是通过引用对象的地址,基本类型不是;
  2. 包装类型是引用的传递,基本类型是值的传递;
  3. 声明方式不同,基本数据类型不需要new关键字,而包装类型需要new在堆内存中进行new来分配内存空间;
  4. 存储位置不同,基本数据类型直接将值保存在值栈中,而包装类型是把对象放在堆中,然后通过对象的引用来调用他们,因此,包装类的效率会比基本数据类型的效率要低。
  5. 初始值不同,比如:int的初始值为0、boolean的初始值为false,而包装类型的初始值为null;
  6. 使用方式不同,基本数据类型直接赋值使用就好,而包装类型是在集合,比如Collection、Map时会使用。【包装类的作用一】
  7. 数据的包装类可以提供该类最大、最小值查阅和类型转化的功能【包装类的作用二】

46. sleep、wait区别

【todo】

47. Object类有哪些方法

getClass()    //返回此 Object 的运行类。
hashCode()    //用于获取对象的哈希值。
equals(Object obj)     //判断两个对象是否相等,在Object源码中equals就是使用==去判断,所以在Object中equals是等价于==的,但是在String及某些类对equals进行了重写,实现不同的比较。
clone()    //保护方法,实现对象的浅复制,只有实现了Cloneable接口才可以调用该方法,否则抛出CloneNotSupportedException异常。
主要是JAVA里除了8种基本类型传参数是值传递,其他的类对象传参数都是引用传递,我们有时候不希望在方法里将参数改变,这是就需要在类中复写clone方法。
toString()   //返回该对象的字符串表示。
notify()    //唤醒该对象等待的某个线程
notifyAll()     //唤醒该对象等待的所有线程
wait()    //多线程时用到的方法,作用是让当前线程进入等待状态,同时也会让当前线程释放它所持有的锁。直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,当前线程被唤醒
finalize()    //当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。

48. list、map、set区别

【todo】

49. 几种垃圾回收器,线程情况

【todo】

50. ConcurrentHashMap

  • jdk1.7:和hashmap一样,在jdk1.7中ConcurrentHashMap的底层数据结构是数组加链表。和hashmap不同的是ConcurrentHashMap中存放的数据是一段段的,即由多个Segment(段)组成的。每个Segment中都有着类似于数组加链表的结构。分段锁
  • jdk1.8:Java 8为进一步提高并发性,摒弃了分段锁的方案,而是直接使用一个大的数组。同步方式

51. 深拷贝、浅拷贝

  • 基本数据类型的特点:直接存储在栈(stack)中的数据
  • 引用数据类型的特点:存储的是该对象在栈中引用,真实的数据存放在堆内存里
    引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。
  • 深拷贝和浅拷贝(针对Object和Array这样的引用数据类型)
    浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。
  • 赋值和浅拷贝(主要是基本数据类型)
    赋值就是对原对象的栈内存地址进行复制
    浅拷贝是对原对象的属性值进行精准复制,那么对如果原对象的属性值是基本类型那就是值的引用,所以浅拷贝后修改基本类型不会修改到原对象的,如果原对象属性值是引用类型,那么就是对引用类型属性值的栈内存的复制,所以修改引用类型属性值的时候回修改到原对象。

52. String是基本数据类型么,为什么

【todo】

53. class文件加载到jvm中的一个流程是什么?

【todo】

54. 线程如何加锁

【todo】

55. interface接口

【todo】

56. 栈和队列区别,分别应用在哪里

【todo】

57. java中的锁

【todo】https://www.cnblogs.com/jyroy/p/11365935.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值