大数据面试题130道

1、HashMap 和 Hashtable 区别 

 HashMap和Hashtable都实现了Map接口,但决定用哪一个之前先要弄清楚它们之间的分别。主要的区别有:线程安全性,同步(synchronization),以及速度。

HashMap几乎可以等价于Hashtable,除了HashMap是非synchronized的,并可以接受null(HashMap可以接受为null的键值(key)和值(value),而Hashtable则不行)。

HashMap是非synchronized,而Hashtable是synchronized,这意味着Hashtable是线程安全的,多个线程可以共享一个Hashtable;而如果没有正确的同步的话,多个线程是不能共享HashMap的。Java 5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的扩展性更好。

另一个区别是HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。所以当有其它线程改变了HashMap的结构(增加或者移除元素),将会抛出ConcurrentModificationException,但迭代器本身的remove()方法移除元素则不会抛出ConcurrentModificationException异常。但这并不是一个一定发生的行为,要看JVM。这条同样也是Enumeration和Iterator的区别。

由于Hashtable是线程安全的也是synchronized,所以在单线程环境下它比HashMap要慢。如果你不需要同步,只需要单一线程,那么使用HashMap性能要好过Hashtable。

HashMap不能保证随着时间的推移Map中的元素次序是不变的。

要注意的一些重要术语:

sychronized意味着在一次仅有一个线程能够更改Hashtable。就是说任何线程要更新Hashtable时要首先获得同步锁,其它线程要等到同步锁被释放之后才能再次获得同步锁更新Hashtable。

Fail-safe和iterator迭代器相关。如果某个集合对象创建了Iterator或者ListIterator,然后其它的线程试图“结构上”更改集合对象,将会抛出ConcurrentModificationException异常。但其它线程可以通过set()方法更改集合对象是允许的,因为这并没有从“结构上”更改集合。但是假如已经从结构上进行了更改,再调用set()方法,将会抛出IllegalArgumentException异常。

结构上的更改指的是删除或者插入一个元素,这样会影响到map的结构。

我们能否让HashMap同步?

HashMap可以通过下面的语句进行同步:

Map m = Collections.synchronizeMap(hashMap);

结论

Hashtable和HashMap有几个主要的不同:线程安全以及速度。仅在你需要完全的线程安全的时候使用Hashtable,而如果你使用Java 5或以上的话,请使用ConcurrentHashMap吧。

2、Java 垃圾回收机制和生命周期 

C语言:

Java语言:

c的垃圾回收是人工的,工作量大,但是可控性高。

java是自动化的,但是可控性很差,甚至有时会出现内存溢出的情况,

内存溢出也就是jvm分配的内存中对象过多,超出了最大可分配内存的大小。

提到java的垃圾回收机制就不得不提一个方法: ​  

System.gc()用于调用垃圾收集器,在调用时,垃圾收集器将运行以回收未使用的内存空间。它将尝试释放被丢弃对象占用的内存。

然而System.gc()调用附带一个免责声明,无法保证对垃圾收集器的调用。

所以System.gc()并不能说是完美主动进行了垃圾回收。

作为java程序员还是很有必要了解一下gc,这也是面试过程中经常出现的一道题目。

 我们从三个角度来理解gc。

 1jvm怎么确定哪些对象应该进行回收

 2jvm会在什么时候进行垃圾回收的动作

 3jvm到底是怎么清楚垃圾对象的

jvm怎么确定哪些对象应该进行回收

对象是否会被回收的两个经典算法:引用计数法,和可达性分析算法。

引用计数法

简单的来说就是判断对象的引用数量。实现方式:给对象共添加一个引用计数器,每当有引用对他进行引用时,计数器的值就加1,当引用失效,也就是不在执行此对象是,他的计数器的值就减1,若某一个对象的计数器的值为0,那么表示这个对象没有人对他进行引用,也就是意味着是一个失效的垃圾对象,就会被gc进行回收。

 但是这种简单的算法在当前的jvm中并没有采用,原因是他并不能解决对象之间循环引用的问题。

 假设有A和B两个对象之间互相引用,也就是说A对象中的一个属性是B,B中的一个属性时A,这种情况下由于他们的相互引用,从而是垃圾回收机制无法识别。

