java面试

Java基础

1.面向对象的三大特征

继承,  子类继承父类

多态    首先要有继承 然后要能重写方法  就是相同功能 子类父类不同的表现

封装   以对象的形式 封装一些动作,简单说就是方法 undo log mvcc

其实这些都是针对与面向过程而言的   增加了代码的重用,简化啊 等等·

2.抽象类和接口

首先抽象类也是类  接口是用interface关键字

抽象类就可以被继承,所以很多时候呢用抽象类来作为超类  类似于一种模板类  但是抽象类是不能实例化的 

接口一般是给其他实现来进行调用的  

非 abstract 类中 不能存在 abstract 方法

abstract 类中 能存在 非abstract 方法

2.1  String 基础

String x = "a"+"b"   编译器会优化 String x = "ab"   所以创建了1个对象

String x = new String("a"+"b")   

1.new String 对象

2. a   3.b  4 ab

String x = "a"+new String("b");  

1.a 2.new String() 3.b  4.new Stringbuilder   5 new String(“ab”)

 2.2   cache 基础

在比较整数值时,使用"=="运算符而不是equals()方法。但是 超过cache范围  就不行了

int a =127   Integer a = 127   a==b

double 127 Double b = 127  a==b?  true

3.java会存在内存泄漏吗

当然会,只是说java因为有jvm的管理,出现内存泄漏的情况会少一些,但是依旧是会出现的

最明显的一个例子就是io操作或者socket操作等等  我们在打开一个io流或者socket连接后,都会有个一个手动关闭的操作,就是close操作

这就是我们手动的去关闭这些流 而不是让jvm去回收,因为他可能回收不了

还有比如说线程池的shutdown  还有ThreadLocalMap 工具类,如果没有及时 remove  都会导致内存泄漏

对象被错误地持有

对象未被正确地释放

2.final finally finalize有什么不同

final 是一个修饰用的关键字 修饰变量不能背修改  修饰类不能背集成

finally 是try cacht 中处理资源关闭等的关键字

finalize  是一个方法 Object 里的放发

当对象变成(GC Roots)不可达时,GC会判断该对象是否覆盖了finalize方法,若未覆盖,则直接将其回收。否则,若对象未执行过finalize方法,将其放入F-Queue队列,由一低优先级线程执行该队列中对象的finalize方法。执行finalize方法完毕后,GC会再次判断该对象是否可达,若不可达,则进行回收,否则,对象“复活”。

强引用 软引用 弱引用 幻象引用的区别

不同引用之间的可达性和对于垃圾收集时的影响

强引用就是标记这个对象是活着的,是垃圾回收可以被触碰的,如果这个对象被设置为了null,就是可以被收集的了

软引用,是一种相对弱化的引用,可以让对象豁免一些垃圾收集,只有OOM大的时候,才会收集软一用

弱引用,就是提供一种可以被访问的状态的引用,不能豁免垃圾收集,就是获取的时候如果能够获取到,就使用,不然就重现实例化

幻象引用,就是虚引用,不能通过这个访问对象,只是有一个再被finalize之后做些事情的机制

JVM  组成

  1. 类加载器(Class Loader) 类加载器负责将字节码文件加载到JVM中,并将其转换成可执行的Java类。JVM中有三种不同的类加载器:启动类加载器、扩展类加载器和应用程序类加载器。

        java 类加载机制主要有三个步骤   加载  链接  和初始化

其中加载阶段主要把文件加载成可执行java类

链接阶段主要有  验证 准备和解析

。类的初始化是指执行类中的静态代码块或静态变量初始化语句

(1)创建类的实例。

(2) 访问类的静态变量。

(3) 调用类的静态方法。

  1. 运行时数据区(Runtime Data Area) 运行时数据区是JVM中的核心部分,主要用于存储运行时数据。JVM的运行时数据区包括堆、栈、方法区(在JDK 1.8中已被元空间所取代)、程序计数器和本地方法栈等。

  2. 执行引擎(Execution Engine) 执行引擎是JVM的执行核心,负责执行字节码指令。JVM的执行引擎有两种模式:解释模式和编译模式。在解释模式下,JVM逐条解释执行字节码指令;而在编译模式下,JVM将字节码转换成本地代码,并直接在本地执行。

  3. 垃圾回收器(Garbage Collector) 垃圾回收器是JVM的重要组成部分,负责回收不再使用的对象所占用的内存。在JDK 1.8中,JVM使用了G1垃圾回收器。

  4. JNI接口(Native Interface) JNI接口是JVM与本地系统交互的接口,它允许Java程序调用本地系统中的函数和库。JVM提供了一组JNI函数,以便Java程序能够调用本地系统中的函数和库。

4.jvm内存回收机制

