Loading...
墨滴

我不是廖梓鉴

2022/01/06  阅读:70  主题:橙心

面试造航母,上班拧螺丝?不要被骗啦!

常言道:面试造航母,上班拧螺丝。

使的越来越来的人(特别是刚入行的同学)认为:只要在面试前多刷刷面试题,过过面试就好了,问的东西实际又不会用。

但实际上,大家都是拧螺丝,但是拧螺丝的水平还是不一样的!

有些人拧的螺丝松松垮垮,随便造两下就不行了,有些人是真的给航母拧螺丝,拧的又快又稳还漂亮,比如我。

新年初始,心态一样要摆正。

我还是给大家举个例子吧。

例子

面试题:Java中的对象都是在堆上进行分配的吗?

肯定有不少小伙伴说:那可不!真的是吗?我们先来看一段代码

private void alloc() {
  User user = new User();
}

这个方法里面只做了一件事,创建一个User对象

那我就想问下了:当这个方法结束之后,这个User对象还有被使用吗?很明显,没有。

那这个时候可以把这个User对象给回收掉嘛?可以!

但是结合我们以往的知识:对象是在堆上分配的,回收堆内存是在触发gc时进行的。

也就是说想要回收这个User对象,还得等到gc?

很不合理!我明明知道这个对象在方法结束之后就没用了,就可以被回收了,结果还要我等到你gc??!辣鸡!

所以,Java中的对象都是在堆上进行分配的吗?

是不是动摇啦?「斜眼笑」

栈上分配

在JVM中,对象除了可在堆上分配,也可以在栈上分配:为了减少临时对象在堆内分配的数量,JVM通过逃逸分析确定该对象不会被外部访问。如果不会逃逸可以将该对象在栈上分配内存,这样该对象所占用的内存空间就可以随栈帧出栈而销毁,就减轻了垃圾回收的压力。

对象逃逸分析:就是分析对象动态作用域,当一个对象在方法中被定义后,它可能被外部方法所引用,例如作为调用参数传递到其他地方中。

很明显对于上面的例子就完全符合栈上分配的条件。

我们可以通过gc情况来证明这个理论

public class AllotOnStack {

    public static void main(String[] args) {
        long start = System.currentTimeMillis();
       // 调用alloc方法1亿次
        for (int i = 0; i < 100000000; i++) {
            alloc();
        }
        long end = System.currentTimeMillis();
        System.out.println(end - start);
    }

    private static void alloc() {
        User user = new User();
    }

    static class User{
    }
}

这个案例很简单,就是循环创建对象1亿次

我们将堆内存设置为15M,并打印gc日志,进行启动:-Xmx15m -Xms15m -XX:+PrintGC

我们发现未发生一次gc,运行时间为5ms

如果我们把逃逸分析的功能关闭:-XX:-DoEscapeAnalysis

打印了很多gc日志,运行时间变成了451ms

这足以证明对象确实是会栈上分配,并且栈上分配依赖于逃逸分析(和标量替换)

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

回到主题

收心,收心啊,今天不是讲面试题的。

所以说,如果写代码时一个方法里面几百行,这样的话jvm就得等整个方法退出之后才能回收内存(栈帧关闭)。

比如说一个方法有5个业务逻辑,每个业务逻辑所需内存为1M,那么这整个方法就需要5M内存。

但是如果我们把这5个业务逻辑拆成5个小方法进行调用,那么这个方法需要内存为多少呢?

理论上只要1M!因为程序边运行,内存就在边回收!

这个举例有些极端了,因为一般我们写的代码并非全部都能符合栈上分配的条件

当然,我们也不要因为了解到了这个知识而写起代码来畏首畏尾。

记住一个点就行:以单一职责原则为准则,一个类只做一种事,一个方法只做一件事,剩下的交给JVM。

小结

2022刚刚开始,希望大家都可以摆好心态,在职业道路上越走越远。为了美好的明天!

我不是廖梓鉴

2022/01/06  阅读:70  主题:橙心

作者介绍

我不是廖梓鉴