关于java:面试

2次阅读

共计 7240 个字符,预计需要花费 19 分钟才能阅读完成。

反射的概述

Java 反射的原理:java 类的执行须要经验以下过程,

编译:.java 文件编译后生成.class 字节码文件
加载:类加载器负责依据一个类的全限定名来读取此类的二进制字节流到 JVM 外部,并存储在运行时内存区的办法区,而后将其转换为一个与指标类型对应的 java.lang.Class 对象实例
连贯:细分三步
验证:格局(class 文件标准)语义(final 类是否有子类)操作
筹备:动态变量赋初值和内存空间,final 润饰的内存空间间接赋原值,此处不是用户指定的初值。
解析:符号援用转化为间接援用,调配地址
初始化: 有父类先初始化父类,而后初始化本人;将 static 润饰代码执行一遍,如果是动态变量,则用用户指定值笼罩原有初值;如果是代码块,则执行一遍操作。

Java 的反射就是利用下面第二步加载到 jvm 中的.class 文件来进行操作的。.class 文件中蕴含 java 类的所有信息,当你不晓得某个类具体信息时,能够应用反射获取 class,而后进行各种操作。

Java 反射就是在运行状态中,对于任意一个类,都可能晓得这个类的所有属性和办法;对于任意一个对象,都可能调用它的任意办法和属性;并且能扭转它的属性。总结说:反射就是把 java 类中的各种成分映射成一个个的 Java 对象,并且能够进行操作。

线程的创立形式? 你喜爱哪种? 为什么

形式一:继承 Thread 类

形式二:实现 Runnable 接口

形式三:实现 Callable 接口 –JDK5.0 新增

@PathVariable @RequestParam 和 @RequestBody 的区别

@RequestParam

1. 用于将申请参数区数据映射到性能解决办法的参数上,承受的参数是来自 requestHeader 中,即申请头,通常用于 GET 申请, 也能够用于 post,delete 等申请。
2. 用来解决 Content-Type: 为 application/x-www-form-urlencoded 编码的内容。
3.@RequestParam 有三个属性:
(1)value:申请参数名(必须配置)
(2)required:是否必须,默认 true,即申请中必须蕴含该参数,如果没有蕴含,将会抛出异样(可选配置)
(3)defaultValue:默认值,如果设置了该值,required 将主动设为 false,无论你是否配置了 required,配置了什么值,都是 false(可选配置)

@RequestBody

1.@RequestBody 映射申请到办法体上,承受的参数是来自 requestBody 中,即申请体。
2. 用来解决非 Content-Type: 为 application/x-www-form-urlencoded 编码的内容。例如:application/json、application/xml 等类型的数据。
3.@RequestBody 注解罕用于接管 json 格局的数据,并将其转换成对应的数据类型。

@PathVariable

1.@PathVariable 能够将 URL 中占位符参数绑定到控制器解决办法的入参中, 获取申请门路中的变量作为参数

  1. 带占位符的 URL 是 Spring3.0 新增的性能

为什么 HashMap 线程不平安?(jdk7 版本)

HashMap 会进行 resize 操作,在 resize 操作的时候会造成线程不平安。上面将举两个可能呈现线程不平安的中央。

1、put 的时候导致的多线程数据不统一。这个问题比拟好设想,比方有两个线程 A 和 B,首先 A 心愿插入一个 key-value 对到 HashMap 中,首先计算记录所要落到的桶的索引坐标,而后获取到该桶外面的链表头结点,此时线程 A 的工夫片用完了,而此时线程 B 被调度得以执行,和线程 A 一样执行,只不过线程 B 胜利将记录插到了桶外面,假如线程 A 插入的记录计算出来的桶索引和线程 B 要插入的记录计算出来的桶索引是一样的,那么当线程 B 胜利插入之后,线程 A 再次被调度运行时,它仍然持有过期的链表头然而它对此无所不知,以至于它认为它应该这样做,如此一来就笼罩了线程 B 插入的记录,这样线程 B 插入的记录就凭空隐没了,造成了数据不统一的行为。

上面的代码是 resize 的核心内容:

