【JVM】JVM八股总结二:运行时内存相关问题

119 阅读4分钟

运行时内存

运行时数据区哪些区域?哪些线程共享? 哪些线程独享?哪些会出现OOM

堆:线程共享

虚拟机栈:线程私有

方法区:线程共享

程序计数器:线程私有

本地方法栈:线程私有

JVM内存区域分别的作用

运行时内存

:所有线程共享的区域,所有创建的对象都在堆中。堆中存在OOM和GC

栈/虚拟机栈线程私有,一个线程一个栈,没有GC但可能OOM。一个方法的调用就会压入一个栈帧,栈帧中有操作数栈(存储计算中间值)和局部变量表(存储方法的局部变量)。

方法区:线程共享,有OOM,GC频率低,抽象概念。主要存放类的相关信息(类,域Field,方法)。

程序计数器线程私有存储下一条指令的地址。

本地方法栈线程私有,和虚拟机栈类似存储native方法的调用信息。

方法区和永久代的联系?

方法区是抽象概念

在hotspot中,jdk1.8之前具体实现是永久代,1.8及之后具体实现是元空间

为什么要将永久代 (PermGen) 替换为元空间 (MetaSpace) 呢?

(深入理解Java虚拟机-周志明)

  1. 永久代虽然 可以通过参数设置大小但其使用jvm内存不好制定大小(不能动态调整),受jvm内存限制, (当程序中有大量动态类时如jsp)容易OOM。而元空间使用本地内存,他受到的是本机内存的限制。(主要原因)
  2. oracle合并了hotspot和jrocket,JRocket中没有永久代这个东西。

StringTable字符串常量池 为什么要调整 到堆?

如果放在永久代或者元空间,字符串常量池的回收效率自然也不高。而一般我们很多字符串是可以回收的,如果创建了大量字符串而不及时回收,浪费空间,在永久代实现还容易导致OOM。所以放入堆中,是为了方便垃圾回收。

堆空间的基本结构了解吗?

新生代:伊甸园区、s0区、s1区

老年代。

对象什么情况进入老年代?三种情况

  1. 每次minor GC后存活的对象 年龄+1, 默认情况下对象年龄15岁的时候进入老年代。(长期存活对象进老年代)
  2. 动态判断年龄。一批对象的总大小大于了这块Survivor区域的内存大小的50% ,那么此时大于等于这批对象年龄的对象,就可以直接进入老年代了。

年龄1+年龄2+年龄n的多个年龄对象总和超过了Survivor区的50%,此时就会把年龄n以上的对象都放入老年代。

  1. 大对象直接进老年代。

对象分配和GC流程

28.对象分配过程_哔哩哔哩_bilibili

1.创建一个新对象时,会在新生区的Eden进行分配;如果新生区的空间不足以分配,会进行一次Minor GC(Young GC) 后检查是否够分配;如果还是不够,就放入老年区如果老年区还是放不下,就进行一次Full GC; 进行完Full GC后old区还是放不下,抛出OOM。

2.新生代收集(Minor GC\Young GC)的过程:进行Minor GC时,对Eden区可以回收的对象进行回收不能回收的放入survivor的to区;同时对Survivor的两个区From和To,检查From区可以回收的对象,如果对象不能回收就分代年龄加一并也移到to区,如果分代年龄到达阈值了(一般是15),就晋升老年代

3.老年代收集(Major GC/Old GC):对老年代进行收集。目前只有CMS垃圾回收器会对Old区单独回收。

4.整堆收集(Full GC):收集整个Java堆和方法区。

5.每次GC都会导致Stop The World现象,即停止其它线程的运行。

大对象放在哪个内存区域?

老年代

直接内存有什么用?如何使用?

哪些区域有OOM,如果要你写代码如何模拟?(云智)

  1. 虚拟机栈,写个递归方法一直调。
  2. ,循环new大对象往集合中放。(对象中有很大的byte数组)
  3. 方法区,需要循环加载类, 或者循环一直用jdk生成动态代理类。(如何加载)

用ClassWriter对象模拟生成字节码,然后用classloader的defineClass去加载字节码。

public static void main(String[] args) {
    int j = 0;
    try {
        MyClassLoader loader = new MyClassLoader();
        for (int i = 0; i < 100000000; i++) {
            //创建ClassWriter对象,用于生成类的二进制字节码
            com.sun.xml.internal.ws.org.objectweb.asm.ClassWriter classWriter = new ClassWriter(0);
            //指明版本号,修饰符,类名,包名,父类,接口
            classWriter.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC, "Class" + i, null, "java/lang/Object", null);
            //返回byte[]
            byte[] code = classWriter.toByteArray();
            //类的加载
            loader.defineClass("Class" + i, code, 0, code.length);  //Class对象
            j++;
        }
    } finally {
        System.out.println(j);
    }
}

栈、堆、方法区溢出分别报什么错? 问得少