15. jvm内存模型,各个部分的特点?

平安 Java 面试 平安java笔试题及答案_阻塞队列


1. PC寄存器: a. 每个线程拥有⼀个pc寄存器; b. 指向下⼀条指令的地址。 2. ⽅法区: a. 保存装载的类的元信息:类型的常量池,字段、⽅法信息,⽅法字节码; jdk6时,String等常量信息置于⽅法区,jdk7移到了堆中; b. 通常和永久区(Perm)关联在⼀起; 3. 堆: a. 应⽤系统对象都保存在java堆中; b. 所有线程共享java堆; c. 对分代GC来说,堆也是分代的; 4. 栈: a. 线程私有; b. 栈由⼀系列帧组成(因此java栈也叫做帧栈); c. 帧保存⼀个⽅法的局部变量(局部变量表)、操作数栈、常量池指针;

平安 Java 面试 平安java笔试题及答案_数组_02

d. 每⼀次⽅法调⽤创建⼀个帧,并压栈。


16. 类加载器,双亲委派模型?


1. BootStrap ClassLoader 启动ClassLoader

2. Extension ClassLoader 扩展ClassLoader

3. App ClassLoader 应⽤ClassLoader/系统ClassLoader

4. Custom ClassLoader ⾃定义ClassLoader

除了BootStrap ClassLoader,每个ClassLoader都有⼀个Parent作为⽗亲。1. ⾃底向上检查类是否已经加载;2. ⾃顶向下尝试加载类。2. ⾃顶向下尝试加载类。

5、双亲委派机制:当⼀个类收到了类加载请求,他⾸先不会尝试⾃⼰去加载这个类,⽽是把这个请求委派给⽗类去完成,每⼀个层次类加载器都是如此,因此所有的加载请求都应该传送到启动类加载其中,只有当⽗类加载器反馈⾃⼰⽆法完成这个请求的时候(在它的加载路径下没有找到所需加载的Class),⼦类加载器才会尝试⾃⼰去加载。

平安 Java 面试 平安java笔试题及答案_阻塞队列_03


17. 类加载机制。


1. 概念:虚拟机把描述类的数据⽂件(字节码)加载到内存,并对数据进⾏验证、准备、解析以及类初始化,最终形成可以被虚拟机直接使⽤的java类型(java.lang.Class对象)。

2. 类⽣命周期:类加载过程:读取⼆进制字节流到jvm—>验证格式语义等—>为静态变量分配内存空间—>常量池引⽤解析—>执⾏static标识的代码

a. 加载过程:通过⼀个类的全限定名来获取定义此类的⼆进制字节流,将这个字节流所代表的静态存储结构转化为⽅法区的运⾏时数据结构。在内存中(⽅法区)⽣成⼀个代表这个类的java.lang.Class对象,作为⽅法区这个类的各种数据的访问⼊⼝;

b. 验证过程:为了确保Class⽂件的字节流中包含的信息符合当前虚拟机的要求,⽂件格式验证、元数据验证、字节码验证、符号引⽤验证;

c. 准备过程:正式为类属性分配内存并设置类属性初始值的阶段,这些内存都将在⽅法区中进⾏分配;

准备阶段,static对象会被设置默认值,static fifinal对象会被赋上给予的值。

d. 解析阶段:虚拟机将常量池内的符号引⽤替换为直接引⽤的过程。

  • i. 符号引⽤:字符串,引⽤对象不⼀定被加载;
  • ii. 直接引⽤:指针或者地址偏移量,引⽤对象⼀定在内存中。

e. 初始化阶段:类初始化阶段是类加载过程的最后⼀步。初始化阶段就是执⾏类构造器()⽅法的过程。

f. 使⽤阶段:

g. 卸载阶段:


18. java堆的结构,⼀个bean被new出来之后,在内存空间的⾛向?


平安 Java 面试 平安java笔试题及答案_平安 Java 面试_04

1、JVM中堆空间可以分成三个⼤区,新⽣代、⽼年代、永久代

2、新⽣代可以划分为三个区,Eden区,两个Survivor区,在HotSpot虚拟机Eden和Survivor的⼤⼩⽐例为8:1。


20. 写出⼏个jvm优化配置参数。


1. 设定堆内存⼤⼩,这是最基本的。

2. -Xms:启动JVM时的堆内存空间。

3. -Xmx:堆内存最⼤限制。

4. 设定新⽣代⼤⼩。

