一 面试题概述
- 回答自己理解的java虚拟机、gc机制
- Java多线程、线程池
- 集合原理(hashmap,list)
- java虚引用
- 封装、继承、多态的理解
- activity生命周期
- 安卓activity和fragment数据传递
- Handler
- 内存泄漏、内存溢出、内存抖动 原因及解决办法
- ANR原因以及解决办法
- 性能优化、卡顿优化
- 事件分发机制
面试题解答
2.1 回答自己理解的java虚拟机、gc机制
JVM内存区域
JVM基本构成
JVM主要包括四个部分:
1-类加载器(ClassLoader):在 JVM 启动时或者在类运行将需要的 class 加载到 JVM 中(下图表示了从 java 源文件到 JVM 的整个过程,可配合理解)
2-执行引擎:负责执行 class 文件中包含的字节码指令
3-内存区(也叫运行时数据区):是在 JVM 运行的时候操作所分配的内存区。 运行时内存区主要可以划分为 5个区域,如下图:方法区(MethodArea)、java 堆(Heap)、java 栈(Stack)、程序计数器(PCRegister)、本地方法栈(Native MethodStack)、
4-本地方法接口:主要是调用 C 或 C++实现的本地方法及回调结果
gc机制
1-Java 中有四种引用类型
强引用、软引用、弱引用、虚引用
2-如何判断强引用是 否存在呢?
引用计数法,有对这个对象的引用就+1,不再引用就-1, 但是这种方式看起来简单美好,但它却不能解决循环引用计数的问题
3-GC Roots 对象通常包括
- 虚拟机栈中引用的对象(栈帧中的本地变量表)
- 方法中类的静态属性引用的对象
- 方法区中常量引用的对象
- Native 方法引用的对象
4-可达性分析算法整个流程
- 第一次标记:对象在经过可达性分析后发现没有与 GC Roots 有引用链,则进行 第一次标记并进行一次筛选,筛选条件是:该对象是否有必要执行 finalize()方法。
- 第二次标记:GC 对 F-Queue 队列里的对象进行第二次标记,如果在第二次标记 时该对象又成功被引用,则会被移除即将回收的集合,否则会被回收
5-一般回收算法也有如下几种
- 标记-清除(Mark-sweep)
- 标记-整理(Mark-Compact)
- 复制(Copying)
- 分代收集算法:新生代、老年代、永久代、内存分配和回收策略
2.2 Java多线程、线程池-见参考
Java多线程
1-java线程创建的方式
- 继承Thread类
- 实现runnable接口
- 继承callable接口
- 基于线程池创建线程
2-请详细描述一下线程从创建到死亡的几种状态都有哪些?
- 新建( new ):新创建了一个线程对象。
- 可运行( runnable ):线程对象创建后,其他线程(比如 main 线程)调用了该对象 的 start ()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获 取 cpu 的使用权 。
- 运行( running ):可运行状态( runnable )的线程获得了 cpu 时间片( timeslice ) ,执行程序代码。
- 阻塞( block ):阻塞状态是指线程因为某种原因放弃了 cpu 使用权,也即让出了 cpu timeslice ,暂时停止运行。直到线程进入可运行( runnable )状态,才有 机会再次获得 cpu timeslice 转到运行( running )状态。
- 死亡( dead ):线程 run ()、 main () 方法执行结束,或者因异常退出了 run ()方法,则该线程结束生命周期。死亡的线程不可再次复生。
3-sleep()和wait()区别
- sleep()来自thread,wait()来自object();
- sleep()不释放锁,wait释放锁
- sleep()时间到了会自动恢复,wait()可以使用notify()直接唤醒
4-在 Java 程序中怎么保证多线程的运行安全?
- 方法一:使用安全类,比如 Java. util. concurrent 下的类。
- 方法二:使用自动锁 synchronized。
- 方法三:使用手动锁 Lock。 手动锁 Java 示例代码如下:
5-JAVA如何在两个线程之间共享数据
Java 里面进行多线程通信的主要方式就是共享内存的方式,共享内存主要的关注点有两个:可见性和有序性原子性。Java 内存模型(JMM)解决了可见性和有序性的问题,而锁解决了原子性的问题,理想情况下我们希望做到“同步”和“互斥”。有以下常规实现方法:
将数据抽象成一个类,并将数据的操作作为这个类的方法
- 将数据抽象成一个类,并将对这个数据的操作作为这个类的方法,这么设计可以很容易做到同步,只要在方法上加“synchronized“。
- 将 Runnable 对象作为一个类的内部类,共享数据作为这个类的成员变量,每个线程对共享数据的操作方法也封装在外部类,以便实现对数据的各个操作的同步和互斥,作为内部类的各个 Runnable 对象调用外部类的这些方法。
线程池
1-为什么用线程池,解释下线程池参数
线程池本质上是一种池化技术,而池化技术是一种资源复用的思想,为了减少线程频繁创建和销毁带来的性能开销,因为线程创建会涉及到CPU上下文切换、内存分配等工作。线程池本身会有参数来控制线程创建的数量,这样就可以避免无休止的创建线程带来的资源利用率过高的问题,起到了资源保护的作用。
线程池参数七大参数
- corePoolsize 核心线程数:正常情况下创建的工作的线程数,这些线程创建后并不会立马消除,一种常驻住线程
- maxinumPoolSize 最大线程数:表示允许创建的最大线程数
- keepAliveTime 表示超出线程数之外的线程数空闲存活时间
- unit keepAliveTime 的计量单位
- workQueue:用来存放待执行任务的队列
- threadFactory:创建一个线程工厂用来生产线程,可以用来设定线程名
- handler:任务拒绝策略
2-Java线程池的工作流程
- 线程池判断核心线程池里的核心线程是否都在执行任务。 如果不是,让空闲的核心线程来执行任务。如果核心线程池里的线程都在执行任务,则进入下个流程。
- 线程池判断阻塞队列是否已满。 如果阻塞队列没有满,则将新提交的任务存储在阻塞队列中。如果阻塞队列已满,则进入下个流程。
- 判断线程池里的线程数量是否小于最大线程数量(看线程池是否满了)。 如果小于,则创建一个新的工作线程(非核心线程,并给它设置超时时间,当我们处理完这些任务,无需手动销毁这个非核心线程,超时自动销毁)来执行任务。如果已满,则交给拒绝策略来处理这个任务。
3-线程池都有哪些状态?
- RUNNING:这是最正常的状态,接受新的任务,处理等待队列中的任务。
- SHUTDOWN:不接受新的任务提交,但是会继续处理等待队列中的任务。
- STOP:不接受新的任务提交,不再处理等待队列中的任务,中断正在执行任务的线程。
- TIDYING:所有的任务都销毁了,workCount 为 0,线程池的状态在转换为 TIDYING 状态时,会执行钩子方法 terminated()。
- TERMINATED:terminated()方法结束后,线程池的状态就会变成这个
4-Java线程池如何合理配置核心线程数?如何去设置呢?
- CPU 密集型任务:比如像加解密,压缩、计算等一系列需要大量耗费 CPU 资源的任务,大部分场景下都是纯 CPU 计算。因此,对于 CPU 密集型的计算场景,理论上线程的数量 = CPU 核数就是最合适的,不过通常把线程的数量设置为CPU 核数 +1,会实现最优的利用率。
- IO 密集型任务:比如像 MySQL 数据库、文件读写、网络通信等任务,这类任务不会特别消耗 CPU 资源,但是 IO 操作比较耗时,会占用比较多时间。线程数 = CPU 核心数 * (1 + IO 耗时/ CPU 耗时) 或是 IO密集型:核心线程数 = CPU核数 * 2
2.3 集合原理(hashmap,list)-见参考
集合框架
1-Java List总结
1)ArrayList
优点: 底层数据结构是数组,查询快,增删慢。
缺点: 线程不安全,效率高
2)Vector
优点: 底层数据结构是数组,查询快,增删慢。
缺点: 线程安全,效率低
3)LinkedList
优点: 底层数据结构是链表,查询慢,增删快。
缺点: 线程不安全,效率高
2-Java Set总结
1)HashSet
底层其实是包装了一个HashMap实现的
底层数据结构是数组+链表 + 红黑树
具有比较好的读取和查找性能, 可以有null 值
通过equals和HashCode来判断两个元素是否相等
非线程安全
2)LinkedHashSet
继承HashSet,本质是LinkedHashMap实现
底层数据结构由哈希表(是一个元素为链表的数组)和双向链表组成。
有序的,根据HashCode的值来决定元素的存储位置,同时使用一个链表来维护元素的插入顺序
非线程安全,可以有null 值
3)TreeSet
是一种排序的Set集合,实现了SortedSet接口,底层是用TreeMap实现的,本质上是一个红黑树原理
排序分两种:自然排序(存储元素实现Comparable接口)和定制排序(创建TreeSet时,传递一个自己实现的Comparator对象)
正常情况下不能有null值,可以重写Comparable接口 局可以有null值了。
3-Map总结
2.4 java虚引用-见参考
虚引用需要java.lang.ref.PhantomReference类来实现,虚引用顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会觉定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收,它不能单独使用也不能通过它访问对象,虚引用必须和引用队列(ReferenceQueue)联合使用。
虚引用的主要作用是跟踪对象被垃圾回收的状态,仅仅是提供了一种确保对象被finalize以后,做某些事情的机制
2.5 封装、继承、多态的理解-见参考
1-封装
- 实例讲解什么是封装
- 封装的意义
- 封装原则
- 封装的访问级别
2-继承
- 继承作用:提高了软件复用的效率,缩短了软件开发的周期
- 继承方式:公有继承、私有继承、保护继承
3-多态
- 多态的三个条件
- 编译时多态
- 多态的实现方式:接口多态性、继承多态性、通过抽象类实现的多态性
- 多态的好处:可替换性、可扩充性、灵活性简化性
2.6 activity生命周期
1-activity的生命周期
2-ActivityA 跳转 ActivityB 然后 B 按 back 返回 A,各自的生命周期顺序,A 与 B 均不透明
ActivityA 跳转到 ActivityB
Activity A:onPause
Activity B:onCreate
Activity B:onStart
Activity B:onResume
Activity A:onSto
ActivityB 返回 ActivityA
Activity B:onPause
Activity A:onRestart
Activity A:onStart
Activity A:onResume
Activity B:onStop
Activity B:onDestro
2.7 安卓activity和fragment数据传递-见参考
1-Activity向Fragment传递数据—使用Bundle
- Bundle用来存放数据集
- setArguments()方法将数据集绑定到Fragment中。
2-Fragment向Activity传送数据
- 方法一:使用java接口(观察者模式)
- 方法二:使用第三方组件:eventbus,liveData等
3-实现Fragment之间互传数据
Fragment之间无法互传数据,所以需要一个Activity作为中间桥梁辅助两个Fragment之间的数据传输
2.8 Handler-见参考
相关概念
2.9 内存泄漏、内存溢出、内存抖动 原因及解决办法-见参考
1-内存溢出
原因:
1.内存中加载的数据量过于庞大,如一次从数据库取出过多数据;
2.集合类中有对对象的引用,使用完后未清空,使得JVM不能回收;
3.代码中存在死循环或循环产生过多重复的对象实体;
4.使用的第三方软件中的BUG;
5.启动参数内存值设定的过小
解决方案:
第一步,修改JVM启动参数,直接增加内存。(-Xms,-Xmx参数一定不要忘记加。)
第二步,检查错误日志,查看“OutOfMemory”错误前是否有其它异常或错误。
第三步,对代码进行走查和分析,找出可能发生内存溢出的位置。
2-内存泄漏
引起内存泄漏的场景:
- 资源对象没关闭造成的内存泄漏,如查询数据库后没有关闭游标cursor
- 构造Adapter时,没有使用 convertView 重用
- 对象被生命周期长的对象引用,如activity被静态集合引用导致activity不能释放
- Bitmap对象不再使用时,没有调用recycle()释放内存(?存在疑问)
如何区分:
- 内存泄露, 观察 momory monitor 出现,内存不断增加 然后降低.
- 工具使用:通过 heap viewer 查看
3-内存抖动
原因:内存抖动出现原因主要是频繁(很重要)在循环里创建对象,导致大量对象在短时间内被创建
如何区分:通过momory monitor 发现 出现内存忽上忽下 形成针尖状的情况
2.10 ANR原因以及解决办法
1-什么是ANR
ANR(Application Not Responding)即应用程序无响应
2-ANR一共有四种类型
- 输入事件类型ANR
- 广播类型ANR
- ContentProvider类型ANR
- Service类型ANR
3-如何避免ANR
- 耗时的工作()比如数据库操作,I/O,网络操作),采用单独的工作线程处理
- 用Handler来处理UIthread和工作thread的交互
- 合理使用 Handler 来处理其他线程请求;
- 合理使用并遵循 Android 生命周期, 避免在 onCreate() and onResume() 做过多的事情;
- 使用一些架构形成规范来避免内存等问题,例如:MVP、RxJava;
- 经常使用工具来检查内存问题,例如:MAT、TraceView、AS 自带等工具;
- 避免加载大图片引起内存不足导致 ANR;
- 避免内存泄露引起的 ANR。
2.11 性能优化、卡顿优化-见参考
1-为什么会卡顿
- 读写文件
- 解析大量图片
- 频繁请求网络
- 复杂的布局
- 频繁创建对象
2-如何检测卡顿
- Systrace:Systrace它是轻量级的框架,而且开销小,可以直观反映CPU的利用率而且右侧alter可以针对一些问题给出相关的建议。 比如绘制慢或者GC频繁等
- StrictMode:Android2.3引入的一个工具类:严苛模式。是一种运行时检测机制。可以帮助开发人员检测代码当中不规范的问题。StrictMode主要检测线程策略和虚拟机策略。
3-如何优化卡顿
- 文件读写
- 大量图片解析
- 频繁请求网络
- 复杂布局
2.12 事件分发机制
三 参考
- java中多线程常见面试题_李大寶的博客-CSDN博客_多线程面试题
- Java集合框架最全详解(看这篇就够了)
- JAVA虚引用介绍
- 封装、继承、多态 详解
- Activity与Fragment的通信
- Android——Handler详解
- 内存溢出,内存泄漏,内存抖动
- Android 内存泄漏、内存抖动和内存溢出
- Android ANR分析实践(一):ANR是什么、产生的原因及如何避免ANR
- ANR系列之ContentProvider类型原理讲解
- Android性能优化–卡顿优化与布局优化