java面试题目以及答案(重在理解,别死记硬背哟)

23 篇文章 0 订阅
3 篇文章 0 订阅

Java集合

  1. 说说 ArrayList,Vector, LinkedList 的存储性能和特性。

答案:ArrayList 和 Vector 都是使用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存操作,所以索引数据快而插入数据慢,Vector 由于使用了 synchronized 方法(线程安全),通常性能上较 ArrayList 差,而LinkedList 使用双向链表实现存储,按序号索引数据需要进行前向或后向遍历,但是插入数据时只需要记录本项的前后项即可,所以插入速度较快。

 

  1. ArrayList 和 Vector 的区别。

答案:线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。

线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据;

1)  Vector的方法都是同步的(Synchronized),是线程安全的(thread-safe),而ArrayList的方法不是,由于线程的同步必然要影响性能,因此,ArrayList的性能比Vector好。 

2) 当Vector或ArrayList中的元素超过它的初始大小时,Vector会将它的容量翻倍,而ArrayList只增加50%的大小,这样,ArrayList就有利于节约内存空间。

 

 

  1. 快速失败 (fail-fast) 和安全失败 (fail-safe) 的区别是什么?
  2. HashMap、HashTable、HashSet的区别。

HashSet和HashMap的区别

*HashMap*

*HashSet*

HashMap实现了Map接口

HashSet实现了Set接口

HashMap储存键值对

HashSet仅仅存储对象

使用put()方法将元素放入map中

使用add()方法将元素放入set中

HashMap中使用键对象来计算hashcode值

HashSet使用成员对象来计算hashcode值,对于两个对象来说hashcode可能相同,所以equals()方法用来判断对象的相等性,如果两个对象不同的话,那么返回false

HashMap比较快,因为是使用唯一的键来获取对象

HashSet较HashMap来说比较慢

 

  1. hashmap 的数据结构。

 答案:HashMap也是我们使用非常多的Collection,它是基于哈希表的 Map 接口的实现,以key-value的形式存在。在HashMap中,key-value总是会当做一个整体来处理,系统会根据hash算法来来计算key-value的存储位置,我们总是可以通过key快速地存、取value。

 

  1. HashMap 的工作原理是什么?

通过hash的方法,通过put和get存储和获取对象。存储对象时,我们将K/V传给put方法时,它调用hashCode计算hash从而得到bucket位置,进一步存储,HashMap会根据当前bucket的占用情况自动调整容量(超过Load Facotr则resize为原来的2倍)。获取对象时,我们将K传给get,它调用hashCode计算hash从而得到bucket位置,并进一步调用equals()方法确定键值对。如果发生碰撞的时候,Hashmap通过链表将产生碰撞冲突的元素组织起来,在Java 8中,如果一个bucket中碰撞冲突的元素超过某个限制(默认是8),则使用红黑树来替换链表,从而提高速度。

 

  1. Hashmap 什么时候进行扩容呢?

HashMap的size大于等于(容量*加载因子)的时候,会触发扩容的操作,这个是个代价不小的操作。 

为什么要扩容呢?HashMap默认的容量是16,随着元素不断添加到HashMap里,出现hash冲突的机率就更高,那每个桶对应的链表就会更长,这样会影响查询的性能,因为每次都需要遍历链表,比较对象是否相等,一直到找到元素为止。

为了提升查询性能,只能扩容,减少hash冲突,让元素的key尽量均匀的分布。

扩容基本点

加载因子默认值是0.75

?

1

static final float DEFAULT_LOAD_FACTOR = 0.75f;

容量的默认值是16

?

1

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 等于16

HashMap提供了一个构造参数,可以在创建的时候指定容量和加载因子。

?

1

public HashMap(int initialCapacity, float loadFactor)

默认的情况下,HashMap 的size一旦大于等于16*0.75=12的话, 

同时每个Entry(或者叫桶)里面至少有一个元素的时候就会进行扩容。

 

  1. List、Map、Set 三个接口,存取元素时,各有什么特点?

List以特定索引来存取元素,可以有重复元素。

Set不能存放重复元素(用对象的equals()方法来区分元素是否重复)。

Map保存键值对(key-value pair)映射,映射关系可以是一对一或多对一。

 

  1. Set 里的元素是不能重复的,那么用什么方法来区分重复与否呢? 是用 == 还是 equals()? 它们有何区别?
  2. 两个对象值相同 (x.equals(y) == true),但却可有不同的 hash code,这句话对不对?

 

不对,有相同的 hash code
这是java语言的定义:
1) 对象相等则hashCode一定相等;
2) hashCode相等对象未必相等

String s1="hello world";

String s2=new String("hello world");

