4399一面面试

版权声明:如笔记。一点一滴,伴我成长。可转载 https://blog.csdn.net/Butterfly_resting/article/details/82897426

4399一面挂

Java内存模型有哪些?垃圾收集机制?怎么访问堆外内存

判断是否存活:

1.引用计数法(Reference Counting Collector)2.可达性分析

3、怎么回收?垃圾收集算法:

标记——清除算法

复制算法

分代算法

GC(垃圾收集器)

新生代收集器使用的收集器:Serial、PraNew、Parallel Scavenge

老年代收集器使用的收集器:Serial Old、Parallel Old、CMS

堆外内存,其实就是不受JVM控制的内存。 直接内存, 这一部分内存主要是为nio(new input/output)操作准备的

作为JAVA开发者我们经常用java.nio.DirectByteBuffer对象进行堆外内存的管理和使用,它会在对象创建的时候就分配堆外内存。

1、减少了垃圾回收  2、加快了复制的速度 

Stop-The-World

全局停顿,所有Java代码停止,native代码可以执行,但不能和JVM交互 

Dump线程 

堆Dump 

额外小知识:

Java中一种全局暂停的现象 

多半由于GC引起 

死锁检查 

HashSet底层原理:

http://zhangshixi.iteye.com/blog/673143

https://blog.csdn.net/HD243608836/article/details/80214413

HashSet实现Set接口由哈希表(实际上是一个HashMap实例)支持。它不保证set 的迭代顺序;特别是它不保证该顺序恒久不变。此类允许使用null元素

