Java笔试必考知识点合集七

1.Java垃圾回收机制

两个最基本的java回收算法:

  • 复制算法
  • 标记清理算法 
复制算法:两个区域A和B,初始对象在A,继续存活的对象被转移到B。此为新生代最常用的算法
标记清理:一块区域,标记可达对象(可达性分析),然后回收不可达对象,会出现碎片,那么引出 
标记-整理算法:多了碎片整理,整理出更大的内存放更大的对象
两个概念:新生代和年老代 
新生代:初始对象,生命周期短的
永久代:长时间存在的对象
整个java的垃圾回收是新生代和年老代的协作,这种叫做分代回收。 

P.S:

  • Serial New收集器是针对新生代的收集器,采用的是复制算法 
  • Parallel New(并行)收集器,新生代采用复制算法,老年代采用标记整理 
  • Parallel Scavenge(并行)收集器,针对新生代,采用复制收集算法 
  • Serial Old(串行)收集器,新生代采用复制,老年代采用标记整理
  • Parallel Old(并行)收集器,针对老年代,标记整理 
  • CMS收集器,基于标记清理
  • G1收集器:整体上是基于标记 整理 ,局部采用复制

       首先要搞清垃圾回收的范围(栈需要GC去回收吗?),然后就是回收的前提条件如何判断一个对象已经可以被回收(这里只重点学习根搜索算法就行了),之后便是建立在根搜索基础上的三种回收策略,最后便是JVM中对这三种策略的具体实现。 
1.范围:要回收哪些区域? 
     Java方法栈、本地方法栈以及PC计数器随方法或线程的结束而自然被回收,所以这些区域不需要考虑回收问题。Java堆和方法区是GC回收的重点区域,因为一个接口的多个实现类需要的内存不一样,一个方法的多个分支需要的内存可能也不一样,而这两个区域又对立于栈可能随时都会有对象不再被引用,因此这部分内存的分配和回收都是动态的。 
2.前提:如何判断对象已死? 
(1)引用计数法 
         引用计数法就是通过一个计数器记录该对象被引用的次数,方法简单高效,但是解决不了循环引用的问题。比如对象A包含指向对象B的引用,对象B也包含指向对象A的引用,但没有引用指向A和B,这时当前回收如果采用的是引用计数法,那么对象A和B的被引用次数都为1,都不会被回收。 

下面是循环引用的例子,在Hotspot JVM下可以被正常回收,可以证实JVM 采用的不是简单的引用计数法。通过

-XX:+PrintGCDetails输出GC日志。


[Full GC (System) [Tenured: 2048K->366K(10944K), 0.0046272 secs] 4604K->366K(15872K),

[Perm : 154K->154K(12288K)], 0.0046751 secs] [Times: user=0.02 sys=0.00, real=0.00 secs]

(2)根搜索 

   通过选取一些根对象作为起始点,开始向下搜索,如果一个对象到根对象不可达时,则说明此对象已经没有被引用,是可以被回收的。可以作为根的对象有:栈中变量引用的对象,类静态属性引用的对象,常量引用的对象等。因为每个线程都有一个栈,所以我们需要选取多个根对象。 
