Java面试题(六)美团JVM夺命7连问(灵魂拷问)

news2025/1/6 22:01:11

0.来看一道美团的面试题

这题直接把人给问懵逼了,你能全部答出来吗?

Object o = new Object();
  1. 请解释对象的创建过程?
  2. DCL要不要加volatile问题?
  3. 对象在内存中的存储布局?
  4. 什么是指针压缩?
  5. 对象头具体包含哪些内容?
  6. 对象怎么定位?
  7. 对象怎么分配?
  8. new Object()在内存中占用多少字节?

来大家回答下

1.对象创建过程

T t = new T();
  • new T 分配空间,赋默认值
  • 调用构造方法,赋初始值
  • 把t 引用变量指向 T这个内存空间

2.DCL要不要加volatile

public class SingletonInstance {

    //volatile 禁止指令重排序
    private static volatile SingletonInstance INSTANCE;

    private SingletonInstance(){
        //禁止调用构造方法
    }

    //DCL 双重检查锁
    public static SingletonInstance getInstance(){
        if(INSTANCE == null){
            synchronized (SingletonInstance.class){
                if(INSTANCE == null){
                    INSTANCE = new SingletonInstance();
                }
            }
        }
        return INSTANCE;
    }
}

加volatile是禁止指令重排序,因为一个对象的创建有可能是乱序的,什么意思呢?

正常的创建过程是按下面顺序执行

  • new T 分配空间,赋默认值
  • 调用构造方法,赋初始值
  • 把t 引用变量指向 T这个内存空间

但是,有可能第3步在第2步之前执行,这就会造成对象的属性还没赋值,对象T就被t这个变量指向了,此时这个对象是一个半初始化对象,在多线程的场景下,有可能会被别的线程拿到,所以需要用volatile来禁止这个指令的乱序执行,必须按照顺序去执行

3.对象在内存中的内存布局

一个对象在内存中的布局分为3个部分分别是:

  • 对象头:包括了对象布局、锁、类型、GC状态、同步状态和标识哈希码,class指针的基本信息,这里面有分为markword(占8个字节),和class pointer(占4个字节,原本是8个字节,这里经过指针压缩后变成4个字节)
  • 对象数据:就是对象里面属性变量等数据
  • 对齐填充:64位虚拟机必须是8的倍数,不能被8整除就需要补齐

在这里插入图片描述
来我们通过代码看下
需要用到openjdk 里面的Java Object Layout 对象内存布局工具

<!-- https://mvnrepository.com/artifact/org.openjdk.jol/jol-core -->
<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.9</version>
</dependency>

先准备一个对象T

private static class T{

}

public static void main(String[] args) {
    T t = new T();
    System.out.println(ClassLayout.parseInstance(t).toPrintable());
}

打印出来结果
可以看到header是占用了4+4+4=12个字节
后面一行loss due to the next object alignment是下一次对象补齐缺失的 4个字节,为什么要补齐4个字节呢?
因为前面12个字节/8不能整除,需要补4个自己 12+4=16/8才能整除

所以整个Instance大小是16个字节
在这里插入图片描述

看到这里有同学想问,那 instance data怎么没有占用大小呢?因为对象T里面没有属性,所以不会占用大小

那如果在T对象里面加个属性会怎么样?想一想会占用多少字节?

    private static class T{
        int a;
    }

    public static void main(String[] args) {
        T t = new T();
        System.out.println(ClassLayout.parseInstance(t).toPrintable());
    }

在这里插入图片描述
看到没有,还是16个字节为什么呢?
这是因为T对象里面添加了int a这个属性,他是占用4个字节的,此时4+4+4+4=16/8正好能整除,就不需要补齐了,所以整个实例占用大小还是16个字节

那如果再添加一个boolean属性呢,会怎么样?

    private static class T{
        int a;
        boolean flag;
    }

    public static void main(String[] args) {
        T t = new T();
        System.out.println(ClassLayout.parseInstance(t).toPrintable());
    }

