京东面试题 Java相关

  1.JVM的内存结构和管理机制;

JVM实例:一个独立运行的java程序,是进程级别

JVM执行引擎:用户运行程序的线程,是JVM实例的一部分

JVM实例的诞生
当启动一个java程序时.一个JVM实例就诞生了,任何一个拥有public static void main(string[] args)的函数都可以作为实例的运行启点

      2.  JVM实例运行

main作为程序初始化线程的起点,任何其他线程由其启动。

JVM有两种线程:守护线程和非守护线程。守护线程由JVM使用。main启动后将是非守护线程。

      3.  JVM实例消亡

   当程序中所有非守护线程都中止时,JVM退出;若安全管理器允许,程序也可以使用

Runtime类或者System.exit()退出。
JVM的生命周期
JVM主要包括四个部分:
1.类加载器(ClassLoader):在JVM启动时或者在类运行时将需要的class加载到JVM中。(右图表示了从java源文件到JVM的整个过程,可配合理解。 关于类的加载机制,可以参考http://blog.csdn.net/tonytfjing/article/details/47212291)
2.执行引擎:负责执行class文件中包含的字节码指令(执行引擎的工作机制,这里也不细说了,这里主要介绍JVM结构);
3.内存区(也叫运行时数据区):是在JVM运行的时候操作所分配的内存区。运行时内存区主要可以划分为5个区域
      方法区(Method Area):用于存储类结构信息的地方,包括常量池、静态变量、构造函数等。虽然JVM规范把方法区描述为堆的一个逻辑部分, 但它却有个别名non-heap(非堆),所以大家不要搞混淆了。方法区还包含一个运行时常量池。
      java堆(Heap):存储java实例或者对象的地方。这块是GC的主要区域(后面解释)。从存储的内容我们可以很容易知道,方法区和堆是被所有java线程共享的。
       java栈(Stack):java栈总是和线程关联在一起,每当创建一个线程时,JVM就会为这个线程创建一个对应的java栈。在这个java栈中又会包含多个栈帧,每运行一个方法就创建一个栈帧,用于存储局部变量表、操作栈、方法返回值等。每一个方法从调用直至执行完成的过程,就对应一个栈帧在java栈中入栈到出栈的过程。所以java栈是现成私有的。
        程序计数器(PC Register):用于保存当前线程执行的内存地址。由于JVM程序是多线程执行的(线程轮流切换),所以为了保证线程切换回来后,还能恢复到原先状态,就需要一个独立的计数器,记录之前中断的地方,可见程序计数器也是线程私有的。
        本地方法栈(Native Method Stack):和java栈的作用差不多,只不过是为JVM使用到的native方法服务的。
4.本地方法接口:主要是调用C或C++实现的本地方法及返回结果。
JVM主要包括四个部分:
Method Area 方法区又称永久代(Permanent Generation)
       方法区是被所有线程共享,该区域保存所有字段和方法字节码,以及一些特殊方法如构造函数,接口代码也在此定义。

       方法区中存放了每个Class的结构信息,包括常量池、字段描述、方法描述等等。

      VM Space 描述中对这个区域的限制非常宽松,除了和Java堆一样不需要连续的内存,也可以选择固定大小或者可扩展外,甚至可以选择不实现垃圾收集。相对来说,垃圾收集行为在这个区域是相对比较少发生的,但并不是某些描述那样永久代不会发生GC,这里的GC主要是对常量池的回收和对类的卸载,虽然回收的“成绩”一般也比较差强人意,尤其是类卸载,条件相当苛刻。

      运行时常量池(Runtime Constant Pool)也方法区存放。Class文件中除了有类的版本、字段、方法、接口等描述等信息外,还有一项信息是常量表(constant_pool table),用于存放编译期已可知的常量,这部分内容将在类加载后进入方法区(永久代)存放。但是Java语言并不要求常量一定只有编译期预置入Class的常量表的内容才能进入方法区常量池,运行期间也可将新内容放入常量池(最典型的String.intern()方法)。 运行时常量池是方法区的一部分,自然受到方法区内存的限制,当常量池无法在申请到内存时会抛出OutOfMemoryError异常

      2. PC Register程序计数器

每个线程都有一个程序计数器,就是一个指针,指向方法区中的方法字节码,由执行引擎读取下一条指令。每一个Java线程都有一个程序计数器来用于保存程序执行到当前方法的哪一个指令,对于非Native方法,这个区域记录的是正在执行的VM原语的地址,如果正在执行的是Natvie方法,这个区域则为空(undefined)。此内存区域是唯一一个在VM Spec中没有规定任何OutOfMemoryError情况的区域。

      3.Native Method Stack 本地方法栈

本地方法栈是为虚拟机使用到的Native方法服务。它的实现的语言、方式与结构并没有强制规定,甚至有的虚拟机(譬如Sun Hotspot虚拟机)直接就把本地方法栈和JVM栈合二为一。这个区域也会抛出StackOverflowError和OutOfMemoryError异常。

      4.Stack栈也叫栈内存

VM栈的生命周期也是与线程相同。VM栈描述的是Java方法调用的内存模型:每个方法被执行的时候,都会同时创建一个帧(Frame)用于存储本地变量表、操作栈、动态链接、方法出入口等信息。每一个方法的调用至完成,就意味着一个帧在VM栈中的入栈至出栈的过程。

JVM的内存指令区。是在线程创建时创建,它的生命期是跟随线程的生命期,线程结束栈内存也就释放,对于栈来说不存在垃圾回收问题,只要线程一结束,该栈就Over。问题出来了:栈中存的是那些数据呢?又什么是格式呢?

栈中的数据都是以栈帧(Stack Frame)的格式存在,栈帧是一个内存区块,是一个数据集,是一个有关方法(Method)和运行期数据的数据集,当一个方法A被调用时就产生了一个栈帧F1,并被压入到栈中,A方法又调用了B方法,于是产生栈帧F2也被压入栈,执行完毕后,先弹出F2栈帧,再弹出F1栈帧,遵循“先进后出”原则。

那栈帧中到底存在着什么数据呢?栈帧中主要保存3类数据:本地变量(Local Variables),包括输入参数和输出参数以及方法内的变量;栈操作(Operand Stack),记录出栈、入栈的操作;栈帧数据(Frame Data),包括类文件、方法等。

VM栈中各个帧的本地变量表部分,本地变量表存放了编译期可知的各种标量类型(boolean、byte、char、short、int、float、long、double)、对象引用(不是对象本身,仅仅是一个引用指针)、方法返回地址等。其中long和double会占用2个本地变量空间(32bit),其余占用1个。

本地变量表在进入方法时进行分配,当进入一个方法时,这个方法需要在帧中分配多大的本地变量是一件完全确定的事情,在方法运行期间不改变本地变量表的大小。

在VM Spec中对这个区域规定了2中异常状况:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;如果VM栈可以动态扩展(VM Spec中允许固定长度的VM栈),当扩展时无法申请到足够内存则抛出OutOfMemoryError异常。

       5. Heap 堆内存

一个JVM实例只存在一个堆类存,堆内存的大小是可以调节的。

Java堆可以处于物理上不连续的内存空间,它逻辑上是连续的即可,就像我们的磁盘空间一样。实现时可以选择实现成固定大小的,也可以是可扩展的,

不过当前所有商业的虚拟机都是按照可扩展来实现的(通过-Xmx和-Xms控制)。如果在堆中无法分配内存,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。

类加载器读取了类文件后,需要把类、方法、常变量放到堆内存中,以方便执行器执行,堆内存分为三部分:

1)        Permanent Space永久存储区(也叫方法区)

