自制java面试宝典-从深层次理解

Posted Fire king

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了自制java面试宝典-从深层次理解相关的知识,希望对你有一定的参考价值。

  • 面向对象的理解

    面向对象–》面向过程

    完成同一件事需要拆分成多个步骤,对于面向过程,每个函数就是一个步骤,而对于面向对象,他将多个步骤封装在一个对象里面,然后多个对象协同完成这件事。

  • 面向对象的三大特性:封装、继承和多态。

    封装:为了遵守对象内部定义规则不被外部显示调用破坏而属性私有化,对外界提供唯一公共全局访问点,比如说set方法,set方法可以在方法体内对属性的赋值定义规则,外界参数和规则共同决定属性。

    继承:继承是相对于父类和子类来说的,继承自同一父类的子类都继承了父类同一套属性和方法,也可以说父类是抽取所有子类共同的特征形成的

    多态:

  • Integer i=10发生自动装箱,底层使用Integer.ValueOf(10)返回一个缓存数组中一个由10作为决定数组index的因素之一位置的引用,由于源码中自动装箱的范围是-128~128不包括-128和128,所以Integer i=128底层会new一个Integer类型对象;==两边之一为基本数据类型,那么引用类型那边就会发生自动拆箱,进行值的比较而不是地址的比较。

  • List有序可重复,set无序不可重复。

  • ArrayList线程不安全,效率高,常用;Vector线程安全,效率低。

  • ArrayList和LinkedList的比较:

    数据结构:ArrayList连续的内存空间,LinkedList双向链表

    ArrayList按索引查找较快,因为连续的内存空间使得可以计算偏移量,但按值查找跟LinkedList一样需要遍历;LinkedList中间插入快,不用像ArrayList需要移动后面元素,但尾部插入两者差不多,一个可以计算偏移量,一个有尾指针直接指向尾部;存储1000个元素ArrayList占内存较少,LinkedList除了数据还有指针;ArrayList扩容大小为原数组的1.5倍。

  • Hashtable线程安全,它将整个数据结果上锁;HashMap线程不安全,可通过Collections的synchronizedMap方法使HashMap具有线程安全能力,相当于Hashtable;ConcurrentHashMap采用分段锁;开发时局部变量,非多线程访问,采用HashMap,否则,ConcurrentHashMap。数据结构属于对象,在堆中,线程公有。

  • 锁对象头(threadid),初始空,线程访问资源时设为线程id,另一线程取自己id比较,阻塞,自旋(轮询),第一次阻塞,锁从偏向锁升级轻量级锁,多次为重量级锁,内存可见性,线程copy资源到自己的内存(CAS和TLAB),修改线程私有内存资源值,不会去直接修改公有的主存,为了保证数据的一致性,synchronized和volatile分别做了不同的实现,synchronized在同步到主存时就会发生线程阻塞,这也算是同步访问资源,是线程安全的,这样也保证了另一线程访问数据的一致性,总的来说线程阻塞保证了原子性,而volatile也能在修改数据后即使同步到主存保证了可见性,但在修改的过程并未同步到主存之前其他线程仍然能够访问,因此不能够保证原子性(对long和doouble可以提供);可联想操作系统中分页的主存和快表。(代码是指令,对象是资源)

  • 操作数->零地址->操作数放内存?->cpu频繁读内存操作数运算写到内存->maybe写到主存->影响执行效率->数据向计算靠拢(Hadoop计算向数据靠拢,计算本地化)->读少->HotSpot JVM的设计者提出了栈顶缓存技术->将栈顶元素全部缓存在物理CPU的寄存器中,证明了零地址中操作数是放在寄存器中(零地址也有可能是无需操作数),计算机组成原理:指令16位,操作数6位,有零地址M种、一地址N种和二地址的指令,那么二地址的就有2的(16-6-6)-M-N=16-M-N种,根据二地址指令需要12位作为两个操作数地址算出操作数占的位置。

  • 伪共享:操作同一cacheLine的不同数据就必须读取这一缓存行。线程1的Cpu核1修改cacheLine1的x,先读(产生一次RFO),就会通知其他Cpu核(其他线程)说指向该三级共享高速缓存失效,产生一次RFOhttps://blog.csdn.net/WitsMakeMen/article/details/18360085,线程2的Cpu核2写cacheLine1的y,就必然要读取这一缓存行,因此必须强制Cpu核1写会cacheLine1的x(MESI协议),保证线程2的Cpu核2获取x一致性,产生一次RFO,读缓存行,产生一次RFO,写会修改了的y缓存行,产生一次RFO,严重影响程序性能。解决:缓存行填充(Padding),让不同线程操作的对象处于不同的缓存行,减少RFO,https://blog.csdn.net/z735640642/article/details/84554706。

  • Collections是一个工具类,sort是其中的静态方法,是用来对List类型进行排序的:数组排序

  • 对象锁,顾名思义,只有创建对象才能加的锁=》不能锁static修饰的方法或变量以及代码块;具体锁的资源有:锁住同一个变量的方法块共享同一把锁、用 synchronized(this) 方式的方法都共享同一把锁,加在非静态方法返回类型前。由于同一类可以有多个对象,每个对象都有自己的堆内存地址和引用,加在某个对象的锁不影响其他对象,因此说,即使是多个线程操作同一个方法也不会产生竞争从而自旋,但多个线程可以访问同一个对象的非synchronized修饰,非synchronized(this的倍数 ) 的资源。对象分对象头、实例数据部分以及填充部分:对象要填充数据 部分补足数据使对象大小为;对象头分为数组对象头和普通对象头,普通对象头 :markword(8byte)和classpointer(指向对象的元数据,4byte ),对于markword,new出来时第一个byte后三位全是0,使用synchronized(this)会把三个0中的其中一个变为1,因此锁的是对象。

  • 2.4f单精度,2.4双精度,short i = 1要强制转换,s1+=1隐含强制类型转换(会将结果类型强制转换为持有结果的类型,可能会出现截断高位导致精度丢失)

  • 字符串字面量在编译的时候存在于常量池,加载到jvm落地后会以对象的形式存在于堆中,而常量池中的符号引用落地为实际引用,一些原本指向字符串字面量的符号引用就转化为字符串常量池中字符串对象的物理地址。

  • 12为模,2:00,-4=>逆时针4或顺时针12-4=8达到同样效果;4+8=12=>4与8互补=>某个点x做某种操作y欲达同样效果有两种方法:x正向操作y,或x反向操作(规定的模-y),例如定义-为正向操作,+为反向操作,在10进制2位数加减法中(结果忽略百位),原本10-80=10+(100-80),但10-80=-70,而后者为30,效果不同,但为了符合规律,就认为-70=30,即用整数30表达负数-70+但|-70|+30=100=>一个正数代表的负数通过模-该正数再加一个负号即可,那么问题又来了30本身意义就是个正数又代表负数,不可:099中049代表真正意义的正数,而5099代表各自补数的负值。同理模为2^8的8位2进制数,共有2^8个数包括0,0127代表真正意义的正数,128255代表各自补数的负值即-1-128。参考

  • 线程引用.run不会另起线程,就不会从运行时数据区中挖取线程栈、pc、本地方法栈,只会当成是普通方法使用

  • 动态语言是运行时才会确定类型,静态语言在编译时编译器会检查变量类型,不匹配会报错

  • 成员变量无论是基本数据类型还是引用数据类型都是在类实例化后才分配内存,故它会随类实例化于堆中存在(除了静态变量,常量),方法中的局部变量一般都是存在局部变量表中,因此在栈中。

  • 新编写的类=》编译时jvm创建该类的类对象存于字节码中=》保存类信息,对象有内存,用来封装数据,而类对象在方法区中=》有内存,保存类的信息,何尝不是一种对象,不要狭义理解对象,万物皆对象,人也是,人的特征,能力等等何尝不是数据。所有实例对象共享同一个类对象。

  • 泛型保证类型安全是通过将运行期间的错误提早到编译时期错误,这样的话,开发人员能更快更高效地改正错误,比如在一个ArrayList中,它是可以将任意基本数据类型自动装箱成对应包装类去存储的,如果里面有int、double类型的,增强for循环遍历时你期望取出int类型,但double类型自动拆箱的时候并不是int类型,这时就报错了。

  • 注意repalceAll和replace的区别

  • 构造方法不能被static修饰的原因:static修饰的方法服务于类,在类加载的时候就分配内存了,但是构造方法总是与对象关联的,与其在类加载的时候去浪费内存,不如在动态调用也就是new对象的时候调用更好。

  • 三种回收垃圾的算法:

    1.标记-清除算法:标记存活,清除垃圾,造成内存碎片过多=》大对象很难找到合适的内存地址+维护空闲列表

    2.复制算法:内存空间增加一倍,两个分区,复制存活对象到另一个分区有序排序=》不用维护空闲列表,可能采用指针碰撞,但空间消耗多了一倍,适合存活对象不多的时候,否则复制存在消耗

    3.标记-压缩算法:折中了前面的两种,标记存活,,清除垃圾,挪动存活对象排序到一边,,清除另一边,指针碰撞,但对象的引用就要变。

  • 分代搜集算法,另一种回收垃圾的算法

    1.年轻代:老年代 = 1:2 年轻代中eden区:sur0:sur1 = 8:1:1,新生代中对象命短,朝生夕死,存活率低=》复制算法(存活率低,复制开销小),虽说1:1中使用复制算法会浪费空间,但是整体上浪费的只有1/3*1/10=1/30的内存空间。

    2.老年代:Mark标记:开销来自通过root遍历对象数量成正比,Sweep清除范围是整个堆为标记的对象,而不是仅仅通过root遍历,这就是他的开销,Compact压缩的开销来自于存活对象个数,成正比。算法:Mark+Sweep,速度快=》内存碎片=》Mark Compact算法中的Serial Old执行FullGC内存整理。

  • 解决了上述算法的stop the word挂起所有线程,用户体验不好

    标记-清除算法和复制算法的升级版-增量收集算法:因此交替进行。

    分区算法:将整个堆划分为相同的region,根据设置的stop the word的时间决定回收多少

  • System.gc()(底层就是Runtime.getRuntime.gc())只是提醒jvm进行垃圾收集,具体是否调用垃圾收集器完全看jvm,而System.runFinalization()强制jvm进行垃圾收集,垃圾收集的对象会调用它重写的finalize()方法,记住,gc的时候一般是gc没有引用指向的,没有引用指向的才算垃圾

  • 同一个方法中的方法块中new一个对象有变量指向,本以为出了代码块后变量的作用域失效,相当于没有指引,用System.gc()能够回收,但是局部变量表有占据位置虽不显示,这时可以在代码块下面定义一个形变量取代它占有的位置并且可以显示在局部变量表,就可以回收了,因为局部变量表都没有这个变量了,虚拟栈都没有了,对象自然就没有指引了。

  • 内存溢出,无法使用也无法回收;OOM的底牌fullGc,OOM:无空闲内存,垃圾收集器无法通过垃圾收集提供更多内存。原因:堆内存设置不够;代码创建太多对象,长时间存在引用不能被收集:老板orcal jdk对永久代常量池回收不积极

  • 内存泄漏(对象不需要使用但无法回收=》有指引,常见原因:1,。static引用指引,生命周期和类一样2.一些数据库连接资源不关闭)与可达性分析有关,而不是引用计数

  • 数组的length()得到的是字符而不是字节

  • 包装类equals先比较类型再比较值

  • suspend()阻塞,不会自动恢复,resume调用才可以,配套使用

  • final修饰的变量表示赋值之后不能再进行更改,系统赋默认值也算赋值,因此系统也不会赋默认值。因此可以显示赋值或者在构造函数里面赋值

  • int无法转换为string类型

  • request.getAttribute()返回request范围内的对象,而request.getParameter()是获取http传过来的数据

  • 存根类是一个类,它实现了一个接口,它的作用是:如果一个接口有很多方法,如果要实现这个接口,就要实现所有的方法。但是一个类从业务来说,可能只需要其中一两个方法。如果直接去实现这个接口,除了实现所需的方法,还要实现其他所有的无关方法。而如果通过继承存根类就实现接口,就免去了这种麻烦。

    RMI 采用stubs 和 skeletons 来进行远程对象(remote object)的通讯。stub
    充当远程对象的客户端***,有着和远程对象相同的远程接口,远程对象的调用实际是通过调用该对象的客户端***对象stub来完成的。

    每个远程对象都包含一个对象stub,当运行在本地Java虚拟机上的程序调用运行在远程Java虚拟机上的对象方法时,它首先在本地创建该对象的对象stub,
    然后调用***对象上匹配的方法。每一个远程对象同时也包含一个skeleton对象,skeleton运行在远程对象所在的虚拟机上,接受来自stub对象的调用。这种方式符合等到程序要运行时将目标文件动态进行链接的思想。

  • 静态变量不能放到方法里面

  • Callable接口提供call()方法,堪比run方法,Feture接口代表Callable接口里call方法的的返回值,并且提供了一个实现类FutureTask(实现了Future接口和实现了Runnale接口可以作为Thread类的target)。

  • jdk、jre和jvm三者的区别?

    jdk包含jre,jdk的安装目录里面有jre的目录,jdk是java开发环境的缩写,而jre是java运行环境的缩写,jre包含jvm(对应bin目录),还有lib(类库)。

  • sleep、wait、yield和join的区别?

    先谈谈sleep和wait的区别,sleep是相对于本线程而言,而wait用于多线程之间的通信,sleep是thread类静态本地方法,而wait是Object类的本地方法,sleep不会让出锁,也就意味着不会是当前线程进入锁池(锁池中的线程都是没有锁的,要去竞争的);sleep不需要与synchronized配套使用,而wait需要;sleep不需要唤醒,而wait需要;sleep强制上下文切换让出cpu,而wait还是可以竞争锁运行。

    yield是是线程让出cpu进入就绪状态,但还是可以还是可以被调度。

    join,在某个线程内调用其他线程的join,会使当前线程阻塞,等待其他线程完成后再运行,经典例子:在main线程中调用其他线程的join会使main线程进入阻塞状态,知道另一个线程完成,即使其他线程使用sleep方法。

  • 静态方法为什么不能修饰构造方法?

    两个层面:

    1.java:static修饰的可以被继承,而构造方法不能被继承;倘若可以被继承,那么就违背了java面向对象三大特型之一的封装性:private只能本类访问,继承后可以在子类中实例化访问。

    2.jvm:jvm规定对象的实例化分配内存是在类加载后的,而静态方法在类加载的时候就会执行,这时候如果创建对象分配内存就与jvm的规定背道而驰。