在这里插入图片描述
此时boolean占用1个自己,那么大小就是hearder12+int a 4+ boolean flag 1 = 17个字节
17不能整除8,那么就需要补17+7=24/8才能整除,所以此时实例对象的占用大小是24个字节

那么如果再添加一个String 对象呢?

    private static class T{
        int a;
        boolean flag;
        String str = "hello";
    }

    public static void main(String[] args) {
        T t = new T();
        System.out.println(ClassLayout.parseInstance(t).toPrintable());
    }

在这里插入图片描述

有没有发现 boolean下面多了一行 3 (alignment/padding gap) ?
这是因为boolean ,byte short都需要转成int型,所以这个是内部补齐3个字节,因为int 在JVM中是4个字节

但是4 java.lang.String T.str 为什么会是4个字节呢,因为这里的str只是对象的引用,而hello这个字符串是另外一个对象,他是存放在常量区的

这个时候整个实例大小是24正好整除8,不需要补齐

4.指针压缩

刚刚上面那些内存储存空间都是 jvm默认进行压缩了的,来我们来看下jvm参数

在这里插入图片描述
重点关注2个参数,一个是类指针,一个是普通对象指针

-XX:+UseCompressedClassPointers:类指针
-XX:+UseCompressedOops:普通对象指针

所以jvm针对类和普通对象都进行了压缩,那么我们把这2个参数关闭,就是不压缩,来看看结果

把+改成-就关闭了
-XX:-UseCompressedClassPointers:类指针
-XX:-UseCompressedOops:普通对象指针
在idea 顶部菜单选中Run–然后选择 edit configurations,加入到jvm配置中
在这里插入图片描述
在这里插入图片描述

然后再执行看看结果,发现header 的 class pointer变成了8个字节
而String 对象也变成了8个字节,
在这里插入图片描述
JVM为什么会进行压缩呢?

这是因为如果应用的对象过多,使用 64位的指针将浪费大量内存,64位的 JVM将会比 32位的 JVM多耗费 50%的内存。为了节约内存对类指针,普通对象指针进行压缩。压缩50%的空间

5.对象头包含哪些信息

上面说到对象头header里面是markword 和class pointer2 部分内容,那具体包含哪些信息呢?
markword 又包括锁的信息,GC的信息 ,还有对象的hashcode

    private static class T{
        int a;
        boolean flag;
        String str = "hello";
    }

    public static void main(String[] args) {
        T t = new T();
        System.out.println(ClassLayout.parseInstance(t).toPrintable());

        //hashcode
        t.hashCode();
        System.out.println(ClassLayout.parseInstance(t).toPrintable());

        //加锁
        synchronized (t){
            System.out.println(ClassLayout.parseInstance(t).toPrintable());
        }

        //释放
        System.out.println(ClassLayout.parseInstance(t).toPrintable());
    }

4次打印头里面的数据是不一样的
在这里插入图片描述

6.对象怎么定位

什么意思呢?就是t这个引用变量怎么去找到T这个实例

T t = new T();

有2种方式,一个是句柄方式,一个是直接指针方式
在这里插入图片描述

在这里插入图片描述

7.对象怎么分配内存

在这里插入图片描述

1.判断栈上是否有足够空间

这里和之前理解有所差别。之前一直都认为new出来的对象都是分配在堆上的,其实不是,在满足一定的条件,会先分配在栈上。那么为什么要在栈上分配?什么时候分配在栈上?分配在栈上的对象如何进行回收呢?下面来详细分析。

1.1. 为什么要分配在栈上

通过JVM内存模型中,我们知道Java的对象都是分配在堆上的。当堆空间(新生代或者老年代)快满的时候,会触发GC,没有被任何其他对象引用的对象将被回收。如果堆上出现大量这样的垃圾对象,将会频繁的触发GC,影响应用的性能。其实这些对象都是临时产生的对象,如果能够减少这样的对象进入堆的概率,那么就可以成功减少触发GC的次数了。我们可以把这样的对象放在栈上,这样该对象所占用的内存空间就可以随栈帧出栈而销毁,就减轻了垃圾回收的压力。

