安卓入门一 Java基础

打印 上一主题 下一主题

主题 894|帖子 894|积分 2682

一、团队效率提拔


  • 版本更新
Android增量更新与热修复
增量更新的目的是为了减少更新app所必要下载的包体积大小,常见如手机端游戏,apk包体积为几百M,但偶然更新只需下载十几M的安装包即可完成更新。
增量更新demo地址:https://github.com/po1arbear/bsdiff-android
热修复一般是用于当已经发布的app有Bug必要修复的时间,开发者修改代码并发布补丁,让应用能够在不必要重新安装的情况下实现更新,主流方案有Tinker、AndFix等。
管理软件更新
1.天然升级 2.引导升级
进度感知(用户可以看见更新进度),提示链路(导航栏打开更新弹窗)。
提示更新计谋原则 不阻塞行为操纵
2023年工信部发布了《关于进一步提拔移动互联网应用服务本领》的通知,此中第四条更是明确表示“窗口关闭用户可选”,以是强更弹窗并不是我们的最佳选择。
重要字段
标题
内容
最新版本号
取消倒计时时长
是否提示
是否强更
双端最低支持的体系版本
最大提示次数
未更新最大版本隔断等等
版本治理



  • 编译优化

  • 编译流程(必要相识)
  • 全量编译/增量编译
  • 有效方案:

  • 升级硬件
  • Moudle arr化
  • 构建缓存

  • 其他方案(作用不大)
1.使用最新    版本工具2.Debug环境只运行编译必要的资源3.版本将图片转为WebP4.格式停用PNG5.开启gradle缓存6.开启kotlin增量和并行编译7.使用静态依赖项版本8.合理调整堆大小9.kapt优化10.使用增量注解处理器

  • 检测编译耗时
Build Analyaer模块检察
Gradle命令
$ gradlew app:assemble --profile (Windows)

$ ./gradlew app:assembleDebug --profile (Ubuntu,Mac)

scan命令,扫描后在网址中打开检察性能(推荐使用)
$ gradlew app:assemble --scan(Windows)

$ ./gradlew app:assembleDebug --scan(Ubuntu,Mac)

Android studio 配置修改:https://juejin.cn/post/7094198918065422350

Moudle arr开源方案参考:https://juejin.cn/post/7038157787976695815,这个插件可以直接引用到项目中。
只编译文件变更位置,两种实现方式,通过git上传记录大概本地window变更文件找到必要重新编译的位置。

  • 内存优化
1.内存概念
捏造内存(用户空间 内核空间)
捏造内存是盘算机体系内有管理的一种技术。它使得应用程序以为它拥有连续的可用的内存(一个连续完整的地址空间),而实际上,它通常是被分限成多个物理内存碎片,还有部分暂时存储在外部总盘存健客上,在必要时进行数据互换。

Java堆:堆实际上是一块匿名共享内存,Android捏造机仅仅只是把它封装成一个mSpace。由底层C库管理,仍然可以使用libc提供的函数malloc和free分配和释放内存。
捏造机:早期Dalvik,如今ART。不同点:lmage Space存放一些预加载类,在Zygote进程和应用程序进程之间共享Large Object Space离散地址的聚集,分配一些大对象,用于提高GC的管理效率和整体性能
Native堆
2.内存优化
目的
1.降低OOM,抖动,泄漏       提拔稳定性
2.减少内存占用                       接高应用背景运行时的存活率
3.减少卡顿                           提高流畅性
背景
Java GC接纳:1.判斯对象是否接纳的可达性分析算法2.强软弱虚4种引用范例3.GC接纳算法
内存泄漏:内存泄漏指的是一块内存没有被使用且无法被GC接纳
内存抖动:内存抖动意味着短时间频仍创建对象与接纳,容易触发GC而当GC时全部线程都会制止,因此大概导致卡顿
内存溢出:申请的内存超出可用的内存,即OOM 


  • Java堆内存超限1堆内存单次分配过大2多次分配累计过大3内存累计分配触顶4无充足连续内存空间
  • 文件形貌符(fd)数目超限
  • pthread create 1线程数超限 2捏造空间不敷
内存优化工具
Android Profiler、MAT推荐、VisualVM
内存监控工具
线下LeakCanary、线上Matrix、Koom
内存优化常规


  • 合适的代码设计,数据布局
  • 对象复用,SimplePool与SynchronizedPool
  • 资源,复用,序列化Parcelable 
  • 避免内存泄漏,抖动,溢出... 图片
图片优化


  • BitMap内存演变
  • 图片优化
同一图片库使用glide coil 低端机使用Fresco
设备分极优化计谋 比如低端机单进程


  • 大图监控 Glide--SingleRequest--onResourceReady、Epic
  • 重复图片检测 
重复图片检测的原理:使用内存Hprof分析工具,自动将重复Bitmap的图片和引用堆找输出。


  • 创建全局的线上Btmap监控  ASM编译插桩-Gradle Transform
内存泄漏
已动态分配的堆内存由于未知缘故原由未释放或无法释放 


  • 永世性泄漏 泄漏的内存永远不会接纳
  • 临时性泄漏 泄漏的内存未来某些时间大概被接纳
泄漏场景


  • 资源未关闭      finish关闭
  • 监听器     成对出现
  • 体系Bug  InputMethodManager mCurRootView/AccountManager使用不妥导致的泄漏等
  • 聚集泄漏  自动clear null
  • webview泄漏  ondestory
  • 第三方库泄漏  
  • 线程
Thread   不持有
HandlerThread
1.onDestroy方法中调用HandlerThread的quit方法2.将HandlerThread定义为静态内部类3.使用ApplicationContext


  • Handler
1 onDestroy实行时,调用Handler的removeCallbacksAndMessage,Runnable(Thread)泄漏则可通过终止线程
2 将Handler,Runnable(Thread)定义为静态内部类
3 使用弱引用,大概使用ApplicationContext
4 耗时操纵jobschedule


  • Static
Activity
找到static变量泄漏的Activity的机遇将其引用链剪断
将static变量复兴为非static变量,使其可以正常GC
如果确实必要context.必要保持static.则可使用Application Context


  • View
通过设计改变static为普通变量 不要在Android中时用static修饰View
onDestroy时将static view 置为null


  • 内部类持有外部类引用
非静态内部类   静态内部类  匿名内部类  尽量不要使用
内存溢出
00M 缘故原由


  • 进程捏造内存不敷
  • Java堆内存不敷             一般是内存泄缩大概内存使用不合理导致
  • 连续内存不敷                 相对较少
  • FD数量超过体系限定
  • 线程数量超过体系限刚
  • 手机物理内存不敷
Native内存和内存监控
内存指标监控
二、Java基础



1.1 并发和并行、同步和异步



  • 并发:同一时间段,多个任务交替实行,时间片轮法共享体系资源(Synchronized)
  • 并行:单元时间内,多个任务在多个独立处理单元上同时实行(分布式锁)
  • 同步:任务依次实行,任务必要依次实行(阻塞)
  • 异步:可以同时进行多个任务,任务的调用和完成是独立的(非阻塞)
1.2 并发编程三大特性

