【Android面试周】快问快答之Java篇
  • 类的加载篇
  • 垃圾回收机制篇
  • JVM篇
  • 多线程篇
  • 数据结构篇
  • ...

I. 类的加载篇

MyClass o = new MyClass() 背后发生了什么?【类的加载、链接、初始化】

  1. 首先ClassLoader会基于类的全限定名,将.class文件加载到JVM中,这个加载过程是基于双亲委托方式;
  2. 加载了 .class 后,当然要对它进行校验,确保它符合JVM规范;然后分配内存,如static变量,此时静态变量为默认值;
  3. 一切就绪后,就可以进行初始化了,执行它的构造函数和static代码,初始化刚刚分配好但未赋值的静态变量。

什么场合会触发类的初始化?

JVM规定了以下几种场合必须立即对类进行初始化

  1. 通过new、反序列化机制时;
  2. 调用类的static方法/使用类的static变量时;
  3. 通过反射调用类方法;
  4. 初始化该类的子类时(初始化子类必须先初始化父类);
  5. JVM标记的启动类(可以理解为具备main方法的类)。

简单说下 双亲委托 机制

首先,Java里有下面四种ClassLoader:

  • 启动类加载器 BootstrapClassLoader:C++实现,无法在Java里获取其引用,用于加载基础jar文件给JVM
  • 扩展类加载器 ExtClassLoader:加载扩展类如String
  • 应用程序类加载器 SystemClassLoader(AppClassLoader):加载用户创建的类
  • 自定义ClassLoader

所谓的 双亲委托,就是在加载一个类时,ClassLoader会先委托父级ClassLoader先去加载,只有当父级ClassLoader加载不到,才自己去加载。

上面四种加载器的加载顺序:BootstrapClassLoader > ExtClassLoader > SystemClassLoader > 自定义ClassLoader。

为什么要采用 双亲委托 机制

  1. 保证类的安全加载,防止冒充包名手动篡改核心类,如String类只能由ExtClassLoader加载;
  2. 不同类加载的相同全限定名的类,不是同一个类。

深入文章

《Java 技术之类加载机制》


II. 垃圾回收篇

有哪些垃圾回收算法?

  1. 标记-清理:对堆内存进行遍历,标记出其中无GC-Root引用的内存块进行标记,然后全部进行清理。这种方式会导致内存碎片;
  2. 标记-整理:同上,不过在清理后,将所有使用对象整理到一起,消除内存碎片,适用于“垃圾少,存活多”的场景;
  3. 复制:将堆内存分成A, B两块,使用时只用一块如A,当A填满了,进行一次gc,把A中存活对象复制到B,同时清空A,适用于“垃圾多,存活少”的场景

Java里的垃圾回收具体如何实现?

  1. Java里的堆分成了两部分:新生代、老年代;
  2. 新生代里,“垃圾多,存活少”,采用“复制”算法,分成了Eden区和两块Survivor区,默认比例81;首先对象分配在Eden区,满了后进行Minor GC,迁移到Survivor I 区;又满了后,将Eden区和Survivor I 区迁移进Survivor II 区;来回往复,当一个对象进行了15左右Minor gc或者Survivor区已经满了,则会被放入老年代;
  3. 老年代里,“垃圾少,存活多”,当老年代放满了,进行Major gc,基于“标记-整理”算法。

附加题:Eden区里有一块特殊的用于优化内存分配效率的区块是什么?

为了优化内存分配效率,JVM会在Eden区,为每一个新线程创建一个线程专属的独立空间,称为TLAB(Thread-Local Allocation Buffer),一般为Eden区的1%。
注意:正常JVM在堆上分配内存时是需要加锁的,而在TLAB区分配则无需加锁,因此可以提高内存分配效率;一般而言,尽可能有限在TLAB区进行内存分配,只有TLAB区满了或对象太大才在堆上分配。

深入文章

《Java 技术之垃圾回收机制》

III. JVM篇

JVM内存模型?


运行时方法区和堆是线程共享的;而Java栈和本地方法栈是线程私有的。

JVM内存包括堆内存、方法区和栈区。其中,堆内存用来存放对象;方法区可存放已被加载的类信息、常量、静态变量等,线程共享这些信息;Java栈则是与线程生命周期相同,用来存储线程运行期间的方法调用和局部变量;

在堆内存里,包括了新生代和老年代,其中新生代分成了Eden区、Survivor1区和Survivor2区,比例是81,采用复制算法回收;老年代采用标记-整理算法回收。

Java内存模型

III. 多线程篇

  1. volatile可见性、指令重排等特性
  2. synchronize修饰方法、变量、类
  3. object.wait, notify vs thread.sleep
  4. CountdownLatch, CyclicBarrier
  5. lock和synchronize区别

IV. 数据结构篇

HashMap原理

Hash分数组,数组再指向链表;

扩容:创建一个新数组,把原始数据复制进去;扩容因子0.75左右,当存储的数据量达到总长度*0.75时,自动进行扩容。这种坏处就是如果数据量很大,可能导致内存浪费严重。

退化:如果hash算法很糟糕,key分散不均匀,则会导致大链表的出现,复杂度从O(1)变成了最坏的O(N),变成了一个单链表。

HashMap vs HashTable vs ConcurrentMap

HashMap不是线程安全的;
HashTable是线程安全的,不过它的加锁是对整张表进行加锁,如果表非常大,则会导致写性能很差;
ConcurrentMap在

  • 1.7版本是使用Segment分桶机制,通过hash可以把不同的key分到对应的bucket里面,桶里面再去进行存储,加锁的话只要锁住这个桶就可以了,同步效率提高了。
  • 1.8版本里使用了 数组+链表+红黑树,如果链表长度小于8,则保持链表;如果大于8,也就意味着链表本身查找就很慢了O(N),所以此时会换成红黑树来提高查找效率

HashMap vs SparseArray

自动装箱

© 著作权归作者所有
这个作品真棒,我要支持一下!
我是wingjay,目前就职于阿里。 本系列会基于我的工作与学习经验,通过横向、纵向的Android与Java知...
0条评论
top Created with Sketch.