永久存储区是一个常驻内存区域,用于存放JDK自身所携带的Class,Interface的元数据,也就是说它存储的是运行环境必须的类信息,被装载进此区域的数据是不会被垃圾回收器回收掉的,关闭JVM才会释放此区域所占用的内存。

2)        Young Generation Space 新生区

新生区是类的诞生、成长、消亡的区域,一个类在这里产生,应用,最后被垃圾回收器收集,结束生命。新生区又分为两部分:伊甸区(Eden space)和幸存者区(Survivor pace),所有的类都是在伊甸区被new出来的。幸存区有两个: 0区(Survivor 0 space)和1区(Survivor 1 space)。当伊甸园的空间用完时,程序又需要创建对象,JVM的垃圾回收器将对伊甸园区进行垃圾回收,将伊甸园区中的不再被其他对象所引用的对象进行销毁。然后将伊甸园中的剩余对象移动到幸存0区。若幸存0区也满了,再对该区进行垃圾回收,然后移动到1区。那如果1区也满了呢?再移动到养老区。

3)        Tenure generation space养老区

养老区用于保存从新生区筛选出来的JAVA对象,一般池对象都在这个区域活跃。
   6. 本机直接内存(Direct Memory)

直接内存并不是虚拟机运行时数据区的一部分,它根本就是本机内存而不是VM直接管理的区域。但是这部分内存也会导致OutOfMemoryError异常出现,因此我们放到这里一起描述。在JDK1.4中新加入了NIO类,引入一种基于渠道与缓冲区的I/O方式,它可以通过本机Native函数库直接分配本机内存,然后通过一个存储在Java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在Java对和本机堆中来回复制数据。