mysql篇:

  • b树索引弊端,data和key都在节点上,倘若data很大,key必然很少,树的深度就越深,io次数越多,查询性能越低

  • innodb每次读取数据都是4的整数倍

  • 覆盖索引:where name='zhangsanand ’ and age=10

    ​ where age=10

    ​ where age=10 and name=‘zhangsan’

    组合索引(name,age)和普通索引age

    或者组合索引(age,name)和普通索引name

    两者组合索引无区别,但是普通索引age和name的长度不同,越小一个结点能容纳的key越多,树越胖,io次数越少。

  • 表字段设置小一点,varchar类型长度不用太长,枚举好过文本类型,join,事务保证一致性

  • 有关where和left join的区别:

    left join在根据条件筛选后再补全

    where筛选

    因此二者会出现记录条数不一致

  • mybatis一级缓存是相对同一个sqlsession来说的,同一个sqlsession调用执行的sql第一次会到数据库去查询,然后会缓存结果到内存,第二次如果sql相同就会直接到缓存去获取,但这仅仅作用于同一个sqlsession,相对于不同的sqlsession即使是同一个sql也会从数据库中去查找;而二级缓存正好解决了这个问题,它是全局的,它在mybatis的全局配置文件中开启,解决了不同sqlsession之间相对于同一个sql不能从缓存中获取的弊端。

  • 雪崩效应:服务依赖导致一个服务奔溃与它相关联的一连串服务崩溃,解决:服务隔离(多个接口一个服务,分离并发量高的接口和并发量低的接口);服务熔断:牺牲局部,保全整体;服务降级:通常是与服务熔断使用,在服务熔断后返回一些值给客户端。

    模拟Jmeter高并发:在服务提供者设置tomcat线程数量为10(用完就回收),意为着服务消费者最多能接受10条线程的并发量,然后jmter向获取商品的url发送2500的请求,然后服务提供者的根据商品id对应的请求的url就只有一个请求,由于两个接口都在同一个服务中,根据商品id对应的请求就需要很久才能响应。

    解决:1.请求缓存:获取商品缓存到redis,只有第一次调用商品微服务,之后直接从redis中获取。这时去请求根据商品id对应商品就很快。2.请求合并:将大量并发访问单个接口合并成调用一次批量接口,访问服务的次数也将由多次变为1次,可设置多久,多少个请求合并一次。缺点第一个请求延迟响应。服务熔断:相对于一个服务来说。服务降级:相对于系统整体来说,舍弃一些不重要的部分保住核心部分。

    hashmap四种遍历:

    keySet方法获取key集合,遍历key集合获取value

    entrySet方法获取键值对集合的迭代器再遍历

    entrySet方法获取键值对集合获取key和value

    value方法获取value

  • 为什么说MyISAM比INNODB查询,插入的效率快?

    究其根本:INNODB要维护的东西比MyISAM多

    那就来谈谈他们两者的区别:

    1.首先是INNODB支持事务,说到事务就要谈到事务隔离级别,保证读已提交级别是通过一个read-view的东西来保证的,read-view记录了当前活跃事务id,也就是未提交事务。然后去根据这个read-view去版本链中找到最新的不活跃事务修改提交的记录(版本链-针对每条记录,上有活跃事务和不活跃事务修改的记录,每条记录都有记录修改它的事务的事务id),如果说是可重复读级别的话,在同一个事务未结束之前,保留第一次查询时创建的read-view,也就是说新的已提交事务也会“变相”地认为是活跃事务,就自然而然地只能读到之前第一次读取的记录版本。因此说支持事务为了保证隔离级别就会在查询的时候使用read-view去比较就会造成效率上的性能开销。

    2.INNODB要缓存数据块,myisam只缓存索引块,而数据块远比索引块大得多,而且内存与硬盘数据的交换并不是以块作为单位,而是以扇区作为单位,数据块越大,扇区个数越多,获取到指定的记录的时间越长。

    3.在回表操作时,INNODB根据普通索引获取到主键再到聚簇索引树中去映射到叶子结点,由于多叉树,还要映射到具体每一行,而myisam不需要回表,直接使用offset到磁盘获取具体记录。

  • Innodb不保存表的具体行数,select count(*) from 表的时候需要全表扫描,而myisam会用一个变量缓存起来,innode不设置变量缓存是因为事务的不同隔离级别要求获取的表行数不一样。

  • 如何选择两种引擎:是否支持事务;只读就用myisam,而读写就用Innodb,myisam锁的粒度太大,影响效率

  • 如何理解事务的执行不能破坏数据库数据的完整性和一致性(如果数据库系统在运行过程中发生故障,有些事务尚未完成就被迫中断,这些未完成的事务对数据库所作的修改有一部分已写入物理数据库,这是数据库就处于一种不正确的状态,也就是不一致的状态)?

    完整性:域完整性(保证对对应字段值的修改是有效值),实体完整性:主键不能为空或者不能重复,参照完整性:外键约束。事务影响数据库完整性一般针对修改和插入来说的。

  • acid:原子性,一致性,隔离性,持久性。

  • 对称加密:服务端和移动端使用同一个公钥加密解密,造成的风险是只要拿到公钥,就可以解密,但是非对称加密(RSA):服务端和移动端使用不同密钥加密解密,使用公钥加密就得使用私钥解密,反之亦是如此。公钥一般是公开的,而私钥是保存在本地的,同时公钥和密钥一般成对存在。

以上是关于自制java面试宝典-从深层次理解的主要内容,如果未能解决你的问题,请参考以下文章

自制java面试宝典-从深层次理解

自制面试宝典

Java核心面试宝典Day16“计算机网络协议层次及服务类型”面试题!✊✊✊

Java核心面试宝典Day16“计算机网络协议层次及服务类型”面试题!✊✊✊

[Interview]Java 面试宝典系列之 Spring Boot

Java程序员面试宝典