s1.hashCode()和s2.hashCode()其实是相等的。

 

 

  1. heap 和 stack 有什么区别。

1.heap是堆,stack是栈。

2.stack的空间由操作系统自动分配和释放,heap的空间是手动申请和释放的,heap常用new关键字来分配。

3.stack空间有限,heap的空间是很大的自由区。

Java中,若只是声明一个对象,则先在栈内存中为其分配地址空间,若再new一下,实例化它,则在堆内存中为其分配地址。

方法中的局部变量使用 final修饰后,放在堆中,而不是栈中。 

 

  1. Java 集合类框架的基本接口有哪些?

总共有两大接口:Collection 和Map ,一个元素集合,一个是键值对集合; 其中List和Set接口继承了Collection接口,一个是有序元素集合,一个是无序元素集合; 而ArrayList和 LinkedList 实现了List接口,HashSet实现了Set接口,这几个都比较常用; HashMap 和HashTable实现了Map接口,并且HashTable是线程安全的,但是HashMap性能更好;

 

Java集合类里最基本的接口有:

Collection:单列集合的根接口

List:元素有序  可重复 

ArrayList:类似一个长度可变的数组 。适合查询,不适合增删

LinkedList:底层是双向循环链表。适合增删,不适合查询。

Set:元素无序,不可重复

HashSet:根据对象的哈希值确定元素在集合中的位置

TreeSet: 以二叉树的方式存储元素,实现了对集合中的元素排序

Map:双列集合的根接口,用于存储具有键(key)、值(value)映射关系的元素。

HashMap:用于存储键值映射关系,不能出现重复的键key

TreeMap:用来存储键值映射关系,不能出现重复的键key,所有的键按照二叉树的方式排列

 

 

  1. HashSet 和 TreeSet 有什么区别?

HashSet与TreeSet接口的一点不同,HashSet  保存的数据是无序的,TreeSet保存的数据是有序的,所以如果要想保存的数据有序应该使用TreeSet子类。

 

  1. HashSet 的底层实现是什么?

HashSet底层使用了哈希表来支持的,特点:存储快

往Haset添加元素的时候,HashSet会先调用元素的hashCode方法得到元素的哈希值 ,然后通过元素 的哈希值经过移位等运算,就可以算出该元素在哈希表中 的存储位置。

1.如果算出的元素存储的位置目前没有任何元素存储,那么该元素可以直接存储在该位置上

2. 如果算出的元素的存储位置目前已经存在有其他的元素了,那么还会调用该元素的equals方法与该位置的元素再比较一次,如果equals方法返回的是true,那么该位置上的元素视为重复元素,不允许添加,如果返回的是false,则允许添加、

 

HashSet的底层通过HashMap实现的,而HashMap在1.7之前使用的是数组+链表实现,在1.8+使用的数组+链表+红黑树实现。其实也可以这样理解,HashSet的底层实现和HashMap使用的是相同的方式,因为Map是无序的,因此HashSet也无法保证顺序。HashSet的方法也是借助HashMap的方法来实现的。

 

 

  1. LinkedHashMap 的实现原理?

LinkedHashMap是Map接口的哈希表和链接列表实现,具有可预知的迭代顺序。此实现提供所有可选的映射操作,并允许使用null值和null键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。

 

  1. 什么是迭代器 (Iterator)?

在Java中,有很多的数据容器,对于这些的操作有很多的共性。Java采用了迭代器来为各种容器提供了公共的操作接口。这样使得对容器的遍历操作与其具体的底层实现相隔离,达到解耦的效果。

 

  1. Iterator 和 ListIterator 的区别是什么?

Iterator可用来遍历Set和List集合,但是ListIterator只能用来遍历List。

Iterator对集合只能是前向遍历,ListIterator既可以前向也可以后向。

ListIterator实现了Iterator接口,并包含其他的功能,比如:增加元素,替换元素,获取前一个和后一个元素的索引,等等。

  1. 数组 (Array) 和列表 (ArrayList) 有什么区别?什么时候应该使用Array 而不是 ArrayList?

Array 可以包含基本类型和对象类型,ArrayList 只能包含对象类型。 Array 大小是固定的,ArrayList 的大小是动态变化的。ArrayList 提供了更多的方法和特性,比如:addAll(),removeAll(),iterator()等等。 对于基本类型数据,集合 使用自动装箱来减少编码工作量。但是,当处理固定大小的基本数据类型的时候,这种方式相对比较慢。

 

  1. Java 集合类框架的最佳实践有哪些?

有些集合类型允许指定初始容量。因此,如果我们能估计出存储的元素的数目,我们可以指定初始容量来避免重新计算hash值或者扩容等。