显然本机直接内存的分配不会受到Java堆大小的限制,但是即然是内存那肯定还是要

受到本机物理内存(包括SWAP区或者Windows虚拟内存)的限制的,一般服务器管理员配置JVM参数时,

会根据实际内存设置-Xmx等参数信息,但经常忽略掉直接内存,使得各个内存区域总和大于物理内存限制(包括物理的和操作系统级的限制),而导致动态扩展时出现OutOfMemoryError异常。
JVM的内存管理

  Java堆和栈的区别

内存泄漏:分配出去的内存回收不了

内存溢出:指系统内存不够用了
Java内存泄露和内存溢出

       2.JVM的垃圾回收机制;

JVM中自动的对象内存回收机制称为GC(Garbage Collection)。

为什么要进行垃圾回收?随着程序的运行,内存中存在的实例对象、变量等信息占据的内存越来越多,如果不及时进行垃圾回收,必然会带来程序性能的下降,甚至会因为可用内存不足造成一些不必要的系统异常。

哪些“垃圾”需要回收?

在我们上面介绍的六大区中,有三个是不需要进行垃圾回收的:程序计数器、JVM栈、本地方法栈。因为它们的生命周期是和线程同步的,随着线程的销毁,它们占用的内存会自动释放,所以只有方法区和堆需要进行GC。方法区回收也很少,一般是堆需要GC,具体到哪些对象的话,简单概况一句话:如果某个对象已经不存在任何引用,那么它可以被回收。通俗解释一下就是说,如果一个对象,已经没有什么作用了,就可以被当废弃物被回收了。

什么时候进行垃圾回收?

根据一个经典的引用计数算法,每个对象添加一个引用计数器,每被引用一次,计数器加1,失去引用,计数器减1,当计数器在一段时间内保持为0时,该对象就认为是可以被回收得了。但是,这个算法有明显的缺陷:当两个对象相互引用,但是二者已经没有作用时,按照常规,应该对其进行垃圾回收,但是其相互引用,又不符合垃圾回收的条件,因此无法完美处理这块内存清理,因此Sun的JVM并没有采用引用计数算法来进行垃圾回收。而是采用一个叫:根搜索算法
基本思想就是:从一个叫GC Roots的对象开始,向下搜索,如果一个对象不能到达GC Roots对象的时候,说明它已经不再被引用,即可被进行垃圾回收(此处 暂且这样理解,其实事实还有一些不同,当一个对象不再被引用时,并没有完全“死亡”,如果类重写了finalize()方法,且没有被系统调用过,那么系统会调用一次finalize()方法,以完成最后的工作,在这期间,如果可以将对象重新与任何一个和GC Roots有引用的对象相关联,则该对象可以“重生”,如果不可以,那么就说明彻底可以被回收了),如上图中的Object5、Object6、Object7,虽然它们3个依然可能相互引用,但是总体来说,它们已经没有作用了,这样就解决了引用计数算法无法解决的问题。
GC基本原理
为将内存中不再被使用的对象进行回收,消耗资源和时间。

