java platform se binary已停止工作_JAVA面经(1)

本文详细探讨了Java内存模型,包括线程池、垃圾收集、内存区域、对象分配、同步机制以及异常处理。讲解了线程的状态转换、线程安全、JVM的类加载过程以及线程池的运作原理。此外,还讨论了Java中的锁优化、并发工具如synchronized和Lock的对比,以及数据结构如B+树在数据库索引中的应用。最后,涉及了动态规划、数据库分页、网络协议等方面的知识。
摘要由CSDN通过智能技术生成

JVM 有哪些分区?答案:程序计数器,Java 虚拟机栈,本地方法栈,堆,方法区(Java

栈中存放的是一个一个的栈帧,每一个栈帧对应一个被调用的方法。栈帧包括局部变量表, 操作数栈,方法的返回地址,指向当前方法所属的类的运行时常量池的引用,附加信息)。

JVM 中只有一个堆。方法区中最重要的是运行时常量池。

mysql 使用的引擎?

A:1)MyIsam 不支持事务,适用于选择密集型,插入密集型,mysql 默认的引擎

2)innodb 使用于更新密集型,支持事务,自动灾难恢复,行级锁,外键约束

  1. memory 出发点是速度 采用的逻辑存储介质是内存
  2. merge 一组 myisam 表的组合

linux 查看文件内容的命令

Cat:从第一行开始显示内容,并将所有内容输出

Tac:从最后一行开始显示内容,并将所有内容输出

Head:只显示前几行

Tail:只显示后几行

nl:和 cat 一样,只是 nl 要显示行号

线程池 ThreadPoolExecutor

corepoolsize:核心池的大小,默认情况下,在创建了线程池之后,线程池中线程数为 0, 当有任务来之后,就会创建一个线程去执行任务,当线程池中线程数达到 corepoolsize 后, 就把任务放在任务缓存队列中。

Maximumpoolsize:线程池中最多创建多少个线程。

Keeplivetime:线程没有任务执行时,最多保存多久的时间会终止,默认情况下,当线程 池中线程数>corepoolsize 时,Keeplivetime 才起作用,直到线程数不大于 corepoolsize。

workQueue:阻塞队列,用来存放等待被执行的任务

threadFactory:线程工厂,用来创建线程。

继承关系

Executor 接口

ExecutoServicer 接口

AbstractExecutoServicer 抽象类

ThreadPoolExecutor

线程池的状态

1.当线程池创建后,初始为 running 状态

2.调用 shutdown 方法后,处 shutdown 状态,此时不再接受新的任务,等待已有的任务执行完毕

3.调用 shutdownnow 方法后,进入 stop 状态,不再接受新的任务,并且会尝试终止正在执行的任务。

4.当处于 shotdown 或 stop 状态,并且所有工作线程已经销毁,任务缓存队列已清空,线程池被设为 terminated 状态。

当有任务提交到线程池之后的一些操作:

1.若当前线程池中线程数<corepoolsize,则每来一个任务就创建一个线程去执行。

  1. 若当前线程池中线程数>=corepoolsize,会尝试将任务添加到任务缓存队列中去,若添 加成功,则任务会等待空闲线程将其取出执行,若添加失败,则尝试创建线程去执行这个任务。
  2. 若当前线程池中线程数>= Maximumpoolsize,则采取拒绝策略(有 4 种,1)abortpolicy 丢弃任务, 抛出 RejectedExecutionException 2 ) discardpolicy 拒绝执行, 不抛异常 3 )

discardoldestpolicy 丢弃任务缓存队列中最老的任务,并且尝试重新提交新的任务 4)

callerrunspolicy 有反馈机制,使任务提交的速度变慢)。

生产者-消费者代码-blockingqueue 实现

1cf256dbf1c8a56a9145d1d055d47ff4.png

a30968da07bc8584fbca8f50b9c28f2c.png

6edda5bea9abe7ab6a3c306b63435d2c.png

GC

对象是否存活

1)引用计数法 缺点:很难解决对象之间循环引用的问题。