3.为了类型安全、可读性和健壮性等原因总是要使用泛型。同时,使用泛型还可以避免运行时的ClassCastException。

4.使用JDK提供的不变类(immutable class)作为Map的键可以避免为我们自己的类实现hashCode()和equals()方法。

5.编程的时候接口优于实现

 

  1. Collection 和 Collections 的区别。

Collection是集合类的上级的接口,继承与他的接口主要有Set和List。

Collections是针对集合类别的一个帮助类,,他提供一系列静态方法对各种集合的搜索,排序,线程安全化等操作。

 

 

 

 

 

JVM与调优

  1. Java 类加载过程?

1、加载:这个很简单,程序运行之前jvm会把编译完成的.class二进制文件加载到内存,供程序使用,用到的就是类加载器classLoader ,这里也可以看出java程序的运行并不是直接依   靠底层的操作系统,而是基于jvm虚拟机。如果没有类加载器,java文件就只是磁盘中的一个普通文件。

2、连接:连接是很重要的一步,过程比较复杂,分为三步  验证  》准备  》解析    

  验证:确保类加载的正确性。一般情况由javac编译的class文件是不会有问题的,但是可能有人的class文件是自己通过其他方式编译出来的,这就很有可能不符合jvm的编 译规则,这一步就是要过滤掉这部分不合法文件 

  准备:为类的静态变量分配内存,将其初始化为默认值 。我们都知道静态变量是可以不用我们手动赋值的,它自然会有一个初始值 比如int 类型的初始值就是0 ;boolean类型初始值为false,引用类型的初始值为null 。 这里注意,只是为静态变量分配内存,此时是没有对象实例的 

  解析:把类中的符号引用转化为直接引用。解释一下符号引用和直接引用。比如在方法A中使用方法B,A(){B();},这里的B()就是符号引用,初学java时我们都是知道这是java的引用,以为B指向B方法的内存地址,但是这是不完整的,这里的B只是一个符号引用,它对于方法的调用没有太多的实际意义,可以这么认为,他就是给程序员看的一个标志,让程序员知道,这个方法可以这么调用,但是B方法实际调用时是通过一个指针指向B方法的内存地址,这个指针才是真正负责方法调用,他就是直接引用。

  1. 初始化:为类的静态变量赋予正确的初始值,上述的准备阶段为静态变量赋予的是虚拟机默认的初始值,此处赋予的才是程序编写者为变量分配的真正的初始

 

 

 

 

描述一下 JVM 加载 Class 文件的原理机制?

Java中的所有类,都需要由类加载器装载到JVM中才能运行。类加载器本身也是一个类,而它的工作就是把class文件从硬盘读取到内存中。在写程序的时候,我们几乎不需要关心类的加载,因为这些都是隐式装载的,除非我们有特殊的用法,像是反射,就需要显式的加载所需要的类。

 

 

  1. Java 内存分配。

 

具体的概念:JVM的内存可分为3个区:堆(heap)、栈(stack)和方法区(method,也叫静态区):

堆区: 

1.存储的全部是对象,每个对象都包含一个与之对应的class的信息(class的目的是得到操作指令) ;
2.jvm只有一个堆区(heap),且被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身和数组本身;

栈区: 
1.每个线程包含一个栈区,栈中只保存基础数据类型本身和自定义对象的引用;
2.每个栈中的数据(原始类型和对象引用)都是私有的,其他栈不能访问;
3.栈分为3个部分:基本类型变量区、执行环境上下文、操作指令区(存放操作指令);

方法区(静态区): 
1.被所有的线程共享,方法区包含所有的class(class是指类的原始代码,要创建一个类的对象,首先要把该类的代码加载到方法区中,并且初始化)和static变量。 
2.方法区中包含的都是在整个程序中永远唯一的元素,如class,static变量。 

 

 

  1. GC 是什么? 为什么要有 GC?

 GC是垃圾收集的意思,内存处理是编程人员容易出现问题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃,Java提供的GC功能可以自动监测对象是否超过作用域从而达到自动回收内存的目的,Java语言没有提供释放已分配内存的显示操作方法。Java程序员不用担心内存管理,因为垃圾收集器会自动进行管理。要请求垃圾收集,可以调用下面的方法之一:

System.gc() 或Runtime.getRuntime().gc() 。

垃圾回收可以有效的防止内存泄露,有效的使用可以使用的内存。垃圾回收器通常是作为一个单独的低优先级的线程运行,不可预知的情况下对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收,程序员不能实时的调用垃圾回收器对某个对象或所有对象进行垃圾回收。回收机制有分代复制垃圾回收、标记垃圾回收、增量垃圾回收等方式。

 

 

 

  1. 简述 Java 垃圾回收机制

