java面试题目(持续更新)

题目来自于牛客网。答案如有错误,望君不吝指正。本人目前也在找工作

1、乐观锁如何保证线程安全?

乐观锁总是认为不会产生并发问题,取数据的时候不会对数据上锁,允许其他线程读或者写这个数据。乐观锁只考虑写带来的线程安全问题,以保证不会意外覆盖掉数据的更新值。保证线程安全的方法有:VERSION机制和CAS方式。给数据附上一个版本号,每次更新这个数据,版本号也会更新。乐观锁在更新数据前,先读取数据的版本号,然后到更新数据时,再读取该数据的版本号,如果两次版本号相同,则可以安全地写入新数据。 否则,线程可以选择继续尝试, 或者是放弃尝试。那么为什么又有CAS方式呢?试想一下,如果一个数据被写了好多次后又变回了最开始的值。通过version机制,首次尝试肯定会失败。而理论上,我们这时候进行更新时合法的。所以就有了CAS方式,直接比较当时读的值和现在所读的值是否相等。CAS(compare and swap,比较交换),对应的执行函数是CAS(V,E,N)。V表示要更新的变量(更新前,读取改变量的值),E表示期望值(即一开始读出来的变量值,更新前要和V中的值进行比较),N就是写入到V中的新值。每次更新前,都会比较V中的值和E的值是否相等,相等才会进行更新。同样地,CAS可以选择停止,也可以选择继续尝试。

2、ArrayList和LinkedList的底层

ArrayList:

  • 是使用的连续内存空间的数组。
  • 可以自动扩容。
  • 随机访问速度快,插入或删除则较耗时。

LinkedList:

  • 是双向链表。
  • 由于支持首尾插入和删除操作,可以将他作为栈,队列来使用。

3、HashMap以及线程安全的ConcurrentHashMap

HashMap使用每个key的hashCode()来获得哈希值,然后再调用HashMap自己的映射方法来确定每个key在HashMap中的索引。存储元素时,将键值对以Map.Entry的形式插在对应位置连接的链表头部,如果是重复的键,则将value修改为新的value。检索元素时,先计算出key在HashMap中的index。如果index位置有不止1个对象,就用equals方法依次和每一个对象的key值进行比较。当HashMap中的index总量超过负载因子规定的比例时,就会自动扩容1倍大小,HashMap中的每一个元素都要重新计算index。 在java 8中,如果链表的长度达到8了,就会将链表转为红黑树。

ConcurrentHashMap在java 7中支持多线程的方式是分段(segment),给每一段加锁。在初始化ConcurrentHashMap后,就不能修改段的数量了。默认段的数量是16个,意味着最多允许16个线程并发写,这得是16个线程需要的数据正好在16个不同的段中才行,访问同一段的话,依然要获得锁。每一段连着一个HashMap,也允许对这个HashMap扩容。

ConcurrentHashMap在java 8中则去掉了分段,改用 CAS + synchronized 控制并发操作。ConcurrentHashMap的数据结构大体上看起来和java 8的HashMap没有什么区别。

4、Synchronized

Synchronized可以应用在三个层面。1)对象实例锁,在对象方法签名上加上synchronized关键字。需要注意的是当线程a在执行某个对象的同步方法时,其他线程不允许执行该对象的所有同步方法。这是因为对象实例只有一个锁。2)类对象锁,在类方法签名加上synchronized关键字,用于同步不同的实例对象对共享变量(静态变量)的并发操作。3)同步代码块,将锁加到方法体中,可以对任何对象上锁。synchronized(obj){...}

5、JVM的类加载过程?

过程包括:类的加载(loading),连接(linking)和初始化(Initialization)。

加载:查找并加载类的class文件(代码从硬盘到内存的迁移),类加载的结果就是将类的信息放到方法区中。

连接:

  •  验证(verification): 检查字节码是否和jvm兼容。这里列出一些不兼容的情况。1)你用java 6编译出来的class文件放到java 8中来用,jvm很可能无法正确处理。更实现的情况是,你把10年前的java第三方库拿来用可能就用不了了,因为它是用老版本的java编译的。2)有些人没用javac来生成class文件,jvm可能也无法理解这样的class文件。
  • 准备(preparation):为类的静态变量分配内存,并将其初始化为默认值。不过,这一步不会执行类的静态块,因为静态块里不允许声明静态变量,也就不需要预先分配内存,所以不会在这一步执行静态块。
  • 解析(resolution):将符号引用变为直接引用。比如方法a中,调用了方法b。在解析前,不知道方法b在哪,解析后就给这个调用加上一个指针,指向方法b在方法区的内存位置。

初始化:对类的静态变量进行初始化。有两种途径:1)在静态变量声明时,进行赋值操作。2)在静态代码块中对静态变量初始化。

6、JVM内存划分与垃圾回收机制

这两个知识点是相互关联的。首先,你需要了解内存划分。

JVM的内存分成3个区域: 堆区(heap),栈区(stack)以及方法区。堆区:jvm中只有一个堆区,存储的全是对象(不是引用,是实例本身),不管是哪个线程创建的对象,都放在堆区中。栈区:每个线程都有各自的栈区,用来存储线程运行过程中需要的临时变量,执行上下文,操作指令等。因此,每个栈区都是私有的。main方法对应的就是主线程,他有自己的栈区,每调用一个方法时,就会往栈区压入一个栈帧,用来存储该方法的上下文信息等。方法区:是线程共享的,里面包含了所有的class和static变量。

垃圾回收是针对堆区的。因为栈区在方法退出时,就会销毁栈帧。而方法区在整个程序运行期,基本是不变的。因为java是面向对象的语言,程序运行过程中创建最多的可能就是对象了。因此,要对堆区内存进行科学分配。堆区被进一步分为新生代,老年代和永生代。垃圾回收就是新生代和老年代的内存回收操作。新生代采用的复制算法进行垃圾回收。因为整个老年代内部没有再划分子区域,所以无法使用复制算法进行垃圾回收。(具体的回收过程,就略过了。。。)。另外永久代放的是Class和一些元数据信息,是不是感觉和方法区的作用很像。没错,永久代就是方法区在Java8中,永久代已经被移除了,取而代之的是元空间(元空间就是方法区)。两者的用处是一样的。不同之处在于永久代占用的是JVM的内存,而元空间则直接在本地内存上(宿主机的内存)。这也意味着Java8中的内存划分只有两个区域:堆区和栈区。而方法区被移到了本地内存上。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值