JVM调优汇总

    1. 优先调整堆的大小让服务器自己来选择

        2. 如果内存小于100M,使用串行收集器

        3. 如果是单核,并且没有停顿时间的要求,串行或JVM自己选择

        4. 如果允许停顿时间超过1秒,选择并行或者JVM自己选

        5. 如果响应时间最重要,并且不能超过1秒,使用并发收集器

        6. 4G以下可以用parallel,4-8G可以用ParNew+CMS,8G以上可以用G1,几百G以上用ZGC
 

为什么大内存要用G1?
如果堆内存达到了几十个G以上,那么用cms、parNew这些收集器,垃圾回收的时间会很长,想STW时间也会很长,用户体验很差。
G1是可以调整停顿时间的,去优先收集部分垃圾对象,并不是一次回收所有的垃圾,那这样用户体验就会很好。
但是G1不太适用于小内存,因为内存小,回收速度会很快,G1它只回收部分的垃圾,性价比就不如cms这些了,只有在大内存时才能体现它的价值。

最大  内存8G Xmx设置最大堆大小,而Xms设置初始堆大小

-Xmx8g -Xms8g -XX:MetaspaceSize=256 -XX:MaxMetaspaceSize=256 -Xss256K   -XX:+UseG1GC 

G1参数设置
-XX:+UseG1GC 手动指定使用G1收集器执行内存回收任务。
-XX:G1HeapRegionSize 设置每个Region的大小。值是2的幂,范围是1MB 到32MB之间,目标是根据最小的Java堆大小划分出约2048个区域。默认是堆内存的1/2000。
-XX:MaxGCPauseMillis 设置期望达到的最大Gc停顿时间指标(JVM会尽力实现,但不保证达到)。默认值是200ms
-xX:ParallelGCThread 设置sTw.工作线程数的值。最多设置为8
-XX:ConcGCThreads 设置并发标记的线程数。将n设置为并行垃圾回收线程数(ParallelGCThreads)的1/4左右。
-XX:Ini tiatingHeapOccupancyPercent 设置触发并发GC周期的Java堆占用率阈值。超过此值,就触发GC。默认值是45。

使用Java 8以后,关于元空间的JVM参数有两个:-XX:MetaspaceSize=N和-XX:MaxMetaspaceSize=N,对于64位JVM来说,元空间的默认初始大小是20.75MB,默认的元空间的最大值是无限。MaxMetaspaceSize用于设置metaspace区域的最大值,这个值可以通过mxbean中的MemoryPoolBean获取到,如果这个参数没有设置,那么就是通过mxbean拿到的最大值是-1,表示无穷大。
 

什么场景适合使用G1
        1. 50%以上的堆被存活对象占用  

        2. 对象分配和晋升的速度变化非常大

        3. 垃圾回收时间特别长,超过1秒

        4. 8GB以上的堆内存(建议值)

        5. 停顿时间是500ms以内
 

 JDK 1.8默认使用 Parallel(年轻代和老年代都是)

   JDK 1.9默认使用 G1

在JDK 1.8中,JVM的内存回收机制主要包括两个部分:堆内存和非堆内存。

  1. 堆内存回收机制 JVM的堆内存主要用于存储对象,当堆内存中的对象不再被引用时,JVM会通过垃圾回收机制自动回收这些对象所占用的内存。在JDK 1.8中,JVM使用了G1(Garbage-First)垃圾回收器,它可以自动识别哪些对象是垃圾,并且只回收这些垃圾对象所占用的内存。G1垃圾回收器的目标是在保证吞吐量的同时,尽可能地减少停顿时间。

Eden区是新生代中最大的区域,所有新创建的对象都会被放置在Eden区中。当Eden区满了之后,就会触发一次Minor GC,将其中的存活对象复制到Survivor区中。

Survivor区有两个,分别为From区和To区。在进行Minor GC的时候,存活的对象会被复制到To区中,然后清空From区,将From区和To区交换,使得下一次GC时可以继续利用原来的To区,这样做的目的是为了避免产生内存碎片。Survivor区中的对象经过多次Minor GC仍然存活的,最终会被晋升到老年代中。,Eden区的大小为整个新生代的8/10,Survivor区的大小为整个新生代的1/10,

  1. 非堆内存回收机制 JVM的非堆内存主要包括方法区和虚拟机栈等。方法区主要用于存储类信息、常量池等,虚拟机栈用于存储线程执行时的局部变量等信息。在JDK 1.8中,方法区被移除,取而代之的是元空间(Metaspace),它是使用本地内存而非Java堆内存存储类信息。对于元空间,JVM会通过ClassLoader卸载不再需要的类和相关的元数据,从而回收相关的内存。

总之,在JDK 1.8中,JVM的内存回收机制依然使用垃圾回收机制,通过回收不再使用的对象所占用的内存来保证程序的内存使用效率。同时,G1垃圾回收器和元空间的引入也使得JVM能够更加高效地处理内存回收问题。

4.4 内存回收算法

