本文转载:https://blog.youkuaiyun.com/weixin_45676630/article/details/105799329
但没有转载完全,gc算法之前已经写过了
1.内存模型
1.1 JVM 运行时内存
Java 堆从GC 的角度可以细分为: 新生代(Eden 区、From Survivor 区和To Survivor 区)和老年
代。

1.1.1 young区
是用来存放新生的对象。一般占据堆的1/3 空间。由于频繁创建对象,所以young区会频繁触发MinorGC 进行垃圾回收。young区又分为 Eden 区、ServivorFrom、ServivorTo 三个区。
Java 新对象的出生地(如果新创建的对象占用内存很大,则直接分配到old区)。当Eden 区内存不够的时候就会触发MinorGC,对young区进行一次垃圾回收。正常对象创建所在区域,大多数对象“朝生夕死”
上一次GC 的幸存者,作为这一次GC 的被扫描者。
保留了一次MinorGC 过程中的幸存者。在同一个时间点上,ServivorFrom和ServivorTo只能有一个区有数据,另外一个是空的。
MinorGC 采用复制算法。
- eden、servicorFrom 复制到 ServicorTo,年龄+1
首先,把Eden 和ServivorFrom区域中存活的对象复制到ServicorTo 区域(如果有对象的年龄以及达到了old区的标准,则赋值到old区),同时把这些对象的年龄+1(如果ServicorTo 不够位置了就放到old区); - 清空eden、servicorFrom
然后,清空Eden 和ServicorFrom 中的对象; - ServicorTo 和 ServicorFrom互换
最后,ServicorTo 和ServicorFrom 互换,原ServicorTo 成为下一次GC 时的ServicorFrom区。

1.1.2 old区
主要存放应用程序中生命周期长的内存对象。old区的对象比较稳定,所以MajorGC 不会频繁执行。在进行MajorGC 前一般都先进行了一次MinorGC,使得有young的对象晋身入old区,导致空间不够用时才触发。当无法找到足够大的连续空间分配给新创建的较大对象时也会提前触发一次MajorGC 进行垃圾回收腾出空间。MajorGC 采用标记清除算法:首先扫描一次所有old区,标记出存活的对象,然后回收没有标记的对象。MajorGC 的耗时比较长,因为要扫描再回收。MajorGC 会产生内存碎片,为了减少内存损耗,我们一般需要进行合并或者标记出来方便下次直接分配。当old区也满了装不下的时候,就会抛出OOM(Out of Memory)异常。
1.2 借助工具体验
插件下载链接:https://visualvm.github.io/pluginscenters.html
① 运行命令 jvisualvm
打开工具界面:

在插件中选择上面地址中下载的插件

从这个图中可以看出堆的区域划分,也证明了前面说的ServivorFrom和ServivorTo只能有一个区有数据,另外一个是空的。即图中的S0和S1.
1.2.1 内存溢出的配置体验
堆内存溢出案例:
设置参数:-Xmx20M -Xms20M
测试代码:
public String heap() throws Exception{
while(true){
list.add(new Person());
}
}

方法区内存溢出案例:
设置参数:-XX:MetaspaceSize=50M -XX:MaxMetaspaceSize=50M
测试代码:
public String heap(){
while(true){
list.addAll(MetaspaceUtil.createClasses());
}
}

public class MetaspaceUtil extends ClassLoader {
public static List<Class<?>> createClasses() {
List<Class<?>> classes = new ArrayList<Class<?>>();
for (int i = 0; i < 10000000; ++i) {
ClassWriter cw = new ClassWriter(0);
cw.visit(Opcodes.V1_1, Opcodes.ACC_PUBLIC, "Class" + i, null,
"java/lang/Object", null);
MethodVisitor mw = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>",
"()V", null, null);
mw.visitVarInsn(Opcodes.ALOAD, 0);
mw.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object",
"<init>", "()V");
mw.visitInsn(Opcodes.RETURN);
mw.visitMaxs(1, 1);
mw.visitEnd();
MetaspaceUtil test = new MetaspaceUtil();
byte[] code = cw.toByteArray();
Class<?> exampleClass = test.defineClass("Class" + i, code, 0, code.length);
classes.add(exampleClass);
}
return classes;
}
}
虚拟机栈溢出案例:
测试代码:
public static void method(long i){
System.out.println(count++);
method(i);
}

2 垃圾回收
2.1 如何确定一个对象是垃圾
- (1)引用计数法
在Java 中,引用和对象是有关联的。如果要操作对象则必须用引用进行。因此,很显然一个简单
的办法是通过引用计数来判断一个对象是否可以回收。简单说,即一个对象如果没有任何与之关
联的引用,即他们的引用计数都不为0,则说明对象不太可能再被用到,那么这个对象就是可回收
对象。 - (2)可达性分析
由GC Root出发,开始寻找,看看某个对象是否可达,如果在“GC roots”和一个对象之间没有可达路径,则称该对象是不可达的。
GC Root:类加载器、Thread、本地变量表、static成员、常用引用、本地方法栈中的变量等。
要注意的是,不可达对象不等价于可回收对象,不可达对象变为可回收对象至少要经过两次标记
过程。两次标记后仍然是可回收对象,则将面临回收。
2.2 垃圾回收算法
3.垃圾收集器
3.1如何选择收集器
- 优先调整堆的大小让服务器自己选择
- 如果内存小于100M,使用串行收集器
- 如果是单核,并且没有停顿时间要求,使用串行或JVM自己选
- 如果允许停顿时间超过1秒,选择并行或JVM自己选
- 如果响应时间最重要,并且不能操作1秒,使用并发收集器
3.2如何开启收集器
- ①串行
-XX: +UseSerialGC
-XX: +UseSerialoldGC
- ②并行(吞吐量优先)
-XX: +UseParallelGC
-XX: +UseParalleloldGC
- ③并发收集器(响应时间优先)
-XX: +UseConcMarkSweepGC
-XX: +UseG1GC

1171

被折叠的 条评论
为什么被折叠?



