堆总存对象,栈中存局部变量,引用
4.堆和栈的完美结合就是面向对象的一个实例。其实,面向对象的程序与以前结构化的程序在执行上没有任何区别,但是面向对象的引入使得对待问题的思考方式发生了改变,是更接近于自然的思考方式。当把对象拆开会发现,对象的属性其实就是数据,存放在堆中,而对象的方法就是处理逻辑,存放在栈中。我们编写对象的时候,其实即编写了数据结构,也编写了处理数据的逻辑。
总结:栈主要用来执行程序,堆主要用来存放对象,为栈提供数据存储服务。也正是因为堆与栈分离的思想才使得JVM的垃圾回收成为可能。
java中只有值传递,没有引用传递(永远都传递的是栈里面存的东西的副本,基本类型栈里存的是数字,对象栈里存的是引用的地址,传递的是引用的地址的副本)
public static void main(String[] args) throws InterruptedException {
StringBuilder sb = new StringBuilder("hello");
System.out.println("sb = " + sb);
}
public static void xxx(StringBuilder sb) throws InterruptedException {
sb = new StringBuilder("hi");
}
sb = hello
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.9</version>
</dependency>
String s = ClassLayout.parseInstance(new Object()).toPrintable();
System.out.println(s);
基本类型数组的实例数据大小等于基本类型的大小*数组的长度
long [] ls = new long[10];
for (int i = 0; i < 10; i++) {
ls[i]=i;
}
String s = ClassLayout.parseInstance(ls).toPrintable();
System.out.println(s);
对象数组实例数据大小等于4*数组的长度
Object [] os = new Object[1000];
for (int i = 0; i < 1000; i++) {
os[i]=new Object();
}
String s = ClassLayout.parseInstance(os).toPrintable();
System.out.println(s);
https://blog.csdn.net/gtjsjj/article/details/107360457
从栈开始(局部变量,静态变量)
不一定,还需要判断要不要执行 finalize() 方法,如果有必要执行,等执行完才会回收
虽然 finalize() 方法可以拯救一个被可达性分析标记的垃圾对象不被回收,但是不推荐使用该方法,应该尽量避免使用。
一个对象只要被强引用关联,就不会被回收
一个对象只被软应用关联,oom前会回收,如果回收之后内存足够则继续运行,如果还是内存不足,才oom
一个对象只被弱引用关联,每次都会被回收
虚引用与对象的生命周期无关,只是跟踪对象的回收情况
无论是通过引用计数算法判断对象的引用数量,还是通过可达性分析算法判断对象的引用链是否可达,判定对象是否存活都与“引用”有关。在Java语言中,将引用又分为强引用、软引用、弱引用、虚引用 4 种,这四种引用强度依次逐渐减弱。
在程序代码中普遍存在的,类似 Object obj = new Object() 这类引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。
public class StrongReferenceExample {
public static void main(String[] args) {
// 创建一个强引用,引用一个字符串对象
String str = new String("Hello, world!");
// 打印被引用的对象 通过强引用获取对象/访问对象
System.out.println(str); // 输出:Hello, world!
// 置空引用
str = null;
// 执行垃圾回收
System.gc();
// 等待一段时间,以确保垃圾回收线程完成工作
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 再次尝试打印被引用的对象
System.out.println(str); // 输出:null
}
}
用来描述一些还有用但并非必须的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。如果这次回收后还没有足够的内存,才会抛出内存溢出异常。
public class SoftReferenceExample {
public static void main(String[] args) {
// 创建一个字符串对象,并创建一个软引用
String str = new String("Hello, world!");
SoftReference<String> softRef = new SoftReference<>(str);
// 打印软引用指向的对象 通过软引用可以获取对象 获取到对象后可以操作对象
System.out.println(softRef.get()); // 输出:Hello, world!
// 置空引用
str = null;
// 执行垃圾回收
System.gc();
// 等待一段时间,以确保垃圾回收线程完成工作
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 再次尝试打印软引用指向的对象
System.out.println(softRef.get()); // 输出:Hello, world!
// 创建大量的对象,占用内存空间
try {
byte[] bytes = new byte[1024 * 1024 * 100]; // 创建一个100MB的字节数组
} catch (OutOfMemoryError e) {
e.printStackTrace();
}
// 再次尝试打印软引用指向的对象
System.out.println(softRef.get()); // 输出:null
}
}
软引用(Soft Reference)在Java中有一些常见的使用场景,特别适用于对内存敏感的缓存和高速缓存应用。以下是软引用的一些常见使用场景:
需要注意的是,软引用在内存充足的情况下,会尽量保持对被引用对象的引用,因此在高并发或内存敏感的应用中,可能需要合理地调整软引用的使用策略,避免过多的对象占用内存。
此外,软引用也可以与其他引用类型结合使用,例如结合引用队列(ReferenceQueue)来实现更灵活的资源管理和清理操作。通过监视引用队列中的软引用,可以得知哪些对象已经被垃圾回收器回收,从而进行相应的资源释放或清理操作。
也是用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。
public class WeakReferenceExample {
public static void main(String[] args) {
// 创建一个字符串对象,并创建一个弱引用
String str = new String("Hello, world!");
WeakReference<String> weakRef = new WeakReference<>(str);
// 打印弱引用指向的对象
System.out.println(weakRef.get()); // 输出:Hello, world!
// 置空引用
str = null;
// 执行垃圾回收
System.gc();
// 等待一段时间,以确保垃圾回收线程完成工作
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 再次尝试打印弱引用指向的对象
System.out.println(weakRef.get()); // 输出:null
}
}
也叫幽灵引用或幻影引用,是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会影响其生命周期,也无法通过虚引用来取得一个对象,也无法通过需应该操作对象。它的作用是在对象被回收时收到一个系统通知。
public class PhantomReferenceExample {
public static void main(String[] args) {
Object obj = new Object();
ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
// 创建一个虚引用,关联到对象obj,并指定引用队列
PhantomReference<Object> phantomReference = new PhantomReference<>(obj, referenceQueue);
// 置空obj引用,使对象成为垃圾回收的候选
obj = null;
// 执行垃圾回收
System.gc();
try {
// 等待一段时间,以确保垃圾回收线程完成工作
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 检查引用队列,看虚引用是否被添加到队列中
Reference<?> reference = referenceQueue.poll();
if (reference != null) {
// 从引用队列中获取引用并进行相应处理
System.out.println("PhantomReference is enqueued.");
}
}
}
没有了但没有回收
长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄漏,尽管短生命周期对象已经不再需要,
但是因为长生命周期持有它的引用而导致不能被回收,这就是 Java 中内存泄漏的发生场景。
FileInputStream fis = null;
try {
fis = new FileInputStream("file.txt");
// 使用fis读取文件
} catch (IOException e) {
// 异常处理
} finally {
// 未在finally块中关闭fis,可能导致资源泄漏
}
Map<String, Object> cache = new HashMap<>();
// 向缓存中添加对象
cache.put("key", new Object());
// ...
// 未在合适的时机从缓存中移除不再需要的对象,可能导致内存泄漏
通过采取这些方法,可以最大程度地减少内存泄漏的发生,并确保Java应用程序的内存使用效率和性能。
因为不同对象的声明周期不一样
https://blog.csdn.net/weixin_42405670/article/details/120211605
本次清理清理不掉的垃圾
CMS 是英文 Concurrent Mark-Sweep 的简称,是以牺牲吞吐量为代价来获得最短回收停顿时间的垃圾回收器。是使用标记清除算法实现的,整个过程分为四步:
CMS 的优缺点:
主要优点:并发收集、低停顿;
主要缺点:对 CPU 资源敏感、无法处理浮动垃圾、它使用的回收算法“标记-清除”算法会导致收集结束时会有大量空间碎片产生。
Garbage First /ˈɡɑːbɪdʒ/
垃圾回收的瓶颈:传统分代垃圾回收方式,已经在一定程度上把垃圾回收给应用带来的负担降到了最小,把应用的吞吐量推到了一个极限。但是他无法解决的一个问题,就是 Full GC 所带来的应用暂停。在一些对实时性要求很高的应用场景下,GC 暂停所带来的请求堆积和请求失败是无法接受的。这类应用可能要求请求的返回时间在几百甚至几十毫秒以内,如果分代垃圾回收方式要达到这个指标,只能把最大堆的设置限制在一个相对较小范围内,但是这样有限制了应用本身的处理能力,同样也是不可接受的。
分代垃圾回收方式确实也考虑了实时性要求而提供了并发回收器,支持最大暂停时间的设置,但是受限于分代垃圾回收的内存划分模型,其效果也不是很理想。
G1 可谓博采众家之长,力求到达一种完美。它吸取了增量收集优点,把整个堆划分为一个一个等大小的区域(region)。内存的回收和划分都以region为单位;同时,它也吸取了 CMS 的特点,把这个垃圾回收过程分为几个阶段,分散一个垃圾回收过程;而且,G1 也认同分代垃圾回收的思想,认为不同对象的生命周期不同,可以采取不同收集方式,因此,它也支持分代的垃圾回收。为了达到对回收时间的可预计性,G1 在扫描了 region 以后,对其中的活跃对象的大小进行排序,首先会收集那些活跃对象小的 region,以便快速回收空间(要复制的活跃对象少了),因为活跃对象小,里面可以认为多数都是垃圾,所以这种方式被称为 Garbage First(G1)的垃圾回收算法,即:垃圾优先的回收。
所有对象创建在新生代的 Eden 区,当 Eden 区满后触发新生代的 Minor GC,将 Eden 区和非空闲 Survivor 区存活的对象复制到另外一个空闲的 Survivor 区中。保证一个 Survivor 区是空的,新生代 Minor GC 就是在两个 Survivor 区之间相互复制存活对象,直到 Survivor 区满为止。
Minor/Scavenge 这种方式的 GC 是在年轻代的 Eden 区进行,不会影响到年老代。因为大部分对象都是从 Eden 区开始的,同时 Eden 区不会分配的很大,所以 Eden 区的 GC 会频繁进行。因而,一般在这里需要使用速度快、效率高的算法,使 Eden 去能尽快空闲出来。
2. Major GC
/ˈmeɪdʒə®/ 买洁儿
发生在老年代的GC ,基本上发生了一次Major GC 就会发生一次 Minor GC。并且Major GC 的速度往往会比 Minor GC 慢 10 倍。
什么时候会发生 Major GC 呢?
对整个堆进行整理,包括 Young、Tenured (老年代)和 Perm(元空间)。Full GC 因为需要对整个堆进行回收,所以比 Minor GC 要慢,因此应该尽可能减少 Full GC 的次数。在对 JVM 调优的过程中,很大一部分工作就是对于 Full GC 的调节。
如下原因可能导致 Full GC:
1、 调用 System.gc(),会建议虚拟机执行 Full GC。只是建议虚拟机执行 Full GC,但是虚拟机不一定真正去执行。
2、 老年代空间不足,原因:老年代空间不足的常见场景为大对象直接进入老年代、长期存活的对象进入老年代等。为了避免以上原因引起的 Full GC,应当尽量不要创建过大的对象以及数组。除此之外,可以通过 -Xmn 虚拟机参数调大新生代的大小,让对象尽量在新生代被回收掉,不进入老年代。还可以通过 -XX:MaxTenuringThreshold 调大对象进入老年代的年龄,让对象在新生代多存活一段时间;
3、 空间分配担保失败:使用复制算法的 Minor GC 需要老年代的内存空间作担保,如果担保失败会执行一次 Full GC;
4、JDK 1.7 及以前的永久代空间不足。在 JDK1.7 及以前,HotSpot 虚拟机中的方法区是用永久代实现的,永久代中存放的为一些 Class 的信息、常量、静态变量等数据。当系统中要加载的类、反射的类和调用的方法较多时,永久代可能会被占满,在未配置为采用 CMS GC 的情况下也会执行 Full GC。如果经过 Full GC 仍然回收不了,那么虚拟机会抛出 java.lang.OutOfMemoryError。为避免以上原因引起的 Full GC,可采用的方法为增大永久代空间或转为使用 CMS GC。
5、Concurrent Mode Failure 执行 CMS GC 的过程中,同时有对象要放入老年代,而此时老年代空间不足(可能是 GC 过程中浮动垃圾过多导致暂时性的空间不足),便会报 Concurrent Mode Failure 错误,并触发 Full GC。
(1)在发生 Minor GC 之前,虚拟机先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果条件成立的话,那么 Minor GC 可以确认是安全的;
(2)如果不成立的话,虚拟机会查看 HandlePromotionFailure 设置值是否允许担保失败,如果允许那么就会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次 Minor GC;如果小于,或者 HandlePromotionFailure 设置不允许冒险,那么就要进行一次 Full GC。
虚拟机把描述类的数据从 Class 文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的 Java 类型,这就是虚拟机的类加载机制。
类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载、验证、准备、解析、初始化、使用、卸载 7 个阶段。其中验证、准备、解析 3 个部分统称为连接,这7个阶段发生的顺序如下图所示: