裸辞-疫情-闭关-复习-大厂offer(一)(下)

简介: 裸辞-疫情-闭关-复习-大厂offer(一)

类构造顺序


  • 先父亲,再孩子


  • 先静态再非静态


  • 先字段,后构造器(字段先后有定义顺序决定)


  • 先代码块 后构造方法


HashMap


  • 存储结构是开散列表:地址向量+同义词子表=数组+单链表。


  • 解决哈希冲突的办法是拉链法:将相同散列地址的键值存放在同义词子表中。


  • capacity为啥要为2的幂次,是为了用位与运算代替取模运算,提高性能。


  • 为啥loadFactor 是0.75,因为中庸,若为1,则频繁冲突,若为更小值,则会频繁扩容。


  • 构造~时,并没有初始化地址向量,而是要等到put操作是才构造


  • 遍历HashMap的顺序是从地址向量的第一个开始,先从前到后遍历同义词子表,然后下一个同义词子表


  • HashMap通过hash算法先定位到地址向量中对应的位置,然后遍历同义词子表


  • HashMap不是线程安全的,当~扩容的时候要进行迁移,多线程并发put会导致迁移出环。建议使用Hashtable或者ConcurrentHashMap。Hashtable将put和get方法都加上了synchronized,性能较差


WeakHashMap


  • 用于存放键值对,当发生gc时,其中的键值对可能被回收。适用于对内存敏感的缓存
  • 存放键值对的Entry继承自WeakReference。当发生gc时,Entry被回收并加入到ReferenceQueue中


  • 访问~时,会将已经gc的键值对从中删除(通过遍历ReferenceQueue)


LinkedHashMap


  • 是一个有序 map,可以按插入顺序或者访问顺序排列


  • 在 hashMap 基础上增加了头尾指针形成双向链表,继承 Node 添加前后结点的指针,每次构建结点时会将他链接到链尾。


  • 若是按访问顺序排序,存取键值对的时候会将其拆下插入到链尾,链头是最老的结点,满时会被移出


  • 按访问顺序来排序是LRU缓存的一种实现。


ThreadLocal


  • 用于将对象和当前线程绑定(将对象存储在当前线程的ThreadLocalMap结构中)
  • ThreadLocalMap是一个类似HashMap的存储结构,键是ThreadLocal对象的弱引用,值是要保存的对象


  • set()方法会获取当前线程的ThreadLocalMap对象


  • threadLocal内存泄漏:key是弱引用,gc后被回收,value 被entry持有,再被ThreadLocalMap持有,再被线程持有,如果线程没有结束,则value无法访问到,也无法回收,方案是及时remove掉不用的value


  • threadlocal 会自动清理key为null 的entry


内存泄漏


内存泄漏是因为堆内存无法释放 android内存泄漏就是生命周期长的对象持有了生命周期较短对象的引用


  1. 静态成员变量(单例)


  • 静态成员变量的生命周期和整个app一样,如果它持有短生命周期的对象则会导致这些对象内存泄露


  • 静态变量在不使用时需要置空


  • 静态变量使用弱引用持有Activity


  • 单例持有App context而不是Activity Contex


  • 静态方法可以被子类隐藏,而不是重写


  1. 非静态内部类


  • 匿名内部类持有外部类引用


  • handler是典型的匿名内部类,handler中的消息持有handler引用,如果有未处理完的消息,则会导致handler外层类内存泄露,Looper -> MessageQueue -> Message -> Handler -> Activity,解决办法是静态内部类+Activity弱引用,并且在activity退出时清除所有消息


  • new Thread()是典型的匿名内部类,如果Activity退出后Thread还在执行则会引起Activity内存泄露


  1. 集合类


  • 集合对象会持有孩子的引用,需要及时清除且置空


  1. webview内存泄露


  • WebView在加载网页后会长期占用内存而不能被释放,因此我们在Activity销毁后要调用它的destory()方法来销毁它以释放内存


引用


  1. 强引用


  • 通过=显式的将对象A赋值给变量a,则A就存在一个强引用a


  • 强引用需要显式的置null 以告诉gc该对象可以被回收


  • 在一个方法的内部有一个强引用,这个引用保存在栈中,而真正的引用内容(Object)保存在堆中。当这个方法运行完成后就会退出方法栈,则引用内容的引用不存在,这个Object会被回收。但是如果这个object是全局的变量时,就需要在不用这个对象时赋值为null,因为强引用不会被垃圾回收


  • 清空list时需要遍历所有元素将其置null


  1. 软引用


  • 如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。


  1. 弱引用


  • 弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。


  1. 虚引用


  • 虚引用主要用来跟踪对象被垃圾回收器回收的活动,当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之 关联的引用队列中。用于在对象被回收时做一些事情


