赞
踩
堆中内存分为两部分:新生代(young gen)和老年代(old gen),而新生代中又分为三部分:eden区、from区、to区,其中form区和to区又合称为生存区(survivor)
不特别指定的情况下,eden:from:to的内存比例为8:1:1
当我们实例化对象时,eden区内存足够,那么该对象会存储在eden区,内存不够的情况从左往右以此类推,寻找可以存放的区进行存储,找不到则会报OOM异常
我们可以使用JDK自带的工具:HSDB,来查看常量池的对象分布,我的JDK目录为:C:\Program Files\Java\jdk1.7.0_80,在lib目录下有个sa-jdi.jar,HSDB工具就在这个jar中,使用它之前,先要把jre\bin目录下的sawindbg.dll文件复制到C:\Program Files\Java\jre7\bin下
接下来看下我们的代码:
/**
* Created by aruba on 2021/9/29.
*/
class DumpTest {
public static void main(String[] args) {
DumpTest eden = new DumpTest();
try {
Thread.sleep(Integer.MAX_VALUE);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
我们在主线程实例化一个对象,并无限等待
jps
在JDK的lib目录下,打开命令行工具,执行下面命令:
java -cp ./sa-jdi.jar sun.jvm.hotspot.HSDB
打开后,选择Attach to HotSpot process,并输入DumpTest的进程id:8040
在下图中选择main线程,然后选择第二个选项
就可以看到我们对象在内存的哪里了
图太小了哈哈,可以看到图中DumpTest在新生代(YoungGen)区
老年代中存放着gc(JVM垃圾回收)15次后,还未能回收内存的对象,还存放着大对象和新生代内存不足,无法存放下的对象,我们修改下刚刚的代码,手动gc15次,再来看看对象处于哪个区
/** * Created by aruba on 2021/9/29. */ class DumpTest { public static void main(String[] args) { DumpTest eden = new DumpTest(); for (int i = 0; i < 15; i++) { System.gc(); } try { Thread.sleep(Integer.MAX_VALUE); } catch (InterruptedException e) { e.printStackTrace(); } } }
再次执行jps命令,查看进程id,并使用HSDB工具查看,最后的内存为下图:
可以看到现在的DumpTest对象处于老年代(OldGen)区
Java程序员不需要关注内存回收,是因为JVM中的垃圾收集器会自动收集垃圾对象,并释放它们的内存。对对象进行垃圾回收的操作,虽然后续出了更高性能的垃圾回收器,也无法避免性能的消耗,即所有GC都有stop the world。
GC分为两个部分:
minor GC:对堆中的新生代中所有对象进行一次垃圾回收,轻量级,该gc比较频繁,每回收一次,如果对象未被回收,那么标志位加1,当达到15时,对象进入老年代
full GC:对新生代和老年代进行一次gc,就是minor GC + Major GC,Major GC为老年代的gc,只有内存不够时,才会进行,比minor GC慢十倍以上
至此,JVM为什么把堆中分为新生代和老年代的原因可以得知,minor GC比较频繁,但是有些对象会长期存在内存中,不需要回收,所以对他进行gc检测是没有必要的,那么分为新生代和老年代,把内存分为临时创建的对象和较为常驻内存的对象可以优化性能
说到垃圾回收机制,很容易就能想到引用计数算法,当一个对象被其他对象引用时,他的引用计数就会加1,当一个对象的引用计数为0时,那么说明这个对象可以被回收了
面试中常问的一个问题:当A中有个对象引用B,当B中有个对象引用A,但没有别的对象引用它们,那么这两个对象能否被回收?
我们想要的答案很显然是能被回收,但是使用引用计数算法就无法回收它们了
该算法思想和最小生成树kruskal算法类似,每个对象都是一个顶点,有一些GC Roots顶点作为不可回收的顶点,当一个顶点的边的最远端无法到达GC Roots时,那么该顶点就可以被回收
使用该算法,即使发生1.1 中的情况,对象也可以被回收,因为两个对象并不是GC Roots树下的节点
新生代和老年代也有一系列的垃圾回收算法,其中新生代分为三个部分:
以下不考虑内存不够的情况
eden:新创建的对象存放的位置
from(s0):复制算法相关
to(s1):复制算法相关
最基础的算法,分为两个步骤:
1.对可以被回收的对象进行标记
2.对标记的对象进行内存回收
优点:简单
缺点:标记和回收效率不高,并且造成大量内存碎片,导致申请大内存对象时内存不够现象,导致触发full GC
复制算法为了解决内存碎片问题,将内存分成1:1的两块,每次只使用一块,当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。
优点:每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。
缺点:内存只有一半
上面提到eden:from:to的内存为8:1:1,因为使用的是复制算法
当回收时,将Eden和Survivor中还存活着的对象一次性地复制到另外一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor空间
老年代不能接收内存浪费,所以不使用复制算法,标记-整理算法分为两个步骤:
1:标记过程和标记-清楚算法相同,对可以被回收的对象进行标记
2:整理是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存(双指针算法,一次遍历)
优点:内存连续
缺点:低效
分代收集算法就是对内存进行划分,各个垃圾收集器不太一样,本文基础标准HotSpot虚拟机,一般是分为新生代和老年代,针对不同的内存区域,使用不同的算法,在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记—清理”或者“标记—整理”算法来进行回收。
垃圾回收器分为很多种,并不断有新的垃圾回收器出现,很多公司拥有自己编写的垃圾回收器
stop the world:所有垃圾回收器都避免不了stop the world带来的延迟,试想下,如果我们要对内存进行一次扫描,挑出可以被回收的对象,那么此时的内存还能不能变化?如:实例化一个对象分配新的内存,很显然我们是不希望内存变动的,也就是说stop the world必须暂停所有线程
Java中的引用对象分为四种:
强引用:不能被gc回收
软引用:当内存不够时,发生gc,优先被回收
弱引用:gc发生时,立刻被回收
虚引用:创建一个虚引用对象后,返回的都是一个null,用来检测gc发生情况
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。