可见性:是指一个线程对共享变量进行修改,另一个线程立即得到修改后的最新值。
原子性:在一次或多次操纵中,要么全部的操纵都实行并且不会受其他因素干扰而中断,要么全部的操纵都不实行。
有序性:办理了重排序。重排序:Java在编译时和运行时会对代码进行优化,会导致程序最终的实行顺序不一定就是我们编写代码时的顺序。
锁搞定了多线程中的重排序,单线程中怎么办理重排序?使用as-if-serial--语句
1.3 锁范例



    • 线程要不要锁住同步资源? a.锁住 悲观锁 b.不锁住 乐观锁

   悲观锁:悲观锁的计谋是在访问数据之前先获取锁,这表示它悲观地以为在整个数据操纵过程中都会有并发修改的风险。因此,在悲观锁的机制下,当一个线程访问数据时,会先加锁,其他线程要想访问该数据就必须等候已持有锁的线程释放锁才能继续操纵。典范的悲观锁实现包罗数据库中的行级锁、Java中的synchronized关键字等。
  乐观锁:相对于悲观锁,乐观锁则接纳一种更加乐观的态度,它假设在大多数情况下数据不会发生冲突。因此,乐观锁在更新数据时并不加锁,而是先读取数据版本信息,然后在写回数据时查抄数据版本信息是否发生变化。典范的乐观锁实现方式包罗版本号机制和CAS(Compare And Swap)等。
  悲观锁得当写操纵频仍的场景,得当于长事件,而乐观锁得当读操纵频仍的场景,得当于短事件。在实际应用中,选择合适的锁计谋必要根据具体的业务场景和性能需求进行衡量。
  


    • 锁住同步资源失败,线程要不要阻塞? a.阻塞 b.不阻塞 自旋锁 适应性自旋锁

   自旋锁是一种基于忙等候的锁机制,它在获取锁时不会立即放弃CPU的实行时间片,而是通过循环进行空转等候其他线程释放锁。这样可以减少线程切换的开销,实用于短时间内竞争锁的情况。
  自旋锁的基本头脑是,当一个线程发现该锁已被其他线程占用时,它会不停地在循环中尝试获取锁,直到获取到为止。如果在循环中一直无法获取到锁,该线程大概会消耗较多的CPU资源。
  为了提高自旋锁的效率,偶然候可以采用适应性自旋锁(Adaptive Spin Lock)。适应性自旋锁是根据之前获取锁的汗青记录来调整自旋等候的时间。如果一个线程在已往常常能够快速获取到锁,那么它的自旋等候时间会较短;相反,如果一个线程在已往很少能够快速获取到锁,那么它的自旋等候时间会较长。
  适应性自旋锁可以根据线程的汗青获取情况来动态调整自旋等候时间,更有效地利用CPU资源。例如,如果一个线程常常能够快速获取到锁,那么它的自旋时间可以较短,减少无谓的空转等候时间;如果一个线程常常无法快速获取到锁,那么它的自旋时间可以得当延伸,减少不必要的锁竞争。
  适应性自旋锁的具体实现方式可以根据不同的编程语言和平台而有所不同。在Java中,JVM会使用一些统计信息来调整自旋等候时间,以提高自旋锁的性能。
  


    • 多个线程竞争同步资源的流程细节有没有区别?

  • 不锁住资源,多个线程中只有一个能修改资源成功,别的线程会重试  无锁
  • 同一个线程实行同步资源时自动获取资源 方向锁
  • 多个线程竞争同步资源时,没有获取资源的线程自旋等候锁释放 轻量级锁
  • 多个线程竞争同步资源时,没有获取资源的线程阻塞等候唤醒 重量级锁
   方向锁:方向锁是为相识决大多数情况下都是单线程访问同步块的情况。当一个线程首次访问同步块时,方向锁会将对象头部的标志设置为指向该线程,并且记录下该线程的ID。此后,如果同步块再次被相同的线程访问,无需进行同步操纵,可以直接进入临界区实行,从而减少了获取锁和释放锁的开销。
  轻量级锁:轻量级锁是针对多线程竞争同步块的情况进行优化的,它通过CAS操纵来尝试获取锁。当只有一个线程在竞争同步块时,轻量级锁使用CAS操纵将对象头部的标志更换为指向锁记录的指针,从而避免了传统的互斥量操纵。
  重量级锁:重量级锁是指当多个线程竞争同步块时,锁会膨胀为重量级锁,在这种情况下会引入互斥量,使得其他线程阻塞,等候持有锁的线程释放锁。
  这三种锁在Java中的实现是为了在不同的多线程场景下提供最佳的性能和资源利用率。方向锁实用于大多数情况下都是单线程访问同步块的场景,轻量级锁实用于少量线程竞争同步块的场景,而重量级锁实用于多个线程竞争同步块的场景。 Java捏造机在运行时会根据实际情况动态地选择合适的锁机制来提高程序的性能。
  


    • 多个线程竞争锁时要不要列队?

a.列队 公平锁 b.先尝试插队,插队失败再列队 非公平锁
   在并发编程中,公平锁和非公平锁是两种不同的锁获取计谋。它们影响了线程在竞争锁时的获取顺序和公平性。
  公平锁:公平锁是指当多个线程按照申请锁的顺序来获取锁,即先来先得的原则。当一个线程发出获取锁的哀求时,如果当前锁被其他线程占用,该线程会进入等候队列,按照先后顺序等候锁的释放。当锁释放时,等候时间最长的线程会获得锁。公平锁的特点是能够包管锁的获取是按照线程的申请顺序进行的,避免了线程饥饿的情况。
  非公平锁:非公平锁是指当多个线程竞争锁时,不考虑申请锁的顺序,答应"插队",即如果一个线程发出获取锁的哀求时,如果当前锁没有被其他线程占用,那么该线程可以直接获取到锁,而无需进入等候队列。这样大概导致某些线程会一直获取到锁,而其他线程长时间得不到实行,出现线程饥饿的情况。
  在实际应用中,选择公平锁或非公平锁取决于具体的业务场景和性能需求。公平锁能够包管线程按照申请顺序获取锁,但大概会带来一定的性能开销;而非公平锁大概会提高体系的吞吐量,但大概造成某些线程长时间得不到实行的情况。因此,在使用锁的时间必要根据实际情况进行选择,以达到最佳的性能和公平性。
  


    • 一个线程中的多个流程能不能获取同一把锁? 能可重入锁 不能非可重入锁

   可重入锁:可重入锁是指当一个线程持有锁时,可以再次获取同一个锁,而不会被本身所持有的锁所阻塞。也就是说,可重入锁答应同一个线程多次获得同一个锁,而不会出现死锁的情况。这种特性使得线程可以在实行同步代码块时,调用其他必要获取同一把锁的方法,而不会造成阻塞。
  可重入锁的实现通常是通过给每个锁关联一个获取计数器和一个持有线程的标识来实现的。当线程第一次获取锁时,计数器+1,并将持有线程标志为当火线程。当同一个线程再次获取锁时,计数器再次+1,线程继续持有锁。只有当计数器归零时,锁才会被完全释放。
  非可重入锁:非可重入锁是指当一个线程持有锁时,再次获取同一个锁时会被本身所持有的锁所阻塞,从而导致死锁。也就是说,非可重入锁不答应同一个线程多次获取同一把锁,否则会造成阻塞。
  非可重入锁的设计通常是没有关联的获取计数器,因此同一个线程在获取锁之后再次获取锁时,会由于无法通过获取查抄而被阻塞,导致死锁的发生。在实际应用中,可重入锁是最常见和常用的锁机制。它答应线程多次获取同一把锁,可以提高代码的灵活性和可维护性。而非可重入锁很少使用,由于它容易导致死锁题目,不利于并发编程的实现。
  


    • 多个线程能不能共享一把锁 能共享锁 不能 排他锁

   共享锁:共享锁答应多个事件同时对同一份数据进行读取操纵,但不答应任何事件对该数据进行写入操纵。也就是说,共享锁能够提供并发的读取本领,多个事件可以同时读取相同的数据,而不会相互影响。在共享锁下,读取操纵不会阻塞其他读取操纵,但会阻塞写入操纵。
  排他锁:排他锁是一种独占锁,它在被一个事件获取后会阻塞其他事件对同一份数据的读取和写入操纵。也就是说,排他锁只答应一个事件对数据进行写入操纵,并且在该事件持有锁期间,其他事件无法对该数据进行读取或写入操纵。排他锁确保了在修改数据时的一致性,但大概会降低并发性能。
  在数据库体系中,共享锁和排他锁通常用于实现事件隔离级别。例如,在读取数据时可以使用共享锁,以提高并发性能;在修改数据时可以使用排他锁,以确保数据的一致性。合理地使用共享锁和排他锁可以有效地控制并发访问,包管数据的完整性和一致性。
  1.4 Synchronized锁的特性(同步锁的特性)

可重入性:线程从锁A到锁B之后,还能到锁A里去。
为什么可以重入/怎样判断一个对象如今有没有锁:对象的头存着锁信息(计数器)count,加了锁就++,从锁出去就--,当count==0时,就是没有锁。
不可中断性:一个线程获得锁后,另一个线程想要获得资源,必须处于阻塞或等候状态,如果第一个线程不释放锁,第二个线程会一直阻塞或等候,不可被中断。
不可打断性:一旦进入了这个锁,必须要实行完任务后出来。
1.5 Synchronized原理

监视器——monitor
1.monitorenter
当 JVM 实行某个线程的某个方法内部的monitorenter时,它会尝试去获取当前对象对应的monitor的全部权。过程如下:


  • 若monitor的进入数为0,线程可以进入monitor,并将monitor的进入数置为1。当火线程成为monitor的owner(全部者)。
  • 若线程已拥有monitor的全部权,答应它重入monitor,则进入monitor的进入数加1。
  • 若其他线程已经占有monitor的全部权,那么当前尝试获取monitor的全部权的线程会被阻塞,直到monitor的进入数变为0,才能重新尝试获取monitor的全部权。
2.monitorexit


  • 能实行monitorexit指令的线程一定是拥有当前对象的monitor的全部权的线程。 
  • 实行monitorexit时会将monitor的进入数减1。当monitor的进入数减为0时,当火线程退出monitor,不再拥有monitor的全部权,此时其他被这个monitor阻塞的线程可以尝试去获取这个monitor的全部权 。
1.6 Synchronized使用方式

(悲观锁,非公平锁,可重入锁)
修饰实例方法:作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁。
修饰静态方法:给当前类加锁,会作用于类的全部对象实例,由于静态成员不属于任何一个实例对象,是类成员(static表明这是该类的一个静态资源,不管new了多少对象,只有一份)。以是如果一个线程A调用一个实例对象的非静态synchronized方法,而线程B必要调用这个实例对象所属类的静态synchronized方法,是答应的,不会发生互斥现象,由于访问静态synchronized方法占用的锁是当前类的,而访问非静态synchronized方法占用的锁是当前实例对象锁。
修饰代码块:指定加锁对象,对给定对象加锁,进入同步代码块先获取给定对象的锁
尽量不要使用synchronized(String a)由于JVM中,字符串常量池具有缓存功能。
1.7 Volatile使用方式