软引用、弱引用、虚引用的构造方法均可以传入一个ReferenceQueue与之关联。在引用所指的对象被回收后,引用(reference)本身将会被加入到ReferenceQueue之中,此时引用所引用的对象reference.get()已被回收 (reference此时不为null,reference.get()此时为null)。在一个非强引用所引用的对象回收时,如果引用reference没有被加入到被关联的ReferenceQueue中,则表示还有引用所引用的对象还没有被回收。如果判断一个对象的非强引用本该出现在ReferenceQueue中,实际上却没有出现,则表示该对象发生内存泄漏。


接口和抽象类


  • 类可以实现很多个接口,但是只能继承一个抽象类


  • 类如果要实现一个接口,它必须要实现接口声明的所有方法。但是,类可以不实现抽象类声明的所有方法,当然,在这种情况下,类也必须得声明成是抽象的。


字符串常量池


  • JVM为了减少字符串对象的重复创建,其维护了一个特殊的内存,这段内存被成为字符串常量池


  • 字符串常量池实现的前提条件是java中的String对象是不可变的,否则多个引用指向同一个变量的时候并改变了String对象,就会发生错乱


  • 字符串常量池是用时间换空间,cpu需要在常量池中寻找是否有相同字符串


  • 字符串构造方式


  1. 字面量形式:String str = "droid"  使用这种形式创建字符串时,JVM首先会对这个字面量进行检查,如果字符串常量池中存在相同内容的字符串对象的引用,则将这个引用返回,否则新的字符串对象被创建,然后将这个引用放入字符串常量池,并返回该引用


  1. 新建对象形式:String str = new String("droid"); 使用这种形式创建字符串时,不管字符串常量池中是否有相同内容,新的字符串总是会被创建。 对于上面使用new创建的字符串对象,如果想将这个对象的引用加入到字符串常量池,可以使用intern方法。调用intern后,首先检查字符串常量池中是否有该对象的引用,如果存在,则将这个引用返回给变量,否则将引用加入并返回给变量。String str4 = str3.intern();


异常


  • Exception和Error都继承于Throwable


  • Exception是程序错误


  • Exception又分为checked Exception(编译时异常)和unchecked Exception(运行时)。checked Exception在代码里必须显式的进行捕获,这是编译器检查的一部分。unchecked Exception也就是运行时异常,类似空指针异常、数组越界等,通常是可以避免的逻辑错误


  • Error是比程序更加低层的错误, 包括虚拟机错误OutOfMemoryError,StackOverFlowError


注解


注解为代码添加一些额外的信息,以便稍后可以读取这些信息。这些信息可以帮助代码检查,编译时生成代码以减少模板代码


  • 元注解


  1. @Retention:定义注解生命周期


  • RetentionPoicy.SOURCE注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃;用于做一些检查性的操作,比如 @Override


  • RetentionPoicy.CLASS:注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期;用于在编译时进行一些预处理操作,比如生成一些辅助代码(编译时注解即是编写生成代码的代码),ButterKnife 使用编译时注解,即在编译时通过自定义注释解析器AbstractProcessor读取注解并由此生成java文件(在里面调用了 findViewById)


  • RetentionPoicy.RUNTIME:注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在;用于在运行时去动态获取注解信息


  1. @Target:定义了Annotation所修饰的对象范围


内存模型


dalvik虚拟机内存空间被划分成多个区域 = 虚拟机栈+ 程序计数器+ 方法区+ 堆+ 本地方法栈


  • 方法区:方法区主要是存储已经被 JVM 加载的类信息(版本、字段、方法、接口)、常量、静态变量、即时编译器编译后的代码和数据。该区域被各个线程共享的内存区域。


  • 堆区:又称动态内存分配,存放所有用通过new创建的类对象(包括该对象其中的所有成员变量),也就是对象的实例。这部分内存在不使用时将会由 Java 垃圾回收器来负责回收


  • 堆内存没有可用的空间存储生成的对象,JVM会抛出java.lang.OutOfMemoryError


  • 堆内存分为新生代和老年代和永生代,新生代又分为Eden、From Survivor、To Survivor三个区域


  • 永生代用于存放class信息


  • 虚拟机栈 :虚拟机栈是线程私有的数据结构,它用来描述 Java 方法执行的内存模型,每个方法被执行的时候,JVM 都会在虚拟机栈中创建一个栈帧


  • 栈帧(Stack Frame)


  • 一个线程包含多个栈帧,而每个栈帧内部包含局部变量表、操作数栈、动态连接、返回地址等


  • 局部变量表是变量值的存储空间,我们调用方法时传递的参数,以及在方法内部创建的局部变量都保存在局部变量表中。在 Java 编译成 class 文件的时候,就会在方法的 Code 属性表中的 max_locals 数据项中,确定该方法需要分配的最大局部变量表的容量。


  • 在方法退出后都需要返回到方法被调用的位置,程序才能继续执行。而虚拟机栈中的“返回地址”就是用来帮助当前方法恢复它的上层方法执行状态。


  • 如果栈内存没有可用的空间存储方法调用和局部变量,JVM会抛出java.lang.StackOverFlowError


  • 本地方法栈和虚拟机栈类似,只不过用于执行native方法


  • 程序计数器:每个线程都需要一个程序计数器,用于记录正在执行指令的地址