在java中是通过引用来和对象进行关联的,也就是说如果要操作对象,必须通过引用来进行。那么很显然一个简单的办法就是通过引用计数来判断一个对象是否可以被回收。不失一般性,如果一个对象没有任何引用与之关联,则说明该对象基本不太可能在其他地方被使用到,那么这个对象就成为可被回收的对象了。这种方式成为引用计数法

 

典型的垃圾收集算法:1.Mark-Sweep(标记-清除)算法

这是最基础的垃圾回收算法,之所以说它是最基础的是因为它最容易实现,思想也是最简单的。标记-清除算法分为两个阶段:标记阶段和清除阶段。标记阶段的任务是标记出所有需要被回收的对象,清除阶段就是回收被标记的对象所占用的空间。具体过程如下图所示:

 

2、Generational Collection(分代收集)算法

分代收集算法是目前大部分JVM的垃圾收集器采用的算法。它的核心思想是根据对象存活的生命周期将内存划分为若干个不同的区域。一般情况下将堆区划分为老年代(Tenured Generation)和新生代(Young Generation),老年代的特点是每次垃圾收集时只有少量对象需要被回收,而新生代的特点是每次垃圾回收时都有大量的对象需要被回收,那么就可以根据不同代的特点采取最适合的收集算法。

  目前大部分垃圾收集器对于新生代都采取Copying算法,因为新生代中每次垃圾回收都要回收大部分对象,也就是说需要复制的操作次数较少,但是实际中并不是按照1:1的比例来划分新生代的空间的,一般来说是将新生代划分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden空间和其中的一块Survivor空间,当进行回收时,将Eden和Survivor中还存活的对象复制到另一块Survivor空间中,然后清理掉Eden和刚才使用过的Survivor空间。

  而由于老年代的特点是每次回收都只回收少量对象,一般使用的是Mark-Compact算法。

  注意,在堆区之外还有一个代就是永久代(Permanet Generation),它用来存储class类、常量、方法描述等。对永久代的回收主要回收两部分内容:废弃常量和无用的类。

 

 

 

  1.  java什么时候进行垃圾回收,垃圾回收的执行流程

 

java的垃圾回收分为

三个区域新生代 老年代 永久代

 

一个对象实例化时 先去看伊甸园有没有足够的空间

如果有 不进行垃圾回收 ,对象直接在伊甸园存储.

如果伊甸园内存已满,会进行一次minor gc

然后再进行判断伊甸园中的内存是否足够

如果不足 则去看存活区的内存是否足够.

如果内存足够,把伊甸园部分活跃对象保存在存活区,然后把对象保存在伊甸园.

如果内存不足,向老年代发送请求,查询老年代的内存是否足够

如果老年代内存足够,将部分存活区的活跃对象存入老年代.然后把伊甸园的活跃对象放入存活区,对象依旧保存在伊甸园.

如果老年代内存不足,会进行一次full gc,之后老年代会再进行判断 内存是否足够,如果足够 同上.

如果不足 会抛出OutOfMemoryError.

java可以手动配置堆内存
-Xms 设置初始分配大小,默认为物理内存的1/64
-Xms 设置最大分配内存,默认为物理内存的1/4

 

 

  1. 如何判断一个对象是否存活?(或者 GC 对象的判定方法)

引用计数法:在java中是通过引用来和对象进行关联的,也就是说如果要操作对象,必须通过引用来进行。那么很显然一个简单的办法就是通过引用计数来判断一个对象是否可以被回收。不失一般性,如果一个对象没有任何引用与之关联,则说明该对象基本不太可能在其他地方被使用到,那么这个对象就成为可被回收的对象了。这种方式成为引用计数法。

 

 

  1. 垃圾回收的优点和原理。并考虑 2 种回收机制

1、Java语言最显著的特点就是引入了垃圾回收机制,它使java程序员在编写程序时不再考虑内存管理的问题。 
2、由于有这个垃圾回收机制,java中的对象不再有“作用域”的概念,只有引用的对象才有“作用域”。 
3、垃圾回收机制有效的防止了内存泄露,可以有效的使用可使用的内存。 
4、垃圾回收器通常作为一个单独的低级别的线程运行,在不可预知的情况下对内存堆中已经死亡的或很长时间没有用过的对象进行清除和回收。 
5、程序员不能实时的对某个对象或所有对象调用垃圾回收器进行垃圾回收。

垃圾回收机制有分代复制垃圾回收、标记垃圾回收。

 

  1. 垃圾回收器的基本原理是什么?垃圾回收器可以马上回收内存吗?有什么办法主动通知虚拟机进行垃圾回收?

