面向对象---面试题

文章目录

    • 1 成员变量、局部变量、类变量(静态成员变量)存储在内存的什么地方?
    • 2 下面变量引用存放在哪里?
    • 3 HotSpot 方法区变迁
    • 4 为什么调整字符串常量池和静态变量的位置?
    • 5 为什么用元空间替换永久代?
    • 6 JDK1.8元空间会产生内存溢出么?在什么情况下会产生内存溢出?
    • 7 JVM8的内存结构
    • 8 方法区和永久代的区别
    • 9 几种主要的JVM参数

1 成员变量、局部变量、类变量(静态成员变量)存储在内存的什么地方?

(1)成员变量:

  • 成员变量是定义在类中的变量,它可以是实例变量(非静态成员变量)或类变量(静态成员变量)。
  • 实例变量存储在内存中的对象实例中,每个对象实例都有一份实例变量的副本。
  • 类变量存储在方法区(在JDK 8之前称为永久代,JDK 8及之后称为元数据区)中,它是类的一部分,并且在整个程序运行期间只有一份副本。

(2)局部变量

  • 局部变量是定义在方法、代码块或构造方法中的变量,它只在方法或代码块的生命周期内有效。
  • 局部变量存储在方法栈中,方法栈是线程私有的,用于存储方法的调用和局部变量。

2 下面变量引用存放在哪里?

public class StaticObjTest {
    static class Test{
        // 静态变量
        static ObjectHolder staticObj = new ObjectHolder();
        // 实例变量
        ObjectHolder instanceObj = new ObjectHolder();
        void foo() {
            // 局部变量
            ObjectHolder localObj = new ObjectHolder()();
            System.out.println("done");
        }
    } 
    private static class ObjectHolder{
    } 
    public static void main(String[] args) {
        Test test = new StaticObjTest.Test();
        test.foo();
    }
}
  • 以上代码中,静态变量staticObj随着Test的类型信息存放在方法区
  • 实例变量instanceObj随着Test的对象实例存放在
  • 局部变量localObj则是存放在foo()方法帧的局部变量表中
  • 三个变量引用对应的对象实体都是在空间。

内存结构分析:
面向对象---面试题_第1张图片

3 HotSpot 方法区变迁

版本 演进细节
JDK6及之前 方法区的实现为永久代,静态变量存放在永久代中,字符串常量池(StringTable)位于运行时常量池中。
JDK7 方法区的实现为永久代,但已经逐步“去永久代”,静态变量、字符串常量池移除,保存在堆中
JDK8 方法区的实现为本地内存的元空间,字符串常量池、静态变量仍在堆中

在 HotSpot JVM(Java Virtual Machine)中,方法区(Method Area)是用于存储类的元数据信息、常量池、字段、方法字节码等数据的区域。在早期版本的 HotSpot JVM 中,方法区是永久代(Permanent Generation)的一部分,用于存储类的元数据信息,包括类的结构、常量池、字段、方法等。

然而,随着 Java 8 版本的发布,HotSpot JVM 对内存管理做了重大调整,永久代被彻底移除,并由元空间(MetaSpace)取而代之。元空间是方法区的替代方案,用于存储类的元数据信息和符号引用,并且不再有永久代的大小限制,而是使用本地内存来管理。

元空间的特点:

  • 本地内存管理:元数据区不再是 JVM 的一部分,而是使用本地内存进行管理。这样可以避免了永久代可能引发的内存泄漏和溢出问题。
  • 自动调整大小:元空间可以根据需要自动调整大小,不再受永久代大小的限制。这样可以更好地适应不同应用的内存需求。
  • 元数据压缩:元空间可以对类的元数据信息进行压缩,减少内存的占用。

总的来说,HotSpot JVM 在 Java 8 版本中通过引入元空间替代永久代,从而改善了内存管理的问题,提高了 Java 程序的稳定性和性能。这也是为什么在 Java 8 及之后的版本中不再需要调整永久代大小,并且可以通过配置参数来调整元数据区的大小。

4 为什么调整字符串常量池和静态变量的位置?