1)对新生代对象的收集称为minor GC

2) 对旧生代对象的收集成为Full  GC

3) 程序中主动调用System.GC()强制执行的GC称为FULL GC

对象引用类型分为强引用、软引用、弱引用和虚引用。
1)        强引用:就是我们一般声明对象是时虚拟机生成的引用,强引用环境下,垃圾回收时需要严格判断当前对象是否被强引用,如果被强引用,则不会被垃圾回收。

2)        软引用:软引用一般被做为缓存来使用。与强引用的区别是,软引用在垃圾回收时,虚拟机会根据当前系统的剩余内存来决定是否对软引用进行回收。如果剩余内存比较紧张,则虚拟机会回收软引用所引用的空间;如果剩余内存相对富裕,则不会进行回收。换句话说,虚拟机在发生OutOfMemory时,肯定是没有软引用存在的。

3)        弱引用:弱引用与软引用类似,都是作为缓存来使用。但与软引用不同,弱引用在进行垃圾回收时,是一定会被回收掉的,因此其生命周期只存在于一个垃圾回收周期内。

4)        "虚引用":就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。

虚引用主要用来跟踪对象被垃圾回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列(ReferenceQueue)联合使用。当垃 圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是 否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。

强引用不用说,我们系统一般在使用时都是用的强引用。而“软引用”和“弱引用”比较少见。他们一般被作为缓存使用,而且一般是在内存大小比较受限的情况下做为缓存。因为如果内存足够大的话,可以直接使用强引用作为缓存即可,同时可控性更高。因而,他们常见的是被使用在桌面应用系统的缓存。

特别注意,在世纪程序设计中一般很少使用弱引用与虚引用,使用软用的情况较多,这是因为软引用可以加速JVM对垃圾内存的回收速度,可以维护系统的运行安全,防止内存溢出(OutOfMemory)等问题的产生。

如何进行垃圾回收?
本块内容以介绍垃圾回收算法为主,因为我们前面有介绍,内存主要被分为三块,新生代、旧生代、持久代。三代的特点不同,造就了他们所用的GC算法不同,新生代适合那些生命周期较短,频繁创建及销毁的对象,旧生代适合生命周期相对较长的对象,持久代在Sun HotSpot中就是指方法区(有些JVM中根本就没有持久代这中说法)。首先介绍下新生代、旧生代、持久代的概念及特点:

新生代:New Generation或者Young Generation。上面大致分为Eden区和Survivor区,Survivor区又分为大小相同的两部分:FromSpace 和ToSpace。新建的对象都是用新生代分配内存,Eden空间不足的时候,会把存活的对象转移到Survivor中,新生代的大小可以由-Xmn来控制,也可以用-XX:SurvivorRatio来控制Eden和Survivor的比例.
旧生代:Old Generation。用于存放新生代中经过多次垃圾回收仍然存活的对象,例如缓存对象。旧生代占用大小为-Xmx值减去-Xmn对应的值。

持久代:Permanent Generation。在Sun的JVM中就是方法区的意思,尽管有些JVM大多没有这一代。主要存放常量及类的一些信息默认最小值为16MB,最大值为64MB,可通过-XX:PermSize及-XX:MaxPermSize来设置最小值和最大值。
  5.常见的GC算法:

标记-清除算法(Mark-Sweep)
最基础的GC算法,将需要进行回收的对象做标记,之后扫描,有标记的进行回收,这样就产生两个步骤:标记和清除。这个算法效率不高,而且在清理完成后会产生内存碎片,这样,如果有大对象需要连续的内存空间时,还需要进行碎片整理,所以,此算法需要改进。

         复制算法(Copying)