因为引用计数法的缺点有引入了可达性分析算法,通过判断对象的引用链是否可达来决定对象是否可以被回收。可达性分析算法是从离散数学中的图论引入的,程序把所有的引用关系看作一张图,通过一系列的名为GC Roots的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链。当一个对象到 GC Roots 没有任何引用链相连(就是从 GC Roots 到这个对象不可达)时,则证明此对象是不可用的。

如图:

二在确定了哪些对象可以被回收之后,jvm会在什么时候进行回收

 1会在cpu空闲的时候自动进行回收

 2在堆内存存储满了之后

 3主动调用System.gc()后尝试进行回收

三如何回收

 如何回收说的也就是垃圾收集的算法。

算法又有四个:标记-清除算法,复制算法,标记-整理算法,分代收集算法.

 1 标记-清除算法。

 这是最基础的一种算法,分为两个步骤,第一个步骤就是标记,也就是标记处所有需要回收的对象,标记完成后就进行统一的回收掉哪些带有标记的对象。这种算法优点是简单,缺点是效率问题,还有一个最大的缺点是空间问题,标记清除之后会产生大量不连续的内存碎片,当程序在以后的运行过程中需要分配较大对象时无法找到足够的连续内存而造成内存空间浪费。

执行如图:

2复制算法。

复制将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。这样使得每次都是对其中的一块进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况。只是这种算法的代价是将内存缩小为原来的一半。

复制算法的执行过程如图:

复制收集算法在对象存活率较高时就要执行较多的复制操作,效率将会变低。更关键的是,浪费了一半的空间。

标记-整理算法:

标记整理算法与标记清除算法很相似,但最显著的区别是:标记清除算法仅对不存活的对象进行处理,剩余存活对象不做任何处理,造成内存碎片;而标记整理算法不仅对不存活对象进行处理清除,还对剩余的存活对象进行整理,重新整理,因此其不会产生内存碎片。

分代收集算法:

分代收集算法是一种比较智能的算法,也是现在jvm使用最多的一种算法,他本身其实不是一个新的算法,而是他会在具体的场景自动选择以上三种算法进行垃圾对象回收。

那么现在的重点就是分代收集算法中说的自动根据具体场景进行选择。这个具体场景到底是什么场景。

场景其实指的是针对jvm的哪一个区域,1.7之前jvm把内存分为三个区域:新生代,老年代,永久代。

了解过场景之后再结合分代收集算法得出结论: 1、在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法。只需要付出少量存活对象的复制成本就可以完成收集。 2、老年代中因为对象存活率高、没有额外空间对他进行分配担保,就必须用标记-清除或者标记-整理。

注意:

在jdk8的时候java废弃了永久代,但是并不意味着我们以上的结论失效,因为java提供了与永久代类似的叫做“元空间”的技术。

废弃永久代的原因:由于永久代内存经常不够用或发生内存泄露,爆出异常java.lang.OutOfMemoryErroy。元空间的本质和永久代类似。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。也就是不局限与jvm可以使用系统的内存。理论上取决于32位/64位系统可虚拟的内存大小。

GC垃圾回收:

    jvm按照对象的生命周期,将内存按“代”划分(将堆划分为多个地址池):新生代、老年代和持久代(jdk1.8后移除持久代);

    在JVM中程序(PC)计数器、JAVA栈、本地方法栈3个区域随线程而生、随线程而灭,因此这几个区域的内存分配和回收都具备确定性,就不需要过多考虑回收的问题,因为方法结束或者线程结束时,内存自然就跟随着回收了。而堆和方法区则不一样,这部分内存的分配和回收是动态的,正是垃圾收集器所需关注的部分。

    java中新创建的对象会先被放在新生代区域,该区域对象使用频繁,jvm会在该区域使用不同算法回收一定的短期对象,如果某些对象使用次数达到一定限制后,那么该对象就会被放入老年代区域,老年代区域要比新生代区域更大一些(堆内存大部分分配给了老年代区域),而持久代保存的是类的元数据、常量、类静态变量等。  

方法区和永久代的区别:

   对于方法区和永久代的区别的话,人们一直将它们看作一个部件,其实永久代实现了方法区,比作java中类的话,永久代就是接口实现类,方法区就是接口。