Volatile只能作用于变量,不能作用于方法和代码块,是线程同步的轻量级实现,性能比synchronized好,与synchronized是互补关系。

  • 线程
2.1 进程和线程



  • 进程:操纵体系的独立实行单元,可以类比于一个应用程序运行的实体,进程间资源不共享(可以跨进程实现需求)。
  • 线程:进程的子任务,CPU调度基本单元,资源共享。
  • 进程间通讯ipc:数据传输、资源共享、变乱通知、进程控制。
进程详解:
在linux体系中,通过task_struct这个布局体抽象表示一个进程。
这里mm*的指针用来指向一块捏造内存,当程序想要运行的时间,操纵体系就会把程序里这些重要的段加载到捏造内存里进行运行。
BSS段不用直接加载,通常是指用来存放程序中未初始化的大概初始化为0的全局变量和静态变量的一块内存地域。特点是可读写的,在程序实行之前BSS段会自动清0。
随着程序运行,它会使用各种资源,task_struct这个布局体用来管理这些资源和程序
注:可以明白为程序就是静态文件,进程就是程序运行着的实体



2.2 创建线程方式(七种)

java创建和启动线程较为常用的方式有继续Thread类、实现Runnable接口和匿名内部类的方式,带返回值的方式实现implements Callable<返回值范例>
继续Thread类
重写run方法,start()方法来启动,线程间实行时线程是以抢占式的方式运行。

1.Thread.currentThread(),是Thread类的静态方法,该方法返回当前正在实行的线程对象。
2.getName():该方法是Thread类的实例方法,该方法返当前正在实行的线程的名称。    
在默认情况下,主线程的名称为main,用户启动的多线程的名称依次为Thread-0,Thread-1,Thread-3..Thread-n等。
实现Runnable接口
重写run()方法,调用该线程对象的start()方法启动该线程。

匿名内部类
本质上也是一个类实现了Runnable接口,重写了run方法,只不过这个类没著名字,直接作为参数传入Thread类。(实用于创建启动线程次数较少的环境)


实现implements Callable<返回值范例>
以上方式,都没有返回值且都无法抛出非常。
Callable和Runnbale一样代表着任务,只是Callable接口中不是run(),而是call()方法,但两者相似,即都表示实行任务,call()方法的返回值范例即为Callable接口的泛型。

定时器方法java.util.Timer
Timer有不可控的缺点,当任务未实行完毕或我们每次想实行不同任务时间,实现起来比较麻烦。
Lambda表达式的实现(parallelStream)

盘算后的效果为21,事实证实是并行实行
线程池方式java.util.concurrent.Executor接口

2.3 线程池(四种)

1.Executor 框架
ThreadPoolExecutor类(推荐使用)
线程池实现类ThreadPoolExecutor是Executor框架最焦点的类。


  • corePoolSize : 焦点线程数线程数。
  • maximumPoolSize : 最大线程数。
  • workQueue: 当新任务来的时间会先判断当前运行的线程数量是否达到焦点线程数,如果达到的话,信托就会被存放在队列中。
  • keepAliveTime:当线程池中的线程数量大于corePoolSize的时间,如果这时没有新的任务提交,焦点线程外的线程不会立即烧毁,而是会等候,直到等候的时间超过了keepAliveTime才会被接纳烧毁;
  • unit : keepAliveTime参数的时间单元。
  • threadFactory :工厂。
  • handler :饱和计谋。