前面我们谈过,新生代内存分为了三份,Eden区和2块Survivor区,一般Sun的JVM会将Eden区和Survivor区的比例调为8:1,保证有一块Survivor区是空闲的,这样,在垃圾回收的时候,将不需要进行回收的对象放在空闲的Survivor区,然后将Eden区和第一块Survivor区进行完全清理,这样有一个问题,就是如果第二块Survivor区的空间不够大怎么办?这个时候,就需要当Survivor区不够用的时候,暂时借持久代的内存用一下。此算法适用于新生代。

         标记-整理(或叫压缩)算法(Mark-Compact)

和标记-清楚算法前半段一样,只是在标记了不需要进行回收的对象后,将标记过的对象移动到一起,使得内存连续,这样,只要将标记边界以外的内存清理就行了。此算法适用于持久代。

       6.常见的垃圾收集器: 

根据上面说的诸多算法,每个JVM都有不同的实现,我们首先介绍三种实际的垃圾回收器:串行GC(SerialGC)、并行回收GC(Parallel Scavenge)和并行GC(ParNew)。
1)、Serial GC。是最基本、最古老的收集器,但是现在依然被广泛使用,是一种单线程垃圾回收机制,而且不仅如此,它最大的特点就是在进行垃圾回收的时候,需要将所有正在执行的线程暂停(Stop The World),对于有些应用这是难以接受的,但是我们可以这样想,只要我们能够做到将它所停顿的时间控制在N个毫秒范围内,大多数应用我们还是可以接受的,而且事实是它并没有让我们失望,几十毫米的停顿我们作为客户机(Client)是完全可以接受的,该收集器适用于单CPU、新生代空间较小及对暂停时间要求不是非常高的应用上,是client级别默认的GC方式,可以通过-XX:+UseSerialGC来强制指定。

2)、ParNew GC。基本和Serial GC一样,但本质区别是加入了多线程机制,提高了效率,这样它就可以被用在服务器端(Server)上,同时它可以与CMS GC配合,所以,更加有理由将它置于Server端。

3)、Parallel Scavenge GC。在整个扫描和复制过程采用多线程的方式来进行,适用于多CPU、对暂停时间要求较短的应用上,是server级别默认采用的GC方式,可用-XX:+UseParallelGC来强制指定,用-XX:ParallelGCThreads=4来指定线程数。以下给出几组使用组合:

4)、CMS (Concurrent Mark Sweep)收集器。该收集器目标就是解决Serial GC 的停顿问题,以达到最短回收时间。常见的B/S架构的应用就适合用这种收集器,因为其高并发、高响应的特点。CMS收集器是基于“标记-清除”算法实现的,整个收集过程大致分为4个步骤:初始标记(CMS initial mark)、并发标记(CMS concurrenr mark)、重新标记(CMS remark)、并发清除(CMS concurrent sweep)。

CMS收集器的优点:并发收集、低停顿,但是CMS还远远达不到完美。

5)、G1收集器。相比CMS收集器有不少改进,首先基于标记-整理算法,不会产生内存碎片问题,其次,可以比较精确的控制停顿,此处不再详细介绍。

6)、Serial Old。Serial Old是Serial收集器的老年代版本,它同样使用一个单线程执行收集,使用“标记-整理”算法。。

7)、Parallel Old。Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。

8)、RTSJ垃圾收集器,用于Java实时编程,后续会补充介绍。
摘要:https://www.cnblogs.com/dragonsuc/p/4380409.html
JVM垃圾回收机制

       3.Java的类加载机制;

 一个java类的完整的生命周期会经历加载、连接、初始化、使用、和卸载五个阶段
