每天50个JAVA八股~(六)

1、Java 中你怎样唤醒一个阻塞的线程?

Java 发展史上曾经使用 suspend()、resume()方法对于线程进行阻塞唤醒,但随之出现很多问题,比较典型的还是死锁问题。

解决方案可以使用以对象为目标的阻塞,即利用 Object 类的 wait()和 notify()方法实现线程阻塞

wait、notify 方法是针对对象的,调用任意对象的 wait()方法都将导致线程阻塞,阻塞的同时也将释放该对象的锁,相应地,调用任意对象的 notify()方法则将随机解除该对象阻塞的线程,但它需要重新获取改对象的锁,直到获取成功才能往下执行;其次,wait、notify方法必须在synchronized 块或方法中被调用,并且要保证同步块或方法的锁对象与调用 wait、notify 方法的对象是同一个,如此一来在调用 wait 之前当前线程就已经成功获取某对象的锁,执行 wait 阻塞后当前线程就将之前获取的对象锁释放。

2、在 Java 中 CycliBarriar 和 CountdownLatch 有什么区别?

CyclicBarrier 可以重复使用,而 CountdownLatch 不能重复使用。

3、什么是不可变对象,它对写并发应用有什么帮助

不可变对象(Immutable Objects)即对象一旦被创建它的状态(对象的数据,也即对象属性值)就不能改变

不可变对象永远是线程安全的。只有满足如下状态,一个对象才是不可变的;它的状态不能在创建后再被修改;所有域都是 final 类型

4、Java 中用到的线程调度算法是什么?

计算机通常只有一个 CPU,在任意时刻只能执行一条机器指令,每个线程只有获得CPU 的使用权才能执行指令.

所谓多线程的并发运行 各个线程轮流获得 CPU 的使用权,分别执行各自的任务.在运行池中,会有多个处于就绪状态的线程在等待 CPU,JAVA 虚拟机的一项任务就是负责线程的调度,线程调度是指按照特定机制为多个线程分配 CPU 的使用权

5、什么是线程组,为什么在 Java 中不推荐使用?

线程组和线程池是两个不同的概念,他们的作用完全不同,

线程组是为了方便线程的管理,

线程池是为了管理线程的生命周期,复用线程,减少创建销毁线程的开销。

6、java中会存在内存泄漏吗

会。自己实现堆载的数据结构时有可能会出现内存泄露,可参看effective java.

7、64 位 JVM 中,int 的长度是多数?

int 类型变量的长度是一个固定值,与平台无关,都是 32 位。

在 32 位 和 64 位 的 Java 虚拟机中,int 类型的长度是相同的。

8、Serial 与 Parallel GC 之间的不同之处?

Serial 与 Parallel 在 GC 执行的时候都会引起 stop-the-world。

主要不同 serial 收集器是默认的复制收集器,执行 GC 的时候只有一个线程,而parallel 收集器使用多个 GC 线程来执行

9、32 位和 64 位的 JVM,int 类型变量的长度是多数?

32 位和 64 位的 JVM 中,int 类型变量的长度是相同的,都是 32 位或者 4个字节。

10、Java 中 WeakReference 与 SoftReference 的区别?

 WeakReference 与 SoftReference 都有利于提高 GC 和 内存的效率,但是 WeakReference ,一旦失去最后一个强引用,就会被 GC回收,而软引用虽然不能阻止被回收,但是可以延迟到 JVM 内存不足的时候。

11、JVM 选项 -XX:+UseCompressedOops 有什么作用?为什么要使用

当你将你的应用从 32 位的 JVM 迁移到 64 位的 JVM 时,由于对象的指针从32 位增加到了 64 位,因此堆内存会突然增加,差不多要翻倍。这也会对 CPU缓存(容量比内存小很多)的数据产生不利的影响。

因为,迁移到 64 位的 JVM主要动机在于可以指定最大堆大小,通过压缩 OOP 可以节省一定的内存。通过-XX:+UseCompressedOops 选项,JVM 会使用 32 位的 OOP,而不是 64 位的 OOP。

12、怎样通过 Java 程序来判断 JVM 是 32 位 还是 64位?

你可以检查某些系统属性如 sun.arch.data.model 或 os.arch 来获取该信息。

13、32 位 JVM 和 64 位 JVM 的最大堆内存分别是多数?