1.2. 什么情况下会分配在栈上

为了减少临时对象在堆内分配的数量,JVM通过逃逸分析确定该对象会不会被外部访问。如果不会逃逸可以将该对象在栈上分配内存。随栈帧出栈而销毁,减轻GC的压力。

1.3. 什么是逃逸

public class Test {

    public User test1() {
        User user = new User();
        user.setId(1);
        user.setName("张三");
        return user;
    }

    public void test2() {
        User user = new User();
        user.setId(2);
        user.setName("李四");
    }
}

Test里有两个方法,test1()方法构建了user对象,并且返回了user,返回回去的对象肯定是要被外部使用的。这种情况就是user对象逃逸出了test1()方法。

而test2()方法也是构建了user对象,但是这个对象仅仅是在test2()方法的内部有效,不会在方法外部使用,这种就是user对象没有逃逸。

判断一个对象是否是逃逸对象,就看这个对象能否被外部对象访问到。

结合栈上分配来理解为何没有逃逸出去的对象为什么应该分配在栈上呢?来看下图:

在这里插入图片描述
Test2()方法的user对象只会在当前方法内有效,如果放在堆里,在方法结束后,其实这个对象就已经是垃圾的,但却在堆里占用堆内存空间。如果将这个对象放入栈中,随着方法入栈,逻辑处理结束,对象就变成垃圾了,再随着栈帧出栈。这样可以节约堆空间。尤其是这种非逃逸对象很多的时候。可以节省大量的堆空间,降低GC的次数。

1.4. 什么是对象的逃逸分析

就是分析对象动态作用域,当一个对象在方法中被定义后,它可能被外部方法所引用,例如作为参数传递到其他地方中。 上面的例子中,很显然test1()方法中的user对象被返回了,这个对象的作用域范围不确定,test2方法中的user对象我们可以确定当方法结束这个对象就可以认为是无效对象了,对于这样的对象我们其实可以将其分配在栈内存里,让其在方法结束时跟随栈内存一起被回收掉。

大白话说就是:判断user对象是否会逃逸到方法外,如果不会逃逸到方法外,那么就建议在栈中分配一块内存空间,用来存储临时的变量。是不是不会逃逸到方法外的对象就一定会分配到栈空间呢?不是的,需要满足一定的条件:第一个条件是JVM开启了逃逸分析。可以通过设置参数来开启/关闭逃逸分析。

-XX:+DoEscapeAnalysis开启逃逸分析
-XX:-DoEscapeAnalysis关闭逃逸分析  

JVM对于这种情况可以通过开启逃逸分析参数(-XX:+DoEscapeAnalysis)来优化对象内存分配位置,使其通过标量替换优先分配在栈上(栈上分配),JDK7之后默认开启逃逸分析,如果要关闭使用参数(-XX:-DoEscapeAnalysis)

1.5. 什么是标量替换

如果一个对象通过逃逸分析能过确定他可以在栈上分配,但是我们知道一个线程栈的空间默认也就1M,栈帧空间就更小了。而对象分配需要一块连续的空间,经过计算如果这个对象可以放在栈帧上,但是栈帧的空间不是连续的,对于一个对象来说,这样是不行的,因为对象需要一块连续的空间。那怎么办呢?这时JVM做了一个优化,即便在栈帧中没有一块连续的空间方法下这个对象,他也能够通过其他的方式,让这个对象放到栈帧里面去,这个办法就是标量替换。

什么是标量替换呢?

如果有一个对象,通过逃逸分析确定在栈上分配了,以User为例,为了能够在有限的空间里能够放下User中所有的东西,我们不会在栈上new一个完整的对象了,而是只是将对象中的成员变量放到栈帧里面去。如下图:

在这里插入图片描述
栈帧空间中没有一块完整的空间放User对象,为了能够放下,我们采用标量替换的方式,不是将整个User对象放到栈帧中,而是将User中的成员变量拿出来分别放在每一块空闲空间中。这种不是放一个完整的对象,而是将对象打散成一个个的成员变量放到栈帧上,当然会有一个地方标识这个属性是属于那个对象的,这就是标量替换。

通过逃逸分析确定该对象不会被外部访问,并且对象可以被进一步分解时,JVM不会创建该对象,而是将该对象成员变量分解若干个被这个方法使用的成员变量所代替,这些代替的成员变量在栈帧或寄存器上分配空间,这样就不会因为没有一大块连续空间导致对象内存不够分配了。开启标量替换参数是

-XX:+EliminateAllocations

JDK7之后默认开启。

1.6. 标量替换与聚合量

那什么是标量,什么是聚合量呢?

标量即不可被进一步分解的量,而JAVA的基本数据类型就是标量(如:int,long等基本数据类型以及 reference类型等),标量的对立就是可以被进一步分解的量,而这种量称之为聚合量。而在JAVA中对象就是可以被进一步分解的聚合量。

2.判断是否是大对象,不是就放到Eden区

断是否是大对象,如果是则直接放入到老年代中。如果不是,则判断是否是TLAB?如果是则在Eden去分配一小块空间给线程,把这个对象放在Eden区。如果不采用TLAB,则直接放到Eden区。

什么是TLAB呢?本地线程分配缓冲(Thread Local Allocation Buffer,TLAB)。简单说,TLAB是为了避免多线程争抢内存,在每个线程初始化的时候,就在堆空间中为线程分配一块专属的内存。自己线程的对象就往自己专属的那块内存存放就可以了。这样多个线程之间就不会去哄抢同一块内存了。jdk8默认使用的就是TLAB的方式分配内存。

通过-XX:+UseTLAB参数来设定虚拟机是否启用TLAB(JVM会默认开启-XX:+UseTLAB),­-XX:TLABSize 指定TLAB大小。

2.1. 对象是如何在Eden区分配的呢?

在这里插入图片描述

public class GCTest { 
    public static void main(String[] args) throws InterruptedException { 
        byte[] allocation1, allocation2;
        allocation1 = new byte[60000*1024];
    } 
}

来看这段代码,定义了一个字节数组allocation2,给他分配了一块内存空间60M。

来看看程序运行的效果,这里为了方便检测效果,设置一下jvm参数打印GC日志详情

-XX:+PrintGCDetails 打印GC相信信息

2.2. Eden区刚好可以放得下对象

2.3. Eden区满了,会触发GC

3.大对象直接放入到老年代

3.1. 什么是大对象

  • Eden园区放不下了肯定是大对象。
  • 通过参数设置什么是大对象。-XX:PretenureSizeThreshold=1000000 (单位是字节) -XX:+UseSerialGC。如果对象超过设置大小会直接进入老年代,不会进入年轻代,这个参数只在 Serial 和ParNew两个收集器下有效。
  • 长期存活的对象将进入老年代。虚拟机采用分代收集的思想来管理内存,虚拟机给每个对象设置了一个对象年龄(Age)计数器。 如果对象在 Eden 出生并经过第一次 Minor GC 后仍然能够存活,并且能被 Survivor 容纳的话,将被移动到 Survivor 空间中,并将对象年龄设为1。对象在 Survivor 中每熬过一次 MinorGC,年龄就增加1岁,当它的年龄增加到一定程度(默认为15岁,CMS收集器默认6岁,不同的垃圾收集器会略微有点不同),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 -XX:MaxTenuringThreshold 来设置。

3.2. 为什么要将大对象直接放入到老年代呢

为了避免为大对象分配内存时的复制操作而降低效率。

3.3. 什么情况要手动设置分代年龄呢

如果我的系统里80%的对象都是有用的对象,那么经过15次GC后会在Survivor中来回翻转,这时候不如就将分代年龄设置为5或者8,这样减少在Survivor中来回翻转的次数,直接放入到老年代,节省了年轻代的空间。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/75604.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