1.加载 
我们编写一个java的源文件,经过编译后生成一个后缀名为.class的文件,这结合四字节码文件,
java虚拟机就识别这种文件,java的生命周期就是class文件从加载到消亡的过程。 
关于加载,其实,就是将源文件的class文件找到类的信息将其加载到方法区中,
然后在堆区中实例化一个java.lang.Class对象,作为方法区中这个类的信息的入口。
但是这一功能是在JVM之外实现的,主要的原因是方便让应用程序自己决定如何获取这个类,
在不同的虚拟机实现的方式不一定相同,hotspot虚拟机是采用需要时在加载的方式,
也有其他是先预先加载的。 
2.连接 
一般会跟加载阶段和初始化阶段交叉进行,过程由三部分组成:验证、准备和解析三步 
(1)验证:确定该类是否符合java语言的规范,有没有属性和行为的重复,继承是否合理,总之,就是保证jvm能够执行 
(2)准备:主要做的就是为由static修饰的成员变量分配内存,并设置默认的初始值 
默认初始值如下:

1.八种基本数据类型默认的初始值是0 
2.引用类型默认的初始值是null 
3.有static final修饰的会直接赋值,例如:static final int x=10;则默认就是10.
(3)解析:这一阶段的任务就是把常量池中的符号引用转换为直接引用,说白了就是jvm会将所有的类或接口名、字段名、方法名转换为具体的内存地址。 
3.初始化 
这个阶段就是将静态变量(类变量)赋值的过程,即只有static修饰的才能被初始化,执行的顺序就是:

父类静态域或着静态代码块,然后是子类静态域或者子类静态代码块
4.使用 
在类的使用过程中依然存在三步:对象实例化、垃圾收集、对象终结

(1)对象实例化:就是执行类中构造函数的内容,如果该类存在父类JVM会通过显示或者隐示的方式先执行父类的构造函数,在堆内存中为父类的实例变量开辟空间,并赋予默认的初始值,然后在根据构造函数的代码内容将真正的值赋予实例变量本身,然后,引用变量获取对象的首地址,通过操作对象来调用实例变量和方法 
(2)垃圾收集:当对象不再被引用的时候,就会被虚拟机标上特别的垃圾记号,在堆中等待GC回收 
(3)对象的终结:对象被GC回收后,对象就不再存在,对象的生命也就走到了尽头
5.类卸载 
即类的生命周期走到了最后一步,程序中不再有该类的引用,该类也就会被JVM执行垃圾回收,从此生命结束…
摘要:https://www.cnblogs.com/ipetergo/p/6441310.html
Java类加载机制

      4.Java的集合类有哪些;

java的集合类

      5、LinkList和ArrayList的区别;

LinkedList和ArrayList的差别主要来自于Array和LinkedList数据结构的不同。如果你很熟悉Array和LinkedList,你很容易得出下面的结论:

1) 因为Array是基于索引(index)的数据结构,它使用索引在数组中搜索和读取数据是很快的。Array获取数据的时间复杂度是O(1),但是要删除数据却是开销很大的,因为这需要重排数组中的所有数据。

2) 相对于ArrayList,LinkedList插入是更快的。因为LinkedList不像ArrayList一样,不需要改变数组的大小,也不需要在数组装满的时候要将所有的数据重新装入一个新的数组,这是ArrayList最坏的一种情况,时间复杂度是O(n),而LinkedList中插入或删除的时间复杂度仅为O(1)。ArrayList在插入数据时还需要更新索引(除了插入数组的尾部)。

3) 类似于插入数据,删除数据时,LinkedList也优于ArrayList。

4) LinkedList需要更多的内存,因为ArrayList的每个索引的位置是实际的数据,而LinkedList中的每个节点中存储的是实际的数据和前后节点的位置。

什么场景下更适宜使用LinkedList,而不用ArrayList

我前面已经提到,很多场景下ArrayList更受欢迎,但是还有些情况下LinkedList更为合适。譬如:

1) 你的应用不会随机访问数据。因为如果你需要LinkedList中的第n个元素的时候,你需要从第一个元素顺序数到第n个数据,然后读取数据。

2) 你的应用更多的插入和删除元素,更少的读取数据。因为插入和删除元素不涉及重排数据,所以它要比ArrayList要快。