附:对象复活 
在根搜索中得到的不可达对象并不是立即就被标记成可回收的,而是先进行一次 
标记放入F-Queue等待执行对象的finalize()方法,执行后GC将进行二次标记,复活 
的对象之后将不会被回收。因此,使对象复活的唯一办法就是重写finalize()方法, 
并使对象重新被引用。 
[java] view plaincopy
package com.cdai.jvm.gc;   
public class DeadToRebirth {   
    private static DeadToRebirth hook;    
    @Override   
    public void finalize() throws Throwable {   
        super.finalize();   
        DeadToRebirth.hook = this;   
    }   
    public static void main(String[] args) throws Exception {   
        DeadToRebirth.hook = new DeadToRebirth();   
        DeadToRebirth.hook = null;   
        System.gc();   
        Thread.sleep(500);   
        if (DeadToRebirth.hook != null)   
            System.out.println("Rebirth!");   
        else   
            System.out.println("Dead!");   
        DeadToRebirth.hook = null;   
        System.gc();   
        Thread.sleep(500);   
        if (DeadToRebirth.hook != null)   
            System.out.println("Rebirth!");   
        else   
            System.out.println("Dead!");   
    }   
}   
要注意的两点是: 
第一,finalize()方法只会被执行一次,所以对象只有一次复活的机会。 
第二,执行GC后,要停顿半秒等待优先级很低的finalize()执行完毕。 
3.策略:垃圾回收的算法 
(1)标记-清除 
没错,这里的标记指的就是之前我们介绍过的两次标记过程。标记完成后就可以 
对标记为垃圾的对象进行回收了。怎么样,简单吧。但是这种策略的缺点很明显, 
回收后内存碎片很多,如果之后程序运行时申请大内存,可能会又导致一次GC。 
虽然缺点明显,这种策略却是后两种策略的基础。正因为它的缺点,所以促成了 
后两种策略的产生。 
(2)标记-复制 
将内存分为两块,标记完成开始回收时,将一块内存中保留的对象全部复制到另 
一块空闲内存中。实现起来也很简单,当大部分对象都被回收时这种策略也很高效。 
但这种策略也有缺点,可用内存变为一半了! 
怎样解决呢?聪明的程序员们总是办法多过问题的。可以将堆不按1:1的比例分离, 
而是按8:1:1分成一块Eden和两小块Survivor区,每次将Eden和Survivor中存活的对象 
复制到另一块空闲的Survivor中。这三块区域并不是堆的全部,而是构成了新生代。 
从下图可以看到这三块区域如何配合完成GC的,具体的对象空间分配以及晋升请 
参加后面第6条补充。 
为什么不是全部呢?如果回收时,空闲的那一小块Survivor不够用了怎么办?这就是 
老年代的用处。当不够用时,这些对象将直接通过分配担保机制进入老年代。那么 
老年代也使用标记-复制策略吧?当然不行!老年代中的对象可不像新生代中的, 
每次回收都会清除掉大部分。如果贸然采用复制的策略,老年代的回收效率可想而知。 
(3)标记-整理 
根据老年代的特点,采用回收掉垃圾对象后对内存进行整理的策略再合适不过,将 
所有存活下来的对象都向一端移动。 
4.实现:虚

综上:新生代基本采用复制算法,老年代采用标记整理算法。cms采用标记清理。 

2.JSP的九大内置对象

application对象是共享的,多个用户共享一个,以此实现数据共享和通信 JSP内置对象和属性列举如下:
  • 1.request对象
     客户端的请求信息被封装在request对象中,通过它才能了解到客户的需求,然后做出响应。它是HttpServletRequest类的实例。
  • 2.response对象
     response对象包含了响应客户请求的有关信息,但在JSP中很少直接用到它。它是HttpServletResponse类的实例。
  • 3.session对象
     session对象指的是客户端与服务器的一次会话,从客户连到服务器的一个WebApplication开始,直到客户端与服务器断开连接为止。它是HttpSession类的实例.
  • 4.out对象
     out对象是JspWriter类的实例,是向客户端输出内容常用的对象
  • 5.page对象
     page对象就是指向当前JSP页面本身,有点象类中的this指针,它是java.lang.Object类的实例
  • 6.application对象
     application对象实现了用户间数据的共享,可存放全局变量。它开始于服务器的启动,直到服务器的关闭,在此期间,此对象将一直存在;这样在用户的前后连接或不同用户之间的连接中,可以对此对象的同一属性进行操作;在任何地方对此对象属性的操作,都将影响到其他用户对此的访问。服务器的启动和关闭决定了application对象的生命。它是ServletContext类的实例。
  • 7.exception对象
   exception对象是一个例外对象,当一个页面在运行过程中发生了例外,就产生这个对象。如果一个JSP页面要应用此对象,就必须把isErrorPage设为true,否则无法编译。他实际上是java.lang.Throwable的对象
  • 8.pageContext对象
      pageContext对象提供了对JSP页面内所有的对象及名字空间的访问,也就是说他可以访问到本页所在的SESSION,也可以取本页面所在的application的某一属性值,他相当于页面中所有功能的集大成者,它的本类名也叫pageContext。
  • 9.config对象
      config对象是在一个Servlet初始化时,JSP引擎向它传递信息用的,此信息包括Servlet初始化时所要用到的参数(通过属性名和属性值构成)以及服务器的有关信息(通过传递一个ServletContext对象)

3.类的加载过程

 类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:
  • 加载(Loading)
  • 验证(Verification)
  • 准备(Preparation)
  • 解析(Resolution)
  • 初始化(Initialization)
  • 使用(Using)
  • 卸载(Unloading)

其中准备、验证、解析3个部分统称为连接(Linking),加载、验证、准备、初始化和卸载这5个阶段的顺序是

确定的,类的加载过程必须按照这种顺序按部就班地开始,而解析阶段则不一定:它在某些情况下可以在初始