5. 新⽣代不宜太⼩,否则会有⼤量对象涌⼊⽼年代。

6. -XX:NewRatio:新⽣代和⽼年代的占⽐。

7. -XX:NewSize:新⽣代空间。

8. -XX:SurvivorRatio:伊甸园空间和幸存者空间的占⽐。

9. -XX:MaxTenuringThreshold:对象进⼊⽼年代的年龄阈值。

10. 设定垃圾回收器

  • 年轻代:-XX:+UseParNewGC。

⽼年代:-XX:+UseConcMarkSweepGC。CMS可以将STW时间降到最低,但是不对内存进⾏压缩,有可能出现“并⾏模式失败”。⽐如⽼年代空间还有300MB空间,但是⼀些10MB的对象⽆法被顺序的存储。这时候会触发压缩处理,但是CMS GC模式下的压缩处理时间要⽐Parallel GC⻓很多。

G1采⽤”标记-整理“算法,解决了内存碎⽚问题,建⽴了可预测的停顿时间类型,能让使⽤者指定在⼀个⻓度为M毫秒的时间段内,消耗在垃圾收集上的时间不得超过N毫秒。


21. 有哪⼏种GC机制?

1. 引⽤计数法(没有被java采⽤):

a. 原理:对于⼀个对象A,只要有任何⼀个对象引⽤了A,则A的引⽤计数器就加1,当引⽤失效时,引⽤计数器就减1,只要对象A的引⽤计数器的值为0,则对象A就会被回收。 b. 问题:i. 引⽤和去引⽤伴随加法和减法,影响性能; ii. 很难处理循环引⽤。 2. 标记清除法:

a. 原理:现代垃圾回收算法的思想基础。标记-清除算法将垃圾回收分为两个阶段:标记阶段和清除阶段。⼀种可⾏的实现是,在标记节点,⾸先通过根节点,标记所有从根节点开始的可达对象。因此,未被标记的对象就是未被引⽤的垃圾对象。然后在清除阶段,清除所有未被标记的对象。 b. 问题:i. 标记和清除两个过程效率不⾼,产⽣内存碎⽚导致需要分配较⼤对象时⽆法找到⾜够的连续内存⽽需要触发⼀次GC操作。 3. 标记压缩法:

a. 原理:适合⽤于存活对象较多的场合,如⽼年代。它在标记-清除算法的基础上做了⼀些优化。标记阶段⼀样,但之后,将所有存活对象压缩到内存的⼀端。之后,清除边界外所有的空间。 b. 优点:i. 解决了标记- 清除算法导致的内存碎⽚问题和在存活率较⾼时复制算法效率低的问题。 4. 复制算法:

a. 原理:将原有的内存空间分为两块,每次只使⽤其中⼀块,在垃圾回收时,将正在使⽤的内存中的存活对象复制到未使⽤的内存块中,之后清除正在使⽤的内存块中的所有对象,交换两个内存的⻆⾊,完成垃圾回收。 b. 问题:i. 不适⽤于存活对象⽐较多的场合,如⽼年代。 5. 分代回收法:

a. 原理:根据对象存活周期的不同将内存划分为⼏块,⼀般是新⽣代和⽼年代, 新⽣代基本采⽤复制算法,⽼年代采⽤标记整理算法。 22. springboot启动过程。


平安 Java 面试 平安java笔试题及答案_加载_05

1.SpringApplicationRunListener对象

2. 然后由 SpringApplicationRunListener来发出 starting 消息

3. 创建参数,并配置当前 SpringBoot 应⽤将要使⽤的 Environment

4. 完成之后,依然由 SpringApplicationRunListener来发出 environmentPrepared 消息

5. 创建 ApplicationContext

6. 初始化 ApplicationContext,并设置 Environment,加载相关配置等

7. 由 SpringApplicationRunListener来发出 contextPrepared消息,告知SpringBoot 应⽤使⽤的ApplicationContext已准备OK

8. 将各种 beans 装载⼊ ApplicationContext,继续由 SpringApplicationRunListener来发出 contextLoaded 消息,告知 SpringBoot 应⽤使⽤的 ApplicationContext已装填OK

9. refresh ApplicationContext,完成IoC容器可⽤的最后⼀步

10. 由 SpringApplicationRunListener来发出 started 消息

11. 完成最终的程序的启动

12. 由 SpringApplicationRunListener来发出 running 消息,告知程序已运⾏起来了