JDK7中将字符串常量池放到了堆空间中:因为永久代的回收效率很低,在Full GC时才会触发,而Full GC在老年代的空间不足、永久代不足时才会触发,这就导致字符串常量池回收效率不高,而回收效率低会导致永久代内存不足。将字符串常量池放到堆里,能及时回收内存。
在 Java 8 版本之后,由于永久代被移除,静态变量通常存储在元空间,而不是传统的永久代。调整静态变量的位置可以避免永久代可能引发的内存泄漏和溢出问题,并提供更好的内存管理和优化。

5 为什么用元空间替换永久代?

以下是一些使用元空间替换永久代的原因:

  • 避免内存泄漏和溢出
    永久代在某些情况下可能会引发内存泄漏或内存溢出问题。由于永久代的大小是有限制的,当类的元数据信息过多时,可能导致永久代空间不足,从而引发内存溢出问题。元空间使用本地内存进行管理,可以避免这些问题,并根据应用的需要动态调整空间大小。

  • 灵活的内存管理
    元空间的大小可以根据应用的需要进行动态调整,不再受永久代大小的限制。这样可以更好地适应不同应用的内存需求,提高了内存管理的灵活性。

  • 减少内存碎片
    永久代使用的是堆内存,而元空间使用本地内存,减少了在堆上分配内存和释放内存的开销,从而减少了内存碎片。

  • 提高性能
    元空间的内存管理机制相对于永久代更加高效,提高了类加载和卸载的性能。元空间使用本地内存来管理类的元数据信息,减少了垃圾回收对类元数据的影响,提高了类加载和卸载的效率。

  • 对永久代进行调优是很困难的

6 JDK1.8元空间会产生内存溢出么?在什么情况下会产生内存溢出?

(1)解析
在JDK 1.8中,永久代被移除,取而代之的是元空间。元空间使用本地内存进行管理,并且其大小不再受永久代大小的限制。因此,JDK 1.8中的元空间不会产生传统意义上的永久代内存溢出。
虽然元空间不会产生永久代内存溢出,但仍然可能产生内存溢出。例如,如果加载大量的类和资源,或者进行大量的对象创建,都可能导致本地内存耗尽,从而引发本地内存溢出(OutOfMemoryError: Metaspace)问题。
解决办法:增加 Metaspace 的大小

-XX:MaxMetaspaceSize=512m

(2)代码演示
模拟Metaspace空间溢出,我们不断生成类往元空间灌,类占据的空间是会超过Metaspace指定的空间大小的

  • 查看元空间大小
java -XX:+PrintFlagsInitial

在这里插入图片描述

  • 默认是大约20.80M

在IDEA中新建SpringBoot项目

  • 修改https://start.aliyun.com/作为spring模板
    面向对象---面试题_第2张图片

测试代码:

public class MetaspaceDemo {

    static class OOM{}

    public static void main(String[] args) {
        int i = 0;//模拟计数多少次以后发生异常
        try {
            while (true){
                i++;
                Enhancer enhancer = new Enhancer();
                enhancer.setSuperclass(OOM.class);
                enhancer.setUseCache(false);
                enhancer.setCallback(new MethodInterceptor() {
                    @Override
                    public Object intercept(Object o, Method method, Object[] objects,
                                            MethodProxy methodProxy) throws Throwable {
                        return methodProxy.invokeSuper(o,args);
                    }
                });
                enhancer.create();
            }
        } catch (Throwable e) {
            System.out.println("=================多少次后发生异常:"+i);
            e.printStackTrace();
        }
    }
}

执行后查看内存会发现一直在增加,停止运行后设置元空间(Metaspace)的初始大小和最大大小。参数如下:

-XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m

面向对象---面试题_第3张图片
运行结果:
在这里插入图片描述
cglib默认开启了UserCache生成的代理类都会复用原先产生在缓存中的类,所以至始至终都只有一个代理类,所以不会产生内存溢出。手动关闭它,enhancer.setUseCache(false)

7 JVM8的内存结构

面向对象---面试题_第4张图片
在JVM 8中,内存结构主要包括以下几个部分:

(1)方法区(Metaspace):用于存储类的元数据信息、常量池、字段、方法等。JVM 8中,取代了永久代(Permanent Generation),称为元空间。元空间使用本地内存进行管理,并且大小不再受永久代大小的限制。