化阶段之后再开始,这是为了支持Java语言的运行时绑定(也称为动态绑定或晚期绑定)。





类加载过程:

  • 1, JVM会先去方法区中找有没有相应类的.class存在。如果有,就直接使用;如果没有,则把相关类的.class加载到方法区
  • 2, 在.class加载到方法区时,会分为两部分加载:先加载非静态内容,再加载静态内容
  • 3, 加载非静态内容:把.class中的所有非静态内容加载到方法区下的非静态区域内
  • 4, 加载静态内容:
  •      4.1、把.class中的所有静态内容加载到方法区下的静态区域内
  •      4.2、静态内容加载完成之后,对所有的静态变量进行默认初始化
  •      4.3、所有的静态变量默认初始化完成之后,再进行显式初始化
  •      4.4、当静态区域下的所有静态变量显式初始化完后,执行静态代码块

  • 5,当静态区域下的静态代码块,执行完之后,整个类的加载就完成了。

4.HashMap和Hashtable的区别


附加:
①继承不同
  • public class Hashtable extends Dictionary implements Map
  • public class HashMap extends AbstractMap implements Map
②键值范围不同
Hashtable中,key和value都不允许出现null值。在HashMap中,null可以作为键,这样的键只有一个;可以有一个 多个键所对应
的值为null。当get()方法返回null值时,即可以表示 HashMap中没有该键,也可以表示该键所对应的值为null。因此,
在HashMap中不能由get()方法来判断HashMap中是否存在某个键, 而应该用containsKey()方法来判断。
③哈希值使用不同
  • HashTable直接使用对象的hashCode。
  • HashMap重新计算hash值。
Hashtable和HashMap内部实现方式的数组的初始大小和扩容的方式不同
HashTable中hash数组默认大小是 11, 增加的方式 是 old*2+1。HashMap中hash数组的默认大小是16,而且一定是
2的指数。 

5.哈希冲突

HashMap中的key-value都是存储在Entry数组中的

解决冲突的方法:

  • 1. 开放定址法:线性探测再散列、二次探测再散列、再随机探测再散列;
  • 2. 再哈希法:换一种哈希函数;
  • 3. 链地址法 :在数组中冲突元素后面拉一条链路,存储重复的元素(HashMap使用此方式);
  • 4. 建立一个公共溢出区:其实就是建立一个表,存放那些冲突的元素。

什么时候会产生冲突:

     当我们往HashMap中put元素的时候:当程序试图将一个key-value对放入HashMap中时,HashMap中调用 hashCode() 方法来计算hashCode。由于在Java中两个不同的对象可能有一样的hashCode,所以不同的键可能有一样hashCode,从而导致冲突的产升。HashMap底层是 数组和链表 的结合体。底层是一个线性数组结构,数组中的每一项又是一个链表。当新建一个HashMap的时候,就会初始化一个数组。数组是 Entry[] 数组,静态内部类。 Entry就是数组中的元素,每个 Map.Entry 其实就是一个key-value对,它持有一个指向下一个元素的引用 next ,这就构成了链表。所以很明显是链地址法。

具体过程:
  • 1 . 程序首先根据该 key 的 hashCode() 返回值决定该 Entry 的存储位置;
  • 2 . 若 Entry 的存储位置上为 null ,直接存储该对象;若不为空,两个 Entry 的 key 的 hashCode() 返回值相同,那它们的存储位置相同,
  • 3 . 循环遍历链表,如果这两个 Entry 的 key 通过 equals 比较返回 true,新添加 Entry 的 value 将覆盖集合中原有 Entry 的 value,但key不会覆盖;如果这两个 Entry 的 key 通过 equals 比较返回 false,新添加的 Entry 将与集合中原有 Entry 形成 Entry 链,而且新添加的 Entry 位于 Entry 链的头部

6.DispatcherServlet

主要用作职责调度工作,本身主要用于控制流程,主要职责如下:

  • 1、文件上传解析,如果请求类型是multipart将通过MultipartResolver进行文件上传解析;
  • 2、通过HandlerMapping,将请求映射到处理器(返回一个HandlerExecutionChain,它包括一个处理器、多个HandlerInterceptor拦截器);
  • 3、通过HandlerAdapter支持多种类型的处理器(HandlerExecutionChain中的处理器);
  • 4、通过ViewResolver解析逻辑视图名到具体视图实现;
  • 5、本地化解析;
  • 6、渲染具体的视图等;
  • 7、如果执行过程中遇到异常将交给HandlerExceptionResolver来解析。


猜你喜欢

转载自blog.csdn.net/goodli199309/article/details/80965777