这是 jdk7 的实现形式,jdk8 不是这样的。

// 这个办法的性能是将原来的记录从新计算在新桶的地位,而后迁徙过来。void transfer(Entry[] newTable, boolean rehash) {  
        int newCapacity = newTable.length;  
        for (Entry<K,V> e : table) {while(null != e) {  
                Entry<K,V> next = e.next;           
                if (rehash) {e.hash = null == e.key ? 0 : hash(e.key);  
                }  
                int i = indexFor(e.hash, newCapacity);   
                e.next = newTable[i];  
                newTable[i] = e;  
                e = next;  
            } 
        }  
    }  

为什么 HashMap 是不平安的?(jdk8 版本)

依据下面 JDK1.7 呈现的问题,在 JDK1.8 中曾经失去了很好的解决,如果你去浏览 1.8 的源码会发现找不到 transfer 函数,因为 JDK1.8 间接在 resize 函数中实现了数据迁徙。另外说一句,JDK1.8 在进行元素插入时应用的是尾插法。

为什么说 JDK1.8 会呈现数据笼罩的状况喃,咱们来看一下上面这段 JDK1.8 中的 put 操作代码:

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null) // 如果没有 hash 碰撞则直接插入元素
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {for (int binCount = 0; ; ++binCount) {if ((e = p.next) == null) {p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

其中第六行代码是判断是否呈现 hash 碰撞,假如两个线程 A、B 都在进行 put 操作,并且 hash 函数计算出的插入下标是雷同的,当线程 A 执行完第六行代码后因为工夫片耗尽导致被挂起,而线程 B 失去工夫片后在该下标处插入了元素,实现了失常的插入,而后线程 A 取得工夫片,因为之前曾经进行了 hash 碰撞的判断,所有此时不会再进行判断,而是间接进行插入,这就导致了线程 B 插入的数据被线程 A 笼罩了,从而线程不平安。

总结: HashMap 的线程不平安次要体现在上面两个方面:

1. 在 JDK1.7 中,当并发执行扩容操作时会造成环形链和数据失落的状况。

2. 在 JDK1.8 中,在并发执行 put 操作时会产生数据笼罩的状况。

单点登录业务实现原理

实现步骤:
1. 用户输出用户名和明码之后点击登录按钮开始进行登录操作.
2.JT-WEB 向 JT-SSO 发送申请, 实现数据校验
3. 当 JT-SSO 获取数据信息之后, 实现用户的校验, 如果校验通过则将用户信息转化为 json. 并且动静生成 UUID. 将数据保留到 redis 中. 并且返回值 uuid.
如果校验不存在时, 间接返回 ” 不存在 ” 即可.
4.JT-SSO 将数据返回给 JT-WEB 服务器.
5. 如果登录胜利, 则将用户 UUID 保留到客户端的 cookie 中.

索引是什么?有什么作用以及优缺点?以及常见的几种索引

1、索引是对数据库表中一或多个列的值进行排序的构造,是帮忙 MySQL 高效获取数据的数据结构

2、索引就是放慢检索表中数据的办法。数据库的索引相似于书籍的索引。在书籍中,索引容许用户不用翻阅残缺个书就能迅速地找到所须要的信息。在数据库中,索引也容许数据库程序迅速地找到表中的数据,而不用扫描整个数据库。

MySQL 数据库几个根本的索引类型:一般索引、惟一索引、主键索引、全文索引

1、索引放慢数据库的检索速度
2、索引升高了插入、删除、批改等保护工作的速度
3、惟一索引能够确保每一行数据的唯一性
4、通过应用索引,能够在查问的过程中应用优化暗藏器,进步零碎的性能、索引须要占物理和数据空间

主键索引 : 数据列不容许反复,不容许为 NULL,一个表只能有一个主键。
惟一索引 : 数据列不容许反复,容许为 NULL 值,一个表容许多个列创立惟一索引。
一般索引 : 根本的索引类型,没有唯一性的限度,容许为 NULL 值。
全文索引:是目前搜索引擎应用的一种关键技术。

为什么 redis 这么快

  1. Redis 是齐全基于内存的数据库
  2. 解决网络申请应用的是单线程,防止了不必要的上下文切换和锁的竞争保护。
  3. 应用了 I / O 多路复用模型。

齐全基于内存

为什么要用齐全呢。因为像 mysql 这样的传统关系型数据库是存储在硬盘的,那么硬盘的性能和瓶颈将会影响到数据库。

单线程

须要留神的是,这里的单线程指的是,Redis 解决网络申请的时候只有一个线程,而不是整个 Redis 服务是单线程的。

I/ O 多路复用模型

  • 传统多过程并发模型: 每监听到一个 Socket 连贯就会调配一个线程解决
  • 多路复用模型: 单个线程,通过记录跟踪每一个 Socket 连贯的 I / O 的状态来同时治理多个 I / O 流。
    这里的 I / O 指的是 网络 I /O,多路指的是 多个网络连接 ,复用指的是 复用一个线程

联合 Redis:

  1. 在 Redis 中的 I/ O 多路复用程序 会监听多个客户端连贯的 Socket
  2. 每当有客户端通过 Socket 流向 Redis 发送申请进行操作时,I/ O 多路复用程序会将其放入一个 队列 中。
  3. 同时 I / O 多路复用程序会 同步 有序、每次传送一个工作给处理器解决。
  4. I/ O 多路复用程序会在上一个申请处理完毕后再持续分派下一个工作。(同步)

怎么判断对象曾经死亡?

援用计数法

给对象中增加一个计数器, 每当一个中央援用他, 则计数器加 1, 当援用生效, 计数器减 1, 计数器为 0 的对象就是不可能在被应用的。
这个办法实现简略, 效率高, 然而目前支流并没有抉择这个算法来治理内存, 次要起因是无奈解决对象之间互相循环援用的问题。

可达性算法

通过一系列 ”GC Roots” 对象作为终点,开始向下搜寻节点所走过的路称为援用链,当一个对象到 ”GC Roots” 没有任何援用链相连,则证实该对象是不可达的。

可作为 GC Roots 的对象包含上面几种

  • 虚拟机栈 (栈帧中的本地变量表) 中援用的对象
  • 本地办法栈 (Native 办法) 中援用的对象
  • 办法区中类动态属性援用的对象
  • 办法区中常量援用的对象

援用

强援用
咱们经常 new 进去的对象都是强援用,只有强援用存在垃圾回收器永远不会回收,哪怕内存不足。

软援用
应用 SoftReference 润饰的对象被称为软援用,如果内存足够则垃圾回收器不会回收,如果内存不足就会回收这写对象的援用。

弱援用
应用 WeakReference 润饰的对象被称为弱援用,只有产生垃圾回收,不论以后内存是否足够,都会回收它的内存。不过因为垃圾回收器是一个优先级很低的线程, 因而不肯定会很快发现那些只具备弱援用的对象。

虚援用
应用 PhantomReference 润饰的对象被称为虚援用,虚援用并不会决定对象的生命周期。如果一个对象仅持有虚援用,那么就和没有任何援用一样,在任何时候都会被垃圾回收。惟一的作用就是用队列承受对象行将死亡的告诉。

如何判断一个类是一个无用的类

  • 该类的所有实例都曾经被回收,堆中不存在该类的任何实列
  • 加载该类的 ClassLoader 曾经被回收
  • 该类对应的 java.lang.Class 对象没在任何中央被援用,无奈在任何中央通过反色和拜访该类的办法

虚拟机能够对满足上诉 3 个条件的无用类进行回收,并不是和对象一样不应用了就会必然被回收

类加载器

JVM 中内置了三个重要的 ClassLoader,除了 BootstrapClassLoader 其余类加载器均由 Java 实现且全副继承自 java.lang.ClassLoader:

BootstrapClassLoader(启动类加载器)
最顶层的加载类,由 C ++ 实现,负责加载 %JAVA_HOME%/lib目录下的 jar 包和类或者或被 -Xbootclasspath参数指定的门路中的所有类。

ExtensionClassLoader(扩大类加载器)
次要负责加载目录 %JRE_HOME%/lib/ext 目录下的 jar 包和类,或被 java.ext.dirs 零碎变量所指定的门路下的 jar 包。

AppClassLoader(应用程序类加载器)
面向咱们用户的加载器,负责加载以后利用 classpath 下的所有 jar 包和类。

双亲委派模型

零碎中的 ClassLoder 在协同工作的时候会默认应用 双亲委派机制。在类加载的时候,零碎会先判断以后类是否被加载过。曾经被加载的类会间接返回,否则才会尝试加载。加载的时候,首先会把申请委派给该父类加载器的 loadClass() 解决,因而所有的申请最终都应该传送到顶层的启动类加载器 BootstrapClassLoader 中。当父类加载器无奈解决时,才由本人来解决。当父类加载器为 null 时,会应用启动类加载器 BootstrapClassLoader 作为父类加载器。

类加载器之间的 ” 父子 ” 关系不是通过继承来体现的,是由 ” 优先级 ” 来决定的。

如果咱们不想突破双亲委派模型,就重写 ClassLoader 类中的 findClass() 办法即可,无奈被父类加载器加载的类最终会通过这个办法被加载。然而如果想突破双亲委派机制模型则须要重写 loadClass() 办法。

应用线程池的益处

  • 升高资源耗费。通过反复利用曾经创立的线程升高创立线程和销毁线程带来的耗费。
  • 进步效应速度。当工作达到时,工作能够不须要期待线程创立就能立刻执行。
  • 进步线程的可管理性。如果无限度的创立线程,不仅会耗费系统资源,还会升高零碎的稳定性,应用线程能够进行对立的调配,调优和监控。

Excutor 框架

Excutor 框架是在 JDK1.5 之引进的,通过 Excutor 来启动线程比应用 Thread 的 start 办法更好,除了更易治理和效率更好之外,最要害的一点是:有助于避免 this 逃逸。

this 逃逸是指在构造函数返回之前其余线程就持有该对象的援用. 调用尚未结构齐全的对象的办法可能引发令人纳闷的谬误。

Excutor 框架不仅包含了线程池的治理,还提供了线程工厂,队列以及回绝策略。

ThreadPoolExcutor

ThreadPoolExcutor 是 Excutor 框架最外围的类

ThreadPoolExcutor 最外围的三个参数

  • corePoolSize:外围线程数定义了最小能够同时运行的线程数量。
  • maximumPoolSize:当队列中的工作达到队列容量的时候,以后能够同时运行的线程数量变为最大线程数。
  • workQueue:当新的工作来的时候会判断以后运行的线程数量是否达到外围线程数量,如果达到,则会被寄存到队列当中。

ThreadPoolExecutor 其余常见参数:

  1. keepAliveTime: 当线程池中的线程数量大于 corePoolSize 的时候,如果这时没有新的工作提交,外围线程外的线程不会立刻销毁,而是会期待,直到期待的工夫超过了 keepAliveTime 才会被回收销毁;
  2. unit: keepAliveTime 参数的工夫单位。
  3. threadFactory: executor 创立新线程的时候会用到。
  4. handler: 饱和策略。

ThreadPoolExecutor 饱和策略定义:

如果以后同时运行的线程数量达到最大线程数量并且队列也曾经被放满了工作时,ThreadPoolTaskExecutor 定义一些策略:

ThreadPoolExecutor.AbortPolicy:抛出 RejectedExecutionException 来回绝新工作的解决。
ThreadPoolExecutor.CallerRunsPolicy:调用执行本人的线程运行工作,也就是间接在调用 execute 办法的线程中运行 (run) 被回绝的工作,如果执行程序已敞开,则会抛弃该工作。因而这种策略会升高对于新工作提交速度,影响程序的整体性能。如果您的应用程序能够接受此提早并且你要求任何一个工作申请都要被执行的话,你能够抉择这个策略。
ThreadPoolExecutor.DiscardPolicy:不解决新工作,间接抛弃掉。
ThreadPoolExecutor.DiscardOldestPolicy:此策略将抛弃最早的未解决的工作申请。

正文完
 0