理论上说上 32 位的 JVM 堆内存可以到达 2^32,即 4GB,但实际上会比这个小很多。

不同操作系统之间不同,如 Windows 系统大约 1.5 GB,Solaris 大约3GB。

64 位 JVM 允许指定最大的堆内存,理论上可以达到 2^64,这是一个非常大的数字,实际上你可以指定堆内存大小到 100GB。

14、JRE、JDK、JVM 及 JIT 之间有什么不同?

JRE 代表 Java 运行时(Java run-time),是运行 Java 引用所必须的。

JDK 代表 Java 开发工具(Java development kit),是 Java 程序的开发工具,如 Java编译器,它也包含 JRE

JIT 代表即时编译(Just In Time compilation),当代码执行的次数超过一定的阈值时,会将 Java 字节码转换为本地代码,如,主要的热点代码会被准换为本地代码,这样有利大幅度提高 Java 应用的性能。

15、解释 Java 堆空间及 GC?

通过 Java 命令启动 Java 进程的时候,会为它分配内存。内存的一部分用于创建堆空间,当程序中创建对象的时候,就从对空间中分配内存。GC 是 JVM 内部的一个进程,回收无效对象的内存用于将来的分配

16、JVM 内存区域

JVM 内存区域主要分为

线程私有区域【程序计数器、虚拟机栈、本地方法区】

线程共享区域【JAVA 堆、方法区】

直接内存。

线程私有数据区域生命周期与线程相同, 依赖用户线程的启动/结束 而 创建/销毁(在 Hotspot VM 内, 每个线程都与操作系统的本地线程直接映射, 因此这部分内存区域的存/否跟随本地线程的生/死对应)。

17、程序计数器(线程私有)

一块较小的内存空间, 是当前线程所执行的字节码的行号指示器,每条线程都要有一个独立的程序计数器,这类内存也称为“线程私有” 的内存。

正在执行 java 方法的话,计数器记录的是虚拟机字节码指令的地址(当前指令的地址)

18、程序计数器(线程私有)

一块较小的内存空间, 是当前线程所执行的字节码的行号指示器,每条线程都要有一个独立的程序计数器,这类内存也称为“线程私有” 的内存。

正在执行 java 方法的话,计数器记录的是虚拟机字节码指令的地址(当前指令的地址) 。如果还是 Native 方法,则为空。这个内存区域是唯一一个在虚拟机中没有规定任何 OutOfMemoryError 情况的区域。

19、虚拟机栈(线程私有)

是描述java方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。 每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。

栈帧( Frame)是用来存储数据和部分过程结果的数据结构,同时也被用来处理动态链接(Dynamic Linking)、 方法返回值和异常分派( Dispatch Exception)。 栈帧随着方法调用而创建,随着方法结束而销毁——无论方法是正常完成还是异常完成(抛出了在方法内未被捕获的异常)都算作方法结束。

20、本地方法区(线程私有)

本地方法区和 Java Stack 作用类似, 区别是虚拟机栈为执行 Java 方法服务, 而本地方法栈则为Native 方法服务, 如果一个 VM 实现使用 C- linkage 模型来支持 Native 调用, 那么该栈将会是一个C 栈,但 HotSpot VM 直接就把本地方法栈和虚拟机栈合二为一 。

21、你能保证 GC 执行吗?

不能,虽然你可以调用 System.gc() 或者 Runtime.gc(),但是没有办法保证 GC的执行。

22、怎么获取 Java 程序使用的内存?堆使用的百分比?

可以通过 java.lang.Runtime 类中与内存相关方法来获取剩余的内存,总内存及最大堆内存。

Runtime.freeMemory() 方法返回剩余空间的字节数,

Runtime.totalMemory()方法总内存的字节数,

Runtime.maxMemory() 返回最大内存的字节数。

23、Java 中堆和栈有什么区别?

JVM 中堆和栈属于不同的内存区域,使用目的也不同。

栈常用于保存方法帧和局部变量,

而对象总是在堆上分配。

栈通常都比堆小,不会在多个线程之间共享,

堆被整个 JVM 的所有线程共享。

24、描述一下 JVM 加载 class 文件的原理机制

JVM 中类的装载是由类加载器(ClassLoader)来实现的,Java 中的类加载器是一个重要的 Java 运行时系统组件,它负责在运行时查找和装入类文件中的类。