以上就是关于ArrayList和LinkedList的差别。你需要一个不同步的基于索引的数据访问时,请尽量使用ArrayList。ArrayList很快,也很容易使用。但是要记得要给定一个合适的初始大小,尽可能的减少更改数组的大小。
摘要:http://www.importnew.com/6629.html
LinkedList和ArrayList的区别

     6、HashMap的实现原理;

HashMap:按照特性来说明一下,储存的是键值对,线程不安全,非Synchronied,储存的比较快,能够接受null。按照工作原理来叙述一下,Map的put(key,value)来储存元素,通过get(key)来得到value值,通过hash算法来计算hascode值,根据hashcode值来决定bucket(桶),储存结构就算哈希表。 
提问:两个hashcode相同的时候会发生说明? 
hashcode相同,bucket的位置会相同,也就是说会发生碰撞,哈希表中的结构其实有链表(LinkedList),这种冲突通过将元素储存到LinkedList中,解决碰撞。储存顺序是仿在表头。 
如果两个键的hashcode相同,如何获取值对象? 
如果两个键的hashcode相同,我们通过key.equals()找到LinkedList中正确的节点。 
如果HashMap的大小超过了负载因子?怎么办? 
默认的HashMap里面的负载因子大小是0.75,将会重新创建一个原来HashMap大小的 
两倍bucket数组。 
重新调整的话会出现什么问题? 
多线程情况下会出现竞争问题,因为你在调节的时候,LinkedList储存是按照顺序储存,调节的时候回将原来最先储存的元素(也就是最下面的)遍历,多线程就好试图重新调整,这个时候就会出现死循环。 
为什么要用String、Wrapper类,来作为键值对?因为他们一般不是不可变的,源码上面final,使用不可变类,而且重写了equals和hashcode方法,避免了键值对改写。提高HashMap性能。 
使用CocurrentHashMap代替Hashtable? 
可以,但是Hashtable提供的线程更加安全。
hashMap 面试题

   8、简单讲一下工厂模式的优势;

工厂模式分为:简单工厂模式,工厂方法模式和抽象工厂模式
工厂模式,顾名思义,最少有一个生产产品的机器存在的工厂Factory,与此同时,也要有一个构建好的产品模块Product。所以,我们要用到Factory来创造Product。在简单工厂模式中,有几种角色存在。一个是所有产品的父类P,即所有产品的模板;另外一个是继承了父类P的产品子类p1,p2...;当然,最重要的是Factory,在Factory中可以将这些的产品实例化,根据需求来将Factory和Product产生联系。
简单工厂优点:
这样模块清晰化,每个部分都各司其职,分工明确,代码就实现最渐层意义上的“可维护”啦。说到缺点,当我们需要增加一产品,比如在计算机中加入一个新的功能,可以求M的N次方,这样个小功能我们就要去添加一个新的类,同时我们需要在Factory中改动到switch里面的代码,这是耦合性很高的表现啦,所以我们就有了“工厂模式”的出现啦。
优缺点
a.在客户端Client中可以将工厂模式的主要结构看着很清楚,首先我们要有IFactory这个工厂的父接口,所有的子类或者子接口都可以实现它。AddFactory则是子类的代表之一,所以利用java的多态来实现,降低代码的耦合性。而同时每个子工厂中拥有每条生产独特产品的生产线。由此,工厂和产品挂上钩了,联系上了。每个子工厂生产出来的都是独特的产品。

b.比“简单工厂模式”的优缺点

优点:克服了简单工厂违背开放-封闭原则的缺点,又保留了封装对象创建过程的优点,降低客户端和工厂的耦合性,所以说“工厂模式”是“简单工厂模式”的进一步抽象和推广。

缺点:每增加一个产品,相应的也要增加一个子工厂,加大了额外的开发量。
View Code

9、Spring的事务管理;

posted on 2018-01-11 21:56  端着咖啡码农  阅读(3185)  评论(0编辑  收藏  举报