生成树问题汇总

生成树问题汇总注1、最小(大)生成树思路代码例子&#xff1a;1、最小生成树结果是2、最大生成树结果2、在最小生成树中再加一条边&#xff0c;求新的最小生成树思路代码核心代码全部代码例子3、次小生成树思路:在上一个功能基础上进一步扩充代码核心代码全部代码例子4、判断最小…

一个轻量级的分布式日志标记追踪神器,十分钟接入,非常好用!

TLog简介 1、TLog通过对日志打标签完成企业级微服务的日志追踪。它不收集日志&#xff0c;使用简单&#xff0c; 产生全局唯一的追踪码。除了追踪码以外&#xff0c;TLog还支持SpanId和上下游服务信息 标签的追加。 2、为用户使用方便而设计&#xff0c;提供完全零侵入式接入…

es入门(上)

笔记来源于学习 b站中的【IT李老师】的elasticsearch课程 自己在实习做的es模块中的理解。 后续会有 中&#xff0c;下篇笔记更新&#xff0c;目前这一篇是上篇。 目录 Elastic Stack简介 1.1简介 1.2特色 1.3组件介绍 2.Elasticsearch的接收与核心概念 2.1搜索是什么…

【Keras+计算机视觉+Tensorflow】OCR文字识别实战(附源码和数据集 超详细必看)

需要源码和数据集请点赞关注收藏后评论区留言私信~~~ 一、OCR文字识别简介 利用计算机自动识别字符的技术&#xff0c;是模式识别应用的一个重要领域。人们在生产和生活中&#xff0c;要处理大量的文字、报表和文本。为了减轻人们的劳动&#xff0c;提高处理效率&#xff0c;从…

[python]初步练习脚本

之前练习的python&#xff0c;编写的脚本&#xff0c;现在作为记录&#xff0c;方便查看~ python 初步练习脚本基础部分的练习脚本脚本代码1、helloworld.py&#xff0c;有for循环语句2、main.py3、range—test.py&#xff0c;范围4、RE.py&#xff0c;花式输出内容5、turtle练…

Jekins安装和部署

1.官网下载 注意jekins各版本不同支持jdk的版本也不同 https://www.jenkins.io/download/ 如图进去后可看见最新版&#xff0c;而past releases是历史版本 查看自己各版本的支持 我下载的是2.346.1版本&#xff0c;是war包形式 2.启动jekins 直接在war包路径 java命令启动…

lspci命令整理

1. 作用&#xff1a; 显示当前主机的所有PCI总线信息 2. 常用指令&#xff1a; lspci -nn 第一列的数字&#xff1a;总线编号(Bus Number)&#xff1a;设备编号&#xff08;Device Number&#xff09;&#xff1a;功能编号&#xff08;Function Number&#xff09; 第一个中括…

全国青少年软件编程等级考试C语言标准解读(1_10级)

考试性质 全国青少年软件编程等级考试标准&#xff08;C/C&#xff09;由中国电子学会科普培训与应用推广中心指定。由全国青少年电子信息科普创新联盟标准工作组开发&#xff0c;由中国电子学会普及工作委员会审核通过&#xff0c;适用于由中国电子学会主办的青少年软件编程等…

vue中的process.env的理解

创建项目的时候突然发现好多前端有好多地方用到了这个process.env.xxxx但是发现其实我的新项目并没有定义这个内容&#xff0c;于是就对这个变量产生了好奇&#xff0c;这里总结一下 上图是我在node命令行下执行的查看了一下变量&#xff0c;看这情况直接是把系统的环境变量给…

少走弯路,关于在线客服系统的二三事

日常生活中&#xff0c;我们购买一个服务或一个商品时&#xff0c;时常会遇到以下场景&#xff1a; 售前咨询&#xff1a;向商家咨询服务的信息咨询、商品的规格产品咨询、以及商场活动、优惠咨询等 售后服务&#xff1a;商品使用问题、商品不满意退/换货等 在移动通信没有普…