GC


  • 垃圾定义:有两种定义垃圾的方法


  1. 引用计数:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;计数器为0的对象就是垃圾


  1. 可到达性:从GC Roots作为起点,向下搜索它们引用的对象,可以生成一棵引用树,树的节点视为可达对象,反之视为不可达。不可到达的对象是垃圾,被定义为垃圾的对象不代表马上会被回收,还会去检查是否要执行finalize方法


  • GC种类:Minor GC、Full GC ( 或称为 Major GC )


  • 垃圾回收回收不可到达的对象,即没有引用的对象,可到达的对象一定被根引用
  • Minor GC 是发生在新生代中的垃圾收集动作,所采用的是copy and sweep(经过6次gc还存活的对象会被放到老年代)


  • Full GC 是发生在老年代的垃圾收集动作,所采用的是mark and sweep


  • 分代回收(generational collection):每个对象记录有它的世代(generation)信息。所谓的世代,是指该对象所经历的垃圾回收的次数。世代越久远的对象,在内存中存活的时间越久


  • GC回收算法


  • copy and sweep:内存被分为两个区域。对象总存活于两个区域中的一个。当垃圾回收启动时,Java程序暂停运行。JVM从根出发,找到可到达对象,将可到达对象复制到空白区域中并紧密排列,修改由于对象移动所造成的引用地址的变化。最后,直接清空对象原先存活的整个区域,使其成为新的空白区域。适用于存活对象少,垃圾对象多的场景


  • mark and sweep:每个对象将有标记信息,用于表示该对象是否可到达。当垃圾回收启动时,Java程序暂停运行。JVM从根出发,找到所有的可到达对象,并标记(mark)。随后,JVM需要扫描整个堆,找到剩余的对象,并清空这些对象所占据的内存堆,缺点是容易产生内存碎片。适用于存活对象多,垃圾对象少的场景


  • 分代回收算法:老年代每次gc只有少量对象被回收,而新生代有大量对象被回收,对于新生代采用copy and sweep,对老年代采用mark and sweep。


OOM类型


  1. 堆内存不足


  1. 无足够的连续内存空间


  1. 文件描述符超过数量限制


  1. 线程数量超过限制


  1. 虚拟内存不足


内存优化


  1. 使用内存友好的数据结构 SpareseArray,ArrayMap


  1. 避免内存泄漏,避免长生命周期对象持有短生命周期对象


  1. 使用池结构,复用对象避免内存抖动。


  1. 根据手机内存大小,设置内存缓存的大小。


  1. 多进程,扩大可使用内存。


  1. 通过ComponentCallback2 监听内存吃紧,进行内存缓存的释放。


LeakCanary


  • 通过ActivityLifecycleCallbacks监听Activity生命周期,在onActivityDestroy时获取Activity实例,并为其构建弱引用并关联引用队列。


  • 起异步线程,观察ReferenceQueue是否有Activity的弱引用,如果有则说明回收成功,否则回收失败


  • 回收失败后会手动触发一次gc,再监听ReferenceQueue,如果还是回收失败,则dump内存


  • LeakCanary 通过contentProvider安装


  • 当一个Activity的onDestory方法被执行后,说明该Activity的生命周期已经走完,在下次GC发生时,该Activity对象应将被回收


equals()


  • equals() 定义在JDK的Object.java中。可以定义两个对象是否相等的逻辑


  • "=="相等判断符用于比较基本数据类型和引用类型数据。 当比较基本数据类型的时候比较的是数值,当比较引用类型数据时比较的是引用(指针)即指向堆内存的地址


  • ==的语义是固定的,而equals()的语义是自定义的