(2)(Heap):用于存储对象实例。堆是Java程序运行时所需内存的主要部分。在JVM启动时通过-Xmx和-Xms参数来设置堆的最大大小和初始大小。

(3)Java栈(Java Stack):用于存储线程的方法调用和局部变量。每个线程都有自己的Java栈,用于存储方法调用的栈帧,以及线程局部变量和操作数栈。Java栈的大小可以通过-Xss参数设置。

(4)本地方法栈(Native Stack):用于存储JNI(Java Native Interface)方法的调用和本地方法的栈帧。

(5)程序计数器(Program Counter Register):记录了线程正在执行的字节码指令地址。

(6)直接内存(Direct Memory):JVM使用直接内存来支持NIO(New I/O)库,直接内存不是JVM运行时数据区的一部分,但也是JVM内存结构的一部分。

(7)虚拟机栈(JVM Stacks):虚拟机栈是线程私有的,随线程生灭。虚拟机栈描述的是线程中的方法的内存模型,每个方法被执行的时候,都会在虚拟机栈中同步创建一个栈帧(stack frame),方法被执行时入栈,执行完后出栈。

在JVM 8中,永久代被移除,取而代之的是元空间。元空间使用本地内存进行管理,避免了永久代可能引发的内存泄漏和溢出问题。这也是为什么在JVM 8及之后版本不再需要调整永久代大小,并且可以通过配置元空间大小来优化内存管理。

8 方法区和永久代的区别

(1)方法区

JVM规范只是规定了有方法区这么个概念和它的作用,并没有规定如何去实现它。在JVM规范中,方法区主要用于存储类的信息、常量池、方法数据、方法代码等。由于方法区是JVM的规范,所以所有的虚拟机都必须遵守。常见的JVM 虚拟机 Hotspot 、 JRockit(Oracle)、J9(IBM)。

(2)永久代
永久代在JVM启动时就会被固定分配一块内存空间,大小由-XpermSize参数指定,无法动态调整。
永久代的大小限制可能导致内存溢出问题,特别是在大量动态生成类的场景下。
永久代是 HotSpot 虚拟机基于 JVM 规范对方法区的一个落地实现,且永久代是 HotSpot 虚拟机特有的概念,而如 JRockit(Oracle)、J9(IBM) 虚拟机有方法区 ,但是没有永久代。
Metaspace(元空间)是 JDK8及之后, HotSpot 虚拟机对方法区新的实现。

9 几种主要的JVM参数

(1)堆栈配置相关

  • -Xmx3550m: 设置最大堆大小为3550m。
  • -Xms3550m: 设置初始堆大小为3550m。
  • -Xmn2g: 设置年轻代大小为2g。
  • -Xss128k: 设置每个线程的堆栈大小为128k。
  • -XX:MaxPermSize: 设置持久代大小为16m
  • -XX:NewRatio=4: 设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代),比例为1:4
  • -XX:SurvivorRatio=4: 设置年轻代中Eden区与Survivor区的大小比值。设置为4,则两个Survivor区与一个Eden区的比值为2:4,一个Survivor区占整个年轻代的1/6
  • -XX:MaxTenuringThreshold=0: 设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。

(2)垃圾收集器相关

  • -XX:+UseParallelGC: 选择垃圾收集器为并行收集器。
  • -XX:+UseParNewGC:指定使用 ParNew + Serial Old 垃圾回收器组合
  • -XX:ParallelGCThreads=20: 配置并行收集器的线程数
  • -XX:+UseConcMarkSweepGC:指定使用 CMS + Serial Old 垃圾回收器组合
  • -XX:CMSFullGCsBeforeCompaction:由于并发收集器不对内存空间进行压缩、整理,所以运行一段时间以后会产生“碎片”,使得运行效率降低。此值设置运行多少次GC以后对内存空间进行压缩、整理。
  • -XX:+UseCMSCompactAtFullCollection: 打开对年老代的压缩。可能会影响性能,但是可以消除碎片

面向对象---面试题_第5张图片

你可能感兴趣的:(java学习,java,开发语言,学习,面试)