本文共 5511 字,大约阅读时间需要 18 分钟。
一、Java内存模型
系统的地址空间 可以划分为 内核空间 和用户空间。 内核空间主要是系统运行空间,包含连接系统硬件和调度程序 以及提供联网 和虚拟内存等服务C进程运行空间,而用户空间是Java程序实际运行时 所在内存空间。
JVM架构图如图所示,其中类加载器(Class Loader)负责依据特定格式,加载class文件到内存;执行引擎(Execution Engine)负责 对命令进行解析;本地库接口(Native Interface) 负责融合不同开发语言的原生库为Java所用。
而 Runtime Data Area 为 JVM中java运行内存部分。
将Java运行内存 从线程角度 可分为线程私有内存和线程共享内存。
程序计数器
1,程序计数器 当前线程所执行的字节码行号指示器(逻辑)
2,CPU改变计数器的值 来选取下一条需要执行的字节码指令
3,和线程是一对一的关系即“线程私有”
4, 对java方法计数, 如果是Native方法则计数器值为Undefined
5,不会发生内存泄漏
虚拟机栈
1,虚拟机栈Java方法执行的内存模型
2,虚拟机栈包含多个栈帧,其中每个方法执行 都会创建一个栈帧 并放入虚拟机栈, 当方法执行完后再将该栈帧移除。
3,一个栈帧包括 局部变量表、操作栈、动态连接、返回地址等
4,虚拟机栈 有大小限制,当超出最大栈深度后,会发生SOF异常
java.lang.StackOverflowError异常
原因 :递归过深,栈帧数 超过虚拟机栈深度
避免方法:限制递归的深度或使用循环代替递归操作
复制代码
5,虚拟机栈为线程私有,运行时结构图如下所示:
java.lang.OutOfMemoryError异常:虚拟机栈过多 引发
复制代码
本地方法栈
与虚拟机栈相似,主要作用是标注了Native的方法
方法区
方法区是JVM的一种规范,保存java类的相关信息(包括Method、Filed等)。元空间 和永久代 都是方法区的实现。
永久代(PermGen)
jdk8之前,使用永久代。永久代使用的是JVM内存。
但是从jdk 7开始,原先位于永久代中的字符串常量池被移入到堆内存中。
元空间(MetaSpace)
jdk8开始 使用元空间替代永久代。元空间使用堆内存。
元空间 相对永久代 具有如下优势:
1,字符串常量池 存在永久代中,容易出现性能问题和内存溢出。
2,类和方法的信息大小难以确定,给永久代的大小指定困难
3,永久代 会为GC带来不必要的复杂度
4,方便HotSpot与其他JVM 如Jrockit的集成
Java堆
1,虚拟机启动时创建,被所有线程共享,是对象实例的分配区域
2,GC管理的主要区域
java内存模型中 堆 和栈的联系和区别
联系:
引用对象、数组时,栈里定义变量保存堆中目标的首地址
区别:
1,管理方式:栈自动释放,堆需要GC
2,空间大小:栈比堆小
3,碎片化: 栈产生的碎片 远小于堆
4,分配方式: 栈支持静态和动态分配,而堆只支持动态分配
5,效率:栈的效率 比堆高
面试:intern()方法的作用
不同JDK版本之间 intern()方法的区别--JDK6 VS JDK6+
JDK6:
当调用intern()方法时,如果字符串常量池先前 已创建出该字符串对象,则返回池中的该字符串的引用。否则,将此字符串对象添加到字符串常量池中,并且返回该字符串常量对象的引用。
JDK6+:
当调用intern()方法时,如果字符串常量池先前已创建出该字符串常量,则返回池中的该字符串的引用。否则,如果该字符串对象已经存在Java堆中,则将堆中对此对象的引用添加到字符串常量池中,并且返回该引用;如果堆中不存在,则在池中创建该字符串并返回其引用。
Java内存分配策略
1,静态存储:编译时 确定每个数据目标在运行时的存储空间需求
2,栈式存储:数据区需求 在编译时未知, 运行时模块入口前确定
3,堆式存储:编译时或运行时模块入口都无法确定,只能运行时动态分配
二、Java垃圾回收机制
1,垃圾对象判断的标准
当虚拟机发现该对象没有被其他对象引用时,则将该对象判断为可以回收的垃圾对象。检测对象是否为被其他对象引用可以使用 引用计数法,和可达性分析算法。
引用计数法
1, 通过判断对象的引用数量 来决定对象 是否可以被回收
2, 每个对象实例都有一个引用计数器,被引用则+1,完成引用则-1
3, 任何引用计数为0的对象实例 可以被当作垃圾收集
优点:执行效率高,程序执行受影响较小
缺点:无法检测出 循环引用的情况,导致内存泄漏
可达性分析算法
通过判断对象的引用链是否可达 来决定对象是否可以被回收
可以作为GC root的对象:
1,虚拟机栈中引用的对象(栈帧中的本地变量表)
2,方法区中的常量引用的对象
3,方法区中的类静态属性引用的对象
4,本地方法栈中JNI(Native方法)的引用对象
5,活跃线程的引用对象
2,垃圾回收算法
标记-清除算法(Mark and Sweep)
标记清除算法分为标记和清除两个阶段:
标记阶段: 从根集合开始进行扫描,对存活的对象 进行标记
清除阶段:对堆内存从头到尾进行线性遍历,回收不可达对象内存
缺点
碎片化,造成内存不连续
复制算法(Copying)
步骤
1,将可用内存分为对象面和空闲面
2,对象在对象面上创建
3,存活的对象被从对象面 复制到空闲面
4,将对象面所有对象内存清除
优点
解决碎片化问题
顺序分配内存,简单高效
适用于对象存活率低的场景
缺点
不适合 对象存活率高的场景,会进行大量复制操作
可用空间减半
标记-整理算法(Compacting)
标记阶段: 从根集合进行扫描,对存活的对象进行标记
整理阶段: 移动所有存活的对象,且按照内存地址次序依次排列,然后将末端内存地址以后的内存全部回收
优点
1,避免内存的不连续行
2,不用设置两块内存互换
3,适用于存活率高的场景
分代收集算法(Generation Collector)
分代收集算法是以上垃圾回收算法的组合拳,按照对象生命周期的不同划分区域 以采用不同的垃圾回收算法。能够明显提高提高JVM的回收效率。
分代收集算法的GC 分为 Minor GC 和 Full GC。
Minor GC: 采用复制算法
Full GC: 采用标记清理算法,或标记整理算法, 老年代GC时,会触发对整个堆内存的垃圾回收
分代收集算法将内存区域分为年轻代、老年代(JDK8开始取消永久代,使用元空间代替)并使用不同的GC进行垃圾回收。
年轻代 (Young Generation)
1,将内存区域进一步划分为 Eden区,和2个Survivor区
2,采用Minor GC(复制算法)
3,尽可能快速地收集掉那么些生命周期短的对象
4,年轻代对象 可以晋升到老年代
老年代 (Old Generation)
1,存放生命周期较长的对象
2,使用Full GC(标记清理算法 或者标记整理算法)
年轻代对象如何晋升到老年代
1,经历一顶Minor GC次数 依然存活的对象
2,Survivo区中存放不下的对象
3,新生成的大对象 (-XX:+PretenuerSizeThreshold)
触发Full GC的条件
1,老年代 空间不足
2,永久代空间不足(jkd 8 之前)
3,CMC GC时 出现 promotion failed, concurrent mode failure
4,Minor GC晋升到老年代的平均大小 大于老年代的剩余空间
5,调用System.gc()
6,使用RMI来进行RPC 或管理的JDK应用,每小时执行1次 Full GC
相关面试题
finalize()方法
1,与C++的析构函数不同, 析构函数调用确定, 而finalize()调用不确定
2, JavaGC时 会对对象做两次标记,
第一次标记, 判断对象是否要执行finalize()方法,如果需要执行,就将对象放置于F-Queue队列中,并在稍后由虚拟机创建的一个低优先级的线程触发执行 该对象的finalize()方法。
3,该方法执行 随时可能被终止
4,作用,是给予对象最后一个重生的机会
Java中的引用
强引用(Strong Reference)
最普遍的引用;
抛出OOM异常时终止程序时,也不会回收具有强引用的对象;
通过将对象设置为null 来弱化引用,使其被回收
软引用(Soft Reference)
对象处在有用但非必须的状态;
只有当内存空间不足时, GC会回收该引用对象的内存;
可以用来实现高速缓存
弱引用(Weak Refrence)
非必须的对象, 比软引用更弱一些;
GC时会被回收;
被回收的概率也不大,因为GC线程的优先级比较低;
适用于引用偶尔被使用,且不影响垃圾收集的对象
虚引用(PhantomReference)
不会决定对象的生命周期;
任何时候都可能被垃圾收集器回收;
用来跟踪对象被垃圾收集器回收的活动,起哨兵作用;
必须和引用队列 ReferenceQueue联合使用, 在GC回收后,会将虚引用加入到 与之关联的ReferenceQueue中;
引用队列
1,无实际存储结构,存储逻辑依赖于内部节点之间的关系来表达
2,存储关联的且被GC的软引用、弱引用以及虚引用
引用队列 应用Demo
自定义实例类,以及弱引用类(方便记录和打印名字)
//被创建实例对象
public static class Dog {
public String name;
public Dog(String name) {
this.name = name;
}
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println(">>>finalized " + name);
}
}
复制代码//弱引用对象
public class MWeakRef extends WeakReference {
public String name;
public MWeakRef(Dog referent, ReferenceQueue super Dog> q) {
super(referent, q);
name = referent.name;
}
}
复制代码public class ReferenceDemo {
private static ReferenceQueue sRq = new ReferenceQueue<>();
public static void main(String[] args) throws InterruptedException{
ArrayList objects = new ArrayList<>();
for (int i = 0; i < 3; i++) {
objects.add(new MWeakRef(new Dog("dog " + i), sRq));
}
System.out.println("----------- first check -----------");
checkReferenceQueue();
System.out.println("----------- start gc -----------");
System.gc();
Thread.sleep(1000);
System.out.println("----------- second check -----------");
checkReferenceQueue();
}
private static void checkReferenceQueue() {
Reference extends Dog> ref;
while ((ref = sRq.poll()) != null) {
Dog dog = ref.get();
System.out.println("finalized wrf:" + ((MWeakRef) ref).name + " , ref to:" + dog);
}
}
}
复制代码
程序运行结果如下所示,第一次 检查引用队列时,弱引用对象并没有被回收,所以打印队列为空;然后调用System.gc(), 请求gc时,对象依次被回收,再次检查引用队列时,发现刚刚被回收的引用已被添加到引用队列中
----------- first check -----------
----------- start gc -----------
>>>finalized dog 2
>>>finalized dog 1
>>>finalized dog 0
----------- second check -----------
finalized wrf:dog 0 , ref to:null
finalized wrf:dog 2 , ref to:null
finalized wrf:dog 1 , ref to:null
Process finished with exit code 0
复制代码
完~
(如果纰漏,欢迎指出)
转载地址:https://blog.csdn.net/weixin_33865450/article/details/114140709 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!