hashCode()


  • hashCode() 的作用是获取哈希码,它实际上是返回一个int整数。仅仅当创建并某个“类的散列表”(关于“散列表”见下面说明)时,该类的hashCode() 才有用,作用是:确定该类的每一个对象在散列表中的位置,Java集合中本质是散列表的类,如HashMap,Hashtable,HashSet


  • HashMap 如果使用equals判断key是否重复,需要逐个比较,时间复杂度为O(n),但如果使用hashCode(),因为它是一个int值。所以可以直接作为数组结构的某个索引值,如果该索引位置没有内容则表示key没有重复,复杂度为O(1)


  • 如果两个对象相等,那么它们的hashCode()值一定相同。这里的相等是指,通过equals()比较两个对象时返回true。


  • 如果两个对象hashCode()相等,它们并不一定相等。因为在散列表中,hashCode()相等,即两个键值对的哈希值相等。然而哈希值相等,并不一定能得出键值对相等。补充说一句:“两个不同的键值对,哈希值相等”,这就是哈希冲突。


sealed class


  • 是一个继承结构固定的抽象类,即在编译时已经确定了子类数量,不能在运行时动态新增


  • 它的子类只能声明在同一个包名下


  • 是一个抽象类,且构造方法是私有的,它的孩子是final类,而且孩子声明必须嵌套在sealed class内部。


  • 枚举的局限性 限制枚举每个类型只允许有一个实例 限制所有枚举常量使用相同的类型的值


crossinline


在具有inline 特性的同时,避免非局部返回,因为直接return掉函数会影响原有功能,crossinline的lambda内部必须使用局部返回,比如return@foo


sequence


  • ~是惰性的:中间操作不会被执行,只有终端操作才会(toList())


  • ~的计算顺序和迭代器不同:~是对一个元素应用全部的操作,然后第二个元素应用全部操作,而迭代器是对列表所有元素应用第一个操作,然后对列表所有元素应用第二个操作


虚拟内存


  • 每个应用访问的地址空间是虚拟地址空间,所以可以无穷大,Linux负责将虚拟内存转换为物理地址。


  • 为了方便将虚拟内存地址和物理地址进行映射,内存空间被分割成若干个页(通常是4kb大小)


  • Memory Management Unit(MMU)这个硬件专门用于将虚拟地址转换为物理地址。它通过查询映射表得到物理地址


  • 虚拟地址分为高4位的页号,和后面的偏移量,每次通过页号查询映射表得到物理地址的页号,然后再将偏移量拼在后面得到物理地址


kotlin 空安全


  • 编译成java后是通过if判空实现空安全的


  • kotlin和java交互的时候空安全被破坏,可以通过在java代码添加@NotNull注解进行非空约束


Channel


  • 是一个挂起队列,和java 中 blocking queue 类似


  • 生产者叫 SendChannel,消费者叫 ReceiveChannel


  • 生产者和消费者之间有一条缓冲通道


  • 执行多线程同时生产,多线程同时消费


  • flow 只有订阅才生产数据,Channel 发送数据和订阅无关,所以是热流


协程


  • 是建立在线程之上的,更轻量级的(用户态),更容易控制生命周期(结构化并发)的计算单元。


  • 借助于suspend方法实现用户态非抢占式的并发调度,协程通过挂起主动让出执行权(普通的线程映射为内核线程,内核线程的调度是抢占cpu时间片)


  • 比使用线程池更容易取消异步操作,享受结构化并发,异常处理


  • 挂起方法并不会挂起线程,因为就像调用一个带回调的方法一样,它挂起的是协程剩下的代码。


结构化并发


java 线程间的并发是没有级联关系的,所以是非结构的


  1. 结束一个线程时,怎么同时结束这个线程中创建的子线程?


  1. 当某个子线程在执行时需要结束兄弟线程要做怎么做?


  1. 如何等待所有子线程都执行完了再结束父线程? 这些问题都可以通过共享标记位、CountDownLatch 等方式实现。但这两个例子让我们意识到,线程间没有级联关系;所有线程执行的上下文都是整个进程,多个线程的并发是相对整个进程的,而不是相对某一个父线程。


CPS


  • Continuation Passing Style,传递剩余的计算,将剩余的计算作为一个回调传递给方法。


suspend


  • cps+状态机: 每个suspend 方法都构建一个continuation不经济,一个协程块中的suspend方法会共用一个 continuation(持有一个label)。将原先不同的continuation写在了不同的 switch case 分支内,以挂起点为分割点。每执行一个分支点,label 就+1,表示进入下一个分支。挂起方法会被多次调用(invokeSuspend),因为label值不同每次都会走不同的分支
  • suspend 的返回值标志着挂起方法有没有被挂起