我觉得jvm内存回收机制 第一步需要确认哪些内存是需要被回收的,这里有几种算法,比如说计数,对象不可达等等的算法,知道哪些需要被回收后,就可以下一步回收了

1. 标记-清除算法(Mark-Sweep)     扫描2次,第一次标记所有需要回收的  第二次回收所有标记的。

2.  复制算法(Copying)        但是很明显这种回收机制效率不太行,需要扫描2次嘛  ,就有了第二种,就是直接不需要回收的对象从一个内存块复制出来,这样的 话一次扫描结束后,就把之前那个整个的给干掉

还是会有内存碎片 怎么办 

3.但是这样的情况也会有问题,就是需要的内存大 所以出现了第三种。  标记-整理(或叫压缩)算法(Mark-Compact)  对一些 无非就是把标记的放到一起 没标记的放一起 。

真正在作回收时 还是需要根据java对象不同的生命周期来判断的

新生代会有大量的对象需要回收,一般 内存复制,效率高

但是到了老年代或者持久代,内存都是获得比较久了 需要被换的少,所以会造成大量的内存浪费如果用复制的话,所以需要用标记整理

这样的话,既保证了新生代的效率,有保障了内存的使用

永久代(方法区):
用于存放静态文件,如今Java类、方法等。永久代代对垃圾回收没有显著影响,但是有些应
用可能动态生成或者调用一些class,例如Hibernate 等,在这种时候需要设置一个比较大的持
久代空间来存放这些运行过程中新增的类。持久代大小通过-XX:MaxPermSize=进行设置。


持久代补充:持久带也称为方法区


方法区:方法区存储每一个java类的结构信息:比如运行时常量池,字段和方法数据,构造函数和普通方法的字节码内容以及类、实例、接口初始化时需要使用到的特殊方法等数据。
方法区也被称为永久代,如果不显示指定的话,GC回收的目标仅针对方法区的常量池和类型卸载

1.7 常量池 和类的静态变量 转到了内存堆中


JDK8中已经把持久代(PermGen Space) 干掉了,取而代之的元空间(Metaspace)。Metaspace占用的是本地内存,不再占用虚拟机内存。

分代收集算法
当前虚拟机大部分采用,分代收集算法,这种算法并没有特别思想,只是根据对象的存活周期不同把内存划分为几块,一般是吧java堆分为新生代和老年代,这样就可以根据年代的特点采用不同的算法,提高效率,新生代每次垃圾回收都会有大量的对象死去,少量存活,那就用复制算法,老年代存活率较低,那就使用标记-清除,或标记-整理法
 

打开Jconsole控制台    cmd  jconsole

可以观察有几个区  我们主要观察的就是新生带的 edn区和s0 s1  和老年代

每一次edn区满了之后  进行垃圾回收,如果 还存活的  就一起放到空闲的s区

4.5双亲委派

类加载器的双亲委派  实际上就是把自己的类加载器委派给上层加载器  由父类先加载 如果父类加载过了 就不会继续加载  保证了类加载的安全 

当你重写部分源码的时候,保证类加载器不去加载

首先自己写的类  类加载器由应用程序类加载器 委托给父类    但是当存在系统类已经加载的情况  就不会加载自己写的类

双亲委派破坏

加载mysql驱动包  主要有一个DriverManager 

这个类又在jdk里面有  就需要破坏双亲  来实现spi

绑定app到线程上下文

5.ArrayList和hashMap底层

首先ArrayList 底层是一个简单数组进行存储的,使用数组让他的数据读取特别快,但是呢插入数据什么的就不行  所以如果有大量的插入动作最好不要用数组,用链表

然后ArrayList是有一个默认的容量 10  我们在用的时候最好根据情况,手动设置一哥参数,虽然ArrayList 在扩荣的时候 会根据当前的数量和之前的容量大小进行比较,如果大了会增加之前的一般 

底层是加上一哥右移一位 就是一半  然后把之前的数据copy到新的数组, 这样如果频繁的扩容,很明显就会慢很多,所以最好根据实际情况设置一哥默认大小 一步到位

然后hashmap   默认大小为16,负载因子0.75,阈值12   链表达到8时转化成红黑树或者增加数组长度大于(64)。

 其实也是一样 hashmap 来源于数据结构中的hash表  然后1.7之前呢采用  数组+链表来实现  数组用于存储key经过hash计算后值,链表用来存储value,然后用一个链表来解决线性冲突

而1.8采用数组+链表+红黑树来实现,原本之前版本的entry组 也变成了一个个node节点 方便树的操作    如果单个链表大于8了 他会去判断是不是 整个数组是不是大于64了 如果小于先扩容数组 若果超过64了 回去处理链表 重构成红黑树了

1.7之前采用的是头插发,会有各种问题 特别是并打的时候 重新调整大小的过程中  如果两个线程都要插入,那就会有问题就是A指向B  B指向A 因为都是头插嘛  这不是一个环了  下

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值