对于GC来说,当程序员创建对象时,GC就开始监控这个对象的地址、大小以及使用情况。通常,GC采用有向图的方式记录和管理堆(heap)中的所有对象。通过这种方式确定哪些对象是"可达的",哪些对象是"不可达的"。当GC确定一些对象为"不可达"时,GC就有责任回收这些内存空间。

  可以。

  程序员可以手动执行System.gc(),通知GC运行,但是Java语言规范并不保证GC一定会执行。强制执行垃圾回收:System.gc()。Runtime.getRuntime().gc()

  静态类:static的是属于类的,而不是属于对象的,相当于是全局的,不可能被回收

  静态变量本身不会被回收,但是它所引用的对象应该是可以回收的。

gc只回收heap里的对象,对象都是一样的,只要没有对它的引用,就可以被回收(但是不一定被回收). 对象的回收和是否static没有什么关系!

如:static Vector pane = new Vector();  pane = null;  如果没有其它引用的话,原来pane指向的对象实例就会被回收。

Java程序员在编写程序的时候不再需要考虑内存管理。由于有个垃圾回收机制,Java中的对象不再有"作用域"的概念,只有对象的引用才有"作用域"。垃圾回收可以有效的防止内存泄露,有效的使用可以使用的内存。垃圾回收器通常是作为一个单独的低级别的线程运行,不可预知的情况下对内存堆中已经死亡的或者长时间没有使用的对象进行清楚和回收,程序员不能实时的调用垃圾回收器对某个对象或所有对象进行垃圾回收。

 

 

  1. Java 中会存在内存泄漏吗,请简单描述

内存泄露就是指一个不再被程序使用的对象或变量一直被占据在内存中。Java 使用有向图的方式进行垃圾回收管理,可以消除引用循环的问题,例如有两个对象,相互引用,只要它们和根进程不可达的,那么GC也是可以回收它们的

 

1、Java中内存泄露的发生场景,通俗地说,就是程序员可能创建了一个对象,以后一直不再使用这个对象,这个对象却一直被引用,即这个对象无用但是却无法被垃圾回收器回收的,这就是java中的内存泄露,一定要让程序将各种分支情况都完整执行到程序结束,然后看某个对象是否被使用过,如果没有,则才能判定这个对象属于内存泄露。

 

2.如果一个外部类的实例对象的方法返回了一个内部类的实例对象,这个内部类对象被长期引用了,即使那个外部类实例对象不再被使用,但由于内部类持久外部类的实例对象,这个外部类对象将不会被垃圾回收,这也会造成内存泄露。

 

3.当一个对象被存储进HashSet集合中以后,就不能修改这个对象中的那些参与计算哈希值的字段了,否则,对象修改后的哈希值与最初存储进HashSet集合中时的哈希值就不同了,在这种情况下,即使在contains方法使用该对象的当前引用作为的参数去HashSet集合中检索对象,也将返回找不到对象的结果,这也会导致无法从HashSet集合中单独删除当前对象,造成内存泄露。

 

 

  1. 深拷贝和浅拷贝。

Java中的对象拷贝主要分为:浅拷贝(Shallow Copy)、深拷贝(Deep Copy)。

浅拷贝:

在堆内存中不会分配新的空间,而是增加一个引用变量和之前的引用指向相同的堆空间。

详释:①对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象。因为是两份不同的数据,所以对其中一个对象的该成员变量值进行修改,不会影响另一个对象拷贝得到的数据。

②对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象。因为实际上两个对象的该成员变量都指向同一个实例。在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值。

实例:

int[] a = {1,2,3,4,5};

 

int[]b = a;

 

public class Test {

    

    public static void main(String[] args) {

        

        //数组的浅拷贝,a,b两个引用指向同一个数组

        int[] a = {1,2,3,4,5};

        int[] b = a;

        

        for (int i = 0; i < b.length; i++) {

            System.out.print(b[i] + " ");

        }

        

        System.out.println();

    }

}

 

深拷贝:

在堆内存中分配新空间,将之前的数组堆内存中的内容拷贝到新的空间中。

详释:对于深拷贝来说,不仅要复制对象的所有基本数据类型的成员变量值,还要为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象。也就是说,对象进行深拷贝要对整个对象图进行拷贝!

 

实例:

int[] a = {1,2,3,4,5};

 

int[] b = new int[5];

 

System.arraycopy(a, 0, b, 0, 5);

 

public class Test {

    