Dispatcher


  • 调度器CoroutineDispatcher是一个ContinuationInterceptor。通过interceptContinuation()将continuation包装成DispatchedContinuation
  • 不同的调度器通过重写 dispatch方法实现不同的线程调度。有些是通过handler抛一个runnable,有些是向线程池抛一个


  • default 属于cpu运算密集型:线程被阻塞时,cpu是在忙着运算


  • io 属于io型:线程被阻塞时,cpu是闲着。


Job


  • 可以被取消的任务


  • 他有六种状态:new-active-completing-completed-cancelling-canceled


  • 只有延迟启动的协程的job才会处于new 状态,其他都处于active状态,completing意味着自己的活干完了,在等子协程。 cancelling 是在取消协程之前最后的清理资源的机会。


  • 新建的协程会继承父亲的CoroutineContext,除了其中的job,新协程会新建job并成为父job的子job


背压


  • 生产速度大于消费速度


  • 使用缓冲区,阻塞队列


  • 缓冲区大小 & 缓冲区满之后的策略(丢弃最新,最久,挂起)


异常处理


  • 在 coroutineScope中,异常是向上传播的,只要任意一个子协程发生异常,整个scope都会执行失败,并且其余的所有子协程都会被取消掉;


  • 在 supervisorScope中,异常是向下传播的,一个子协程的异常不会影响整个 scope的执行,也不会影响其余子协程的执行;(重写了childCancelled并返回false) CancellationException 异常总是被忽略


取消协程


  • 每个启动的协程都会返回一个job,调用 job.cancel()会让job处于canceling状态,然后在下一个挂起点抛出CancellationException ,如果协程中没有挂起点,则协程不能被取消。因为每个suspend 方法都会检查job是否活跃,若不活跃则抛出CancellationException ,这个异常只是给上层一次关闭资源的机会,可以通过try-catch 捕获


  • 对于没有挂起的协程,需要通过while(isActive)来检查job是否被取消 或者 yield()
  • 当协程抛出CancellationException 后,在启动协程将会被忽略
目录
相关文章
|
10月前
|
存储 搜索推荐 Linux
秋招简历项目这样写,offer拿到手软(C++方向)
秋招简历项目这样写,offer拿到手软(C++方向)
|
4月前
|
Dubbo NoSQL Java
太为难我了,阿里面试了7轮(5年经验,拿下P7岗offer)
今年的大环境非常差,互联网企业裁员的现象比往年更严重了,可今年刚好是我的第一个“五年计划”截止的时间点,说什么也不能够耽搁了,所以早早准备的跳槽也在疫情好转之后开始进行了。但是,不得不说,这次阿里面试真的太难为我了,可以说是和面试官大战了7个回合,不过好在最后给了offer。
|
4月前
|
消息中间件 缓存 NoSQL
记一次蚂蚁金服四面遭虐,面试水太深,过河的渡船你造好了吗?
有道无术,术可成;有术无道,止于道;以术识道,以道御术
|
4月前
|
设计模式 算法 NoSQL
Java开发三年四面字节跳动复习一个月斩获offer,寒冬并不可怕
目前互联网行业形势越来越严峻,我接连投递了很多的简历,得到的回复却是寥寥无几,索性好好复习了大概一个半月的样子,挑战字节跳动成功!!接下来分享我在字节面试遇到的面试题,欢迎大家文末留言与我一起讨论!
|
4月前
|
消息中间件 Dubbo Java
疫情下的机遇,阿里直招怒斩"P7"offer,自曝狂啃六遍的面试笔记
工作肯定会找的,面试肯定要过的,小编在这里为大家整理了我的一位朋友,一位从中游公司跳槽到阿里P7的面试题库
|
4月前
|
算法 NoSQL Java
“北头条,南BIGO”,BIGO社招Java三面面经分享 怒斩心动offer
我了解到的是:BIGO给予员工机会去发展和创造。在大厂里较大概率会一直接需求做需求,重复性的更新和迭代,在这里有更多机会可以经历从0到1,能发挥自己的能力进行创造。并且在同批次的offer中,BIGO的薪资最有竞争力。
|
存储 算法 NoSQL
膜拜!砍下13个大厂Offer神仙案例! | 彭文华
膜拜!砍下13个大厂Offer神仙案例! | 彭文华
第三期:那些年,我们一起经历过的链表中的浪漫
第三期:那些年,我们一起经历过的链表中的浪漫
68 0
|
存储 缓存 编解码
裸辞-疫情-闭关-复习-大厂offer(二)(下)
裸辞-疫情-闭关-复习-大厂offer(二)
91 0
|
存储 缓存 编解码
裸辞-疫情-闭关-复习-大厂offer(一)(中)
裸辞-疫情-闭关-复习-大厂offer(一)
80 0