面试要点5

 ---Spring 的Bean的生命周期---

在传统的Java应用中,bean的生命周期很简单。使用Java 关键字 new 进行bean 实例化,而后该 bean 就可使用了。一旦该bean 再也不被使用,则由 java 自动进行垃圾回收。html

  相比之下,Spring 容器中的 bean 的生命周期就显得相对复杂多了。正确理解Spring bean 的生命周期很是重要,由于你或许要利用 Spring 提供的扩展点来自定义bean 的建立过程。图1.5展现了  bean 装载到 spring 应用上下文中的一个典型的生命周期过程。(手机拍摄的。。)java

注意图中所说:bean 在 Spring 容器中从建立到销毁经历了若干阶段,每一阶段均可以针对 Spring 如何管理 bean 进行个性化定制web

正如你所见的,在 bean 准备就绪以前, bean 工厂执行了若干启动步骤。咱们对图1.5 进行详细描述:(这里是须要背下来的面试

1. Spring 对 bean 进行实例化;算法

2. Spring 将值和 bean 的引用注入到bean 对应的属性中;spring

3. 若是 bean 实现了 BeanNameAware 接口,Spring 将 bean 的 ID 传递给 setBeanName()方法;数组

4. 若是 bean 实现了  BeanFactoryAware 接口,Spring 将调用 setBeanFactory ()方法,将 BeanFactory 容器实例传入;安全

5. 若是 bean 实现了 ApplicationContextAware 接口,Spring 将调用 set-ApplicationContext () 方法,将 bean 所在的应用上下文的引用传入进来;服务器

6. 若是 bean 实现了 BeanPostProcessor 接口,Spring 将调用他们的 post-Process-Before-Initialization () 方法;微信

7. 若是 bean 实现了 InitializingBean 接口,Spring 将调用它们的 after-Properties-Set () 方法。相似的,若是 bean 使用 init -method 声明了初始化方法,该方法也会被调用;

8. 若是 bean 实现了 BeanPostProcessor 接口, Spring 将调用它们的 post-Process-After-Initialization () 方法;

9. 此时,bean 已经准备就绪,能够被应用程序使用了,它们将一直驻留在应用上下文中,直到该应用上下文被销毁;

10.  若是 bean 实现了 Disposable-Bean 接口,Spring 将调用它的 destory () 接口方法。一样,若是 bean 使用 destory-method 声明了销毁方法,该方法也会被调用。

 ---数组转List常见方式的对比---

一.最经常使用
经过 Arrays.asList(strArray) 方式,将数组转换List后,不能对List增删,只能查改,不然抛异常(由于Arrays.asList(strArray)返回值是java.util.Arrays类中一个私有静态内部类java.util.Arrays.ArrayList,并不是java.util.ArrayList类。java.util.Arrays.ArrayList类有 set(),get(),contains()等方法,但没有添加add()或remove()方法,因此调用add()方法会报错。)。

关键代码:

List list = Arrays.asList(strArray);

使用场景:Arrays.asList(strArray)方式仅能用在将数组转换为List后,不须要增删其中的值,仅做为数据源读取使用。

二.数组转为Arrays.List后,再转java.util.ArrayList,支持增删改查
经过ArrayList的构造器,将Arrays.asList(strArray)的返回值由java.util.Arrays.ArrayList转为java.util.ArrayList。

关键代码:

ArrayList<String> list = new ArrayList<String>(Arrays.asList(strArray)) ;
 

使用场景:须要在将数组转换为List后,对List进行增删改查操做,在List的数据量不大的状况下,可使用。

三.经过集合工具类Collections.addAll()方法(推荐)

经过Collections.addAll(arrayList, strArray)方式转换,根据数组的长度建立一个长度相同的List,而后经过Collections.addAll()方法,将数组中的元素转为二进制,而后添加到List中。

关键代码:

ArrayList< String> arrayList = new ArrayList<String>(strArray.length);
Collections.addAll(arrayList, strArray);

使用场景:须要在将数组转换为List后,对List进行增删改查操做,在List的数据量巨大的状况下,优先使用,能够提升操做速度。

 ---转换String三种方式比较:toString()、String.valueOf()、(String)---

 简单介绍:

一、toString,须要保证调用这个方法的类、方法、变量不为null,不然会报空指针。

二、String.valueOf。这个方法在使用的时候是有些特殊的。通常状况下,若是是肯定类型的null传入,返回的是字符串“null”,而若是直接传入null,则会发生错误。

三、(String) 字符串类型强转。须要保证的是类型能够转成String类型。

 总结:
这三者的使用,我的以为应该使用String.valueOf()的方式。这样的使用安全可靠,不会带来异常,但须要注意null;

 

 

JVM以内存结构详解

对于开发人员来讲,若是不了解Java的JVM,那真的是很难写得一手好代码,很难查得一手好bug。同时,JVM也是面试环节的中重灾区。今天开始,《JVM详解》系列开启,带你们深刻了解JVM相关知识。

咱们不能为了面试而面试,可是学习会这些核心知识你一定会成为面试与工做中“最亮的一颗星”。本系列首发于微信公众号“程序新视界”。下面,开启咱们的第一篇文章《JVM以内存结构详解》。

学习也是要讲究方式方法的,本系列学习过程当中会引导你们经过《费曼学习法》来学习,同时尽可能采用图文方式来进行讲解。正所谓一图胜千言。

思考一下

学习一项知识总该知道为何学习吧。有人会说,这些写代码好像又用不上,貌似全部的事情JVM都替咱们作好了。那就,思考一下为何要学习JVM虚拟机结构。

那你是否遇到这样的困惑:堆内存该设置多大?OutOfMemoryError异常究竟是怎么引发的?如何进行JVM调优?JVM的垃圾回收是如何?甚至建立一个String对象,JVM都作了些什么?

这些疑问随着学习的深刻都会慢慢获得解答,而要解决这些问题的第一步,就是先了解JVM的构成。

JVM内存结构

java虚拟机在执行程序的过程当中会将内存划分为不一样的数据区域,看一下下图。

image

若是理解了上图,JVM的内存结构基本上掌握了一半。经过上图咱们能够看到什么?外行看热闹,内行看门道。从图中能够获得以下信息。

第一,JVM分为五个区域:虚拟机栈、本地方法栈、方法区、堆、程序计数器。PS:你们不要排斥英语,此处用英文记忆反而更容易理解。

第二,JVM五个区中虚拟机栈、本地方法栈、程序计数器为线程私有,方法区和堆为线程共享区。图中已经用颜色区分,绿色表示“通行”,橘黄色表示停一停(需等待)。

第三,JVM不一样区域的占用内存大小不一样,通常状况下堆最大,程序计数器较小。那么最大的区域会放什么?固然就是Java中最多的“对象”了。

学习延伸:若是你记住了这张图,是否是就能够说出关于JVM的内存结构了呢?能够尝试一下,切记不用死记硬背,发挥你的想象。

堆(Heap)

上面已经得出结论,堆内存最大,堆是被线程共享,堆的目的就是存放对象。几乎全部的对象实例都在此分配。固然,随着优化技术的更新,某些数据也会被放在栈上等。

枪打出头鸟,树大招风。由于堆占用内存空间最大,堆也是Java垃圾回收的主要区域(重点对象),所以也称做“GC堆”(Garbage Collected Heap)。

关于GC的操做,咱们后面章节会详细讲,但正由于GC的存在,而现代收集器基本都采用分代收集算法,堆又被细化了。

image

一样,对上图呈现内容汇总分析。

第一,堆的GC操做采用分代收集算法。

第二,堆区分了新生代和老年代;

第三,新生代又分为:Eden空间、From Survivor(S0)空间、To Survivor(S1)空间。

Java虚拟机规范规定,Java堆能够处于物理上不连续的内存空间中,只要逻辑上是连续的便可。也就是说堆的内存是一块块拼凑起来的。要增长堆空间时,往上“拼凑”(可扩展性)便可,但当堆中没有内存完成实例分配,而且堆也没法再扩展时,将会抛出OutOfMemoryError异常。

方法区(Method Area)

方法区与堆有不少共性:线程共享、内存不连续、可扩展、可垃圾回收,一样当没法再扩展时会抛出OutOfMemoryError异常。

正由于如此相像,Java虚拟机规范把方法区描述为堆的一个逻辑部分,但目前其实是与Java堆分开的(Non-Heap)。

方法区个性化的是,它存储的是已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

方法区的内存回收目标主要是针对常量池的回收和对类型的卸载,通常来讲这个区域的回收“成绩”比较难以使人满意,尤为是类型的卸载,条件至关苛刻,可是回收确实是有必要的。

image

程序计数器(Program Counter Register)

关于程序计数器咱们已经得知:占用内存较小,现成私有。它是惟一没有OutOfMemoryError异常的区域。

程序计数器的做用能够看作是当前线程所执行的字节码的行号指示器,字节码解释器工做时就是经过改变计数器的值来选取下一条字节码指令。其中,分支、循环、跳转、异常处理、线程恢复等基础功能都须要依赖计数器来完成。

Java虚拟机的多线程是经过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个肯定的时刻,一个处理器(对于多核处理器来讲是一个内核)只会执行一条线程中的指令。

image

所以,为了线程切换后能恢复到正确的执行位置,每条线程都须要有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储,咱们称这类内存区域为“线程私有”的内存。

若是线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;若是正在执行的是Natvie方法,这个计数器值则为空(Undefined)。

虚拟机栈(JVM Stacks)

虚拟机栈线程私有,生命周期与线程相同。

栈帧(Stack Frame)是用于支持虚拟机进行方法调用和方法执行的数据结构。栈帧存储了方法的局部变量表、操做数栈、动态链接和方法返回地址等信息。每个方法从调用至执行完成的过程,都对应着一个栈帧在虚拟机栈里从入栈到出栈的过程。

image

局部变量表(Local Variable Table)是一组变量值存储空间,用于存放方法参数和方法内定义的局部变量。包括8种基本数据类型、对象引用(reference类型)和returnAddress类型(指向一条字节码指令的地址)。

其中64位长度的long和double类型的数据会占用2个局部变量空间(Slot),其他的数据类型只占用1个。

若是线程请求的栈深度大于虚拟机所容许的深度,将抛出StackOverflowError异常;若是虚拟机栈动态扩展时没法申请到足够的内存时会抛出OutOfMemoryError异常。

操做数栈(Operand Stack)也称做操做栈,是一个后入先出栈(LIFO)。随着方法执行和字节码指令的执行,会从局部变量表或对象实例的字段中复制常量或变量写入到操做数栈,再随着计算的进行将栈中元素出栈到局部变量表或者返回给方法调用者,也就是出栈/入栈操做。

动态连接:Java虚拟机栈中,每一个栈帧都包含一个指向运行时常量池中该栈所属方法的符号引用,持有这个引用的目的是为了支持方法调用过程当中的动态连接(Dynamic Linking)。

方法返回:不管方法是否正常完成,都须要返回到方法被调用的位置,程序才能继续进行。

本地方法栈(Native Method Stacks)

本地方法栈(Native Method Stacks)与虚拟机栈做用类似,也会抛出StackOverflowError和OutOfMemoryError异常。

区别在于虚拟机栈为虚拟机执行Java方法(字节码)服务,而本地方法栈是为虚拟机使用到的Native方法服务。

小结

通过上面的讲解,想必你们已经了解到JVM内存结构的基本状况。下面对照脑图,概括总结一下,看你能说出来多少。

image

JVM内存结构补充

在上篇《JVM以内存结构详解》中有些内容咱们没有讲,本篇结合垃圾回收机制来一块儿学习。还记得JVM中堆的结构图吗?

image

图中展现了堆中三个区域:Eden、From Survivor、To Survivor。从图中能够也能够看到它们的大小比例,准确来讲是:8:1:1。为何要这样设计呢,本篇文章后续会给出解答,仍是根据垃圾回收的具体状况来设计的。

还记得在设置JVM时,经常使用的相似-Xms和-Xmx等参数吗?对的它们就是用来讲设置堆中各区域的大小的。

image


(图片来源于网络)

控制参数详解:

  • -Xms设置堆的最小空间大小。
  • -Xmx设置堆的最大空间大小。
  • -Xmn堆中新生代初始及最大大小(NewSize和MaxNewSize为其细化)。
  • -XX:NewSize设置新生代最小空间大小。
  • -XX:MaxNewSize设置新生代最大空间大小。
  • -XX:PermSize设置永久代最小空间大小。
  • -XX:MaxPermSize设置永久代最大空间大小。
  • -Xss设置每一个线程的堆栈大小。

对照上面两个图,再来看这些参数是否是没有以前那么枯燥了,它们在图中都有了对应的位置。

有没有发现没有直接设置老年代空间大小的参数?咱们经过简单的计算得到。

  1. 老年代空间大小=堆空间大小-年轻代大空间大小

对上面参数当即了,但记忆有困难?那么,如下几个助记词可能更好的帮你记忆和理解参数的含义。

Xmx(memory maximum), Xms(memory startup), Xmn(memory nursery/new), Xss(stack size)。

对于参数的格式能够这样理解:

  • -: 标准VM选项,VM规范的选项。
  • -X: 非标准VM选项,不保证全部VM支持。
  • -XX: 高级选项,高级特性,但属于不稳定的选项。

GC概述

垃圾收集(Garbage Collection)一般被称为“GC”,由虚拟机“自动化”完成垃圾回收工做。

思考一个问题,既然GC会自动回收,开发人员为何要学习GC和内存分配呢?为了可以配置上面的参数配置?参数配置又是为了什么?

“当须要排查各类内存溢出,内存泄露问题时,当垃圾成为系统达到更高并发量的瓶颈时,咱们就须要对GC的自动回收实施必要的监控和调节。”

JVM中程序计数器、虚拟机栈、本地方法栈3个区域随线程而生随线程而灭。栈帧随着方法的进入和退出作入栈和出栈操做,实现了自动的内存清理。它们的内存分配和回收都具备肯定性。

所以,GC垃圾回收主要集中在堆和方法区,在程序运行期间,这部份内存的分配和使用都是动态的。

下面经过概念和具体的算法来了解GC垃圾回收的过程。

如何判断对象存活

判断对象常规有两种方法:引用计数算法和可达性分析算法(Reachability Analysis)。

引用计数算法:给对象添加一个引用计数器,每当有一个地方引用它时计数器加1,引用释放时计数减1,当计数器为0时能够回收。

引用计数算法实现简单,判断高效,在微软COM和Python语言等被普遍使用,但在主流的Java虚拟机中没有使用该方法,主要是由于没法解决对象相互循环引用的问题。

可达性分析算法:基本思想是经过一系列称为“GC Root”的对象(如系统类加载器、栈中的对象、处于激活状态的线程等)做为起点,基于对象引用关系,开始向下搜索,所走过的路径称为引用链,当一个对象到GC Root没有任何引用链相连,证实对象是不可用的。

image

上图中中绿色部分为存活对象,灰色部分为可回收对象。虽然灰色部份内部依旧有关联,但它们到GC Root是不可达的。

面试问题

面试官,说说Java GC都用了哪些算法?分别应用在什么地方?

答:复制算法、标记清除、标记整理……

你还在单纯的死记硬背么?继续往下看,你会豁然开朗,不再用死记硬背了。

标记清除算法

标记清除(Mark-Sweep)算法,包含“标记”和“清除”两个阶段:首先标记出全部须要回收的对象,在标记完成后统一回收掉全部被标记的对象。

标记清除算法是最基础的收集算法,后续的收集算法都是基于该思路并对其缺点进行改进而获得的。

image

主要缺点:一个是效率问题,标记和清除过程的效率都不高;另外是空间问题,标记清除以后会产生大量不连续的内存碎片,空间碎片太多可能会致使,当程序在之后的运行过程当中须要分配较大对象时没法找到足够的连续内存而不得不提早触发另外一次垃圾收集动做。

复制算法

复制(Copying)算法:将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当一块内存用完了,就将还存活着的对象复制到另一块上,而后清理掉前一块。

image

每次对半区内存回收时、内存分配时就不用考虑内存碎片等复杂状况,只要移动堆顶指针,按顺序分配内存便可,实现简单,运行高效。

缺点:将内存缩小为一半,性价比低,持续复制长生存期的对象则致使效率低下。

JVM堆中新生代便采用复制算法。回到最初推分配结构图。

image

在GC回收过程当中,当Eden区满时,还存活的对象会被复制到其中一个Survivor区;当回收时,会将Eden和使用的Survivor区还存活的对象,复制到另一个Survivor区,而后对Eden和用过的Survivor区进行清理。

若是另一个Survivor区没有足够的内存存储时,则会进入老年代。

这里针对哪些对象会进入老年代有这样的机制:对象每经历一次复制,年龄加1,达到晋升年龄阈值后,转移到老年代。

在这整个过程当中,因为Eden中的对象属于像浮萍同样“瞬生瞬灭”的对象,因此并不须要1:1的比例来分配内存,而是采用了8:1:1的比例来分配。

而针对那些像“水熊虫”同样,历经屡次清理依旧存活的对象,则会进入老年代,而老年的清理算法则采用下面要讲到的“标记整理算法”。

标记整理算法

标记整理(Mark-Compact)算法:标记过程与“标记-清除”算法同样,但后续步骤不是直接对可回收对象进行清理,而是让全部存活的对象都向一端移动,而后直接清理掉端边界之外的内存。

image

这种算法不既不用浪费50%的内存,也解决了复制算法在对象存活率较高时的效率低下问题。

分代收集算法

分代收集算法,基本思路:将Java的堆内存逻辑上分红两块,新生代和老年代,针对不一样存活周期、不一样大小的对象采起不一样的垃圾回收策略。

而在新生代中大多数对象都是瞬间对象,只有少许对象存活,复制较少对象便可完成清理,所以采用复制算法。而针对老年代中的对象,存活率较高,又没有额外的担保内存,所以采用标记整理算法。

其实,回头看,分代收集算法就是对新生代和老年代算法从策略维度的规划而已。

 

Servlet 生命周期

 

过程:加载 --> 实例化 --> 服务 --> 销毁

init():在Servlet生命周期中,init()方法只执行一次,不管有多少客户端访问,都不会重复执行。它是在服务器装入Servlet时执行的,负载初始化Servlet对象。

service():当Servlet容器接收到一个请求时,Servlet容器会针对这个请求建立ServletRequest ServletResponse对象。而后调用service()方法。并把这两个参数传递给service()方法。service()方法经过ServletRequest对象得到请求的信息。并处理该请求。再经过ServletResponse对象生成这个请求的响应结果。

destroy():在Servlet生命周期中,destroy()方法只会被执行一次。当Servlet对象结束生命周期时,负责释放资源。

 

 

Servlet 工做原理

 

web服务器接受到一个http请求后,web服务器会将请求移交给servlet容器

 

 

servlet容器首先对所请求的URL进行解析并根据web.xml 配置文件找到相应的处理servlet

 

 

同时将request、response对象传递给它,servlet经过request对象可知道客户端的请求者、请求信息以及其余的信息等

 

 

servlet在处理完请求后会把全部须要返回的信息放入response对象中并返回到客户端

 

 

servlet一旦处理完请求,servlet容器就会刷新response对象,并把控制权从新返回给web服务器。

相关文章
相关标签/搜索