网站表现形式/打广告去哪个平台免费
目录
- JVM垃圾回收
- 分代收集
- 如何识别垃圾
- 引用计数法
- 可达性分析法
- 引用关系四种类型: 强、软、弱、虚
- 强引用
- 软引用 SoftReference
- 弱引用 WeakReference
- WeakHashMap
- 软引用与虚引用的使用场景
- 虚引用与引用队列
- 引用队列
- 虚引用 PhantomReference
- 垃圾回收算法
- 引用计数
- 复制 Copying
- 标记 Mark-Sweep
- 标整 Mark-Compact
- 垃圾回收器种类
- 串行回收器
- 并行回收器
- 并发回收器
- G1
- ZGC
- 常用垃圾回收器实例
- 新生代
- Serial
- Parallel Scavenge
- ParNew
- 老年代
- Serial Old
- Parallel Old
- CMS
- CMS四个阶段
- 年轻代+老年代
- G1
JVM垃圾回收
为什么进行垃圾回收:Java不像C语句那样,使用时要自己通过代码开辟空间,用过后在手动销毁。Java为我们程序提供虚拟机,使得我们在写代码时更加关注业务逻辑,至于内存空间的开辟,释放这些交给JVM。代码运行在JVM上,JVM会通过算法识别出哪一个是垃圾,再通过垃圾回收算法,自动的将程序中用不到的空间进行释放。C语言,C++好比手动挡,Java好比自动挡。
GC
:Garbage Collection 垃圾收集
,年轻代的垃圾收集也叫GC,老年代的垃圾收集称为Full GC。
什么是垃圾
程序在运行时,需要在内存中开辟的空间存储数据。但此空间被使用一次后,可能再也不会使用,没有指向它的地址,程序就无法使用这个地址空间的存储内容,像内存中缥缈的幽灵。但这些数据是真真实实在内存存在的,这就是垃圾。java程序在编写代码是,只会开辟空间却不能释放空间,就会使得内存中无用的数据占据的内存空间过多,一点点的缩小着程序实际的可用内存,最终导致内存溢出。
下面,以一个单链表删除一个节点为例,被删除的节点就是一个垃圾
public class Application {public static void main(String[] args) {// 创建三个节点Node n1 = new Node("张三");Node n2 = new Node("李四");Node n3 = new Node("王五");// 将3个节点连接起来,形成 n1->n2->n3n1.next=n2;n2.next=n3;// 遍历链表Node node=n1;while (node!=null){System.out.println(node.data);node=node.next;}// 删除一个节点 将中间的节点删除n1.next=n3;n3.pre=n1;n2=null;// 遍历链表node=n1;while (node!=null){System.out.println(node.data);node=node.next;}}
}
@Data
class Node{Node pre;Node next;Object data;public Node(Object data){this.data=data;}
}
思考:n2这个节点是不是没有用了?那么它还占用着空间呢?
原链表:
删除n2节点后:我们已经知道n2就是个垃圾了,但使用Java不像C,不能够手动操作内存释放空间。但JVM会自动识别出垃圾,帮我们释放。
这时,JVM就会通过算法(可达性分析)识别出n2就是个垃圾,最后再将它所在的内存空间回收
垃圾回收的作用区域与范围
俗话说:栈管运行,堆管存储,堆占用的空间最多。GC负责对方法区和堆进行垃圾回收,但主要是针对堆。其他地区空间空间占用极少,且不会产生什么垃圾。
既然堆是垃圾回收的主要场所,那么先忽视垃圾回收的一系列问题,接下来先介绍堆的具体逻辑结构以及垃圾在堆中是怎样个转运过程
分代收集
GC垃圾回收极有可能能不止只回收一次,而同一个内存区域也可能在第一次垃圾回收时还不是垃圾,但在第二次回收就是垃圾。如何高效的扫描出垃圾,高效的清除垃圾,尽可能的不影响JVM正常运行,GC将堆空间在逻辑上划分为三代(物理上都是在内存中),分别是新生代,老年代,元空间(JDK7以前为永久代)。JVM会将这三个区域的存储信息,根据区域的特点采用针对它们的方式进行分代管理
。例如:在年轻代进行GC(YoungGC),老年代进行FullGC。
分代收集的思想主要有两个:
- 绝大多数对象朝生夕死
- 熬过越多次数的垃圾回收过程的对象就越不可能成为垃圾
接下来,将描述这几个区域在实际的垃圾回收中是怎样协调运转以及为何这样划分:
我们一开始创建的类对象在初始时都会放在伊甸园区(Eden),随着创建的对象越来越多,如果新创建的对象在Eden没有空间存放,这时会就会触发YoungGC。第一次触发YongGC:会扫描Eden区所有的垃圾和幸存者from区(此时from区为空),然后将不是垃圾
的复制
到幸存区的to区,然后清空Eden区
和幸存者from区所有的数据,同时将经历过一次GC扫描后判断不是垃圾的内容的年龄+1。
经历过第一次GC后,当前的堆空间状态:Eden区为空,幸存者to区有少量存活的数据,,这些数据的年龄都+1,将to区转为from区,原幸存者from区转为to区,此时to区为空。由于经历过一次GC后空间得到释放,以后再新创建的对象接着放入Eden区,还是随着程序的运行创建的对象越来越多,当Eden再次满了,这时会触发第二次GC(注意:和这一次幸存者区中有一为空即to区)GC会扫描出Eden区和幸存者from区
中不是垃圾的部分,然后将这部分复制到另一个空的幸存者区to区,同时也会将不是垃圾的数据年龄再次+1。由于经历过一次GC后空间得到释放,以后再新创建的对象接着放入Eden区循环往复。
直到某一次(至少GC15次后),由于每一次不被当为垃圾的数据年龄都会+1,直到有些数据的年龄达到15岁(可通过配置JVM参数修改),这时达到15岁的数据已经经历过15次GC,这时系统认为这些数据在接下来会有很少的概率成为垃圾,每次来回移动它们耗时费力,于是将这些数据放入老年代。当触发YoungGC时,不涉及老年代。
新生代的流程就是这样,总结就是 复制+1->清空->互换
。复制 不是垃圾的数据到为空的幸存者区(假设0区,此时0区为to区)同时这些数据年龄+1,然后清空Eden区和复制前不为空的幸存者区(假设1区,此时1区为from区),然后将from区与to区互换。此时0区是from区,1区就是to区。下次GC时再次互换。
接下来转到年龄已经达到15次在老年区的数据,每一次发生在年轻代的GC都不会波及到这里,但有可能GC后有新数据加入。随着频繁的GC,不断的有新数据添加,系统会提前预测老年代的空间是否会满(每次新增数据的平均值>老年代剩余空间),当预测下次可能使得老年代空间满,这时就会触发Full GC
。扫描老年代的垃圾,并将其清除,释放老年代的内存空间。
(这个图只是大致流程,忽略很多细节)
接下来介绍元空间
JDK7及以前,元空间之前成为永久代,是方法区的实现,所以又称为非堆。但在逻辑上它是堆的一部分(堆分为年轻代,老年代,永久代),物理上使用的也是同一块内存。它和老年代绑定在一起,无论谁满了都会触发清除老年代和永久代的垃圾。这样就不用单独为永久代编写对应的代码,直接使用老年代的。永久代主要存储类信息
,普通常量
,静态常量
,编译器编译后的代码
等。例如:我们创建的对象,根据哪个类创建的,这个类的模板就在永久代中,对象的头信息的中的类针就会指向永久代中它实例的类。在JDK7后,将字符串常量池移动到了堆中。但仍有一个问题,由于永久代存储的数据和堆中不同,永久代到底设置为多大合适,很难确定,因此在JDK8
以后将永久代移动到了直接内存
中,并改名为元空间(Metaspace),在逻辑上和物理上与老年代分离。
这样元空间使用的就是本地内存,默认最大使用空间就是本地内存大小(也可以配置上限),可以自由的根据实际情况加载类的信息。有自己的垃圾回收频率而不用跟随老年代。 元空间是代替了老年代成为方法区规范的实现。
永久代与元空间最大的不同就是 元空间使用的是直接内存,大小可以不必写死
注意:
新创建的对象都会放入Eden区,但在扫描垃圾时,会将Eden区和不为空的幸存者区放在一起扫描。
幸存者from区和幸存者to区大小永远一致
- 并不是所有对象一定要年龄达到15才可以进入到老年区。如:一些大对象或在Eden区和幸存者From区幸存者空间大小超过幸存者to区一半大小等这些都是直接放入老年区。
- 触发老年区的Full GC的条件有很多:
- 在代码中执行
System.gc()
,在代码中极少使用 - 老年代空间不足
- 空间分配担保失败,在GC前计算出平均每次从年轻代晋升到老年代的空间大小,当大小超出老年代剩余空间就会进行FUll GC
- 元空间超过阈值,默认情况下,元空间的大小与本地大小有关。当元空间使用总大小超过阈值就会进行Full GC。
- 在代码中执行
- Full GC造成的STW时间过长(STW是GC的10倍以上),所以GC的调优思想就是要么减少Full GC的次数,要么减少Full GC的STW时间。
思考:既然从Eden区和幸存者区中扫描出不是垃圾的内容要放入另一个幸存者区,那这两个区的功能是固定好的吗?
复制之后有交换,谁空谁是to
在经历过第一次GC后,谁空谁是to区。因为在年轻代垃圾清除采用的是复制算法。扫描出不为垃圾的部分将其转移到另一个目的地。谁做目的地是不固定的,如果固定话假设幸存者1区永远为from区,幸存者0区永远为to区:第一次GC,将不为垃圾部分复制到to区,第二次垃圾回收就要扫描Eden区和to区,要复制的地方就不能为to区了,应该是from区。因为幸存者区也需要进行垃圾回收,所以要有to区,且from区与to区要能够实现逻辑互换。
思考:为什么在新生区清除垃圾时是复制有用的,而不是直接清除无用的?这样不是就不需要幸存者区,会更加节省空间了吗?
根据统计,在使用中98%的对象都是临时对象,也就是说Eden区大部分都是垃圾。在垃圾堆里挑出有用的,比一个个清除垃圾效率更高,所以新生代采用复制有用的部分。但这种复制方式需要额外空间。老年代的数据经过15次GC在成为垃圾的概率很小,所以只需要清除无用的部分即可。
思考:什么时候会触发GC?(GC通常指YoungGC)
当Eden区满了的时候就会触发GC。创建对象时是存储在Eden区的,幸存者from区不参与直接存储新创建的对象。
上面已经简单介绍了垃圾回收过程,那么从微观上来讲,无论是年轻代还是老年代垃圾回收器是如何识别出哪些是垃圾的呢?
如何识别垃圾
无论是那种算法,核心就是找出在程序中没有引用关系的内存地址,因为没有引用关系程序中就一定不会再使用到它了,将其回收。
引用计数法
堆内存中的数据每被引用一次次数就+1,取消引用则次数减1,如果堆内存中某块数据的引用次数为0,则代表没有地方引用此数据则判定为垃圾。
例如:有一个Node类,由node1引用它,引用次数为1->node1,node2都引用它,引用次数为2->node1取消引用它,引用次数为1->node2取消引用它,引用次数为0,此地址的数据是垃圾。
public class Application {public static void main(String[] args) {Node node1 = new Node("张三");Node node2=node1;System.out.println(node2);node1=null;System.out.println(node2);node2=null;}
}
但这种方式存在一个问题:循环引用问题
public class Application {public static void main(String[] args) {Node node1 = new Node("张三");Node node2 = new Node("李四");node1.setData(node2);node2.setData(node1);node1=null;node2=null;}
}
以name为"张三"的Node为例:node1引用,引用次数为1->node2中data引用,引用次数为2->node1引用无效,引用次数为1,但此时由于Node2已经为null,程序中已无法使用此Node,此Node已经为垃圾,但引用次数为1 不为0,无法被识别为垃圾。这就是采用引用计数法带来的循环引用问题。
引用计数法的缺点:
- 每个对象都要维护一个引用计数器,有性能损耗
- 拿以处理循环引用问题
由于引用计数的缺点突出,现在已经基本上不会使用引用计数法了,而是使用下面的可达性分析法。
可达性分析法
从一个可以作为GC roots的对象作为起点,顺着它的引用关系,遍历它的引用链。所有的GC roots对象都没有遍历到就是内存中的垃圾,此地址的数据在程序中已经没有使用它的地方。就像葡萄串一样,从根开始顺着藤蔓向下找,能找到的葡萄粒一定是在串中的,只有掉落的葡萄粒不会被遍历到,这种就是垃圾。
还是上面采用技术法无法解决的例子
public class Application {public static void main(String[] args) {Node node1 = new Node("张三");Node node2 = new Node("李四");Node node3 = new Node("王五");new Node("赵六");node1.setData(node2);node2.setData(node1);node1=node3;node2=null;}
}
上面代码可以表示为:
如果使用可达性分析法,node1,node2和node3都可以作为GC roots,从它三开始遍历它的引用关系。最终也没有遍历到内存中name为张三,李四,赵六这三个节点,因此判定这三个为垃圾会被回收
既然是从GC roots对象集合进行遍历引用关系,那么那些对象可以作为GC roots呢?GC roots是四类对象的集合
- 栈中局部变量引用的对象
- 方法区中静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈JNI引用的对象
在上面的例子中,所有作为GC roots的对象都是第1种。
从GC roots遍历有引用关系的对象不是一定不会
作为垃圾,要分当时内存情况和那种引用关系。但没有引用关系的一定会
被当做垃圾。
由遍历GC roots对象们的引用关系来确定哪些是垃圾,这又牵扯到引用的关系类型。
引用关系四种类型: 强、软、弱、虚
对象间的引用关系可以分为4种,分别是强引用
,软引用
,弱引用
,虚引用
。我们平时使用到的引用关系都是强引用,剩余三种方式都是在特殊的功能下使用。
补充:
- JVM会在Eden区空间不足或老年代空间不足时触发GC,我们在代码中也可以手动调用GC。使用
System.gc();
这种方式不常见,通常只在测试中使用。而且:使用这种方式GC是Full GC。 - 在默认的情况下,堆内存最小分配占服务器总内存的
六十四分之一
,最大内存占服务器总内存的四分之一
,JVM会以最小内存启动,当内存不够用它会调整直到达到最大内存,如果仍不够用则OOM错误。
验证:
本机内存16G
也可以通过代码的方式获取:
public class Application {public static void main(String[] args) {OperatingSystemMXBean mem = (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean();// 获取内存总容量long totalMemorySize = mem.getTotalPhysicalMemorySize();}
}
查看JVM中最小堆内存和最大堆大小
public class Application {public static void main(String[] args) {System.out.println(""+Runtime.getRuntime().totalMemory()/1024/1024+"M");System.out.println(""+Runtime.getRuntime().maxMemory()/1024/1024+"M");}
}
近似的满足 默认
最大堆内存大小=总内存大小/4,默认
最小堆内存大小=总内存大小/64
这些默认的大小参数可以在Idea中调整(服务器上可以在启动jar包时以命令行的方式调整),通常为了防止内存波动,将最大对堆内存空间与最小堆内存空间调成一致。
例如: -Xms5m :将堆内存最小
空间设置为5M,-Xmx5m 将堆内存最大
空间设置为5M
在Idea中配置
代码验证结果:
强引用
我们平常使用到的引用都是强引用,其他引用需要特意用代码标出。例如:
public class Application {public static void main(String[] args) {Node node1 = new Node("张三");Node node2 = new Node("李四");Node node3=node1;}
}
node1和node3引用name为张三的Node,node2引用name为李四的Node,共三个引用都是强引用。
强引用的特点就是:在GC roots可达的情况下,强引用永远不会发生垃圾回收。 如果内存不足,也不会回收,直接抛出OOM异常。
软引用 SoftReference
在内存不足的时候,软引用的堆空间地址无效,也会当做垃圾回收。
代码测试思路:测试同一个代码在不同的环境下 (内存充足和内存不足),只有软引用下的堆空间是否会被当做垃圾回收
public class Application {public static void main(String[] args) {User u1 = new User("张三"); //强引用方式SoftReference<User> softReferenceU1 = new SoftReference<>(u1); // 软引用方式 与强引用都是引用同一块地址System.out.println(u1); // 以强引用的方式获取引用地址的数据(User (name=“张三”))System.out.println(softReferenceU1.get()); // 以软引用的方式获取引用地址的数据(User (name=“张三”))u1=null; //此时 User(“张三”)失去了强引用 ,只有一个软引用System.gc(); // 经历了一次 Full GCSystem.out.println(softReferenceU1.get()); // 测试 User("张三")是否被回收掉}
}
@Data
@AllArgsConstructor
class User{private String username;
}
- 内存充足的情况下
结果:
由此可见:在内存充足时,由于堆内存中User(name=“张三”)仍有一个软引用,使得它没有被当做垃圾回收。
- 在内存不足的情况下 设置JVM参数,将JVM堆的最大最小内存设置为5MB
由于内存不足会直接报错,这里对上面代码进行修改,使得内存不足的语句进行try包裹,以便输出
public class Application {public static void main(String[] args) {User u1 = new User("张三"); //强引用方式SoftReference<User> softReferenceU1 = new SoftReference<>(u1); // 软引用方式 与强引用都是引用同一块地址System.out.println(u1); // 以强引用的方式获取引用地址的数据(User (name=“张三”))System.out.println(softReferenceU1.get()); // 以软引用的方式获取引用地址的数据(User (name=“张三”))u1=null; //此时 User(“张三”)失去了强引用 ,只有一个软引用try {Byte[] load = new Byte[1024 * 1024 * 10];// 直接开辟一个10M的内存空间 使得堆内存不足 这是检测是否会只有软引用是否会被当做垃圾回收}catch (Exception e){}finally {System.out.println(softReferenceU1.get()); // 测试 User("张三")是否被回收掉}}
}
结果:
弱引用 WeakReference
只要触发GC,软引用就会失效(只被软引用的空间会被当做垃圾处理)
没有进行GC
public class Application {public static void main(String[] args) {User u1 = new User("张三"); //强引用方式WeakReference<User> weakReferenceU1 = new WeakReference<>(u1); // 弱引用方式 与强引用都是引用同一块地址System.out.println(u1); // 以强引用的方式获取引用地址的数据(User (name=“张三”))System.out.println(weakReferenceU1.get()); // 以弱引用的方式获取引用地址的数据(User (name=“张三”))u1=null; //此时 User(“张三”)失去了强引用 ,只有一个弱引用System.out.println(weakReferenceU1.get());}
}
@Data
@AllArgsConstructor
class User{private String username;
}
结果:在没有GC之前,若引用下的对象仍可以使用
进行GC
思考:代码中为什么要将u1置为null?
使用u1是强引用,强引用的特点就是:在任何情况下,强引用指向的对象永远不会被当做垃圾处理。如果在测试软引用,弱引用,虚引用时不将强引用断开,就无法看到结果。
WeakHashMap
当我们使用HashMap时,Key是一个强引用关系。例如:我创建了一个User u1=User(name=“张三”),想要给u1对象加个附属的值,于是将u1作为key传递给HashMap。当u1对象使用完毕(附属的值也应该消失),想要释放时u1=null,但这时HashMap仍有一个强引用在指着User这个对象。这个对象就无法当做垃圾释放。
这样对象在垃圾回收时还会存在就会浪费内存引发OOM问题
使用WeakHashMap是一个与对象建立弱引用关系,想要使用此对象,又不想让成为对象不是垃圾的依据。当u1使用完毕,断开强引用时,在GC时就会将垃圾回收,忽略WeakHashMap中的引用。
public class Application {public static void main(String[] args) {WeakHashMap<User, String> weakHashMap = new WeakHashMap<>();User u1 = new User("张三");User u2 = new User("李四");weakHashMap.put(u1,"v1");weakHashMap.put(u2,"v2");u1=null; // User("张三")由于断开强引用,只有一个弱引用的WeakHashMap与之相连,在发生GC时会被回收System.out.println(weakHashMap);System.gc();System.out.println(weakHashMap); // 判断只有WeakHashMap引用下的对象是否被释放}
}
结果:
但当对象作为weakHashMap作为Value时,User对象断开u1的强引用,在GC后对象仍然存在,所以,WeakHashMap的value部分可能是强引用。如图:
思考:当key指向的对象被回收后,在通过WeakHashMap的key去获取值时,会返回什么?
思考:WeakHashMap可能由于Key是弱引用,当引用对象没有强引用后被回收,那么回收后WeakHashMap的value值去哪了?
思考:WeakHashMap<Object,Object>是否类似于HashMap<Reference<Object>,Object>
?
补充:我在ThreadLocalMap源码中中就看到过这种使用弱引用的思路来解决ThreadLocal内存溢出问题:
将ThreadLocal作为key,当ThreadLocal销毁时,Map中关于此ThreadLocal的数据也会释放。
软引用与虚引用的使用场景
软引用和弱引用通常配合着强引用来使用。软引用是当强引用断开时,在内存允许的情况下还想使用该地址空间。软引用是当强引用断开自己也没必要去使用该地址空间,通常配合着集合,当强引用断开,集合中的元素也没有意义,使用弱引用能够就释放引用的堆地址,集合移除的方式堆释放麻烦,交给GC去释放。
例如:利用软引用生成缓存
读取一个图片时,如果每次使用都从磁盘读取会影响性能,如果一次全部读取又有可能内存溢出。如何既能提升效率,又不会使得内存溢出呢?这时就要将内存利用最大化,在内存空间不足快要发生OOM时,将加载到内存的图片释放。如果运行期间内存充足,则可以一直使用内存中的图片。 HashMap<String, Reference<Byte>> hashMap = new HashMap<>()
虚引用与引用队列
虚引用的使用要配合着引用队列,没有引用队列使用虚引用将无意义。
引用队列
在引用的对象被销毁前会先放入引入队列中,另一端监听这个队列可以进行一些关于即将销毁的对象的善后处理
软引用,弱引用,虚引用都可以在构建时指定引用队列,这里的虚引用在构建时必须要使用指定引用队列。
下面虚引用结合引用队列的使用:
public class Application {public static void main(String[] args) throws InterruptedException {ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();User u1 = new User("张三");WeakReference<Object> weakReference = new WeakReference<>(u1, referenceQueue);u1=null;new Thread(()->{while (referenceQueue.poll()==null){}System.out.println("对象被销毁");}).start();System.gc();TimeUnit.SECONDS.sleep(1);}
}
很少会使用到
虚引用 PhantomReference
phantom:幽灵,幻觉,虚引用无法获取引用对象,它唯一的用处就是和引用队列结合,使得被引用的对象在销毁前能够加入引用队列,这样在引入队列的另一头可以做一些善后工作。使用虚引用可以不对类产生任何额外的影响,极少会用到。
public class Application {public static void main(String[] args) throws InterruptedException {User user = new User("张三");ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();PhantomReference<User> reference = new PhantomReference<>(user, referenceQueue); // 在创虚引用对象时就要指定消息队列System.out.println(reference.get()); //尝试获取虚引用引用的对象user=null;new Thread(()->{while (referenceQueue.poll()==null){}System.out.println("虚引用引用的对象被销毁");}).start();System.gc();TimeUnit.SECONDS.sleep(1);}
}
结果:
思考:其他引用队列可以在销毁前可以加入引用队列吗?
可以只是很少用到,只是虚引用只有这个功能,所以特别介绍
总结:至于对象是否会被回收,只需要查看引用此对象的个数和引用的方式。
上面已经介绍了如何识别垃圾,接下来就是发现垃圾后如何处理呢?
垃圾回收算法
引用计数
计数为0的就是垃圾,有一次引用次数就+1,有一个引用失效次数就-1。
这种算法是结合扫描垃圾的引用计数法,但这种方法存在循环引用问题,已经很少使用,所以引用计数这种算法方法也不会使用。
复制 Copying
复制A区不是垃圾部分到B区,然后清空A区。这种方式适用于堆的年轻代,因为年轻代不是垃圾占少数,移动量相对较少。将Eden区和幸存者from区不为垃圾的部分复制到幸存者to区,然后清空Eden区和幸存者from区。
这种方式效率较高,而且在复制是从to区全为空的地方开始复制,清除是将Eden区和幸存者from区全部清除,所以不会产生内存碎片。
这种方式的缺点
- 需要额外的空间,如幸存者to区,就需要和from区一样大小,在任何时刻在幸存区中都有一半的内存被浪费
- 如果遇到极端的情况,如Eden区100%全不为垃圾,这是复制有用部分耗时不说,还会将幸存者to区撑爆。所以适合存活率低的地方(在默认的情况下,Eden区:幸存者from区:幸存者to去=8:1:1)
标记 Mark-Sweep
标记清除:分为两部分 1. 遍历所有GC roots标记哪一部分是垃圾 2. 遍历整个堆清除垃圾。在标记清除垃圾和清除垃圾时都需要STW(stop the world)暂停整个应用。发生在老年代,因为老年代绝大多数对象都是经历过15次GC的,再次成为垃圾的概率较小。
它有两大缺点:
- 效率低,需要暂停整个应用
- 清除使得内存不连续,造成内存碎片过多。JVM就不得不维持一个内存的空闲列表,这又是一种开销。而且在分配数组对象的时候(大对象在老年区)需要一大块连起来的空间,寻找连续的内存空间会不太好找。
标整 Mark-Compact
标记整理:分为三部分,1. 遍历所有GC roots标记哪一部分是垃圾 2. 遍历整个堆清除垃圾。3.将内存进行整理,减少碎片的产生。相较于标记算法多了整理一步,这就使得内存碎片问题得到解决,但也带来了新的问题:整理时花费时间和耗费CPU。
标记清除和标记整理都发生在老年代,在使用时常常会将二者结合,在进行多次标记清除后,进行一次整理。这样既避免了频繁标记整理耗费时间和性能又能使得内存碎片在合理的范围内。
这三个算法各有各的优势,各有各的劣势。没有完美的算法,要根据合适的长江选择合适的算法。
上面的是哪个算法是一种解决垃圾回收的思路,具体的实现工具就是下面的垃圾回收器。
垃圾回收器种类
真正将根据算法实现的工具,垃圾回收器总的来说回收的方式分为5类。
串行回收器
只有一个线程在进行回收,在回收垃圾时会暂停所有用户线程。适合单线程的场景。就好比,上课时,只有一个保洁阿姨进来要打扫卫生。想要继续上课只能等到保洁阿姨打扫完卫生。所有用户线程都要暂停等待这一个线程GC完毕,效率较低。
具体实现有 Serial
(用于年轻代),Serial Old
(用于老年代)
并行回收器
相较于串行回收器,在回收时不再只有一个线程,而是多个线程一起参与。这样就不再等待一个人干活,任务量不变,多个人一起干活相较于一个人干活能够减少用户线程的等待时间。
具体实现有 ParNew
(用于年轻代),Parallel Scavenge
(用于年轻代),Parallel Old
(用于老年代)
并发回收器
用户线程和回收线程可以一起工作(虽然有暂停但时间较短),它的最大特点就是没有长时间暂停,适合用于于用户对交互请求强的场景。因为用户在交互时肯定不希望突然长时间的暂停,使用并发垃圾回收器可以减少响应时间。
具体的实现有 CMS
(用于老年代)
G1
ZGC
思考:什么是STW?
STW:Stop The Word,是在进行垃圾回收时,会暂停所有用户线程,造成卡顿的现象。
思考:复制算法也会到导致STW吗?为什么要GC时要STW?
所有垃圾回收器都会导致STW,只是时间长短问题。因为在最终确认垃圾时,一定要确保一致性,否则程序不断运行,引用关系不断变化,分析的结果会不准确。如果在复制时,没有暂停,导致在这期间创建的对象就不会被标记为存活对象,就会导致幸存者没被移动到幸存者区而被全部清除,强引用被清除,程序会出错。且在复制算法和标记整理算法中,会导致原引用对象的地址会发生变化。防止引用混乱。
常用垃圾回收器实例
新生代
Serial
只有一个
线程在年轻代中使用复制算法将伊甸园中与幸存者From区所有幸存的对象复制到幸存者To区。在GC时需要STW。可以与Serial Old垃圾回收器一起搭配工作。
Parallel Scavenge
开启多个
线程在年轻代中使用复制算法将伊甸园中与幸存者From区所有幸存的对象复制到幸存者To区。在GC时需要STW,由于采用多线程一起工作,STW的时间可能会短些。
可以通过参数 XX:MaxGCPauseMillis及直接控制吞吐量的参数-XX:GCTimeRatio设置吞吐量。吞吐量=程序运行时间/(程序运行时间+GC时间)。提升吞吐量能够提升CPU的利用率,但与响应速度无关,并不一定能提升用户体验。
可以与CMS垃圾回收器
,Parallel Old垃圾回收器
一起搭配工作。
ParNew
同样是多个线程一起GC,与Parallel略有不同(不能设置吞吐量)。可以与CMS垃圾回收器
一起工作
老年代
Serial Old
采用单一线程的方式回收垃圾,在回收垃圾时会暂停所有的用户线程。与上面青年代的Serial类似,但在老年代采用的是标记整理算法,不会产生内存碎片。
Parallel Old
并发线程的回收,与年轻代的Parallel 类似,但在老年代采用的标记整理算法,不会产生内存碎片。
CMS
CMS全称:concurrent mark sweep
,翻译过来就是并发清除。
CMS四个阶段
初始标记
:在老年区将所有GC roots即根节点全部扫描出来。在这期间在暂停根节点的变化,所以是STW的,但由于GC roots相对较少,所以STW时间相对较短- 并发标记:由于在初始阶段已经将根节点全部扫描出来,在这个阶段只需要顺着根节点扫描出无法引用到的对象并标记为垃圾。在这期间,是与用户线程并行执行的。由于通常根节点下面的节点要比根节点要多,所以是相对耗时的,但由于是与用户线程并行执行,并没有STW。缺点就是这节点比较耗费性能。
重新标记
:由于用户线程一边执行一边扫描垃圾,垃圾可能会新增。这是暂停所有用户线程,来个最终的大扫除。将这一过程新增垃圾再次进行标记,确保打扫彻底,不能由于并发产生的垃圾不知道。虽然这个阶段是STW,但由于新增的垃圾比较少,所以时间很短。- 并发清除:到这里就已经标记完所有的垃圾,剩余的就是清除(清除算法采用的是标记清除)。清除的线程可以与用户线程一起执行。
CMS最大的特点就是STW较短,在交互中系统卡顿的时间较短,适用于与用户交互时使用。
但CMS有两大的缺点:
- 在并发清除的过程中,由于用户线程没有暂停,这是可能有新的数据加入老年代(像大对象直接放入老年代),所以并不是满了再去清除,而是预留一定的空间。比如10%,然而如果在并发清除过程中新进入老年代的数据超过预留的10%,则此时老年代没有空间存放,会触发
Concurrent Mode Failure
,然后CMS将退化为Serial Old垃圾回收器。暂停所有用户线程,采用单线程的方式进行回收。造成严重卡顿。预留空间大会造成频繁Full GC,预留空间小会造成CMS回收失败。 - CMS采用的算法是并发清除算法,所以会产生内存碎片。碎片过多时,就会触发Serial GC,采用单线程标记整理算法进行清除和碎片整理。
思考:CMS为什么不使用标记整理算法来解决碎片问题?
如果使用 整理算法,那么在并发清除时对象的地址就可能发生变化,需要暂停所有用户线程。
年轻代+老年代
G1
推荐阅读这篇博客
至于GC垃圾回收器的配置,搭配使用,以及JVM的一些参数、命令,JVM结合Linux的调优等会在后续的博客中介绍。如果发现错误的地方欢迎一起探讨。
相关文章:

JVM垃圾回收机制GC理解
目录JVM垃圾回收分代收集如何识别垃圾引用计数法可达性分析法引用关系四种类型: 强、软、弱、虚强引用软引用 SoftReference弱引用 WeakReferenceWeakHashMap软引用与虚引用的使用场景虚引用与引用队列引用队列虚引用 PhantomReference垃圾回收算法引用计数复制 Cop…...

C++中的容器
1.1 线性容器1)std::array看到这个容器的时候肯定会出现这样的问题:为什么要引入 std::array 而不是直接使用 std::vector?已经有了传统数组,为什么要用 std::array?先回答第一个问题,与 std::vector 不同,…...

2023备战金三银四,Python自动化软件测试面试宝典合集(五)
接上篇八、抓包与网络协议8.1 抓包工具怎么用 我原来的公司对于抓包这块,在 App 的测试用得比较多。我们会使用 fiddler 抓取数据检查结果,定位问题,测试安全,制造弱网环境;如:抓取数据通过查看请求数据,请…...

SpringDI自动装配BeanSpring注解配置和Java配置类
依赖注入 上篇博客已经提到了DI注入方式的构造器注入,下面采用set方式进行注入 基于set方法注入 public class User {private String name;private Address address;private String[] books;private List<String> hobbys;private Map<String,String>…...

2月面经:真可惜...拿了小米的offer,字节却惨挂在三面
我是2月份参加字节跳动和华为的面试的,虽然我只拿下了小米的offer,但是我自己也满足了,想把经验分享出来,进而帮助更多跟我一样想进大厂的同行朋友们,希望大家可以拿到理想offer。 自我介绍 我是16年从南京工业大学毕…...

磐云PY-B8 网页注入
文章目录1.使用渗透机场景windows7中火狐浏览器访问服务器场景中的get.php,根据页面回显获取Flag并提交;2.使用渗透机场景windows7中火狐浏览器访问服务器场景中的post.php,根据页面回显获取Flag并提交;3.使用渗透机场景windows7中…...

多传感器融合定位十-基于滤波的融合方法Ⅰ其二
多传感器融合定位十-基于滤波的融合方法Ⅰ其二3. 滤波器基本原理3.1 状态估计模型3.2 贝叶斯滤波3.3 卡尔曼滤波(KF)推导3.4 扩展卡尔曼滤波(EKF)推导3.5 迭代扩展卡尔曼滤波(IEKF)推导4. 基于滤波器的融合4.1 状态方程4.2 观测方程4.3 构建滤波器4.4 Kalman 滤波实际使用流程4…...

Java集合面试题:HashMap源码分析
文章目录一、HashMap源码二、HashMap数据结构模型图三、HashMap中如何确定元素位置四、关于equals与hashCode函数的重写五、阅读源码基本属性参考文章:史上最详细的 JDK 1.8 HashMap 源码解析参考文章:Hash详解参考文章:hashCode源码分析参考…...

华为OD机试 - 数组合并(Python),真题含思路
数组合并 题目 现在有多组整数数组, 需要将他们合并成一个新的数组。 合并规则, 从每个数组里按顺序取出固定长度的内容合并到新的数组中, 取完的内容会删除掉, 如果该行不足固定长度或者已经为空, 则直接取出剩余部分的内容放到新的数组中, 继续下一行。 如样例 1, 获得长度…...

Vue2创建移动端项目
一、Vscode Vscode 下载安装以及常用的插件 1、Vscode 下载 下载地址:Vscode 中文语言插件 搜索 chinese 主题 Atom 主题 文件图标主题 搜索 icon 源代码管理插件GitLens 搜索 GitLens Live Server _本地服务器 搜索 Live Server Prettier - Code formatt…...
PorterDuffXfermode与圆角图片
版权声明 本文原创作者:谷哥的小弟作者博客地址:http://blog.csdn.net/lfdfhl 圆角图片 在项目开发中,我们常用到这样的功能:显示圆角图片。 这个是咋做的呢?我们来瞅瞅其中一种实现方式 /*** param bitmap 原图* p…...

如何准备大学生电子设计竞赛
大学生电子设计竞赛难度中上,一般有好几个类型题目可以选择,参赛者可以根据自己团队的能力、优势去选择合适自己的题目,灵活自主空间较大。参赛的同学们可以在暑假好好学习相关内容,把往年的题目拿来练练手。这个比赛含金量还是有…...

【Java容器(jdk17)】ArrayList深入源码,就是这么简单
ArrayList深入源码一、ArrayList源码解析1. MIXIN 的混入2. 属性说明3. 构造方法4. 其他方法(核心)iterator 和 listIterator 方法add方法remove 方法sort方法其他二、ArrayList 为什么是线程不安全的?体现哪些方面呢?三、ArrayLi…...

【Java 面试合集】简述下Java的三个特性 以及项目中的应用
简述下Java的特征 以及项目中的应用 1. 概述 上述截图中就是Java的三大特性,以及特性的实现方案。接下来就每个点展开来说说 2. 封装 满足:隐藏实现细节,公开使用方法 的都可以理解为是封装 而实现封装的有利手段就是权限修饰符了。可以根据…...

git基本概念图示【学习】
基本概念工作区(Working Directory)就是你在电脑里能看到的目录,比如名字为 gafish.github.com 的文件夹就是一个工作区本地版本库(Local Repository)工作区有一个隐藏目录 .git,这个不算工作区,…...

微前端qiankun架构 (基于vue2实现)使用教程
工具使用版本 node --> 16vue/cli --> 5 创建文件 创建文件夹qiankun-test。 使用vue脚手架创建主应用main和子应用dev 主应用 安装 qiankun: yarn add qiankun 或者 npm i qiankun -S 使用qiankun: 在 utils 内创建 微应用文件夹 microApp,在该文件夹…...

记录robosense RS-LIDAR-16使用过程3
一、wireshark抓包保存pcap文件并解析ubuntu18安装wireshark,参考下面csdn教程,官网教程我看的一脸蒙(可能英语太差)https://blog.csdn.net/weixin_46048542/article/details/121730448?spm1001.2101.3001.6650.2&utm_medium…...

【博学谷学习记录】大数据课程-学习第七周总结
Hadoop配置文件修改 Hadoop安装主要就是配置文件的修改,一般在主节点进行修改,完毕后scp下发给其他各个从节点机器 文件中设置的是Hadoop运行时需要的环境变量。JAVA_HOME是必须设置的,即使我们当前的系统中设置了JAVA_HOME,它也…...

154、【动态规划】leetcode ——494. 目标和:回溯法+动态规划(C++版本)
题目描述 原题链接:494. 目标和 解题思路 (1)回溯法 本题的特点是nums中每个元素只能使用一次,分别试探加上nums[index]和减去nums[index],然后递归的遍历下一个元素index 1。 class Solution { public:int res …...

MySQL-窗口函数
窗口函数概念常用窗口函数聚合窗口函数专用窗口函数语法OVER子句window_specwindow_name (命名窗口)partition_clause 分区order_clause 排序frame_clause 范围 (指定窗口大小)使用限制练习准备概念 窗口函数对一组查询执行类似于聚合的操作。然而&#…...

【C++设计模式】学习笔记(1):面向对象设计原则
目录 简介面向对象设计原则(1)依赖倒置原则(DIP)(2)开放封闭原则(OCP)(3)单一职责原则(SRP)(4)Liskov替换原则(LSP)(5)接口隔离原则(ISP)(6)优先使用对象组合,而不是类继承(7)封装变化点(8)针对接口编程,而不是针对实现编程结语简介 Hello! 非常感谢您阅读海…...

[测开篇]设计测试用例的方法如何正确描述Bug
文章目录为什么测试人员要写测试用例?怎样设计测试用例?(总的方面)1.基于需求设计测试用例(总的方面) 2.页面(总的方面) 3.非功能性测试(具体方面) 4.1 等…...

设计模式学习笔记--单例、建造者、适配器、装饰、外观、组合
以下内容根据以下网址及相关视频整理:Android设计模式之单例模式_谬谬清不给我取名字的博客-CSDN博客_android 单例模式 Android设计模式--单例模式的六种实现和单例模式讲解Volatile与Synchronized相关的并发_龙腾腾的博客-CSDN博客_android 单例 volatile java …...

English Learning - Day5 L1考前复习 2023.2.10 周五
English Learning - Day5 L1考前复习 2023.2.10 周五1 单选题:She has the face _________.2 单选题: The goals ________ he fought all his life no longer seemed important to him.3 单选题:Sales director is a position ______ communi…...

C. Prepend and Append
time limit per test 1 second memory limit per test 256 megabytes input standard input output standard output Timur initially had a binary string†† s� (possibly of length 00). He performed the following operation several (possibly zero)…...

javassm超市在线配送管理系统
为了解决用户便捷地在网上购物,本文设计和开发了一个超市管理系统。本系统是基于web架构设计,SSM框架 ,使用Mysql数据库管理,综合采用JSP模式来完成系统的相关功能。主要实现了管理员与用户的注册与登陆,个人中心、用户…...

Scratch少儿编程案例-多模式贪吃蛇(无尽和计时)
专栏分享 点击跳转=>Unity3D特效百例点击跳转=>案例项目实战源码点击跳转=>游戏脚本-辅助自动化点击跳转=>Android控件全解手册点击跳转=>Scratch编程案例👉关于作者...

谷歌蜘蛛池怎么搭建?Google蜘蛛池可以帮助谷歌排名吗?
本文主要分享关于谷歌蜘蛛池的搭建疑问,以及Google对谷歌排名的影响到底有多大。 本文由光算创作,有可能会被剽窃和修改,我们佛系对待这种行为吧。 谷歌蜘蛛池怎么搭建? 答案是:需要一个内链外链体系复杂的站群系统…...

Kubernetes集群-部署Java项目
Kubernetes集群-部署Java项目(SSG) k8s部署项目java流程图 第一步 打包制作镜像 打包 java源码: application.properties #在有pom.xml的路径下执行 mvn clean package制作镜像: 将刚才打包后的文件夹传到,装有dock…...

English Learning - Day54 作业打卡 2023.2.8 周三
English Learning - Day54 作业打卡 2023.2.8 周三引言1. 就算你不喜欢喝酒,也请尝一杯吧。2. 便纵有千种风情,更与何人说?——柳永《雨霖铃》 (来,挑战一下古诗词)3. 虽然忙,我也要参加会议。4. 无论发生什么…...