2)可达性分析法 基本思想:通过一系列的称为“GC roots”的对象作为起始点,从这些节点,开始向下搜索,搜索所走过的路径称为引用链,当一个对象到 GC root 没有任何引用链相连(用图论的话来说,就是从 GC roots 到这个对象不可达),则证明此对象是不可用的。

可作为 GC roots 的对象

1)java 虚拟机栈(栈帧中的本地变量表)中引用的对象

2)方法区中类的静态属性引用的对象

3)方法区中常量引用的对象

4)本地方法栈中 JNI 引用的对象

引用强度 强引用>软引用>弱引用>虚引用

任何一个对象的 finalize()方法都只会被系统调用一次。

若对象在进行可达性分析后发现没有与 GC roots 相连接的引用链,那么他将会被第一次标记并进行一次筛选,筛选的条件是该对象是否有必要执行 finalize()方法,当对象没有重写

finalize()方法或者 finalize()方法已经被虚拟机调用过,虚拟机将这两种情况都视为没必要执行。

若该对象被判定为有必要执行 finalize 方法,则这个对象会被放在一个 F-Queue 队列,

finalize 方法是对象逃脱死亡命运的最后一次机会,稍后 GC 将对 F-queue 中的对象进行第二次小规模的标记,若对象要在 finalize 中成功拯救自己—只要重新与引用链上的任何一个对象建立关联即可,那么在第二次标记时他们将会被移出“即将回收”集合。

Finalize 方法不是 c 或 c++的析构函数。

停止-复制算法:它将可用内存按照容量划分为大小相等的两块,每次只使用其中一块。当这一块的内存用完了,则就将还存活的对象复制到另一块上面,然后再把已经使用过的内存空间一次清理掉。商业虚拟机:将内存分为一块较大的 eden 空间和两块较小的 survivor 空间,默认比例是 8:1:1,即每次新生代中可用内存空间为整个新生代容量的 90%,每次使用 eden 和其中一个 survivour。当回收时,将 eden 和 survivor 中还存活的对象一次性复制到另外一块 survivor 上,最后清理掉 eden 和刚才用过的 survivor,若另外一块 survivor 空间没有足够内存空间存放上次新生代收集下来的存活对象时,这些对象将直接通过分配担保机制进入老年代。

标记-清除算法:缺点 1)产生大量不连续的内存碎片 2)标记和清除效率都不高

标记-清理算法: 标记过程和“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清除,而是让 all 存活对象都向一端移动,然后直接清理掉端边界以外的内存。

分代收集:新生代 停止-复制算法 老年代 标记-清理或标记-清除垃圾收集器,前 3 个是新生代,后 3 个是老年代

  1. serial 收集器:单线程(单线程的意义不仅仅说明它会使用一个 cpu or 一条垃圾收集

线程去完成垃圾收集工作,更重要的是在它进行垃圾收集的时候,必须暂停其他 all 工作线程,直到他收集结束)。对于运行在 client 模式下的虚拟机来说是个很好的选择。停止-复制

  1. parNew 搜集器:serial 收集器的单线程版本,是许多运行在 server 模式下的虚拟机首选的新生代收集器。停止-复制
  2. parallel scaverge:目标 达到一个可控制的吞吐量,适合在后台运算,没有太多的交互。停止-复制。
  3. serial old:serial 的老年代版本,单线程,标记-清理
  4. parallel old:parallel scaverge 老年代的版本,多线程 标记-清理
  5. cms 收集器:一种以获取最短回收停顿时间为目标的收集器 “标记-清除”,有 4 个过程初始标记(查找直接与 gc roots 链接的对象) 并发标记(tracing 过程) 重新标记(因为并发标记时有用户线程在执行,标记结果可能有变化) 并发清除 其中初始标记和重新标记阶段,要“stop the world”(停止工作线程)。优点:并发收集,低停顿 缺点:1)不能处理浮动垃圾 2)对 cpu 资源敏感 3)产生大量内存碎片 {每一天具体的详解请看《深入理解 Java 虚拟机:JVM 高级特性与最佳实践》}

对象的分配:1)大多数情况下,对象在新生代 eden 区中分配,当 Eden 区中没有足够的