23. 说说⼏个常⽤的注解?


@RestController :@ResponseBody和@Controller的合集。

@EnableAutoConfiguration :尝试根据你添加的jar依赖⾃动配置你的Spring应⽤。

@ComponentScan:表示将该类⾃动发现(扫描)并注册为Bean,可以⾃动收集所有的Spring组件,包括@Configuration类。

@ImportResource :⽤来加载xml配置⽂件。

@Configuration :相当于传统的xml配置⽂件,如果有些第三⽅库需要⽤到xml⽂件,建议仍然通过@Configuration类作为项⽬的配置主类——可以使⽤@ImportResource注解加载xml配置⽂件。

@SpringBootApplication:相当于@EnableAutoConfiguration、@ComponentScan和@Configuration的合集。


24. spring的bean的⽣命周期?


平安 Java 面试 平安java笔试题及答案_数组_06


25. 说说HashMap、ConcurrentHashMap数据结构,1.7与1.8的区别?

1、数据结构: 以下是ConcurrentHashMap的类图:

平安 Java 面试 平安java笔试题及答案_平安 Java 面试_07


ConcurrentHashMap是由Segment数组结构和HashEntry数组结构组成。Segment是⼀种可重⼊锁ReentrantLock,在ConcurrentHashMap中扮演锁的⻆⾊,HashEntry则⽤于存储键值对数据。⼀个ConcurrentHashMap中包含⼀个Segment数组,Segment的结构和HashMap类似,是⼀种数组和链表结构。⼀个Segment⾥⾯包含⼀个HashEntry数组,每个HashEntry是⼀个链表的元素。每个Segment拥有⼀个锁,当对HashEntry数组的数据进⾏修改时,必须先获得对应的Segment锁,如图所示:

平安 Java 面试 平安java笔试题及答案_java面试真题 烽火通信_08


2、concurrenthashmap 1.7和1.8区分:

  • 去除 Segment + HashEntry + Unsafe的实现,改为 Synchronized + CAS + Node + Unsafe的实现


其实 Node 和 HashEntry 的内容⼀样,但是HashEntry是⼀个内部类。 ⽤ Synchronized + CAS 代替 Segment ,这样锁的粒度更⼩了,并且不是每次都要加锁了,CAS尝试失败了在加锁。

  • put()⽅法中 初始化数组⼤⼩时,1.8不⽤加锁,因为⽤了个 sizeCtl变量,将这个变量置为-1,就表明table正在初始化。

26. 红⿊树左旋与右旋的区别?

当在对红⿊树进⾏插⼊和删除等操作时,对树做了修改可能会破坏红⿊树的性质。为了继续保持红⿊树的性质,可以通过对结点进⾏重新着⾊,以及对树进⾏相关的旋转操作,即通过修改树中某些结点的颜⾊及指针结构,来达到对红⿊树进⾏插⼊或删除结点等操作后继续保持它的性质或平衡的⽬的。 树的旋转分为左旋和右旋,下⾯借助图来介绍⼀下左旋和右旋这两种操作。 1.左旋


平安 Java 面试 平安java笔试题及答案_数组_09

如上图所示,当在某个结点pivot上,做左旋操作时,我们假设它的右孩⼦y不是NIL[T],pivot可以为任何不是NIL[T]的左⼦结点。左旋以pivot到Y之间的链为“⽀轴”进⾏,它使Y成为该⼦树的新根,⽽Y的左孩⼦b则成为pivot的右孩⼦。

LeftRoate(T, x) y ← x.right //定义y:y是x的右孩⼦x.right ← y.left //y的左孩⼦成为x的右孩⼦if y.left ≠ T.nily.left.p ← x y.p ← x.p //x的⽗结点成为y的⽗结点if x.p = T.nilthen T.root ← yelse if x = x.p.leftthen x.p.left ← yelse x.p.right ← y y.left ← x //x作为y的左孩⼦x.p ← y

2.右旋

右旋与左旋差不多,再此不做详细介绍。

平安 Java 面试 平安java笔试题及答案_java面试真题 烽火通信_10

树在经过左旋右旋之后,树的搜索性质保持不变,但树的红⿊性质则被破坏了,所以,红⿊树插⼊和删除数据后,需要利⽤旋转与颜⾊重涂来重新恢复树的红⿊性质。


27. concurrent包下有哪些常⽤类?


1.CountDownLatch:api