参数设计
焦点线程数设计为二八原则
队列长度设计为焦点线程数除以(每个线程实行必要的时间*2
最大线程数=(最大任务数-任务队列长度)*单个任务实行时间
最大空闲时间根据体系调整参数
Ctl是一个原子类的常量,为了包管线程安全,它存储了线程池状态,以及工作的线程数(二进制方式)3bit+29bit
2.SingleThreadExecutor只有一个线程的线程池
不推荐使用 SingleThreadExecutor?
SingleThreadExecutor使用无界队列 LinkedBlockingQueue作为线程池的工作队列。会对线程池带来的影响,导致OOM。
3.CachedThreadPool是一个会根据必要创建新线程的线程池
不推荐使用CachedThreadPool?
CachedThreadPool大概会创建大量线程,导致OOM。
4.ScheduledThreadPoolExecutor重要用来在给定的延迟后运行任务,大概定期实行任务。
2.4 阿里巴巴Java开发手册规范

在《阿里巴巴 Java 开发手册》“并发处理”章节,明确指出线程资源必须通过线程池提供,不答应在应用中自行显示创建线程。
别的,《阿里巴巴Java开发手册》中强制线程池不答应使用Executors去创建,而是通过ThreadPoolExecutor构造函数的方式,这样的处理方式规避资源耗尽的风险。
2.5 常见题目

为什么调用start()方法时会实行run()方法,为什么不能直接调用run()方法?
调用 start 方法方可启动线程并使线程进入就绪状态,而run方法只是thread的一个普通方法调用,还是在主线程里实行。
线程池中为什么要使用阻塞队列的缘故原由?
线程池创建线程必要获取mainlock这个全局锁,影响并发效率,阻塞队列可以很好的缓冲。
别的一方面,如果新任务的到达速率超过了线程池的处理速率,那么新到来的哀求将累加起来,这样的话将耗尽资源。
什么是线程死锁怎样避免线程死锁?
什么是线程死锁
线程死锁形貌的是这样一种情况:多个线程同时被阻塞,它们中的一个大概全部都在等候某个资源被释放。由于线程被无限期地阻塞,因此程序不大概正常终止。
如下图所示,线程 A 持有资源 2,线程 B 持有资源 1,他们同时都想申请对方的资源,以是这两个线程就会互相称待而进入死锁状态。

产存亡锁必须具备以下四个条件:


  • 互斥条件:该资源恣意一个时刻只由一个线程占用。
  • 哀求与保持条件:一个进程因哀求资源而阻塞时,对已获得的资源保持不放。
  • 不剥夺条件:线程已获得的资源在末使用完之前不能被其他线程强行剥夺,只有本身使用完毕后才释放资源。
  • 循环等候条件:若干进程之间形成一种头尾相接的循环等候资源关系。
怎样避免线程死锁?


  • 破坏互斥条件 :这个条件我们没有办法破坏,由于我们用锁原来就是想让他们互斥的(临界资源必要互斥访问)。
  • 破坏哀求与保持条件 :一次性申请全部的资源。
  • 破坏不剥夺条件 :占用部分资源的线程进一步申请其他资源时,如果申请不到,可以自动释放它占有的资源。
  • 破坏循环等候条件 :靠按序申请资源来防备。按某一顺序申请资源,释放资源则反序释放。破坏循环等候条件。
说说sleep()方法和wait()方法区别和共同点?
两者最重要的区别在于:sleep方法没有释放锁,而wait方法释放了锁。
两者都可以停息线程的实行。
Wait通常被用于线程间交互/通讯,sleep通常被用于停息实行。
wait()方法被调用后,线程不会自动清醒,必要别的线程调用同一个对象上的notify() 大概notifyAll()方法。sleep()方法实行完成后,线程会自动清醒。大概可以使用wait(long timeout)超时后线程会自动清醒。
现有线程T1、T2和T3。怎样确保T2线程在T1之后实行,T3线程在T2后实行?
可以用Thread类的join方法实现这一效果、Wait 、线程池
多线程的线程安全题目怎么办理?有哪几种方法?
(悲观锁)synchronized代码块、synchronized方法、Lock锁。
(乐观锁)假设不会发生并发冲突,每次不加锁而是假设没有冲突而去完成某项操纵,只在提交操纵时查抄是否违反数据完整性。如果由于冲突失败就重试,直到成功为止。CAS算法。
Lock 和 Synchronized 区别?


  • lock 是一个接口,而synchronized是java的一个关键字,synchronized是内置的语言实现;
  • synchronized在发生非常时间会自动释放占有的锁,因此不会出现死锁;而lock发生非常时间, 不会自动释放占有的锁,必须手动unlock来释放锁,大概引起死锁的发生(以是最好将同步代码块用try catch包起来,finally中写入unlock,避免死锁的发生。);
  • lock等候锁过程中可以用interrupt来终端等候,synchronized 只能等候锁的释放,不能相应中 断;
  • lock可以通过try lock来知道有没有获取锁,而synchronized不能;
  • Lock可以提高多个线程进行读操纵的效率(可以通过readwritelock实现读写分离)。

  • 聚集
3.1 聚集基础、数据布局



  • 时间复杂度:算法实行所需时间的增长率。
  • 空间复杂度:算法实行所需的存储空间的增长率。
  • List:可以有多个元素引用相同的对象,有序。
  • Set:不答应重复的聚集。不会有多个元素引用相同的对象。
  • Map: 使用键值对存储。Key不能重复,两个Key可以引用相同的对象。

####聚集相关
1 Set聚集去重之后会存在哪些题目?
经set去重后,列表会变成乱序,和原列表比较的话,会显示两者不等以是set去重后不能直接比较再次排一次序就可以了,两个就相称了。
2 ArrayList怎么去重?
声明2个ArrayList,分别为listA与listB,listA为待去重list,listB生存去重之后数据。遍历listA,然后判断listB中是否包含各个元素,若不包含,把此元素参加到listB中。
利用set聚集进行去重。
3 Arraylist和LinkedList数据布局?
ArrayList底层是一个数组,以是查询快,LinkedList底层是一个链表,以是增删快。
LinkedList是一个实现了List接口和Deque接口的双向链表。
ArrayList 的底层是数组队列,相称于动态数组。
4 Arraylist与LinkedList区别?
1.是否包管线程安全:两者都是不同步的,不包管线程安全;
2.底层数据布局:Arraylist底层使用的是Object数组;LinkedList底层使用的是双向链表;
3.是否支持快速随机访问:LinkedList不支持高效的随机元素访问,ArrayList支持。快速随机访问就是通过元素的序号快速获取元素对象get(int index)方法;
4.内存空间占用:ArrayList的空间浪费重要体如今在list列表的结尾会预留一定的容量空间,而LinkedList的空间花费则体如今它的每一个元素都必要消耗比ArrayList更多的空间(由于要存放直接后继和直接前驱以及数据)。
5 ArrayList的拓容机制?
ArrayList每次扩容都为原先容量1.5倍。
6 ArrayList和Vector的区别?
这两个类都实现了List接口(List接口继续了Collection接口),他们都是有序聚集。
(1)同步性:
Vector是线程安全的,也就是说是它的方法之间是线程同步的,而ArrayList是线程序不安全的,它的方法之间是线程不同步的。如果只有一个线程会访问到聚集,那最好是使用ArrayList,由于它不考虑线程安全,效率会高些;如果有多个线程会访问到聚集,那最好是使用Vector,由于不必要我们本身再去考虑和编写线程安全的代码。
(2)数据增长:
ArrayList与Vector都有一个初始的容量大小,当存储进它们内里的元素的个数超过了容量时,就必要增长ArrayList与Vector的存储空间,每次要增长存储空间时,不是只增长一个存储单元,而是增长多个存储单元,每次增长的存储单元的个数在内存空间利用与程序效率之间要取得一定的均衡。
即Vector增长原来的一倍,ArrayList增长原来的0.5倍。
####数据布局相关
排序算法


  • 冒泡排序:通过多次遍历数组,比较相邻元素的大小并互换位置,将较大的元素渐渐“冒泡”到数组的末尾。时间复杂度为O(n^2),属于稳定排序算法。冒泡排序可以应用于对象排序,必要在比较时定义对象间的大小关系。
  • 选择排序:每次从未排序的部分中选择最小(或最大)的元素,并将其放置在已排序部分的末尾。时间复杂度为O(n^2),属于不稳定排序算法。选择排序同样可以应用于对象排序,必要在比较时定义对象间的大小关系。
  • 插入排序:将数组分为已排序和未排序两部分,每次从未排序部分选择一个元素插入到已排序部分的正确位置。时间复杂度为O(n^2),属于稳定排序算法。插入排序同样实用于对象排序,必要在比较时定义对象间的大小关系。
高级排序算法


  • 快速排序:通过选定一个基准元素,将数组分割成两个子数组,然后对子数组分别进行快速排序。时间复杂度平均情况为O(nlogn),属于不稳定排序算法。快速排序通常比较高效,在大多数情况下都优于冒泡排序、选择排序和插入排序。
  • 归并排序:采用分治法,将数组递归地分成更小的数组,然后合并已经排序的子数组。时间复杂度始终为O(nlogn),属于稳定排序算法。归并排序的优点是可以应用在链表上,并且性能稳定。
  • 希尔排序:也称作缩小增量排序,是插入排序的一种更高效改进版本。希尔排序通过将相距较远的元素进行比较和互换,从而使得元素能够更快地回到合适的位置。时间复杂度取决于选择的增量序列,最差情况下为O(n^2),但是在一般情况下要好于简单插入排序。
查找算法


  • 线性查找:线性查找是一种基本的查找算法,也称为顺序查找。它从数组或列表的起始位置开始逐个比较元素,直到找到目标元素或遍历完整个数据布局。时间复杂度为O(n),此中n是元素的数量。线性查找实用于小规模的数据或无序数据。
  • 二分查找:二分查找是一种高效的查找算法,但要求待查找的数据布局必须是有序的。该算法通过将目标元素与数组或列表的中央元素进行比较,从而将查找范围缩小一半。如果目标元素小于中央元素,则在左半部分继续查找;如果目标元素大于中央元素,则在右半部分继续查找;如果目标元素等于中央元素,则找到了目标元素。时间复杂度为O(log n),此中n是元素的数量。
  • 哈希查找:哈希查找利用哈希函数将关键字映射到特定的存储位置,从而快速定位目标元素。在哈希表中,通过给定的关键字盘算出对应的哈希值,然后访问该位置的元素。哈希查找的平均时间复杂度为O(1),但在最坏情况下大概达到O(n),此中n是元素的数量。
  • 二叉查找树:二叉查找树是一种二叉树布局,此中每个节点的左子树上的全部节点都小于该节点的值,右子树上的全部节点都大于该节点的值。通过比较目标值与当前节点的值,可以在二叉查找树上进行快速查找。二叉查找树的平均查找时间复杂度为O(log n),但在最坏情况下大概退化为链表,导致查找时间复杂度为O(n)。
3.2 HashMap详解

1 hashCode()介绍
定义在JDK的Object.java中的方法,返回一个int整数,这就意味着 Java中的任何类都包含有hashCode()函数。
作用是获取哈希码,确定该对象在哈希表中的索引位置。
2 为什么要有hashCode
我们以“HashSet怎样查抄重复”为例子来说明为什么要有 hashCode:
把对象参加HashSet时,HashSet会先盘算对象的hashcode值来判断对象参加的位置,同 时也会与其他已经参加的对象的hashcode值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调用 equals()方法来查抄 hashcode 相称的对象是否真的相同。如果两者相同,HashSet 就不会让其参加操纵成功。如果不同的话,就会重新散列到其他位置。
3 hashMap和HashSet的区别
HashSet底层调用的是 HashMap。
HashMap储存键值对                       HashSet仅仅存储对象
使用 put()方法将元素放入map中            使用add()方法将元素放入
HashMap中使用键对象key来盘算hashcode值 
HashSet使用成员对象来盘算hashcode值,对于两个对象来说hashcode大概相同,以是 equals()方法用来判断对象的相称性,如果两个对象不同的话,那么返回false
4 HashMap 的数据布局;线程安全么?为什么?
答:不安全,大概造成死循环,具体表现链表的循环指向;应该使用 ConcurrentHashMap。
5 谈一下HashMap的底层原理是什么?
HashMap的主干是Entry数组。
Entry是HashMap的基本组成单元,每一个Entry包含一个key-value键值对。
HashMap由数组+链表组成的。
数组是HashMap的主体,链表则是重要为相识决哈希冲突而存在的。
如果定位到的数组索引位置不含链表,那么对于查找,添加等操纵很快,
如果定位到的数组包含链表,对于添加操纵,其时间复杂度为O(n),首先遍历链表,存在即覆盖,否则新增;对于查找操纵来讲,仍需遍历链表,然后通过key对象的equals方法逐一比对查找。以是,性能考虑,HashMap中的链表出现越少,性能才会越好。
基于hashing的原理,jdk8后采用数组+链表+红黑树的数据布局。我们通过put和get存储和获取对象。当我们给put()方法传递键和值时,先对键做一个hashCode()的盘算来得到它在bucket数组中的位置来存储Entry对象。当获取对象时,通过get获取到bucket的位置,再通过键对象的equals()方法找到正确的键值对,然后在返回值对象。
6 谈一下HashMap中put是怎样实现的?
1.盘算关于key的hashcode值(与Key.hashCode的高16位做异或运算)
2.如果散列表为空时,调用resize()初始化散列表
3.如果没有发生碰撞,直接添加元素到散列表中去
4.如果发生了碰撞(hashCode值相同),进行三种判断
4.1若key地址相同大概equals后内容相同,则更换旧值
4.2如果是红黑树布局,就调用树的插入方法
4.3链表布局,循环遍历直到链表中某个节点为空,尾插法进行插入,插入之后判断链表个数是否到达变成红黑树的阙值8;也可以遍历到有节点与插入元素的哈希值和内容相同,进行覆盖。
5.如果桶满了大于阀值,则resize进行扩容
7 谈一下HashMap中什么时间必要进行扩容,扩容resize()又是怎样实现的?
调用场景:
1.初始化数组table
2.当数组table的size达到阙值时即++size > load factor * capacity 时,也是在putVal函数中
实现过程:
扩容必要重新分配一个新数组,新数组是老数组的2倍长,然后遍历整个老布局,把全部的元素挨个重新hash分配到新布局中去。
8 谈一下HashMap中get是怎样实现的?
对key的hashCode进行hashing,与运算盘算下标获取bucket位置,如果在桶的首位上就可以找到就直接返回,否则在树中找大概链表中遍历找,如果有hash冲突,则利用equals方法去遍历链表查找节点。
9 为什么不直接将key作为哈希值而是与高16位做异或运算?
由于数组位置的确定用的是与运算,仅仅末了四位有效,设计者将key的哈希值与高16为做异或运算使得在做&运算确定命组的插入位置时,此时的低位实际是高位与低位的结合,增长了随机性,减少了哈希碰撞的次数。
10 为什么是16?为什么必须是2的幂?如果输入值不是2的幂比如10会怎么样?
HashMap默认初始化长度为16,并且每次自动扩展大概是手动初始化容量时,必须是2的幂。
1.为了数据的匀称分布,减少哈希碰撞。由于确定命组位置是用的位运算,若数据不是2的次幂则会增长哈希碰撞的次数和浪费数组空间。(PS:其实若不考虑效率,求余也可以就不用位运算了也不用长度必须为2的幂次)
2.输入数据若不是2的幂,HashMap通过一通位移运算和或运算得到的肯定是2的幂次数,并且是离那个数近来的数字
11 谈一下当两个对象的hashCode相称时会怎么样?
产生哈希碰撞,key值相同则更换旧值,不然链接到链表反面,链表长度超过阙值8就转为红黑树存储
12 请解释一下HashMap的参数loadFactor,它的作用是什么?
loadFactor表示HashMap的拥挤水平,影响hash操纵到同一个数组位置的概率。默认loadFactor等于0.75,当HashMap内里目面貌纳的元素已经达到HashMap数组长度的75%时,表示HashMap太挤了,必要扩容,在HashMap的构造器中可以定制loadFactor。
13 如果HashMap的大小超过了负载因子(load factor)定义的容量,怎么办?
超过阙值会进行扩容操纵,概括的讲就是扩容后的数组大小是原数组的2倍,将原来的元素重新hashing放入到新的散列表中去。
14 传统HashMap的缺点(为什么引入红黑树)?
JDK 1.8以前HashMap的实现是 数组+链表,即使哈希函数取得再好,也很难达到元素百分百匀称分布。当HashMap中有大量的元素都存放到同一个桶中时,这个桶下有一条长长的链表,这个时间HashMap 就相称于一个单链表,假如单链表有n个元素,遍历的时间复杂度就是O(n),完全失去了它的优势。针对这种情况,JDK 1.8中引入了红黑树(查找时间复杂度为 O(logn))来优化这个题目。
15 使用HashMap时一般使用什么范例的元素作为Key?
择Integer,String这种不可变的范例,像对String的统统操纵都是新建一个String对象,对新的对象进行拼接分割等,这些类已经很规范的覆写了hashCode()以及equals()方法。作为不可变类天生是线程安全的。
16 HashMap和HashTable和ConcurrentMap的区别和联系?
HashMap黑白线程安全的,HashTable是线程安全的。
HashMap的键和值都答应有null值存在,而HashTable则不可。
HashMap最多只答应一条记录的键为null,答应多条记录的值为null。
由于线程安全的题目,HashMap效率比HashTable的要高。
HashMap非线程安全,即任一时刻可以有多个线程同时写HashMap,大概会导致数据的不一致。
如果必要满足线程安全,可以用Collections的synchronizedMap方法使HashMap具有线程安全的本领,大概使用ConcurrentHashMap,是HashMap的多线程版本。
3.3 LinkedHashMap详解

继续自HashMap,可以按照元素的插入顺序来遍历或访问元素。
双向链表。
特点
支持键值对的存储。
答应插入null键和null值。
保持插入顺序。
提供了按照访问顺序进行遍历的功能(通过构造函数设置accessOrder参数为true)。
3.4 ConcurrentHashMap(线程安全原理)

HashMap和ConcurrentHashMap的区别
1、HashMap不是线程安全的,而ConcurrentHashMap是线程安全的。
2、ConcurrentHashMap采用锁分段技术,将整个Hash桶进行了分段segment,也就是将这个大的数组分成了几个小的片段segment,而且每个小的片段segment上面都有锁存在,那么在插入元素的时间就必要先找到应该插入到哪一个片段segment,然后再在这个片段上面进行插入,而且这里还必要获取segment锁。
3、ConcurrentHashMap让锁的粒度更精致一些,并发性能更好。
ConcurrentHashmap原理
synchronized+CAS+Node+红黑树,Node的val和next都用volatile修饰,包管可见性查找,更换,赋值操纵都使用CAS
锁:锁链表的head节点,不影响其他元素的读写,锁粒度更细,效率更高,扩容时,阻塞全部的读写操纵、并发扩容。
Node的val和next使用volatile修饰,读写线程对该变量互相可见数组用volatile修饰,包管扩容时被读线程感知。
实际的数据布局是跟hashmap一样的,只是多了一些并发的控制。
LongAddr:作为ConcurrentHashMap统计元素总数数据布局。使用Cells数组进行多线程的值的累加,求和的时间把base+数组中每个元素的值
添加大概删除元素的时间,使用Synchronized锁住数组位置上的首元素。
3.5 SparseArray(Android)

介绍
以键值对形式进行存储,基于分查找,因此查找的时间复杂度为0(LogN);
由于SparseArray中Key存储的是数组形式,因此可以直接以int作为Key。避免了HashMap的装箱拆箱操纵,性能更高且int的存储开销远远小于Integer;
采用了延迟删除的机制(针对数组的删除扩容开销大的题目的优化) ;
延迟删除机制
通过将删除KEY的Value设置DELETED,方便之后对该下标的存储进行复用;
使用二分查找,时间复杂度为O(LogN),如果没有查找到,那么取反返回左边界,再取反后,左边界即为应该插入的数组下标;
如果无法直接插入,则根据mGarbage标识(是否有潜在延迟删除的无效数据),进行数据扫除,再通过System.arraycopy进行数组后移,将目标元素插入二分查找左边界对应的下标;
mSize小于等于keys.length,小于的部分为空数据大概是gc后前移的数据的原数据(也是无效数据),因此二分查找的右边界以mSize为准;mSize包含了延迟删除后的元素个数;
如果遇到频仍删除,不会触发gc机制,导致mSize 远大于有效数组长度,造成性能损耗;
根据源码,大概触发gc操纵的方法有(1、put;2、与index有关的全部操纵,setValueAt()等;3、size()方法;)
mGarbage为true不一定有无效元素,由于大概被删除的元素恰好被新添加的元素覆盖;
使用场景
key为整型;
不必要频仍的删除;
元素个数相对较少;

  • JVM
JVM工作原理
跨平台,java源文件通过javac命令启动编译器,将java文件编译成class字 节码文件,class字节码文件通过java命令启动jvm捏造机加载class字节码文件。
内存管理:线程共享和线程私有内存线程
共享内存:堆、方法区、常量池线程
私有内存:java栈、本地方法栈
类加载机制:父类委托模式
垃圾接纳:大部分的GC都是采用分代网络算法的
GC调优[译]GC专家系列3-GC调优_maxgcpausemillis-CSDN博客


从上图可以看出:一个进程中可以有多个线程,多个线程共享进程的堆和方法区(JDK1.8之后的元空间)资源,但是每个线程有本身的程序计数器、捏造机栈和本地方法栈。
与进程不同的是同类的多个线程共享进程的堆和方法区资源,但每个线程有本身的程序计数器、捏造机栈和本地方法栈。
1 为什么程序计数器、捏造机栈和本地方法栈是线程私有的呢?为什么堆和方法区是线程共享的呢
程序计数器重要用于改变程序计数器来依次读取指令用于流程控制,还有在多线程的情况下,程序计数器用于记录当火线程实行的位置,从而当线程被切换回来的时间能够知道该线程上次运行到哪儿了
以是,程序计数器私有重要是为了线程切换后能规复到正确的实行位置。
为了包管线程中的局部变量不被别的线程访问到,捏造机栈和本地方法栈是线程私有的。
2 堆和方法区
堆是进程中最大的一块内存,重要用于存放新创建的对象
方法区重要用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
4.1 内存模式


1.Java对象的创建过程(五步,每一步捏造机做了什么)
类加载:在程序首次使用该类时,捏造机会通过类加载器加载对象的类文件,并进行类的初始化工作,包罗静态变量的初始化和实行静态代码块。

分配内存:在类加载完成后,接下来捏造机必要在堆(Heap)中为对象分配内存空间。捏造机的分配方式有两种:指针碰撞(Bump the Pointer)和空闲列表(Free List)。在选择完合适的内存空间之后,捏造机将会对内存空间进行零值化,确保对象的属性都能被正确初始化。

初始化零值:在分配内存空间后,捏造机会对对象进行必要的零值初始化,包罗对象头信息、实例变量等。

实行构造方法:接着,捏造机会调用对象的构造方法来进行一些特定的初始化操纵,包罗初始化实例变量等。构造方法的实行大概会涉及到对象的初始化顺序、父类构造方法的调用等。

返回对象地址:末了一步是将对象的引用返回给程序,使得程序能够通过该引用来访问新创建的对象。

Java对象的创建过程包罗类加载、内存分配、零值初始化、构造方法实行以及返回对象地址这五个步骤。这些步骤确保了对象的正确初始化和合理分配内存,从而让程序能够正常地使用新创建的对象。
2.对象的访问定位的两种方式(句柄和直接指针两种方式)
句柄方式,Java堆中不直接存储对象实例数据,而是将对象存储在一个称为“堆外”的地方。在Java堆中则只存储对象实例的句柄,即对象的引用。这个句柄是一个具体的指针,指向堆外存储的对象实例数据的地址。当要访问对象实例时,首先通过句柄在Java堆中找到对象的引用,然后再根据引用中的指针定位到堆外的实际对象数据。
当代的Java捏造机中,已经不再使用句柄,使用直接指针方式,Java堆中直接存储对象实例的数据,而对象引用就是指向对象实例数据的指针。这样,当必要访问对象时,可以直接通过引用指针来定位到对象实例的数据,而不必要额外的句柄进行间接定位。
4.2 垃圾接纳算法

1.GC 垃圾接纳的几种算法
标志-扫除算法 该算法先标志,后扫除,将全部必要接纳的算法进行标志,然后扫除;这种算法的缺点是:效率比较低;标志扫除后会出现大量不连续的内存碎片,这些碎片太多大概会使存储大对象会触发GC接纳,造成内存浪费以实时间的消耗。

复制算法 复制算法将可用的内存分成两份,每次使用此中一块,当这块接纳之后把未接纳的复制到另一块内存中,然后把使用的扫除。这种算法运行简单,办理了标志-扫除算法的碎片题目,但是这种算法代价过高,必要将可用内存缩小一半,对象存活率较高时,必要连续的复制工作,效率比较低。

标志整理算法 标志整理算法是针对复制算法在对象存活率较高时连续复制导致效率较低的缺点进行改进的,该算法是在标志-扫除算法基础上,不直接清算,而是使存活对象往一端游走,然后扫除一端边界以外的内存,这样既可以避免不连续空间出现,还可以避免对象存活率较高时的连续复制。这种算法可以避免 100%对象存活的极度状况,因此老年代不能直接使用该算法。

分代网络算法 分代网络算法就是目前捏造机使用的接纳算法,它办理了标志整理不实用于老年代的题目,将内存分为各个年代,在不同年代使用不同的算法,从而使用最合适的算法,新生代存活率低,可以使用复制算法。而老年代对象存活率搞,没有额外空间对它进行分配包管,以是只能使用标志扫除大概标志整理算法。

2.怎样判断对象是否殒命(两种方法)
引用计数法:引用计数法是一种简单的内存管理技术,它通过对对象的引用计数进行跟踪来确定对象是否可以被接纳。当对象被创建时,引用计数为1;每当有新的引用指向该对象时,引用计数加1;当引用失效或被烧毁时,引用计数减1。当对象的引用计数变为0时,就意味着该对象已经没有被任何引用指向,可以被接纳。

可达性分析法:可达性分析法是一种更为普遍和高效的内存管理技术,它是当代编程语言中常用的垃圾接纳算法之一。可达性分析法从一组称为"根"的对象开始,递归地遍历全部通过引用和指针可达的对象。如果一个对象无法通过任何路径与"根"对象相连,那么这个对象就被以为是不可达的,即"殒命"的对象,可以被接纳。

3.简单的介绍一下强引用、软引用、弱引用、虚引用(虚引用与软引用和弱引用的区别、使用软引用能带来的好处)。
强引用:强引用是最常见的引用范例。当我们通过一个变量来引用一个对象时,这个引用就是强引用。只要强引用存在,垃圾接纳器就无法接纳被引用的对象。

软引用:软引用是一种相对强引用弱化的引用范例。如果一个对象只有软引用关联,垃圾接纳器在内存不敷时,会尝试接纳这些对象。使用软引用可以实现缓存等功能,提高体系的性能和效率。

弱引用:弱引用比软引用更弱化。如果一个对象只有弱引用关联,那么在下一次垃圾接纳时,无论内存是否充足,都会被接纳。弱引用常常用于实现一些容器类,如WeakHashMap。

虚引用:虚引用是最弱化的引用范例。它无法通过虚引用来获取被引用的对象,也无法阻止对象被垃圾接纳。虚引用重要用于跟踪对象被垃圾接纳的状态。

软引用和弱引用的区别在于垃圾接纳器对待它们的接纳计谋不同。软引用只有在内存不敷时才会被接纳,而弱引用则在下一次垃圾接纳时就大概被接纳。

使用软引用可以带来一些好处,例如:


  • 在内存紧张的情况下,垃圾接纳器可以接纳被软引用引用的对象,释放内存空间,避免内存溢出。
  • 软引用可以用于实现缓存机制,当内存不敷时,自动扫除缓存中的对象,避免大量占用内存。
  • 软引用还可以用于临时生存对象,当必要时再获取,避免频仍地创建和烧毁对象。


4.HotSpot为什么要分为新生代和老年代?
将堆内存划分为新生代和老年代有助于根据对象的生命周期特点,采用更合适的垃圾接纳算法,并优化内存管理,从而提高垃圾接纳效率和体系性能。

5.常见的垃圾接纳器有哪些,介绍一下CMS,G1网络器。
CMS网络器:它是一种以获取最短接纳停顿时间为目标的垃圾接纳器。CMS网络器采用了并发标志和并发扫除的方式,在垃圾接纳期间,部分工作与应用线程并发实行,以减少接纳停顿时间。

G1网络器:它是一种面向服务器端应用的垃圾接纳器,它的设计目标是更好地满足长时间运行的应用程序的性能需求。G1网络器采用了分代式的垃圾接纳计谋,并且具备增量并发标志和并发清算的本领。

6.Android的GC和Java的GC之间区别
由于Android平台的特殊性和需求,Android的GC实现与Java的GC有一些差异。

GC算法选择:Android的GC实现使用了不同的垃圾接纳算法。在早期版本的Android中,Dalvik捏造机使用的是标志-扫除(Mark and Sweep)算法。而在当前的Android版本中,使用的是基于分代的并发式复制(Generational Concurrent Copying)算法的垃圾接纳器,称为G1 GC(Garbage First Garbage Collector)。

堆布局和大小:Android设备通常具有更有限的内存资源。因此,Android的GC会针对较小的堆空间进行优化,以提高效率和相应性能。相比之下,Java捏造机通常运行在具有更大堆空间和更高内存容量的服务器或桌面环境中。

并发和停息时间:Android的GC必要考虑到移动设备的相应性能,因此尽量减少GC停息时间。Android的G1 GC是一种并发式的垃圾接纳器,它可以在应用程序运行的同时进行垃圾网络,以减少停息时间。相比之下,Java捏造机的垃圾接纳通常会导致较长的停息时间,由于在接纳过程中必要制止应用程序的实行。

内存压缩:Android的GC实现通常包含内存压缩功能。由于Android设备的内存有限且容易产生内存碎片,GC会尝试对空闲内存块进行整理和压缩,以提高内存利用率和性能。这种内存压缩功能在Java捏造机中并不常见。

必要注意的是,Android的GC实现大概会因不同的Android版本和设备而有所差异。每个版本和设备都大概使用不同的GC计谋和算法来优化内存管理和性能。

  • 网络
5.1 网络模式


根据TCP/IP协议,各层的要实现的功能如下:


  • LLC层:处理传输错误;调治数据流,协调收发数据双方速度,防止发送方发送得太快而接收方丢失数据。重要使用数据链路协议。
  • 网络层:本层也被称为IP层。LLC层负责把数据从线的一端传输到另一端,但很多时间不同的设备位于不同的网络中(并不是简单的网线的两端)。此时就必要网络层来办理子网路由拓扑题目、路径选择题目。在这一层重要有IP协议、ICMP协议。
  • 传输层:由网络层处理好了网络传输的路径题目后,端到端的路径就创建起来了。传输层就负责处理端到端的通讯。在这一层中重要有TCP、UDP协议
  • 应用层:颠末前面三层的处理,通讯完全创建。应用层可以通过调用传输层的接口来 编写特定的应用程序。而TCP/IP协议一般也会包含一些简单的应用程序如Telnet远程登录、 FTP文件传输、SMTP邮件传输协议。实际上,在发送数据时,颠末网络协议栈的每一层,都会给来自上层的数据添加上一个数据包的头,再传递给下一层。在接收方收到数据时,一层层地把所在层的数据包的头去掉,向上层递交数据

tcp和udp的区别和应用场景
TCP 面向毗连(如打电话要先拨号创建毗连);UDP是无毗连的,即发送数据之前不必要创建毗连。
TCP 提供可靠的服务。也就是说,通过TCP毗连传送的数据,无不对,不丢失,不重复,且按序到达;UDP 尽最大积极交付,即不包管可靠交付。
TCP 面向字节流,实际上是 TCP 把数据看成一连串无布局的字节流;UDP是面向报文的UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等)
每一条TCP毗连只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通讯
TCP 首部开销 20 字节;UDP 的首部开销小,只有 8 个字节 6、TCP 的逻辑通讯信道是全双工的可靠信道,UDP 则是不可靠信道 应用场景:TCP 协议(如文件传输、重要状态的更新等);反之,则使用 UDP 协议(如视频传输、实时通讯等)
5.2 TCP/IP协议栈

尺度TCP/IP协议是用于盘算机通讯的一组协议,通常称为TCP/IP协议栈,通俗讲就是符合以太网通讯要求的代码聚集,一般要求它可以实现网络模式图中对应的协议,比如应用层的HTTP、FTP、DNS、SMTP协议,传输层的TCP、UDP协议、网络层的IP、ICMP协议等等。
为什么使用协议栈
物理层重要定义物理介质性子,MAC子层负责与物理层进行数据交接,这两部分是与 硬件紧密联系的,就嵌入式控制芯片来说,很多都内部集成了MAC控制器,完成MAC子 层功能,以是依靠这部分功能是可以实现两个设备数据互换,而时间传输的数据就是MAC数据包,发送端封装好数据包,接收端则解封数据包得到可用数据,这样的一个模型与使用USART控制器实现数据传输黑白常类似的。但如果将以太网运用在如此基础的功能上,完全是大材小用,由于以太网具有传输速度快、可传输间隔远、支持星型拓扑设备毗连等等强大功能。功能强大的东西一般都会用高级的应用,这也是设计者的初志。
5.3 HTTP

用于在客户端和服务器之间传输超文本数据的应用层协议。

http 的哀求方式有哪些
opions 返回服务器针对特定资源所支持的 HTML 哀求方法 或 web 服务器发送*测试服务器 功能(答应客户端检察服务器性能)
Get向特定资源发出哀求(哀求指定页面信息,并返回实体主体)
Post向指定资源提交数据进行处理哀求(提交表单、上传文件),又大概导致新的资源的创建 或原有资源的修改
Put向指定资源位置上上传其最新内容(从客户端向服务器传送的数据取代指定文档的内容)
Head与服务器索与 get 哀求一致的相应,相应体不会返回,获取包含在小消息头中的原信息(与get哀求类似,返回的相应中没有具体内容,用于获取报头)
Delete哀求服务器删除request-URL所标示的资源*(哀求服务器删除页面)
Trace回显服务器收到的哀求,用于测试和诊断
Connect HTTP/1.1 协议中能够将毗连改为管道方式的代理服务器 http 服务器至少能实现 get、head、post 方法,其他都是可选的。