内存空间进行分配时,虚拟机将发起一次 minor GC {minor gc:发生在新生代的垃圾收集动作,非常频繁,一般回收速度也比较快 full gc:发生在老年代的 gc} 2)大对象直接进入老年代 3)长期存活的对象将进入老年代 4)若在 survivor 空间中相同年龄 all 对象大小的总和 >survivor 空 间 的 一 半 , 则 年 龄 >= 改 年 龄 的 对 象 直 接 进 入 老 年 代 , 无 须 等 到

MaxTeuringThreshold(默认为 15)中的要求。

空间分配担保

在发生 minor gc 前,虚拟机会检测老年代最大可用的连续空间是否>新生代 all 对象总空间,若这个条件成立,那么 minor gc 可以确保是安全的。若不成立,则虚拟机会查看

HandlePromotionFailure 设置值是否允许担保失败。若允许,那么会继续检测老年代最大可用的连续空间是否>历次晋升到老年代对象的平均大小。若大于,则将尝试进行一次 minor

gc,尽管这次 minor gc 是有风险的。若小于或 HandlePromotionFailure 设置不允许冒险,则这时要改为进行一次 full gc。

单例模式

1)双重检测

2)枚举实现

630f23e76b5f41b46e57fe4cb53417c5.png

3)静态内部类的

Out of Memory

1.程序计数器是唯一一个在 Java 虚拟机规范中没有规定任何 oom 情况的区域。

2.在 java 虚拟机规范中,对于 java 虚拟机栈,规定了 2 中异常,1)若线程请求的栈深度> 虚拟机所允许的深度,则抛出 Stack Overflowerror 异常 2)若虚拟机可以动态扩展,若扩展时无法申请到足够的内存空间,则抛出 oom 异常。

  1. java 虚拟机栈为执行 java 方法,本地方法栈为虚拟机使用 native 方法服务,本地方法栈也会抛出 Stack Overflowerror 和 oom。
  2. Java 堆可以处于物理上连续的内存空间,只要逻辑上是连续的即可。可固定,可扩展。

若堆中没有内存完成实例分配,并且堆也无法再扩展,则会抛出 oom。

bc245ed3848b8120b37ae48869334e5f.png

5.直接内存不是运行时数据区的一部分。堆上的 OOM 测试

Java 内存模型和线程

每个线程都有一个工作内存,线程只可以修改自己工作内存中的数据,然后再同步回主内存,主内存由多个内存共享。

下面 8 个操作都是原子的,不可再分的:

  1. lock:作用于主内存的变量,它把一个变量标识为一个线程独占的状态。
  2. unlock:作用于主内存的变量,他把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
  3. read:作用于主内存变量,他把一个变量的值从主内存传输到线程的工作内存,以便随后的 load 操作使用。
  4. load:作用于工作内存的变量,他把 read 操作从主内存中得到的变量值放入工作内存的变量副本中。
  5. use:作用于工作内存的变量,他把工作内存中一个变量的值传递给执行引擎,每 当虚拟机遇到一个需要使用到变量的值得字节码指令时将会执行这个操作。
  6. assign:作用于工作内存的变量,他把一个从执行引擎接收到的值付给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
  7. store:作用于工作内存的变量,他把工作内存中一个变量的值传送到主内存中,以便随后 write 使用。
  8. write:作用于主内存的变量,他把 store 操作从工作内存中得到的变量的值放入主内存的变量中。

关键字 volatile 是轻量级的同步机制。

Volatile 变量对于 all 线程的可见性,指当一条线程修改了这个变量的值,新值对于其他线程来说是可见的、立即得知的。

Volatile 变量在多线程下不一定安全,因为他只有可见性、有序性,但是没有原子性。

线程的状态

在任意一个时间点,一个线程只能有且只有其中的一种状态

1)新建:创建后尚未启动的线程处于这种状态。

2)运行:包括了 OS 中 Running 和 Ready 状态,也就是处于次状态的线程可能正在运行, 也可能正在等待 cpu 为他分配执行时间。

3)无限期等待:处于这种状态的线程不会被分配 cpu 执行时间,要等待其他线程显示唤醒。以下方法会让线程进入无限期等待 :1.没有设置 timeout 的 object.wait()方法 2.没有设置 timeout 参数的 Thread.join()方法 3.LockSupport.park();

