一、内存模型
方法区包含什么
方法区存储于使用本地内存的元空间中
- 类的元信息:类的结构信息、字段信息、方法信息
- 常量池:字面量、符号引用、运行时常量池(存储类加载的解析阶段符号引用解析成的直接引用)
- 静态变量:
方法区中的方法的执行过程
- 类加载检查
- 创建栈帧,调用方法并处理返回值
为什么String是不可变的
- 字符串常量池的需要:创建一个String对象时,若该字符串已存在于常量池,则会直接引用已存在对象,减少JVM开销
- 数据安全:不可变对象是天生线程安全的,在多线程共享时不需要同步处理;同时,若字符串是可变的,可能会导致其他依赖该字符串的地方出现错误
- 缓存哈希:String的hashcode会将哈希值缓存于hash字段,当其作为HashMap的key时效率极高
String s = new String(“abc”)执行过程中分别对应哪些内存区域?
1. 字符串"abc"
构造方法传递一个字符串"abc",JVM会尝试拿字面量"abc"去字符串常量池获取对应String对象的引用。若没有对应引用则在堆中创建一个"abc"的String对象并将其引用保存到字符串常量池中。
2. new创建对象
new关键字将创建一个类的实例对象,创建的字符串对象在堆内存上
引用类型有哪些,有什么区别
- 强引用:永远不会被GC回收
Object obj = new Object();
- 软引用:在发生内存溢出前会进行回收;单内存足够时垃圾回收器不会回收
SoftReference<Object> softRef = new SoftReference<>(obj);
- 弱引用:下次垃圾回收时一定会被回收,不论内存是否充足
WeakReference<Object> weakRef = new WeakReference<>(obj);
- 虚引用:虚引用不能直接访问对象,它们只能通过 ReferenceQueue 接收对象被回收的通知。主要用于资源清理
Object obj = new Object(); // 创建对象
ReferenceQueue<Object> queue = new ReferenceQueue<>(); // 创建引用队列
PhantomReference<Object> phantomRef = new PhantomReference<>(obj, queue);
弱引用的应用场景
- 对象池:用于管理暂时不使用的对象。当对象不再被强引用时,它们可以被垃圾回收,释放内存
- 避免内存泄漏:当一个对象不应该被长期引用时,使用弱引用可以防止该对象被意外保留,从而避免潜在的内存泄漏风险
什么情况下会发生内存溢出
- 堆内存溢出:OutOfMemoryError:Java heap space异常,原因可能是存在大对象分配、存在太多未关闭的资源或发生内存泄漏
- 栈溢出:StackOuverFlowError异常
- 元空间溢出:OutOfMemoryError:Metaspace,原因可能是引用太多第三方包
什么是内存泄漏
- 内存泄漏指占用的内存无法被正确释放,导致资源的浪费
谈谈ThreadLocal导致的内存泄漏
- 堆中的ThreadLocalMap用于存放线程局部变量的键值对,其key是ThreadLocal的弱引用
- 当线程消亡时,其对应的ThreadLocal不再被强引用,会被垃圾回收器回收,但其value仍然存在于ThreadLocalMap中,Entry对象仍然强引用着Value
- 因此,建议使用ThreadLocal之后清理ThreadLocalMap(threadLocal.remove();)
内存泄漏的例子
- 静态属性导致内存泄漏:静态属性的生命周期通常伴随应用的整个生命周期
- ThreadLocal
二、类初始化和类加载
类加载检查
- 加载:通过类加载器读取类的字节码文件,并将其转换为一个 java.lang.Class对象
- 验证:确保字节码文件的格式正确,符合JVM的规范
- 准备:为类的静态变量分配内存,并设置默认初始值
- 解析:将类、接口、字段和方法的符号引用(用符号描述引用对象,间接定位目标)转换为直接引用(能够定位到目标对象)
- 初始化:执行类构造器<clinit>方法,完成静态变量的初始化
双亲委派模型
- 类加载器尝试加载类时,会先将请求委派给父加载器,只有当父加载器无法加载该类时,才会尝试自己加载
- 该机制避免类的重复加载
对象创建过程
- 类加载检查
- 分配内存
- 初始化对象:零初始化(将对象实例变量初始化为默认值)和字段初始化(执行类构造器/方法初始化对象的实例变量)
- 设置对象头
- 返回对象引用
三、垃圾回收
什么是Java垃圾回收机制
- 垃圾回收是一个自动管理内存的机制,负责自动释放不再被持续应用的对象所占的内存
- JVM有一个垃圾回收线程,它是低优先级的,只有虚拟机空闲或堆内存不足时才会执行
如何判断对象是否可以被回收
- 引用计数器法:为每个对象创建一个引用计数,计数为0时就可被回收。但无法解决循环引用问题
- 可达性分析法:从GC Root(垃圾收集根)开始遍历,追溯它们所引用的对象,不可达的判断为可回收
垃圾回收算法有哪些
- 标记-清除算法
先标记可访问对象,然后遍历堆回收未标记对象
应用场景:一般用于老年代,因为老年代对象生命周期较长
缺点:会造成内存碎片 - 标记-压缩算法
标记可访问对象后,让所有存活对象向一端移动,然后清除边界外的内存
优缺点:解决了内存碎片问题,但移动了可用对象需要更新引用 - 复制算法
将存活对象复制到一个新的对象空间中,然后清除原对象空间
一般用于新生代,Eden区与From Survivor区/To Survivor区 复制到 To Survivor区/From Survivor区
垃圾回收器有哪些
- Serial收集器(复制算法): 新生代单线程收集器,简单高效
- ParNew收集器 (复制算法): 新生代收并行集器,Serial收集器的多线程版本
- CMS(Concurrent Mark Sweep)收集器(标记-清除算法):老年代并行收集器,以获取最短回收停顿时间为目标的收集器,具有高并发、低停顿的特点,追求最短GC回收停顿时间。
- G1(Garbage First)收集器 (标记-整理算法): Java堆并行收集器,G1收集器是JDK1.7提供的一个新收集器,G1收集器基于“标记-整理”算法实现,也就是说不会产生内存碎片。此外,G1收集器不同于之前的收集器的一个重要特点是:G1回收的范围是整个Java堆(包括新生代,老年代),而其他收集器回收的范围仅限于新生代或老年代
CMS和G1的区别
- 使用范围不同
CMS收集器是老年代的收集器,可配合新生代的Serial和ParNew收集器一起使用
G1收集器的收集范围是新生代和老年代,不需要配合其他收集器使用 - 垃圾回收算法
CMS使用标记-清除算法,力求最小的停顿时间,但容易产生内存碎片
G1使用标记-整理算法,没有内存碎片 - 应用场景
CMS适用于有低延迟需求的场景
G1适用于需要管理大堆内存、对内存碎片敏感的场景
讲讲新生代和老生代
- 新生代和老生代
新生代:每次垃圾回收有大批对象死去,使用复制算法
老年代:对象存活率高,使用标记-清除算法 - 年龄增长的工作机制
虚拟机给每个对象定义了一个对象年龄计数器。如果对象在 Eden 出生并经过第一次 Minor GC 后仍然存活,并且能被 Survivor 容纳的话,将被移动到 Survivor 空间中,并将对象年龄设为 1。对象在 Survivor 区中每熬过一次 Minor GC,年龄就增加 1 岁,当它的年龄增加到一定程度(默认为 15 岁)时,就会被晋升到老年代中。
Minor GC、Major GC、Full GC的区别
Minor GC(Young GC)
- 作用范围:只对新生代进行回收,包括Eden区和Survivor区
- 触发条件:Eden区空间不足时
- 特点:发生频繁,回收效率高,STW时间短
Major GC
- 作用范围:主要针对老生代进行回收
- 触发条件:老年代空间不足,但新生代空间足够
Full GC
- 作用范围:整个堆内存(包括新生代、老生代、元空间)
- 触发条件:新生代和老生代都需要被回收时、老生代空间不足且新生代无法晋升对象时
- 特点:Full GC需要停止所有工作线程(Stop The World),遍历堆内存来查找和回收不再使用的对象,因此应尽量减少Full GC的触发
GC只会对堆进行GC吗
- 当发生Full GC时,会对堆和方法区进行回收
1342

被折叠的 条评论
为什么被折叠?