2.    HashSet的实现:

   对于HashSet而言,它是基于HashMap实现的,HashSet底层使用HashMap来保存所有元素,因此HashSet 的实现比较简单,相关HashSet的操作,基本上都是直接调用底层HashMap的相关方法来完成 (实际底层会初始化一个空的HashMap,并使用默认初始容量为16和加载因子0.75。

HashSet的源代码

对于HashSet中保存的对象,请注意正确重写其equals和hashCode方法,以保证放入的对象的唯一性。

插入

当有新值加入时,底层的HashMap会判断Key值是否存在(HashMap细节请移步深入理解HashMap),如果不存在,则插入新值,同时这个插入的细节会依照HashMap插入细节;如果存在就不插入

HashMap底层原理:(面试过)

1.    HashMap概述:

   HashMap是基于哈希表的Map接口的非同步实现。此实现提供所有可选的映射操作,并允许使用null值和null键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。

2.    HashMap的数据结构:

HashMap实际上是一个“数组+链表+红黑树”的数据结构

3.    HashMap的存取实现:

(1.8之前的)

当我们往HashMap中put元素的时候,先根据key的hashCode重新计算hash值根据hash值得到这个元素在数组中的位置(即下标),如果数组该位置上已经存放有其他元素了,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放在链尾。如果数组该位置上没有元素,就直接将该元素放到此数组中的该位置上。

1.8:

put():

1. 根据key计算得到key.hash = (h = k.hashCode()) ^ (h >>> 16);

2. 根据key.hash计算得到桶数组的索引index = key.hash & (table.length - 1),这样就找到该key的存放位置了:

① 如果该位置没有数据,用该数据新生成一个节点保存新数据,返回null

② 如果该位置有数据是一个红黑树,那么执行相应的插入 / 更新操作

③ 如果该位置有数据是一个链表,分两种情况一是该链表没有这个节点,另一个是该链表上有这个节点,注意这里判断的依据是key.hash是否一样: 如果该链表没有这个节点,那么采用尾插法新增节点保存新数据,返回null; 如果该链表已经有这个节点了,那么找到該节点并更新新数据,返回老数据。 注意: HashMap的put会返回key的上一次保存的数据。

get():

计算需获取数据的hash值(计算过程跟put一样),计算存放在数组table中的位置(计算过程跟put一样),然后依次在数组,红黑树,链表中查找(通过equals()判断),最后再判断获取的数据是否为空,若为空返回null否则返回该数据

树化与还原

* 哈希表的最小树形化容量

* 当哈希表中的容量大于这个值时(64),表中的桶才能进行树形化

* 否则桶内元素太多时会扩容,而不是树形化

* 为了避免进行扩容、树形化选择的冲突,这个值不能小于 4 * TREEIFY_THRESHOLD

* 一个桶的树化阈值

* 当桶中元素个数超过这个值时(8),需要使用红黑树节点替换链表节点

* 这个值必须为 8,要不然频繁转换效率也不高

* 一个树的链表还原阈值

* 当扩容时,桶中元素个数小于这个值(6),就会把树形的桶元素 还原(切分)为链表结构

* 这个值应该比上面那个小,至少为 6,避免频繁转换

条件1. 如果当前桶数组为null或者桶数组的长度 < MIN_TREEIFY_CAPACITY(64),则进行扩容处理(见代码片段2:resize());

条件2. 当不满足条件1的时候则将桶中链表内的元素转换成红黑树!!!稍后再详细讨论红黑树。

扩容机制的实现

1. 扩容(resize)就是重新计算容量。当向HashMap对象里不停的添加元素,而HashMap对象内部的桶数组无法装载更多的元素时,HashMap对象就需要扩大桶数组的长度,以便能装入更多的元素

2. capacity 就是数组的长度/大小,loadFactor 是这个数组填满程度的最大比比例。 

3. size表示当前HashMap中已经储存的Node<key,value>的数量,包括桶数组和链表 / 红黑树中的的Node<key,value>。

4. threshold表示扩容的临界值,如果size大于这个值,则必需调用resize()方法进行扩容。 

5. 在jdk1.7及以前,threshold = capacity * loadFactor,其中 capacity 为桶数组的长度。 这里需要说明一点,默认负载因子0.75是是对空间和时间(纵向横向)效率的一个平衡选择,建议大家不要修改。 jdk1.8对threshold值进行了改进,通过一系列位移操作算法最后得到一个power of two size的值

什么时候扩容

当向容器添加元素的时候,会判断当前容器的元素个数,如果大于等于阈值---即当前数组的长度乘以加载因子的值的时候,就要自动扩容啦。

扩容必须满足两个条件:

1、 存放新值的时候   当前已有元素的个数  (size) 必须大于等于阈值

2、 存放新值的时候当前存放数据发生hash碰撞(当前key计算的hash值换算出来的数组下标位置已经存在值)

//如果计算的哈希位置有值(及hash冲突),且key值一样,则覆盖原值value,并返回原值value

      if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {

        V oldValue = e.value;

        e.value = value;

        e.recordAccess(this);

        return oldValue;

      }

resize()方法: 该函数有2种使用情况1.初始化哈希表 2.当前数组容量过小,需扩容

过程:

插入键值对时发现容量不足,调用resize()方法方法,

1.首先进行异常情况的判断,如是否需要初始化,二是若当前容量》最大值则不扩容,

2.然后根据新容量(是就容量的2倍)新建数组,将旧数组上的数据(键值对)转移到新的数组中,这里包括:(遍历旧数组的每个元素,重新计算每个数据在数组中的存放位置(原位置或者原位置+旧容量),将旧数组上的每个数据逐个转移到新数组中,这里采用的是尾插法。)

3.新数组table引用到HashMap的table属性上

4.最后重新设置扩容阙值,此时哈希表table=扩容后(2倍)&转移了旧数据的新table

什么是IO、NIO、AIO?

https://blog.csdn.net/skiof007/article/details/52873421

https://blog.csdn.net/wzwdcld/article/details/81571110

NIO 指新IO,核心是 同步非阻塞解决传统IO的阻塞问题操作对象是Buffer 其实NIO的核心是IO线程池,一定要记住这个关键点。 NIO中的IO多路复用调用系统级别的select和poll模型,由系统进行监控IO状态,避免用户线程通过反复尝试的方式查询状态。

AIO即Asynchronous I/O(异步非阻塞 I/O),这是Java 1.7引入的NIO 2.0中用到的。

Channel 

一个Channel(通道)代表和某一实体的连接,这个实体可以是文件、网络套接字等。既可以读又可以写。而流是单向的。

Selector 为Channel服务,Channel为要访问的数据服务。

selector采集各个channel的状态(或者说监听事件:

Accept:有可以接受的连接

Connect:连接成功

Read:有数据可读

Write:可以写入数据了

Buffer中有3个很重要的变量capacity (总容量)position (指针当前位置)limit (读/写边界位置)

如果有1000万跳数据MySQL该怎么优化?

https://www.cnblogs.com/crystaltu/p/6877228.html

数据的切分(Sharding)根据其切分规则的类型,可以分为两种切分模式:

垂直(纵向)切分:业务切分 

水平(横向)切分: 根据表中的数据的逻辑关系

2. sql语句优化, 合理利用以及因地制宜的利用索引, 正确的建立索引。并且避免索引失效

3. 数据量大还是要选择单表多次可能更优,因为单表多次查询有利于后面对数据的分库分表,且多次查询可以支持部分的缓存操作以及分为多次减少数据库锁的竞争。

4. 分区(MySQL,比如按时间分区);

在数据库I/O方面

(1)增加缓冲区

1、分库分表,怎么分?

将其散列到100个表中,分别从members0到members99,然后根据mid分发记录到这些表中,

水平分表:3 取模分表就属于随机分表,而时间维度分表则属于连续分表

如何设计好垂直拆分,我的建议:将不常用的字段单独拆分到另外一张扩展表. 将大文本的字段单独拆分到另外一张扩展表, 将不经常修改的字段放在同一张表中,将经常改变的字段放在另一张表中。

对于海量用户场景,可以考虑取模分表,数据相对比较均匀,不容易出现热点和并发访问的瓶颈。

2.不停机修改MySQL表结构 

3、常用SQL语句优化

数据库(表)设计合理, 表设计要符合3NF 3范式(规范的模式) , 有时我们需要适当的逆范式

 Tomcat怎么优化?https://blog.csdn.net/kally_wang/article/details/74989885

一、内存优化

1.修改内存等 JVM相关配置, 优化JDK/ 优化JDK

可以放在CLASSPATH=下面1. JAVA_OPTS="-server -XX:PermSize=512M -XX:MaxPermSize=1024m -Xms2048m -Xmx2048m"  

二、配置优化

 1.Connector 优化, 优化连接配置.  Connector是连接器,负责接收客户的请求,以及向客户端回送响应的消息。 默认情况下 Tomcat只支持200线程访问,超过这个数量的连接将被等待甚至超时放弃,所以我们需要提高这方面的处理能力。

修改TOMCAT_HOME/conf/server.xml

1. <Connector port="8080" protocol="HTTP/1.1"  

2.            connectionTimeout="20000"  

3.            redirectPort="8443" />  

Connector 支持参数属性可以参考Tomcat官方网站 protocol:协议类型,可选类型有四种,分别为BIO(阻塞型IO),NIO,NIO2和APR。

3.线程池

要想使用线程池,首先需要在 Service标签中配置 Executor,

1. <Service name="Catalina">  

2.   

3.   <Executor name="tomcatThreadPool"   

4.          namePrefix="catalina-exec-"   

5.          maxThreads="1000"   

6.          minSpareThreads="100"  

7.          maxIdleTime="60000"  

8.          maxQueueSize="Integer.MAX_VALUE"  

9.          prestartminSpareThreads="false"  

10.          threadPriority="5"  

11.          className="org.apache.catalina.core.StandardThreadExecutor"/>  

 4.Listener

另一个影响Tomcat 性能的因素是内存泄露, Server标签中可以配置多个Listener, 用来预防JRE内存泄漏

三、组件优化

1.APR     是一个高可移植库

2.Tomcat Native

Redis和memcache的区别

Memcache和Redis区别: 

1.Redis中,并不是所有的数据都一直存储在内存中的,这是和Memcache相比一个最大的区别。 

2.Redis在很多方面具备数据库的特征,或者说就是一个数据库系统,而Memcache只是简单的K/V缓存。 

他们的扩展都需要做集群;实现方式:master-slave、Hash。 

4.在100k以上的数据中,Memcache性能要高于Redis。 

5.如果要说内存使用效率,使用简单的key-value存储的话,Memcached的内存利用率更高,而如果Redis采用hash结构来做key-value存储,由于其组合式的压缩,其内存利用率会高于Memcache。当然,这和你的应用场景和数据特性有关。 如果你对数据持久化和数据同步有所要求,那么推荐你选择Redis,因为这两个特性Memcache都不具备。即使你只是希望在升级或者重启系统后缓存数据不会丢失,选择Redis也是明智的。 

6.Redis和Memcache在写入性能上面差别不大,读取性能上面尤其是批量读取性能上面Memcache更强 

共同点:Memcache,Redis 都是内存数据库 

Memcache   

Memcache可以利用多核优势,单实例吞吐量极高,可以达到几十万QPS,适用于最大程度扛量     只支持简单的key/value数据结构,不像Redis可以支持丰富的数据类型。     无法进行持久化,数据不能备份,只能用于缓存使用,且重启后数据全部丢失 

个人总结一下,有持久化需求或者对数据结构和处理有高级要求的应用,选择redis,其他简单的key/value存储,选择memcache。

猜你喜欢

转载自blog.csdn.net/Butterfly_resting/article/details/82897426
今日推荐