4)有限期的等待:处于这种状态的线程也不会被分配 cpu 执行时间,不过无需等待被其他线程显示唤醒,而是在一定时间后,他们会由 os 自动唤醒 1.设置了 timeout 的

object.wait() 方法 2. 设 置 了 timeout 参数的 Thread.join() 3.LockSupport.parkNanos()

  1. LockSupport.parkUnit() 5)阻塞:线程被阻塞了与“等待状态”的区别是:阻塞状态在等待获取一个排它锁,

这个事件将在另外一个线程放弃这个锁的时候发生。等待状态在等待一段时间或者唤醒动

作。

6)结束:已终止线程的线程状态,线程已经结束执行。

synchronized(S) VS lock(L)

1) L 是接口,S 是关键字

2)S 在发生异常时,会自动释放线程占有的锁,不会发生死锁。L 在发生异常时,若没有主动通过 unlock()释放锁,则很有可能造成死锁。所以用 lock 时要在 finally 中释放锁。

3)L 可以当等待锁的线程响应中断,而 S 不行,使用 S 时,等待的线程将会一直等下去, 不能响应中断。

4)通过 L 可以知道是否成功获得锁,S 不可以。

5)L 可以提高多个线程进行读写操作的效率。

mysq 索引使用的是 B+的数据结构

索引:用于提高数据访问速度的数据库对象。

优点:1)索引可以避免全表扫描;2)对于非聚集索引,有些查询甚至可以不访问数据项;3)聚集索引可以避免数据插入操作集中于表的最后一个数据页;4)一些情况下,索引还可以避免排序。

虽然索引可以提高查询速度,但是他们也会导致数据库更新数据的性能下降,因为大部分数据更新时需要同时更新索引。

聚集索引:数据按索引顺序存储,叶子节点存储真实的数据行,不再有另外单独的数据页。在一张表上只能创建一个聚集索引,因为真实数据的物理顺序只能有 1 种,若一张表没有聚集索引,则他被称为堆集,这样表的数据行无特定的顺序,所有新行将被添加到表的末尾。

非聚集索引与聚集索引的区别:

1)叶子节点并非数据节点

2)叶子节点为每一个真正的数据行存储一个“键-指针”对

3)叶子节点中还存储了一个指针偏移量,根据页指针及指针偏移可以定位到具体的数据行。

4)在除叶节点外的其他索引节点,存储的是类似内容,只不过是指向下一级索引页。类加载

类从加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括: 加载-验证-准备-解析-初始化-使用-卸载,其中验证-准备-解析称为链接。

在遇到下列情况时,若没有初始化,则需要先触发其初始化(加载-验证-准备自然需要在此之前):

1)1.使用 new 关键字实例化对象 2.读取或设置一个类的静态字段 3.调用一个类的静态方法。

2)使用 java.lang.reflect 包的方法对类进行反射调用时,若类没有进行初始化,则需要触发其初始化

3)当初始化一个类时,若发现其父类还没有进行初始化,则要先触发其父类的初始

化。

4)当虚拟机启动时,用户需要制定一个要执行的主类(有 main 方法的那个类),虚拟机会先初始化这个类。

在加载阶段,虚拟机需要完成下面 3 件事:

1)通过一个类的全限定名获取定义此类的二进制字节流;

2)将这个字节流所表示的静态存储结构转化为方法区运行时数据结构

3)在内存中生成一个代表这个类的 class 对象,作为方法区的各种数据的访问入口。

验证的目的是为了确保 clsss 文件的字节流中包含的信息符合当前虚拟机的要求,且不会危害虚拟机自身的安全。验证阶段大致会完成下面 4 个阶段的检验动作:1)文件格式验证 2)元数据验证 3)字节码验证 4)符号引用验证{字节码验证将对类的方法进行校验分析,保证被校验的方法在运行时不会做出危害虚拟机的事,一个类方法体的字节码没有通过字节码验证,那一定有问题,但若一个方法通过了验证,也不能说明它一定安全}。