由于 Java 的跨平台性,经过编译的 Java 源程序并不是一个可执行程序,而是一个或多个类文件。当 Java 程序需要使用某个类时,JVM 会确保这个类已经被加载、连接(验证、准备和解析)和初始化。

类的加载是指把类的.class 文件中的数据读入到内存中,通常是创建一个字节数组读入.class 文件,然后产生与所加载类对应的 Class 对象。

加载完成后,Class 对象还不完整,所以此时的类还不可用。当类被加载后就进入连接阶段,这一阶段包括验证、准备(为静态变量分配内存并设置默认的初始值)和解析(将符号引用替换为直接引用)三个步骤。

最后 JVM 对类进行初始化,包括:

1)如果类存在直接的父类并且这个类还没有被初始化,那么就先初始化父类;

2)如果类中存在初始化语句,就依次执行这些初始化语句。

类的加载是由类加载器完成的,类加载器包括:

根加载器(BootStrap)、扩展加载器(Extension)、系统加载器(System)和用户自定义类加载器(java.lang.ClassLoader 的子类)。

加载器的说明:

Bootstrap:一般用本地代码实现,负责加载 JVM 基础核心类库(rt.jar);

Extension:从 java.ext.dirs 系统属性所指定的目录中加载类库,它的父加载器是 Bootstrap;

System:又叫应用类加载器,其父类是 Extension。它是应用最广泛的类加载器。它从环境变量 classpath 或者系统属性

java.class.path 所指定的目录中记载类,是用户自定义加载器的默认父加载器。

25、GC 是什么?为什么要有 GC

GC是垃圾收集的意思,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃, Java 提供 的 GC 功能 可以自动监测对象是否超过作用域从而达到自动回收内存的目的

垃圾回收可以有效的防止内存泄露,有效的使用可以使用的内存。垃圾回收器通常是作为一个单独的低优先级的线程运行,不可预知的情况下对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收,程序员不能实时的调用垃圾回收器对某个对象或所有对象进行垃圾回收。

26、堆(Heap-线程共享) -运行时数据区

是被线程共享的一块内存区域, 创建的对象和数组都保存在 Java 堆内存中,也是垃圾收集器进行垃圾收集的最重要的内存区域。 

27、方法区/永久代(线程共享)

用于存储被 JVM 加载的类信息、 常量、 静态变量、 即时编译器编译后的代码等数据. 