Camera Surface 从应用到cameraserver的流转

一、Android相机应用与Surface Camera应用的预览一般通过SurfaceView去显示&#xff0c;SurfaceView作为显示的载体&#xff0c; Surface surface mSurfaceView.getSurfaceHolder().getSurface(); 获取的surface会通过Camera API1/API2的接口下发到framework层&#xff1b;…

基于模型的设计(MBD)在汽车ECU软件开发中的实践

基于模型的设计&#xff08;Model-based Design&#xff0c;以下简称MBD&#xff09;是一种围绕模型展开的项目开发方法&#xff0c;指对开发对象或者项目产品进行精确建模&#xff0c;项目的需求分析、功能设计、系统框架、代码生成、测试验证等开发环节都在模型的基础上展开。…

如何用策略模式,优化你代码里的的if-else?

最近有一个学妹在跟我沟通如何有效的去避免代码中一长串的if else判断或者switch条件判断&#xff1f;针对更多的回答就是合理的去使用设计来规避这个问题。 在设计模式中&#xff0c;可以使用工厂模式或者策略模式来处理这类问题&#xff0c;之前已经分享了工厂模式&#xff…

Hadoop集群中HDFS的API测试案例以及MapReduce的多种提交Job方式案例

这两个案例默认是hadoop集群环境已经搭建好以及IDEA环境也已经配置好 1、HDFS客户端测试案例 1.1、pom依赖 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www…

Java使用ftl模板文件生成Word,以及Word转换图片或Pdf工具类

Java使用ftl模板文件生成Word 一、写在前面 最近在项目中使用打印功能&#xff0c;发现这个功能我已经写过多次了&#xff0c;下面这个文章的发步日期在2020年&#xff0c;不得不感慨时间之快啊。 https://blog.csdn.net/weixin_43238452/article/details/109636200?spm1001…

this关键字,是如何把你难倒的?

作为一名实战前端工程师&#xff0c;在jq时代&#xff0c;是经常被this关键字难倒的。几年前每次我意识到程序出现问题的时候&#xff0c;都本能反应是自己的this没有绑定好&#xff0c;于是重新绑定一下&#xff0c;就能解决了。但是他确实一直为难着我。 转眼到了2022年底&a…

图解LeetCode——1780. 判断一个数字是否可以表示成三的幂的和(难度:中等)

一、题目 给你一个整数 n &#xff0c;如果你可以将 n 表示成若干个不同的三的幂之和&#xff0c;请你返回 true &#xff0c;否则请返回 false 。 对于一个整数 y &#xff0c;如果存在整数 x 满足 y 3^x &#xff0c;我们称这个整数 y 是三的幂。 二、示例 2.1> 示例…

SpringBoot面试杀手锏——自动配置原理

引言 不论在工作中&#xff0c;亦或是求职面试&#xff0c;Spring Boot已经成为我们必知必会的技能项。除了某些老旧的政府项目或金融项目持有观望态度外&#xff0c;如今的各行各业都在飞速的拥抱这个已经不是很新的Spring启动框架。 当然&#xff0c;作为Spring Boot的精髓…

凌恩客户文章|JCR 一区:多组学联合分析揭示PCOS真元凶

期刊&#xff1a;Journal of Ovarian Research 影响因子&#xff1a;5.506 发表时间&#xff1a;2022年10月 客户单位&#xff1a;汕头大学医学院第一附属医院鄞国书课题组 一、研究背景 多囊卵巢综合征(PCOS)是导致育龄妇女不孕的最常见内分泌疾病…

MyBatis二 MyBatis常见面试题

一 MyBatis是什么&#xff1f; MyBatis是一款优秀的持久层框架&#xff0c;一个半ORM &#xff08;对象关系映射&#xff09;框架&#xff0c;它支持定制化SQL、存储过程以及高级映射。MyBatis避免了几乎所有的JDBC代码和手动设置参数以及获取结果集。MyBatis可以使用简单的XM…