finalize()和System.gc()方法介绍:

    提到GC就要提到finalize()方法,该方法是在jvm确定了一个对象符合GC的条件下执行的,用于对一些外部资源的释放等操作,但是何时对这个对象回收我们就不知道了;需要注意的是在jvm调用了该方法后,这个符合GC的对象也不一定最后就被回收了,因为在执行了finalize()方法后由于在方法体给对该方法进行了一些操作,使得该对象不符合GC的条件,例如将一个引用指向这个对象,最终导致该对象不会被GC,但这也只能求这个对象依次。

    同样还有System.gc()方法,这个方法的调用,jvm也不会立即执行对对象的回收,gc()仅仅是提醒jvm可以回收该方法了,但实际上要根据jvm内存需求来确定何实回收这个可以回收的对象。

那么gc()和finalize()的区别是什么呢?

    首先finalize()方法是jvm调用的,但是在回收期间不一定每个对象都会调用这个方法进行收尾工作,这也是这个方法不被提倡使用的原因。而System.gc()方法可以人为调用进行标记一个对象可以被回收。

最后我们从何时回收对象比较,finalize()标记的对象是在被标记后的第二次回收时进行回收,而System.gc()方法没有这种规定,它只是被标记,何时回收由jvm决定。

代码示例:

public class Test {

@Override

protected void finalize() throws Throwable {

super.finalize();

System.out.println("调用");

}

public static void main(String[] args){

Test test = new Test();

test=null;

System.gc();

}

}

分析:

    我们这里创建了Test类并重写了finalize()方法,然后我在主方法里创建了一个Test对象,并使其引用为空(此时符合回收条件)我们先调用System.gc()

结果:

    调用

    我们发现执行了finalize()方法,OK,我们现在将System.gc()注释掉,我们会发现并没有输出“调用”,也就是没有调用finalize()方法,这就是不一定每个垃圾对象jvm都会自动调用finalize()方法。

3、怎么解决 Kafka 数据丢失的问题 

1)消费端弄丢了数据

唯一可能导致消费者弄丢数据的情况,就是说,你那个消费到了这个消息,然后消费者那边自动提交了offset,让kafka以为你已经消费好了这个消息,其实你刚准备处理这个消息,你还没处理,你自己就挂了,此时这条消息就丢咯。

这不是一样么,大家都知道kafka会自动提交offset,那么只要关闭自动提交offset,在处理完之后自己手动提交offset,就可以保证数据不会丢。但是此时确实还是会重复消费,比如你刚处理完,还没提交offset,结果自己挂了,此时肯定会重复消费一次,自己保证幂等性就好了。

生产环境碰到的一个问题,就是说我们的kafka消费者消费到了数据之后是写到一个内存的queue里先缓冲一下,结果有的时候,你刚把消息写入内存queue,然后消费者会自动提交offset。

然后此时我们重启了系统,就会导致内存queue里还没来得及处理的数据就丢失了

2)kafka弄丢了数据

这块比较常见的一个场景,就是kafka某个broker宕机,然后重新选举partiton的leader时。大家想想,要是此时其他的follower刚好还有些数据没有同步,结果此时leader挂了,然后选举某个follower成leader之后,他不就少了一些数据?这就丢了一些数据啊。

生产环境也遇到过,我们也是,之前kafka的leader机器宕机了,将follower切换为leader之后,就会发现说这个数据就丢了

所以此时一般是要求起码设置如下4个参数:

给这个topic设置replication.factor参数:这个值必须大于1,要求每个partition必须有至少2个副本

在kafka服务端设置min.insync.replicas参数:这个值必须大于1,这个是要求一个leader至少感知到有至少一个follower还跟自己保持联系,没掉队,这样才能确保leader挂了还有一个follower吧

在producer端设置acks=all:这个是要求每条数据,必须是写入所有replica之后,才能认为是写成功了

在producer端设置retries=MAX(很大很大很大的一个值,无限次重试的意思):这个是要求一旦写入失败,就无限重试,卡在这里了

我们生产环境就是按照上述要求配置的,这样配置之后,至少在kafka broker端就可以保证在leader所在broker发生故障,进行leader切换时,数据不会丢失

3)生产者会不会弄丢数据

如果按照上述的思路设置了ack=all,一定不会丢,要求是,你的leader接收到消息,所有

  • 0
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值