    public static void main(String[] args) {

        

        //数组的深拷贝,a,b两个引用指向同一个数组

        int[] a = {1,2,3,4,5};

        int[] b = new int[5];

        

        /**

         * System.arraycopy(src, srcPos, dest, destPos, length);

         * src:源数组

         * srcPos:源数组中拷贝的起始位置

         * dest:目标数组

         * destPos:目标数组中开始存放的位置

         * length:拷贝的长度

         */

        System.arraycopy(a, 0, b, 0, a.length);

        

        for (int i = 0; i < b.length; i++) {

            System.out.print(b[i] + " ");

        }

        

        System.out.println();

    }

}

 

注意基本数据类型是值传递,所以修改值后不会影响另一个对象的该属性值;引用数据类型是地址传递(引用传递),所以修改值后另一个对象的该属性值会同步被修改。String类型非常特殊,首先,String类型属于引用数据类型,不属于基本数据类型,但是String类型的数据是存放在常量池中的,也就是无法修改的!

 

 

  1. System.gc() 和 Runtime.gc() 会做什么事情?

java.lang.System.gc()只是java.lang.Runtime.getRuntime().gc()的简写,两者的行为没有任何不同

System.gc()和runtime.gc()用于提示jvm进行垃圾回收,但是否立即回收还是延迟回收由java虚拟机决定

 

 

  1. finalize() 方法什么时候被调用?析构函数 (finalization) 的目的是什么?

finalize()用在当垃圾回收器,因内存紧张,而去回收某些对象时,这时候会去调用其finalize()方法;而如果内存不紧张,就不会去回收对象,那finalize()就不会被调用;但是呢,考虑到JNI(java native interface),有时候finalize()就可以去回收这部分的内存;

 

  1. 如果对象的引用被置为 null,垃圾收集器是否会立即释放对象占用的内存?

不会,在下一个垃圾回调周期中,这个对象将是被可回收的。

也就是说并不会立即被垃圾收集器立刻回收,而是在下一次垃圾回收时才会释放其占用的内存。

 

 

  1. 串行(serial)收集器和吞吐量(throughput)收集器的区别是什么?

串行GC:整个扫描和复制过程均采用单线程的方式,相对于吞吐量GC来说简单;适合于单CPU、客户端级别。

吞吐量GC:采用多线程的方式来完成垃圾收集;适合于吞吐量要求较高的场合,比较适合中等和大规模的应用程序。

 

  1. 简述 Java 内存分配与回收策率以及 Minor GC 和 Major GC。

 新生代 GC(Minor GC):指发生在新生代的垃圾收集动作,因为 Java 对象大多都具

备朝生夕灭的特性,所以 Minor GC 非常频繁,一般回收速度也比较快。

 

 

 老年代 GC(Major GC  / Full GC):指发生在老年代的 GC,出现了 Major GC,经常

会伴随至少一次的 Minor GC(但非绝对的,在 ParallelScavenge 收集器的收集策略里

就有直接进行 Major GC 的策略选择过程) 。MajorGC 的速度一般会比 Minor GC 慢 10

 

 

  1. JVM 的永久代中会发生垃圾回收么?

hotspot的方法区存放在永久代中,因此方法区被人们称为永久代。永久代的垃圾回收主要包括类型的卸载和废弃常量池的回收。当没有对象引用一个常量的时候,该常量即可以被回收。而类型的卸载更加复杂。必须满足一下三点,该类型的所有实例都被回收了,该类型的ClassLoader被回收了,该类型对应的java.lang.Class没有在任何地方被引用,在任何地方都无法通过反射来实例化

 

 

  1. 什么是类加载器,类加载器有哪些?

一、什么是类加载器?

 

​Java类加载器是Java运行时环境的一部分,负责动态加载Java类到Java虚拟机的内存空间中。类通常是按需加载,即第一次使用该类时才加载。由于有了类加载器,Java运行时系统不需要知道文件与文件系统。学习类加载器时,掌握Java的委派概念很重要。 

二、它是干什么的?

类加载器它是在虚拟机中完成的,负责动态加载Java类到Java虚拟机的内存空间中,在经过 Java 编译器编译之后就被转换成 Java 字节代码(.class 文件)。类加载器负责读取 Java 字节代码,并转换成 java.lang.Class类的一个实例。

类加载器的四个方面

  1. 启动类加载器,没有父类。
  2. 拓展类加载器由Java语言实现,父类加载器为null
  3. 系统类加载器,由Java语言实现
  4. 自定义类加载器,父类加载器肯定为AppClassLoader

 

 

并发编程

  1. Synchronized 用过吗,其原理是什么?

主要有两种用法,分别是同步方法和同步代码块。也就是说,synchronized既可以修饰方法也可以修饰代码块。

synchronized,是Java中用于解决并发情况下数据同步访问的一个很重要的关键字。当我们想要保证一个共享资源在同一时间只会被一个线程访问到时,我们可以在代码中使用synchronized关键字对类或者对象加锁。