运行时常量池(Runtime Constant Pool)是方法区的一部分。 Class 文件中除了有类的版本、字段、方法、接口等描述等信息外,还有一项信息是常量池 (Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。

28、JVM 运行时内存

Java 堆从 GC 的角度还可以细分为:

新生代(Eden 区、 From Survivor 区和 To Survivor 区)

老年代

29、新生代

是用来存放新生的对象。一般占据堆的 1/3 空间。由于频繁创建对象,所以新生代会频繁触发MinorGC 进行垃圾回收。

30、老年代

主要存放应用程序中生命周期长的内存对象。

老年代的对象比较稳定,所以 MajorGC 不会频繁执行。在进行 MajorGC 前一般都先进行了一次 MinorGC,使得有新生代的对象晋身入老年代,导致空间不够用时才触发。当无法找到足够大的连续空间分配给新创建的较大对象时也会提前触发一次 MajorGC 进行垃圾回收腾出空间

31、永久代

指内存的永久保存区域,主要存放 Class 和 Meta(元数据)的信息,Class 在被加载的时候被放入永久区域, 它和和存放实例的区域不同,GC不会在主程序运行期对永久区域进行清理。

所以永久代的区域会随着加载的 Class 的增多而胀满,最终抛出 OOM 异常。

32、JAVA8 与元数据

在 Java8 中, 永久代已经被移除,被一个称为“元数据区”(元空间)的区域所取代。

元空间的本质和永久代类似,元空间与永久代之间最大的区别在于: 元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制。

33、引用计数法

Java 中,引用和对象是有关联的。

如果要操作对象则必须用引用进行。

一个简单的办法是通过引用计数来判断一个对象是否可以回收。

即一个对象如果没有任何与之关联的引用, 即他们的引用计数都不为 0,说明对象不太可能再被用到,那么这个对象就是可回收对象。

34、可达性分析

为了解决引用计数法的循环引用问题, Java 使用了可达性分析的方法。

通过一系列的“GC roots”对象作为起点搜索。

如果在“GC roots”和一个对象之间没有可达路径,则称该对象是不可达的。

35、标记清除算法( Mark-Sweep)

最基础的垃圾回收算法,分为两个阶段,标注和清除。标记阶段标记出所有需要回收的对象,清除阶段回收被标记的对象所占用的空间。

该算法最大的问题是内存碎片化严重,后续可能发生大对象不能找到可利用空间的问题

36、复制算法(copying)

为了解决内存碎片化的缺陷被提出的算法。

按内存容量将内存划分为等大小的两块。每次只使用其中一块,当这一块内存满后将尚存活的对象复制到另一块上去,把已使用的内存清掉

这种算法虽然实现简单,内存效率高,不易产生碎片,但是最大的问题是可用内存被压缩到了原本的一半。且存活对象增多的话效率会大大降低。

37、标记整理算法(Mark-Compact)

结合了复制算法,标记清除算法,为了避免缺陷而提出。

标记阶段和 标记清除算法相同, 标记后不是清理对象,而是将存活对象移向内存的一端。然后清除端边界外的对象。

38、分代收集算法

分代收集法是目前大部分 JVM 所采用的方法,其核心思想是根据对象存活的不同生命周期将内存划分为不同的域,一般情况下将 GC 堆划分为老生代(Tenured/Old Generation)和新生代(YoungGeneration)。

老生代的特点是每次垃圾回收时只有少量对象需要被回收,新生代的特点是每次垃圾回收时都有大量垃圾需要被回收,可以根据不同区域选择不同的算法。

39、新生代与复制算法

大部分 JVM 的 GC 对于新生代都采取 Copying 算法,因为新生代中每次垃圾回收都要回收大部分对象,即要复制的操作比较少,但通常并不是按照 1: 1 来划分新生代。

40、老年代与标记复制算法

老年代因为每次只回收少量对象,因而采用 Mark-Compact 算法。

41、JAVA 强引用

在 Java 中最常见的就是强引用, 把一个对象赋给一个引用变量,这个引用变量就是一个强引用。

不可能被垃圾回收机制回收的,即使该对象以后永远都不会被用到 JVM 也不会回收。

因此强引用是造成 Java 内存泄漏的主要原因之一

42、JAVA软引用

只有软引用的对象来说,当系统内存足够时它不会被回收,当系统内存空间不足时它会被回收。

软引用通常用在对内存敏感的程序中

43、JAVA弱引用

比软引用的生存期更短,对于只有弱引用的对象来说,只要垃圾回收机制一运行,不管 JVM 的内存空间是否足够,总会回收该对象占用的内存

44、JAVA虚引用

不能单独使用,必须和引用队列联合使用。 虚引用的主要作用是跟踪对象被垃圾回收的状态。

45、分代收集算法

会根据对象存活周期的不同将内存划分为几块, 如 JVM 中的 新生代、老年代、永久代, 这样就可以根据各年代特点分别采用最适当的 GC 算法

46、在新生代-复制算法

每次垃圾收集都能发现大批对象已死, 只有少量存活.

因此选用复制算法, 只需要付出少量存活对象的复制成本就可以完成收集

47、在老年代-标记整理算法

因为对象存活率高、没有额外空间对它进行分配担保, 就必须采用“标记—清理”或“标记—整理” 算法来进行回收, 不必进行内存复制, 且直接腾出空闲内存

48、分区收集算法

分区算法则将整个堆空间划分为连续的不同小区间, 每个小区间独立使用, 独立回收. 这样做的好处是可以控制一次回收多少个小区间 , 根据目标停顿时间, 每次合理地回收若干个小区间(而不是整个堆), 从而减少一次 GC 所产生的停顿。

49、GC 垃圾收集器

Java 堆内存被划分为新生代和年老代两部分,新生代主要使用复制和标记-清除垃圾回收算法;年老代主要使用标记-整理垃圾回收算法

50、Serial 垃圾收集器(单线程、 复制算法)

Serial(英文连续) 是最基本垃圾收集器,使用复制算法,Serial 是一个单线程的收集器,它不但只会使用一个 CPU 或一条线程去完成垃圾收集工作,并且在进行垃圾收集的同时,必须暂停其他所有的工作线程,直到垃圾收集结束。

  • 17
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值