http演进过程:1.0-->1.1-->2.0
HTTP 1.0:HTTP 1.0是最早的版本,于1996年发布。它采用短毗连方式,每次哀求都必要创建一个新的TCP毗连,并在完成后断开毗连。该版本功能相对简单,没有持久毗连的概念,每个哀求和相应都必要独立的毗连。

HTTP 1.1:HTTP 1.1是在1999年发布的版本,目前仍然是最广泛使用的版本。它引入了持久毗连(Keep-Alive)的概念,答应在单个TCP毗连上发送多个哀求和相应,减少了创建和断开毗连的开销。此外,HTTP 1.1还引入了哀求管线化(Pipeline)和分块传输编码(Chunked Transfer Encoding)等特性,提高了性能和效率。

HTTP 2.0:HTTP 2.0是在2015年发布的版本,旨在进一步提拔性能和效率。它基于Google的SPDY协议进行扩展,引入了多路复用(Multiplexing)的特性,答应在同一个TCP毗连上同时传输多个哀求和相应。此外,HTTP 2.0还支持头部压缩(Header Compression)、服务器推送(Server Push)等功能,减少了网络延迟和带宽消耗。
每个版本都引入了新的特性和改进,旨在提高性能、效率和用户体验。

  • 设计模式


  • 创建型模式:共5种:工厂方法模式、抽象工厂模式、单例模式、制作者模式、原型模式
  • 布局型模式:共7种:适配器模式、装饰器模式、代理模式、桥接模式、外观模式、组合模式、享元模式
  • 行为型模式:共11种:计谋模式、模板方法模式、观察者模式、责任链模式、访问者模式、中介者模式、迭代器模式、命令模式、状态模式、备忘录模式、解释器模式
  • 还有两类:并发型模式和线程池模式