synchronized与原子性

原子性是指一个操作是不可中断的,要全部执行完成,要不就都不执行。

synchronized与可见性

可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。

synchronized与有序性

有序性即程序执行的顺序按照代码的先后顺序执行。

 

  1. 为什么说 Synchronized 是非公平锁?

synchronized 是非公平锁,可以重入。

公平锁:获取不到锁的时候,会自动加入队列,等待线程释放后,队列的第一个线程获取锁

非公平锁:获取不到锁的时候,会自动加入队列,等待线程释放锁后所有等待的线程同时去竞争

什么是可重入?

同一个线程可以反复获取锁多次,然后需要释放多次

  1. 什么是锁消除和锁粗化?

锁粗化:常情况下,为了保证多线程间的有效并发,会要求每个线程持有锁的时间尽可能短,但是大某些情况下,一个程序对同一个锁不间断、高频地请求、同步与释放,会消耗掉一定的系统资源,因为锁的讲求、同步与释放本身会带来性能损耗,这样高频的锁请求就反而不利于系统性能的优化了,虽然单次同步操作的时间可能很短。锁粗化就是告诉我们任何事情都有个度,有些情况下我们反而希望把很多次锁的请求合并成一个请求,以降低短时间内大量锁请求、同步、释放带来的性能损耗。

锁消除:锁消除是发生在编译器级别的一种锁优化方式。
有时候我们写的代码完全不需要加锁,却执行了加锁操作

  1. 为什么说 Synchronized 是一个悲观锁?乐观锁的实现原理又是什么?什么是 CAS,它有什么特性?

悲观锁

总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。再比如Java里面的同步原语synchronized关键字的实现也是悲观锁。

 

乐观锁

顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。

CAS

CAS是乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。 

CAS存在问题

循环时间长开销大

只能保证一个共享变量的原子操作:

 

spring 题

  1. 什么是 Spring 框架?Spring 框架有哪些主要模块?

Spring框架是一个为Java应用程序的开发提供了综合、广泛的基础性支持的Java平台。Spring帮助开发者解决了开发中基础性的问题,使得开发人员可以专注于应用程序的开发。Spring框架本身亦是按照设计模式精心打造,这使得我们可以在开发环境中安心的集成Spring框架,不必担心Spring是如何在后台进行工作的。

Spring有七大功能模块,分别是Spring Core,AOP,ORM,DAO,MVC,WEB,Context。
1,Spring Core
Core模块是Spring的核心类库,Spring的所有功能都依赖于该类库,Core主要实现IOC功能,Sprign的所有功能都是借助IOC实现的。
2,AOP
AOP模块是Spring的AOP库,提供了AOP(拦截器)机制,并提供常用的拦截器,供用户自定义和配置。
3,ORM
Spring 的ORM模块提供对常用的ORM框架的管理和辅助支持,Spring支持常用的Hibernate,ibtas,jdao等框架的支持,Spring本身并不对ORM进行实现,仅对常见的ORM框架进行封装,并对其进行管理
4,DAO模块
Spring 提供对JDBC的支持,对JDBC进行封装,允许JDBC使用Spring资源,并能统一管理JDBC事物,并不对JDBC进行实现。(执行sql语句)
5,WEB模块
WEB模块提供对常见框架如Struts1,WEBWORK(Struts 2),JSF的支持,Spring能够管理这些框架,将Spring的资源注入给框架,也能在这些框架的前后插入拦截器。
6,Context模块
Context模块提供框架式的Bean访问方式,其他程序可以通过Context访问Spring的Bean资源,相当于资源注入。
7,MVC模块
WEB MVC模块为Spring提供了一套轻量级的MVC实现,在Spring的开发中,我们既可以用Struts也可以用Spring自己的MVC框架,相对于Struts,Spring自己的MVC框架更加简洁和方便。

 

  1. 使用 Spring 框架能带来哪些好处?

1、Dependency Injection(DI) 方法使得构造器和JavaBean properties文件中的依赖关系一目了然。

2、与EJB容器相比较,IoC容器更加趋向于轻量级。这样一来IoC容器在有限的内存和CPU资源的情况下进行应用程序的开发和发布就变得十分有利。

3、Spring并没有闭门造车,Spring利用了已有的技术比如ORM框架、logging框架、J2EE、Quartz和JDK  Timer,以及其他视图技术。

4、Spring框架是按照模块的形式来组织的。由包和类的编号就可以看出其所属的模块,开发者仅仅需要选用他们需要的模块即可。