准备阶段是正式为类变量分配内存并设置变量的初始化值得阶段,这些变量所使用的内存都将在方法区中进行分配。(不是实例变量,且是初始值,若 public static int a=123;准备阶段后 a 的值为 0,而不是 123,要在初始化之后才变为 123,但若被 final 修饰,public static final int a=123;在准备阶段后就变为了 123)

解析阶段是虚拟机将常量池中的符号引用变为直接引用的过程。

静态代码块只能访问在静态代码块之前的变量,在它之后的变量,在前面的静态代码块中可以复制,但是不可以使用。

通过一个类的全限定名来获取定义此类的二进制字节流,实现这个动作的代码就是

“类加载器”。

比较两个类是否相同,只有这两个类是由同一个类加载器加载的前提下才有意义,否则即使这两个类来源于同一个 class 文件,被同一个虚拟机加载,只要加载他们的加载器不同,他们就是不同的类。

从 Java 虚拟机的角度来说,只存在两种不同的类加载器:一种是启动类加载器,这个类加载器使用 c++实现,是虚拟机自身的一部分。另一种就是所有其他的类加载器,这些类加载器都由 Java 实现,且全部继承自 java.lang.ClassLoader。

从 JAVA 开发人员角度,类加载器分为:

1)启动类加载器,这个加载器负责把<HAVA_HOME>lib 目录中或者 –Xbootclasspath

下的类库加载到虚拟机内存中,启动类加载器无法被 Java 程序直接引用。

2)扩展类加载器:负责加载<HAVA_HOME>libext 下或者 java.ext.dirs 系统变量指定路径下 all 类库,开发者可以直接使用扩展类加载器。

3)应用程序类加载器,负责加载用户路径 classpath 上指定的类库,开发者可以直接使用这个类加载器,若应用程序中没有定义过自己的类加载器,一般情况下,这个就是程序中默认的类加载器。

双亲委派模型:若一个类加载器收到了类加载请求,它首先不会自己去尝试加载这个类,而是把所这个请求委派给父类加载器去完成,每一层的加载器都是如此,因此 all 加载请求最终都应该传送到顶级的启动类加载器。只有当父类加载器反馈自己无法加载时(他的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。

双亲委派模型好处:eg,object 类。它存放在 rt.jar 中,无论哪个类加载器要加载这个类,最终都是委派给处于模型顶端的启动类加载器加载,因此 object 类在程序的各种加载环境中都是同一个类。

线程安全与锁优化 东西太多了,直接见《深入理解 Java 虚拟机:JVM 高级特性与最佳实践》最后一章。

同步:多个线程并发访问共享数据时,保证共享数据在同一个时刻,只被一个(or 一些,使用信号量)线程使用,而互斥是实现同步的一种手段,临界区,互斥量,信号量都是主要的互斥实现方式。互斥是因,同步是果;互斥是方法,同步是目的。

公平锁:(先申请先得到)多个线程在等待同一个锁时,必须按照申请锁的时间顺序来依次获取,而非公平锁则不保证这一点。Synchronized 不是公平锁,reetrantlock 默认下也是非公平的,但是在构造函数中,可以设置为公平的。

互斥同步对性能最大的影响是阻塞的实现,挂起线程和恢复线程的需要转入内核态中完成。若物理机器上有一个以上的处理器,能让 2 个 or2 个以上的线程同时并行执行,我们就可以让后面请求锁的那个线程“稍等一下”,但不放弃处理器的执行时间,看看持有锁的线程是否很快就释放锁,为了让线程等待,我们只需要让他执行一个忙循环(自旋),这项技术就是所谓的自旋锁。

自适应自旋锁的自旋时间不再固定,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态决定。

tcp 如何保证传输的可靠性?tcp 是面向连接,可靠的字节流服务。

面向连接意味着两个使用 tcp 的应用(通常是一个客户端和一个服务器)在彼此交换数据之前必须先建立一个 tcp 连接。在一个 tcp 连接中,仅有两方进行彼此通信,广播和多播不能用于 tcp。

Tcp 通过下列方式提供可靠性:

1)将应用数据分割为 tcp 认为最合适发送的数据块;

2)超时重传:当 tcp 发出一个段后,他启动

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值