解释:⼀个同步辅助类,在完成⼀组正在其他线程中执⾏的操作之前,它允许⼀个或多个线程⼀直等待。个⼈理解是CountDownLatch让可以让⼀组线程同时执⾏,然后在这组线程全部执⾏完前,可以让另⼀个线程等待。

2.ReentrantLock:可重⼊互斥锁

3.Condition:此类是同步的条件对象,每个Condition实例绑定到⼀个ReetrantLock中,以便争⽤同⼀个锁的多线程之间可以通过Condition的状态来获取通知。

注意:使⽤Condition前,⾸先要获得ReentantLock,当条件不满⾜线程1等待时,ReentrantLock会被释放,以能让其他线程争⽤,其他线程获得reentrantLock,然后满⾜条件,唤醒线程1继续执⾏。


28. 三种分布式锁。


1、Zookeeper:基于zookeeper瞬时有序节点实现的分布式锁,其主要逻辑如下(该图来⾃于IBM⽹站)。⼤致思想即为:每个客户端对某个功能加锁时,在zookeeper上的与该功能对应的指定节点的⽬录下,⽣成⼀个唯⼀的瞬时有序节点。判断是否获取锁的⽅式很简单,只需要判断有序节点中序号最⼩的⼀个。当释放锁的时候,只需将这个瞬时节点删除即可。同时,其可以避免服务宕机导致的锁⽆法释放,⽽产⽣的死锁问题。

平安 Java 面试 平安java笔试题及答案_数组_11

2、优点

锁安全性⾼,zk可持久化,且能实时监听获取锁的客户端状态。⼀旦客户端宕机,则瞬时节点随之消失,zk因⽽能第⼀时间释放锁。这也省去了⽤分布式缓存实现锁的过程中需要加⼊超时时间判断的这⼀逻辑。 

3、缺点

性能开销⽐较⾼。因为其需要动态产⽣、销毁瞬时节点来实现锁功能。所以不太适合直接提供给⾼并发的场景使⽤。

4、实现

可以直接采⽤zookeeper第三⽅库curator即可⽅便地实现分布式锁。

5、适⽤场景

对可靠性要求⾮常⾼,且并发程度不⾼的场景下使⽤。如核⼼数据的定时全量/增量同步等。

2、memcached:memcached带有add函数,利⽤add函数的特性即可实现分布式锁。add和set的区别在于:如果多线程并发set,则每个set都会成功,但最后存储的值以最后的set的线程为准。⽽add的话则相反,add会添加第⼀个到达的值,并返回true,后续的添加则都会返回false。利⽤该点即可很轻松地实现分布式锁。 

2、优点

并发⾼效 

3、缺点

memcached采⽤列⼊LRU置换策略,所以如果内存不够,可能导致缓存中的锁信息丢失。 

memcached⽆法持久化,⼀旦重启,将导致信息丢失。

4、使⽤场景⾼并发场景。

需要 1)加上超时时间避免死锁; 2)提供⾜够⽀撑锁服务的内存空间; 3)稳定的集群化管理。

3、redis:redis分布式锁即可以结合zk分布式锁锁⾼度安全和memcached并发场景下效率很好的优点,其实现⽅式和memcached类似,采⽤setnx即可实现。需要注意的是,这⾥的redis也需要设置超时时间。以避免死锁。可以利⽤jedis客户端实现。

ICacheKey cacheKey = new ConcurrentCacheKey(key, type); return RedisDao.setnx(cacheKey, "1");


29. 你知道哪些常⽤的阻塞队列?


JDK7 提供了 7 个阻塞队列。分别是:

ArrayBlockingQueue :⼀个由数组结构组成的有界阻塞队列。

LinkedBlockingQueue :⼀个由链表结构组成的有界阻塞队列。

PriorityBlockingQueue :⼀个⽀持优先级排序的⽆界阻塞队列。

DelayQueue:⼀个使⽤优先级队列实现的⽆界阻塞队列。

SynchronousQueue:⼀个不存储元素的阻塞队列。

LinkedTransferQueue:⼀个由链表结构组成的⽆界阻塞队列。

LinkedBlockingDeque:⼀个由链表结构组成的双向阻塞队列。


30. newFixedThreadPool使⽤到了哪个阻塞队列?


看Executors源码可知:newFixedThreadPool使⽤了LinkedBlockingQueue。

public static ExecutorService newFixedThreadPool(int var0) { return new ThreadPoolExecutor(var0, var0, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue());  }