下图为设计模式之间关系:

6.1 设计模式的六大原则



  • 开闭原则
开闭原则指的是对扩展开放,对修改关闭。在对程序进行扩展的时间,不能去修改原有的代码,想要达到这样的效果,我们就必要使用接口大概抽象类


  • 依赖倒转原则
依赖倒置原则是开闭原则的基础,指的是针对接口编程,依赖于抽象而不依赖于具体


  • 里氏更换原则
里氏更换原则是继续与复用的基石,只有当子类可以更换掉基类,且体系的功能不受影响时,基类才能被复用,而子类也能够在基础类上增长新的行为。以是里氏更换原则指的是任何基类可以出现的地方,子类一定可以出现。
里氏更换原则是对 “开闭原则” 的补充,实现 “开闭原则”的关键步骤就是抽象化,而基类与子类的继续关系就是抽象化的具体实现,以是里氏更换原则是对实现抽象化的具体步骤的规范。


  • 接口隔离原则
使用多个隔离的接口,比使用单个接口要好,降低接口之间的耦合度与依赖,方便升级和维护方便


  • 迪米特原则
 迪米特原则,也叫最少知道原则,指的是一个类应当尽量减少与其他实体进行相互作用,使得体系功能模块相对独立,降低耦合关系。该原则的初志是降低类的耦合,固然可以避免与非直接的类通讯,但是要通讯,就一定会通过一个“中介”来发生关系,太过的使用迪米特原则,会产生大量的中介和传递类,导致体系复杂度变大,以是采用迪米特法则时要反复衡量,既要做到布局清晰,又要高内聚低耦合。


  • 合成复用原则
尽量使用组合/聚合的方式,而不是使用继续。
6.2 工厂模式

创建一个工厂类,并定义一个接口对实现了同一接口的产品类进行创建。首先看下关系图:

6.3 单例模式

可以确保体系中某个类只有一个实例,该类自行实例化并向整个体系提供这个实例的公共访问点,除了该公共访问点,不能通过其他途径访问该实例。
单例模式的优点在于:体系中只存在一个共用的实例对象,无需频仍创建和烧毁对象,节省了体系资源,提高体系的性能。
可以严格控制客户怎么样以及何时访问单例对象。
单例模式的写法有好几种,重要有三种:懒汉式单例、饿汉式单例、登记式单例。
懒汉式懒汉式单例是指在第一次使用时才会创建单例实例。这意味着在多线程环境下,大概会出现并发安全性题目,必要通过加锁等本领来确保线程安全。通常使用synchronized关键字大概双重查抄锁(double-checked locking)来实现懒汉式单例的线程安全性。

饿汉式单例: 饿汉式单例是指在类加载的时间就创建单例实例,因此在多线程环境下也可以包管线程安全,不必要额外的同步措施。但是,由于实例在类加载时就创建,因此如果该实例在后续运行过程中没有被使用,会造成资源浪费。

6.4 享元模式

享元模式通过共享技术有效地支持细粒度、状态变化小的对象复用,当体系中存在有多个相同的对象,那么只共享一份,不必每个都去实例化一个对象,极大地减少体系中对象的数量,从而节流资源。
享元模式的焦点是享元工厂类,享元工厂类维护了一个对象存储池,当客户端必要对象时,首先从享元池中获取,如果享元池中存在对象实例则直接返回,如果享元池中不存在,则创建一个新的享元对象实例返回给用户,并在享元池中生存该新增对象,这点有些单例的意思。
工厂类通常会使用聚集范例来生存对象,如HashMap、Hashtable、Vector等,在Java中,数据库毗连池、线程池等都是用享元模式的应用。
享元模式的UML布局图如下:


Java中,String范例就是使用享元模式,String对象是final范例,对象一旦创建就不可改变。而Java 的字符串常量都是存在字符串常量池中的,JVM会确保一个字符串常量在常量池中只有一个拷贝。

下面是一个简单的享元模式实例,假设我们有一个游戏中的棋盘,棋盘上有许多棋子,我们希望在内存中只创建一个棋子对象,并在不同的位置上重复使用它们。
首先,我们定义一个抽象的棋子类 ChessPiece ,包含一个方法 move 用于移动棋子:

然后,我们创建具体的棋子类 ConcreteChessPiece ,继续自 ChessPiece ,并实现 move 方法:

接下来,我们创建一个工厂类 ChessPieceFactory ,用于管理和提供共享的棋子对象:

末了,我们可以在客户端代码中使用享元模式:

享元模式通过共享对象来减少内存使用,提高性能。它实用于必要创建大量细粒度对象,并且这些对象可以共享部分或全部状态的场景。
6.5 其他常用模式



  • 观察者模式:定义了一种一对多的依赖关系,使得多个观察者对象同时监听某一个主题对象。常用于当一个对象的状态改变时,必要通知全部依赖于它的对象进行更新操纵。
  • 计谋模式:定义了一簇算法,将每个算法封装成一个独立的类,并使它们可以相互更换。常用于在运行时动态选择算法,以适应不同的业务需求。
  • 适配器模式:将一个类的接口转换成客户端所期望的另一个接口。常用于办理两个不兼容接口之间的适配题目,使得原本不兼容的类可以合作。

  • 其他题目
1 ==与equals区别?
== : 判断两个对象的地址是不是相称。判断两个对象是不是同一个对象(基本数据范例==比较的是值,引用数据范例==比较的是内存地址)。
equals() : 它的作用也是判断两个对象是否相称。但它一般有两种使用情况:
情况 1:类没有覆盖equals()方法。则通过equals()比较该类的两个对象时,等价于通过“==”比较这两个对象。
情况 2:类覆盖了equals()方法。内容相称,则返回 true (即,以为这两个对象相称)。
2 为什么重写equals时必须重写hashCode方法?
提高效率,接纳重写hashcode方法,先进行hashcode比较,如果不同,那么就没必要在进行equals的比较了,这样就大大减少了equals比较的次数。
3办理hash碰撞?
1、开防定址法2、再哈希法3、链地址法(Java的hashmap的办理办法就是这个)4、创建一个公共溢出区
4 hashCode与equals?
两个对象的hashCode()相同,则equals()一定为true,对吗?
两个对象equals相称,则它们的hashcode必须相称,反之则不一定。
两个对象==相称,则其hashcode一定相称,反之不一定成立。
两个对象相称,对两个对象分别调用equals方法都返回true
因此,equals方法被覆盖,hashcode也必须被覆盖
5 Java序列化中如果有些字段不想进行序列化,怎么办?
对于不想进行序列化的变量,使用transient关键字修饰。
transient关键字的作用是:阻止实例中那些用此关键字修饰的的变量序列化;当对象被反序列化时,被 transient修饰的变量值不会被持久化和规复。transient只能修饰变量,不能修饰类和方法。
6 形貌深拷贝和浅拷贝?
浅拷贝:对基本数据范例进行值传递,对引用数据范例进行引用传递般的拷贝,此为浅拷贝。
深拷贝:对基本数据范例进行值传递,对引用数据范例,创建一个新的对象,并复制其内容,此为深拷贝。
7 &和&&的区别?
&和&&都可以用作逻辑与的运算符,表示逻辑与(and),当运算符双方的表达式的效果都为true时,整个运算效果才为true,否则,只要有一方为false,则效果为false。
&&还具有短路的功能,即如果第一个表达式为false,则不再盘算第二个表达式。
&还可以用作位运算符,当&操纵符双方的表达式不是boolean范例时,&表示按位与操纵,我们通常使用0x0f来与一个整数进行&运算,来获取该整数的最低4个bit位,例如,0x31 & 0x0f的效果为0x01。
8 final 关键字修饰这三个地方:变量、方法、类,会有什么作用?
对于一个final变量,如果是基本数据范例的变量,则其数值一旦在初始化之后便不能更改;如果是引用范例的变量,则在对其初始化之后便不能再让其指向另一个对象。
当用final修饰一个类时,表明这个类不能被继续。final类中的全部成员方法都会被隐式地指定为final方法。
使用final方法的缘故原由有两个。第一个缘故原由是把方法锁定,以防任何继续类修改它的寄义;第二个缘故原由是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,大概看不到内嵌调用带来的任何性能提拔(如今的Java版本已经不必要使用final方法进行这些优化了)。类中全部的private方法都隐式地指定为final。
9 final, finally, finalize的区别?
final用于声明属性,方法和类,分别表示属性不可变,方法不可覆盖,类不可继续。内部类要访问局部变量,局部变量必须定义成final范例。
finally黑白常处理语句布局的一部分,表示总是实行。
finalize是Object类的一个方法,在垃圾网络器实行的时间会调用被接纳对象的此方法,可以覆盖此方法提供垃圾网络时的其他资源接纳,例如关闭文件等。但是JVM不包管此方法总被调用
10 值传递?
当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的效果,那么这里到底是值传递还是引用传递
是值传递。Java语言的方法调用只支持参数的值传递。
11 为什么Java中只有值传递?
首先回首一下在程序设计语言中有关将参数传递给方法(或函数)的一些专业术语。按值调用(call by value)表示方法接收的是调用者提供的值,而按引用调用(call by reference)表示方法 接收的是调用者提供的变量地址。一个方法可以修改传递引用所对应的变量值,而不能修改传递值 调用所对应的变量值。它用来形貌各种程序设计语言(不只是Java)中方法参数传递方式。Java程序设计语言总是采用按值调用。也就是说,方法得到的是全部参数值的一个拷贝,也就是说,方法不能修改传递给它的任何参数变量的内容。
12 值传递和引用传递有什么区别?
值传递:指的是在方法调用时,传递的参数是按值的拷贝传递,传递的是值的拷贝,也就是说传递后就互不相关了。
引用传递:指的是在方法调用时,传递的参数是按引用进行传递,其实传递的引用的地址,也就是变量所对应的内存空间的地址。传递的是值的引用,也就是说传递前和传递后都指向同一个引用(也就是同一个内存空间)。
13 String StringBuffer和StringBuilder的区别是什么?
String为什么是不可变的? 可变性 简单的来说:String类中使用final关键字修饰字符数组来生存字符串,private final char value[],以是String对象是不可变的。补充:在Java 9之后,String类的实现改用
byte数组存储字符串private final byte[] value;而StringBuilder与StringBuffer都继续自AbstractStringBuilder类,在AbstractStringBuilder中也是使用字符数组生存字符串char[]value但是没有用final关键字修饰,以是这两种对象都是可变的。
线程安全性String中的对象是不可变的,也就可以明白为常量,线程安全。AbstractStringBuilder是 StringBuilder与StringBuffer的公共父类,定义了一些字符串的基本操纵,如expandCapacity、 append、insert、indexOf等公共方法。StringBuffer对方法加了同步锁大概对调用的方法加了同步锁,以是是线程安全的。StringBuilder并没有对方法进行加同步锁,以是黑白线程安全的。 性能每次对String 范例进行改变的时间,都会生成一个新的String对象,然后将指针指向新的 String对象。StringBuffer 每次都会对 StringBuffer 对象本身进行操纵,而不是生成新的对象并改变对象引用。 相怜悯况下使用StringBuilder相比使用StringBuffer仅能获得10%~15%左右的性能提拔,但却有多线程不安全的风险。
总结:1. 操纵少量的数据: 实用String 2. 单线程操纵字符串缓冲区下操纵大量数据: 实用StringBuilder 3. 多线程操纵字符串缓冲区下操纵大量数据: 实用StringBuffer。
14String对象是否相称?














2.Java 与 C#区别
函数署名
 Java泛型擦除(伪泛型) 在编译时把泛型更换掉
new ArrayList<String>();
编译过程中会把String擦除掉 C#是真泛型 传进去什么就是什么
真泛型会产生不同的对象 伪泛型会提高捏造机的编译速度
3.接口和继续的区别
4.对String的明白
(内存优化)
String 1.8的时间是char数组 jdk9是字节byte[ ]数组 为什么这样设计?
为什么这样改进?存储少了一倍 char必要2个字符 byte必要1个
大部分String数据只必要一个字符 jdk9通过一个判断分配内存大小
String 为什么设计成final 但是通过反射也会改变
String str1 = “helloworld”
String str2 = “hello”+ “world”加号是StringBuilder做的
str1 == str2
true
5.synchronized(a){} 原子性 线程安全 ...
锁对象的级别 对象 类
锁的原理 字节码对象类的区别
锁升级过程  锁的性能1.6后没有什么题目了
锁三大特性
synchronized 和 joc 区别 当前锁的弊端 改进地方
单例的双锁模式
Class A;
A a = new A();懒汉式双重判断
if(a == null)当它为空就不用进入synchronized 了 加快cpu处理
synchronized (A.class){ 锁当前类对象
      if(a == null)  CPU排序导致的题目
     new A();
}线程内存(栈)    主内存(捏造内存)   synchronized 在内存屏障包管原子性
volatile 指令重排序 可见性 不会通过内存屏障 直接通过栈写入主内存
1.泛型
泛型的边界 向上的边界是可读的 向下的边界是可写的
Kotlin 中的泛型支持变型,即协变(covariant)和逆变(contravariant)




基础知识:
Object类相关:Object类的几个关键函数、String涉及到的常量池概念,序列化 & 反序列化
内部类:内部类的分类、应用场景、内部类编译成class后是怎么样的。
抽象类 & 接口:区别、应用场景。
非常:非常体系、自定义非常。
注解:注解的基本概念、分类、编译时注解 & 运行时注解。
类加载的过程。
泛型:分类、通配符 & 上下边界、泛型擦除。
反射:使用。
动态代理
NIO:Android NIO 系列教程(一) NIO概述-CSDN博客


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

王海鱼

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表