JVM内存模型
线程隔离的数据区:
-
程序计数器
较小的内存空间,当前线程执行的字节码的行号指示器。
由于 Java 是多线程语言,线程之间会根据时间片轮询争夺 CPU 资源。如果一个线程的时间片用完了,或者是其它原因导致这个线程的 CPU 资源被提前抢夺,那么这个退出的线程就需要单独的一个程序计数器,来记录下一条运行的指令。
内存区域中是唯一不会OOM的区域
-
虚拟机栈(子弹夹)
栈:First in last out
虚拟机栈在JVM运行过程中存储当前线程运行方法所需的数据,指令、返回地址。
栈里的每条数据,就是栈帧。在每个 Java 方法被调用的时候,都会创建一个栈帧,并入栈。一旦完成相应的调用,则出栈。Java 虚拟机栈是基于线程的,所有的栈帧都出栈后,线程也就结束了。
每个栈帧,都包含四个区域:局部变量表、操作数栈、动态连接、返回地址。
-
本地方法栈
本地方法栈跟 Java 虚拟机栈的功能类似,Java 虚拟机栈用于管理 Java 函数的调用,而本地方法栈则用于管理本地方法(Native)的调用。
线程共享的区域
-
方法区
方法区用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译期编译后的代码等数据。 运行时常量池是方法区的一部分 。
-
Java堆
堆是 JVM 上最大的内存区域,我们申请的几乎所有的对象,都是在这里存储的。我们常说的垃圾回收,操作的对象就是堆。
方法去存放静态数据,Java堆存放经常动态创建、回收的数据,便于提高垃圾回收效率。
2 垃圾回收
2.1 分带收集理论
虚拟机垃圾回收遵循分代收集理论。
- 绝大部分对象都是朝生夕死的,即创建一个对象,用完后,就可以被回收。
- 熬过多次垃圾回收的对象就越难回收。 根据以上两个理论,朝生夕死的对象放一个区域,难回收的对象放另外一个区域,这个就构成了新生代(1/3)和老年代(2/3)。
2.2 GC种类
- 新生代回收(Minor GC/Young GC):指只是进行新生代的回收。
- 老年代回收(Major GC/Old GC):指只是进行老年代的回收。
- 堆收集(Full GC):收集整个Java堆和方法区(注意包含方法区)。
2.3 垃圾回收算法
垃圾对象检测主要有两种算法:引用计数法和可达性分析法。
-
可达性分析法:
这个算法的基本思路就是通过一系列名为"GC Roots"的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。
-
引用计数法
引用计数法师垃圾收集的早期策略,在这中方法中,堆中每个对象都有一个引用计数,每当有一个地方引用他时,引用计数值就+1,当引用失效时,引用计数值就-1,任何时刻引用计数值为0的对象就是可以被回收,当一个对象被垃圾收集时,被它引用 的对象引用计数值就-1,所以在这种方法中一个对象被垃圾收集会导致后续其他对象的垃圾收集行动。
-
复制算法
将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。
复制算法实现简单,运行高效,内存复制,没有内存碎片。但是其内存利用率只有一半。
复制回收算法适合于新生代,因为大部分对象朝生夕死,那么复制过去的对象比较少,效率自然就高,另外一半的一次性清理是很快的。
-
标记-清除算法
算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。
回收效率不稳定,如果大部分对象是朝生夕死,那么回收效率降低,因为需要大量标记对象和回收对象,对比复制回收效率很低。
标记清除之后会产生大量不连续的内存碎片。
回收的时候如果需要回收的对象越多,需要做的标记和清除的工作越多,所以标记清除算法适用于老年代。
-
标记-整理算法
首先标记出所有需要回收的对象,在标记完成后,后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。标记整理算法虽然没有内存碎片,但是效率偏低。
标记整理与标记清除算法的区别主要在于对象的移动。对象移动不单单会加重系统负担,同时需要全程暂停用户线程才能进行,同时所有引用对象的地方都需要更新。
-
分代收集
新生代复制算法,老年代以标记整理算法为主。
3 String对象是如何创建的
在 Java 中,通常有两种创建字符串对象的方式:
-
一种是通过字符串常量的方式创建,如 String str=“abc”;
首先会检查该对象是否在字符串常量池中,如果在,就返回该对象引用,否则新的字符串将在常量池中被创建。这种方式可以减少同一个值的字符串对象的重复创建,节约内存。
-
另一种是字符串变量通过 new 形式的创建,如 String str = new String(“abc”)。
首先在编译类文件时,"abc"常量字符串将会放入到常量结构中,在类加载时,“abc"将会在常量池中创建;其次,在调用 new 时,JVM 命令将会调用 String 的构造函数,同时引用常量池中的"abc” 字符串,在堆内存中创建一个 String 对象;最后,str 将引用 String 对象。
String str = new String(“abc”).intern(); 如果调用 intern 方法,会去查看字符串常量池中是否有等于该对象的字符串的引用,如果没有会把首次遇到的字符串的引用添加到常量池中;如果有,就返回常量池中的字符串引用。
4 常见面试题
-
JVM内存结构说一下!
-
什么情况下内存栈溢出?
java.lang.StackOverflowError 如果出现了可能会是无限递归。
OutOfMemoryError:不断建立线程,JVM申请栈内存,机器没有足够的内存。
-
描述new一个对象的流程!
-
Java对象会不会分配在栈中?
可以,如果这个对象不满足逃逸分析,那么虚拟机在特定的情况下会走栈上分配。
-
如果判断一个对象是否被回收,有哪些算法,实际虚拟机使用得最多的是什么?
引用计数法和根可达性分析两种,用得最多是根可达性分析。
-
GC收集算法有哪些?他们的特点是什么?
复制、标记清除、标记整理。复制速度快,但是要浪费空间,不会内存碎片。标记清除空间利用率高,但是有内存碎片。标记整理算法没有内存碎片,但是要移动对象,性能较低。三种算法各有所长,各有所短。
-
JVM中一次完整的GC流程是怎样的?对象如何晋级到老年代? 对象优先在新生代区中分配,若没有足够空间,Minor GC; 大对象(需要大量连续内存空间)直接进入老年态;长期存活的对象进入老年态。 如果对象在新生代出生并经过第一次MGC后仍然存活,年龄+1,若年龄超过一定限制(15),则被晋升到老年态。
-
Java中的几种引用关系,他们的区别是什么?
-
强引用
一般的Object obj = new Object() ,就属于强引用。在任何情况下,只有有强引用关联(与根可达)还在,垃圾回收器就永远不会回收掉被引用的对象。
-
软引用 SoftReference
一些有用但是并非必需,用软引用关联的对象,系统将要发生内存溢出(OuyOfMemory)之前,这些对象就会被回收(如果这次回收后还是没有足够的空间,才会抛出内存溢出)。
-
弱引用 WeakReference
一些有用(程度比软引用更低)但是并非必需,用弱引用关联的对象,只能生存到下一次垃圾回收之前,GC发生时,不管内存够不够,都会被回收。
-
虚引用 PhantomReference
幽灵引用,最弱(随时会被回收掉) 垃圾回收的时候收到一个通知,就是为了监控垃圾回收器是否正常工作。
- final、finally、finalize的区别?
-
final可修饰类,方法和变量(成员变量或局部变量)。
- 修饰类时:表示类不能被继承,防止被篡改(所有的成员方法都会隐式的定义为final方法)。
- 修饰方法:把方法锁定,以防止继承类对其进行更改。
- 修饰变量:表示是常量,只能被赋值一次,赋值后其值不再改变。
-
finally
作为异常处理的一部分,它只能用在try/catch语句中,并且附带一个语句块,表示这段语句最终一定会被执行(不管有没有抛出异常),经常被用在需要释放资源的情况下。
-
finalize
Object中的Finalize方法,如果一个对象重写了该方法,在被GC回收后,会执行该方法,做回收资源等操作。此方法有很大的不确定性(不保证方法中的任务执行完)而且运行代价较高。对应C++中的析构函数。
-
String s = new String(“xxx”);创建了几个对象?
2个,
- 在一开始字符串"xxx"会在加载类时,在常量池中创建一个字符串对象。
- 调用 new时 会在堆内存中创建一个 String 对象,String 对象中的 char 数组将会引用常量池中字符串。