1、java引用的四种状态
强引用、软引用、弱引用、虚引用。
强引用
new一个Object存放在堆内存,然后用一个引用指向它,这个就是强引用。
如果一个对象具有强引用,那垃圾回收器不会回收它。当内存空间不足,java虚拟机宁愿抛出outOfMemoryError错误,使程序异常终止,也不会随意回收具有强引用的对象来解决内存不足的问题。
软引用
如果一个对象只具有软引用,则内存空间足够时,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。
只要垃圾回收器没有回收它,该对象就可以被程序使用。弱引用可用来实现内存敏感的高速缓存。
弱引用
只具有弱引用的对象拥有更短暂的生命周期。
每次执行GC的时候,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。
虚引用
"虚引用",形同虚设,与其他几种引用不同,虚引用并不会决定对象的生命周期,如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。
虚引用主要用来跟踪对象被垃圾回收器的活动。
2、java中的内存划分
2.1 程序计数器
内存空间小,线程私有。字节码解释器工作是就是通过改变这个计数器的值来选取下一条需要执行指令的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖计数器完成
如果线程正在执行一个 Java 方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是 Native 方法,这个计数器的值则为 (Undefined)。此内存区域是唯一一个在 Java 虚拟机规范中没有规定任何 OutOfMemoryError 情况的区域。
2.2 虚拟机栈
线程私有,生命周期和线程一致。描述的是 Java 方法执行的内存模型:每个方法在执行时都会床创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行结束,就对应着一个栈帧从虚拟机栈中入栈到出栈的过程。
局部变量表:存放了编译期可知的各种基本类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference 类型)和 returnAddress 类型(指向了一条字节码指令的地址)
StackOverflowError:线程请求的栈深度大于虚拟机所允许的深度。 OutOfMemoryError:如果虚拟机栈可以动态扩展,而扩展时无法申请到足够的内存。
2.3 本地方法栈
区别于 Java 虚拟机栈的是,Java 虚拟机栈为虚拟机执行 Java 方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。也会有 StackOverflowError 和 OutOfMemoryError 异常。
2.4 堆
对于绝大多数应用来说,这块区域是 JVM 所管理的内存中最大的一块。线程共享,主要是存放对象实例和数组。内部会划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer, TLAB)。可以位于物理上不连续的空间,但是逻辑上要连续。
OutOfMemoryError:如果堆中没有内存完成实例分配,并且堆也无法再扩展时,抛出该异常。
2.5 方法区
属于共享内存区域,存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
2.6 运行时常量池
属于方法区的一部分。
属于方法区一部分,用于存放编译期生成的各种字面量和符号引用。编译器和运行期(String 的 intern() )都可以将常量放入池中。内存有限,无法申请时抛出 OutOfMemoryError。
2.7 直接内存
非虚拟机运行时数据区的部分
在 JDK 1.4 中新加入 NIO (New Input/Output) 类,引入了一种基于通道(Channel)和缓存(Buffer)的 I/O 方式,它可以使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作。可以避免在 Java 堆和 Native 堆中来回的数据耗时操作。 OutOfMemoryError:会受到本机内存限制,如果内存区域总和大于物理内存限制从而导致动态扩展时出现该异常。
3、java对象在内存中的状态
3.1 可达的/可触及的
从根节点扫描,只要这个对象在引用链,就是可达的。
3.2 可恢复的
java对象不再被任何变量引用就进入了可恢复状态。
在回收该对象之前,该对象的finalize()方法进行资源清理。如果在finalize()方法中重新让变量引用该对象,则对象再次变为可达状态,否则改状态进入不可达状态。
3.3 不可达的
java对象不被任何变量引用,且系统在调用对象finalize()方法后,依然没有使该对象变成可达状态,则该对象变成不可达状态。
当java对象处于不可达状态时,系统才会真正回收该对象所占有的资源。
4.判断对象死亡的两种常用算法
4.1 引用计数算法
4.2 根搜索算法
5.垃圾回收算法
5.1 标记-清除算法
算法分为标记和清除两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。
不足:一个是效率问题,标记和清除两个过程的效率都不高,另一个是空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后再程序运行过程中需要分配较大的对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。 清除不是擦除,内容不会被马上清空,直到有新的的内容写入才会覆盖。
5.2 复制算法
它将可用内存按照容量划分为大小相等的两块,每次只使用其中的一块。当这块的内存用完了,就将还活着的对象复制到另一块上面,然后再把已使用过的内存空间一次清理掉。这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可。
不足:内存浪费。
实际中我们并不需要按照1:1比例来划分内存空间,如堆内存的新生代,将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中一块Survivor,当另一个Survivor空间没有足够空间存放上一次新生代收集下来的存活对象时,这些对象将直接通过分配担保机制进入老年代。
5.3 标记-整理算法
让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。 主要用在老年代中
5.4 分代收集算法
存活率低:青年代采用复制算法。 存活率高:标记清理/标记整理算法
6.垃圾收集器
6.1 Serial收集器
6.2 ParNew 收集器
6.3 ParNew Scanvenge 收集器
6.4 CMS收集器
6.5 G1 收集器
7.java堆内存划分
7.1 青年代
7.2 老年代
8.类加载机制
9.初始化顺序
10.JVM参数
10.1 java对象分配与vm调优
引用
https://blog.csdn.net/qq_41701956/article/details/81664921