5、要测试一项用Spring开发的应用程序十分简单,因为测试相关的环境代码都已经囊括在框架中了。更加简单的是,利用JavaBean形式的POJO类,可以很方便的利用依赖注入来写入测试数据。

6、Spring的Web框架亦是一个精心设计的Web  MVC框架,为开发者们在web框架的选择上提供了一个除了主流框架比如Struts、过度设计的、不流行web框架的以外的有力选项。

7、Spring提供了一个便捷的事务管理接口,适用于小型的本地事物处理(比如在单DB的环境下)和复杂的共同事物处理(比如利用JTA的复杂DB环境)。

  1. 什么是控制反转(IOC)?什么是依赖注入?

oC全称是Inversion of Control,就是控制反转,他其实不是spring独有的特性或者说也不是java的特性,他是一种设计思想。而DI(Dependency Injection),即依赖注入就是Ioc的一种实现方式。

@Componentpublic class ComponentA {

    @Autowired // 1.接口注入

    private ComponentB componentB;

    

    @Autowired // 2.设值方法注入

    public void setComponentB(ComponentB componentB) {

        this.componentB = componentB;

    }

 

    @Autowired // 3.构造注入

    public ComponentA(ComponentB componentB) {

        this.componentB = componentB;

    }

}

  1. BeanFactory 和 ApplicationContext 有什么区别?

BeanFactroy采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化,这样,我们就不能发现一些存在的Spring的配置问题。而ApplicationContext则相反,它是在容器启动时,一次性创建了所有的Bean。这样,在容器启动时,我们就可以发现Spring中存在的配置错误。 

 

  1. Spring 有几种配置方式?

Spring主要包括了三种配置bean元数据的方式:XML文件,java注解,java代码

 

  1. 请解释 Spring Bean 的生命周期?

(1)实例化Bean:

对于BeanFactory容器,当客户向容器请求一个尚未初始化的bean时,或初始化bean的时候需要注入另一个尚未初始化的依赖时,容器就会调用createBean进行实例化。对于ApplicationContext容器,当容器启动结束后,通过获取BeanDefinition对象中的信息,实例化所有的bean。

(2)设置对象属性(依赖注入):

实例化后的对象被封装在BeanWrapper对象中,紧接着,Spring根据BeanDefinition中的信息 以及 通过BeanWrapper提供的设置属性的接口完成依赖注入。

(3)处理Aware接口:

接着,Spring会检测该对象是否实现了xxxAware接口,并将相关的xxxAware实例注入给Bean:

①如果这个Bean已经实现了BeanNameAware接口,会调用它实现的setBeanName(String beanId)方法,此处传递的就是Spring配置文件中Bean的id值;

②如果这个Bean已经实现了BeanFactoryAware接口,会调用它实现的setBeanFactory()方法,传递的是Spring工厂自身。

③如果这个Bean已经实现了ApplicationContextAware接口,会调用setApplicationContext(ApplicationContext)方法,传入Spring上下文;

(4)BeanPostProcessor:

如果想对Bean进行一些自定义的处理,那么可以让Bean实现了BeanPostProcessor接口,那将会调用postProcessBeforeInitialization(Object obj, String s)方法。由于这个方法是在Bean初始化结束时调用的,所以可以被应用于内存或缓存技术;

(5)InitializingBean 与 init-method:

如果Bean在Spring配置文件中配置了 init-method 属性,则会自动调用其配置的初始化方法。

(6)如果这个Bean实现了BeanPostProcessor接口,将会调用postProcessAfterInitialization(Object obj, String s)方法;

以上几个步骤完成后,Bean就已经被正确创建了,之后就可以使用这个Bean了。

(7)DisposableBean:

当Bean不再需要时,会经过清理阶段,如果Bean实现了DisposableBean这个接口,会调用其实现的destroy()方法;

(8)destroy-method:

最后,如果这个Bean的Spring配置中配置了destroy-method属性,会自动调用其配置的销毁方法。

 

  1. 请举例解释@Required 注解?

@Required注解适用于bean属性setter方法,并表示受影响的bean属性必须在XML配置文件在配置时进行填充。否则,容器会抛出一个BeanInitializationException异常。

 

  1. 请举例解释@Autowired 注解?

可以修饰属性,构造方法,set方法,默认依据类型(属性类型,参数类型)为属性注入值.假如Spring容器中有多个相同类型的值,会参考名字进行匹配查找(属性名,set方法参数名,构造方法参数名),假如名字有相同的则注入,没有相同的会注入失败.

 

  1. 请举例说明@Qualifier 注解?
  2. 构造方法注入和设值注入有什么区别?
  3. Spring 框架中都用到了哪些设计模式?
  • 1
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小码哥(xmgcode88)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值