OutOfMemoryError
。比如,new
一个对象 Student ,将对象 Student 通过=
(赋值运算符),赋值给变量 stu,则变量 stu 就强引用了对象 Student。
// 只要 stu 指向 Student 对象,那它就是强引用,永远都不会被 JVM 回收
Student stu = new Student();
// 如果将 stu 置为 null,可以切断 GC Root 引用链,这样 stu 就会被 JVM 回收
stu = null;
// 不直接通过 list 引用 byte[]
// list -----> SoftReference -----> byte[] 添加了一层软引用:
List<SoftReference<byte[]>> list = new ArrayList<>();
List<WeakReference<byte[]>> list = new ArrayList<>();
Unsafe.freeMemory()
)释放直接内存。finalize()
方法。当某个对象不再被其他的对象所引用时,会先将终结器引用对象放入引用队列中(被引用对象暂时没有被回收),然后根据终结器引用对象找到它所引用的对象,然后调用该对象的finalize()
方法。调用以后,该对象就可以被垃圾回收了。在回收软引用、弱引用所指向的对象时,软引用本身不会被清理。如果想要清理引用,需要使借助引用队列:
ReferenceQueue
,当一个引用(软引用、弱引用)关联到了一个引用队列后,当这个引用所引用的对象要被垃圾回收时,就会将它加入到所关联的引用队列中,所以判断一个引用对象是否已经被回收的一个现象就是,这个对象的引用是否被加入到了它所关联的引用队列。
ReferenceQueue<byte[]> queue = new ReferenceQueue<>();
SoftReference<byte[]> ref = new SoftReference<>(new byte[_4MB], queue);
说到底,引用队列就是一个对引用的回收机制,当软引用或弱引用所包装的对象为 null
或被回收时,这个引用也就不在具有价值,引用队列就是清除掉这部分引用的一种回收机制。
+1
,如果该对象被引用 2 次则其引用计数为 2,依次类推。-1
,当该对象的引用计数变为0 时,则表示该对象没用被其他变量所引用,这时候该对象就可以被作为垃圾进行回收。引用计数法弊端:循环引用时,两个对象的引用计数都为 1 ,导致两个对象都无法被释放回收。最终就会造成内存泄漏!
可达性分析算法就是JVM中判断对象是否是垃圾的算法:该算法首先要确定 GC Root (根对象,就是肯定不会被当成垃圾回收的对象)。
在垃圾回收之前,JVM会先对堆中的所有对象进行扫描,判断每一个对象是否能被 GC Root 直接或者间接的引用,如果能被根对象直接或间接引用则表示该对象不能被垃圾回收,反之则表示该对象可以被回收:
这样也可以避免内存碎片的问题,但是会占用双倍的内存空间。流程如下:
1/3
,老生代的默认占比是 2/3
。根据不同年代的特点采用最适当的收集算法。
8:1:1
。下面来逐步介绍一下分代收集算法的流程:
Minor GC 会将伊甸园和幸存区FROM仍需要存活的对象先复制到 幸存区 TO中, 并让其寿命加1,再交换FROM和TO。
流程相同,仍需要存活的对象寿命+1
:(下图中 FROM 中寿命为1的对象是新从伊甸园复制过来的,而不是原来幸存区 FROM 中的寿命为1的对象,这里只是静态图片不好展示,只能用文字描述了)
再次创建对象,若新生代的伊甸园又满了,则会再次触发 Minor GC(会触发 Stop The World, 暂停其他用户线程,只让垃圾回收线程工作),这时不仅会回收伊甸园中的垃圾,还会回收幸存区中的垃圾,再将活跃对象复制到幸存区TO中。回收以后会交换两个幸存区,并让幸存区中的对象寿命加1!
分代收集算法流程小结:
+1
,并且交换 FROM 和 TO。15
时,会晋升至老年代。在触发 GC 的时候,具体如下,这里只说常见的 Young GC (Minor GC) 和 Full GC。
System.gc()
默认也是触发 Full GC。相关概念:
吞吐量 = 运行用户代码时间 / ( 运行用户代码时间 + 垃圾收集时间
)),也就是。例如:虚拟机共运行100分钟,垃圾收集器花掉1分钟,那么吞吐量就是99%
。垃圾回收器的分类:
7种垃圾回收器:
如图:(图片参考自:https://thinkwon.blog.csdn.net/article/details/104390752)
主要来了解一下下面两个回收器(7个回收器,就算背也难记住,先掌握2个再说!)
CMS 和 G1 都是属于响应优先的垃圾回收器:尽可能让**单次 **STW 时间变短(尽量不影响其他线程运行)。
注:STW ,即 Stop The World, 暂停其他用户线程,只让垃圾回收线程工作。
在Java语言中,可作为GC Roots的对象包括下面 4 种:
案例代码如下:
/**
* 从字节码角度分析 a++ 相关题目
*/
public class Demo3_2 {
public static void main(String[] args) {
int a = 10;
int b = a++ + ++a + a--;
System.out.println(a);// 11
System.out.println(b);// 34
}
}
上面a、b的结果是怎样得来的呢?
分析:
iinc
指令是直接在局部变量桶位(slot)上进行运算。iload
指令是用于读取变量a++
和 ++a
的区别是先执行 iload
还是 先执行iinc
。a++
是先 iload
再iinc
,++a
相反。对虚拟机指令不清楚的去看一下这篇文章:JVM_07 类加载与字节码技术(字节码指令)
①bipush 10
操作是把a = 10
放入操作数栈:
② istore 1
操作,把操作数栈中的10弹出,放入到局部变量表的槽位1中:
③ 接下来执行a++
操作,我们上边提前说明了,a++
是先执行iload
读取,再执行iinc
加 1
iload 1
将 变量a=10
,读取到操作数栈stack中:iinc
指令,在局部变量表上对a进行 +1 操作,这时候 a 为11:④ 下面执行++a
操作,先iinc
在iload
:
iinc
指令,在局部变量表上对a进行+1操作,这时候a为12:iload 1
将局部变量表中a=12
,读取到操作数栈stack中:⑤ 下面进行 a++ + ++a
操作,在操作数栈中进行相加,得到结果22,这时候第1个加法完成:
⑥ 下面执行第二个加法(a++ + ++a)+ a--
操作:
a--
先执行 iload
命令,在执行 inc 1,-1
命令,如下,先将局部变量表中的12读取到操作数栈:inc 1,-1
命令,在局部变量表中进行-1操作,此时局部变量表中的值由12减为11:⑦ 最后将操作数栈中的数据弹出到局部变量表中,赋值2号槽位b=34:
因此程序运行结果得到:a为11,b为34。
HotSpot根据对象存活周期的不同将内存划分为几块,一般是把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。
其中新生代又分为1个伊甸园区(Eden)和2个幸存区(Survivor),通常称为From Survivor和To Survivor区。
如图:(图片参考自https://blog.csdn.net/weixin_43591980/article/details/116903332)
主要由 4 个部分组成:
class
字节码文件中的指令。java.lang.Object
)来装载 .class
文件到运行时数据区中的方法区中。答案:Java中会存在内存泄漏。
Java中虽然存在 GC垃圾回收机制,及时回收不再被使用的对象。但是依然存在内存泄露的情况!
Java导致内存泄露的原因很明确:长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄露,尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被回收,这就是Java中内存泄露的发生场景。
参考文章:
总结的面试题也挺费时间的,文章会不定时更新,有时候一天多更新几篇,如果帮助您复习巩固了知识点,还请三连支持一下,后续会亿点点的更新!
为了帮助更多小白从零进阶 Java 工程师,从CSDN官方那边搞来了一套 《Java 工程师学习成长知识图谱》,尺寸 870mm x 560mm
,展开后有一张办公桌大小,也可以折叠成一本书的尺寸,有兴趣的小伙伴可以了解一下,当然,不管怎样博主的文章一直都是免费的~