华为又招了一名天才少年。

沉默王二

共 19702字,需浏览 40分钟

 · 2024-04-19

大家好,我是二哥呀。

今天在微信看一看中刷到一条电子科技大学的帖子:“华为天才少年+1”,这应该是 2024 年我看到的第一个公开资料的天才少年,芜湖,又一个百万年薪选手即将登场。

刘顺程,本科毕业于重庆邮电大学,2020 年获得电子科技大学硕博连读。邮电、成电的同学们看到这是不是又多了几分学习的热情?

要知道,华为天才少年要经历十几轮的面试筛选,要求非常苛刻,我只能说成电还是牛的啊。刘同学刚毕业就能拿到百万年薪,人生也算是走上了一个新的高度。

相信有不少同学对华为是情有独钟,那今天继续给大家分享一个 Java 面试指南中《华为面经-同学 9》的通用软件开发一面原题,来看看华为面试官都喜欢问哪些问题,好做到知彼知己百战不殆。

内容较长,建议正在准备 24 届春招和 25 届暑期实习、秋招的同学先收藏起来,面试的时候大概率会碰到,我会尽量用通俗易懂+手绘图的方式,让天下所有的面渣都能逆袭 😁

华为面经(详细)

介绍项目,技术选型主要问了MySQL、Redis、RabbitMQ

技术派是一个基于 Spring Boot、MyBatis-Plus、MySQL、Redis、ElasticSearch、MongoDB、Docker、RabbitMQ 等技术栈实现的社区系统。

技术派首页

这个系统旨在为创作者提供一个可以发布文章和教程,并赚取佣金的社区平台,同时又兼顾一些社交属性,比如说用户可以通过阅读、点赞、收藏、评论的形式和作者互动。

与此同时,为了紧跟时代潮流,该系统还为用户提供了一套基于 OpenAI、讯飞星火等多家大模型的派聪明 AI 助手,帮助用户在工作和学习中大幅提效。

选择 MySQL 是因为它是互联网主流的关系型数据库,能够帮助我在工作后快速承接公司的开发任务;而 Redis 作为缓存中间件,支持集群、分片,单机就可以支持数十万 QPS,可以大大提高系统性能;选择 RabbitMQ 是因为社区活跃度高,然后 RabbitMQ 还提供了一个易用的用户界面,可以让用户监控和管理消息。

手画Netty原理和流程

Netty 是一个基于Java NIO的高性能异步事件驱动的网络应用框架,极大简化了网络编程的复杂性。

常用于构建 RPC 框架,以提升分布式服务之间的通信效率。像 Dubbo 的网络层就可以基于 Netty 来实现。

Netty 官方架构图

Netty 支持零拷贝、可拓展事件模型;支持 TCP、UDP、HTTP、WebSocket 等多种协议;提供安全传输、可压缩、大文件、编解码等多种功能。

码海:Netty 的样子

Netty 是基于主从 Reactor 模式实现的,主要分为两个线程组:

①、主 Reactor 线程组(Boss Group)

负责处理新的客户端连接请求。它内部维护一个或多个线程,每个线程都包含一个 Selector。

ServerSocketChannel 注册到 BossGroup 的 Selector 上,只关注 OP_ACCEPT 事件,即新的连接建立请求。

当 BossGroup 的 Selector 接收到连接请求时,使用 ServerSocketChannel.accept() 方法来接受新连接。

接受到的新连接被封装为 NioSocketChannel,并注册到 Worker Group 的 Selector 上。

②、从 Reactor 线程组(Worker Group)

WorkerGroup 管理的线程可能有多个,每个线程也是维护自己的 Selector。Netty 通常会根据一定的策略(如轮询)选择一个 Selector 来平衡负载。

每个 Selector 负责监听和处理所有已注册的 NioSocketChannel 的 IO 事件,如读 (OP_READ)、写 (OP_WRITE) 事件等。

当事件发生时,相应的 ChannelHandler 被调用来处理这些事件。这些 Handler 可以是用户自定义的处理器,用于实现具体的业务逻辑。

码海:Netty 工作架构图

请说一下 Netty 的工作流程?

下面是一个简单的 Netty 服务器和客户端的示例,展示了基本的工作流程。这个例子中,服务器接收字符串消息,转换为大写形式后返回给客户端。

NettyServer:

public class NettyServer {
    private final int port;

    public NettyServer(int port) {
        this.port = port;
    }

    public void start() throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workerGroup)
                     .channel(NioServerSocketChannel.class)
                     .childHandler(new ChannelInitializer<SocketChannel>() 
{
                         @Override
                         protected void initChannel(SocketChannel ch) throws Exception {
                             ch.pipeline().addLast(new StringDecoder());
                             ch.pipeline().addLast(new StringEncoder());
                             ch.pipeline().addLast(new ServerHandler());
                         }
                     })
                     .option(ChannelOption.SO_BACKLOG, 128)
                     .childOption(ChannelOption.SO_KEEPALIVE, true);

            ChannelFuture future = bootstrap.bind(port).sync();
            System.out.println("Server started on port " + port);
            future.channel().closeFuture().sync();
        } finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws Exception {
        new NettyServer(8080).start();
    }

    static class ServerHandler extends ChannelInboundHandlerAdapter {
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) {
            String input = (String) msg;
            System.out.println("Received: " + input);
            ctx.writeAndFlush(input.toUpperCase());
        }

        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
            cause.printStackTrace();
            ctx.close();
        }
    }
}

①、BossGroup 和 WorkerGroup:

服务器初始化时,首先创建两个 NioEventLoopGroup 实例。

BossGroup 用于接受客户端的连接,WorkerGroup 用于处理连接后的数据传输。

  • BossGroup 监听端口上的连接请求,每当接收到新连接时,BossNioEventLoop 就会处理连接请求,接受连接,并将新的 SocketChannel 注册到 WorkerGroup 的一个 NioEventLoop 上。
  • 当 WorkerGroup 的 NioEventLoop 监测到 IO 事件(如读取数据),它会根据注册的 ChannelPipeline 中的 ChannelHandlers 处理这些事件。在示例中,服务器端收到数据后,通过一个 ServerHandler 将数据转换为大写并返回给客户端。

②、ServerBootstrap:配置服务器使用的辅助启动类。设置服务器要使用的 channel 类型为 NioServerSocketChannel。

并为新接入的连接定义 ChannelInitializer,在这个初始化器中,配置 ChannelPipeline,包括编解码器和业务处理器。

NettyClient:

public class NettyClient {
    private final String host;
    private final int port;

    public NettyClient(String host, int port) {
        this.host = host;
        this.port = port;
    }

    public void start() throws Exception {
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() 
{
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new StringDecoder());
                            ch.pipeline().addLast(new StringEncoder());
                            ch.pipeline().addLast(new ClientHandler());
                        }
                    });

            Channel channel = bootstrap.connect(host, port).sync().channel();
            Scanner scanner = new Scanner(System.in);
            while (true) {
                String line = scanner.nextLine();
                if ("quit".equalsIgnoreCase(line)) {
                    channel.close();
                    break;
                }
                channel.writeAndFlush(line);
            }
            channel.closeFuture().sync();
        } finally {
            group.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws Exception {
        new NettyClient("localhost"8080).start();
    }

    static class ClientHandler extends SimpleChannelInboundHandler<String{
        @Override
        protected void channelRead0(ChannelHandlerContext ctx, String msg) {
            System.out.println("Received from server: " + msg);
        }

        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
            cause.printStackTrace();
            ctx.close();
        }
    }
}

①、EventLoopGroup:客户端只需要一个 NioEventLoopGroup 来处理所有操作,包括创建连接、发送数据和接收数据。

②、Bootstrap:配置客户端使用的辅助启动类。设置客户端要使用的 channel 类型为 NioSocketChannel。

客户端使用 Scanner 从命令行读取用户输入,发送到服务器;同时,它也能接收服务器返回的数据,并通过 ClientHandler 打印到控制台。

JRE与JDK的区别,JDK多了哪些东西,既安装了JRE又安装了JDK,可以利用JDK做什么事情?

JVM:Java Virtual Machine,也就是 Java 虚拟机,是 Java 实现跨平台的关键所在,针对不同的操作系统,有不同的 JVM 实现。JVM 负责将 Java 字节码转换为特定平台的机器码,并执行。

JRE:Java Runtime Environment,也就是 Java 运行时环境,包含了运行 Java 程序所必需的库,以及 Java 虚拟机(JVM)。

JDK:Java Development Kit,是一套完整的 Java SDK(软件开发工具包),包括了 JRE 以及编译器(javac)、Java 文档生成工具(Javadoc)、Java 调试器等开发工具。为开发者提供了开发、编译、调试 Java 程序的一整套环境。

简单来说,JDK 包含 JRE,JRE 包含 JVM。

三分恶面渣逆袭:JDK、JRE、JVM关系

如何排查OOM?

内存溢出(Out of Memory,俗称 OOM)是指当程序请求分配内存时,由于没有足够的内存空间满足其需求,从而触发的错误。

首先,我会通过异常信息和日志确定OOM的类型。Java的OOM错误通常有几种类型,如堆内存溢出、Metaspace溢出或直接内存溢出。比如,如果日志中显示“java.lang.OutOfMemoryError: Java heap space”,那就说明是堆内存溢出。

一旦确定了是堆内存溢出,我会使用 JConsole 实时监控JVM的内存使用情况,特别是那些占用大量内存的对象和类。

找到可能的内存泄漏源后,我会回到代码中去,查找和修复具体的问题。

之后,我会在本地进行压力测试,模拟高负载情况下的内存表现,确保修改有效,且没有引入新的问题。

如何查看当前Java程序里哪些对象正在使用,哪些对象已经被释放

在 Java 程序中,查看哪些对象正在使用和哪些已经被释放的方法主要涉及运行时的内存监控和分析。需要借助一些专门的工具来进行:

①、JConsole:JDK 自带的监控工具,可以用来监视 Java 应用程序的运行状态,包括内存使用、线程状态、类加载、GC 等,还可以进行一些基本的性能分析。

三分恶面渣逆袭:JConsole概览

②、VisualVM:VisualVM 是一个基于 NetBeans 平台的可视化工具,在很长一段时间内,VisualVM 都是 Oracle 官方主推的故障处理工具。集成了多个 JDK 命令行工具的功能,提供了一个友好的图形界面,非常适用于开发和生产环境。

三分恶面渣逆袭:VisualVM安装插件

③、Java Mission Control:JMC 最初是 JRockit VM 中的诊断工具,但在 Oracle JDK7 Update 40 以后,就绑定到了 HotSpot VM 中。不过后来又被 Oracle 开源出来作为一个单独的产品。

三分恶面渣逆袭:JMC主要界面

还有一些第三方的工具:

①、MAT

  • Java 堆内存分析工具,主要用于分析和查找 Java 堆中的内存泄漏和内存消耗问题。
  • 可以从 Java 堆转储文件中分析内存使用情况,并提供丰富的报告,如内存泄漏疑点、最大对象和 GC 根信息。
  • 支持通过图形界面查询对象,以及检查对象间的引用关系。

②、GChisto:GC 日志分析工具,帮助开发者优化垃圾收集行为和调整 GC 性能。

③、GCViewer:类似于 GChisto,也是用来分析 GC 日志,帮助开发者优化 Java 应用的垃圾回收过程。

④、JProfiler:一个全功能的商业 Java 性能分析工具,提供 CPU、 内存和线程的实时分析。

⑤、arthas

  • 阿里巴巴开源的 Java 诊断工具,主要用于线上的应用诊断。
  • 支持在不停机的情况下进行 Java 应用的诊断。
  • 包括 JVM 信息查看、监控、Trace 命令、反编译等。

⑥、async-profiler:一个低开销的性能分析工具,支持生成火焰图,适用于复杂性能问题的分析。

Java缓冲区溢出,如何预防

Java 缓冲区溢出主要是由于向缓冲区写入的数据超过其能够存储的数据量。可以采用这些措施来避免:

①、合理设置缓冲区大小:在创建缓冲区时,应根据实际需求合理设置缓冲区的大小,避免创建过大或过小的缓冲区。

②、控制写入数据量:在向缓冲区写入数据时,应该控制写入的数据量,确保不会超过缓冲区的容量。Java 的 ByteBuffer 类提供了remaining()方法,可以获取缓冲区中剩余的可写入数据量。

import java.nio.ByteBuffer;

public class ByteBufferExample {

    public static void main(String[] args) {
        // 模拟接收到的数据
        byte[] receivedData = {12345};
        int bufferSize = 1024;  // 设置一个合理的缓冲区大小

        // 创建ByteBuffer
        ByteBuffer buffer = ByteBuffer.allocate(bufferSize);

        // 写入数据之前检查容量是否足够
        if (buffer.remaining() >= receivedData.length) {
            buffer.put(receivedData);
        } else {
            System.out.println("Not enough space in buffer to write data.");
        }

        // 准备读取数据:将limit设置为当前位置,position设回0
        buffer.flip();

        // 读取数据
        while (buffer.hasRemaining()) {
            byte data = buffer.get();
            System.out.println("Read data: " + data);
        }

        // 清空缓冲区以便再次使用
        buffer.clear();
    }
}

进程和线程的区别

进程说简单点就是我们在电脑上启动的一个个应用,比如我们启动一个浏览器,就会启动了一个浏览器进程。进程是操作系统资源分配的最小单位,它包括了程序、数据和进程控制块等。

线程说简单点就是我们在 Java 程序中启动的一个 main 线程,一个进程至少会有一个线程。当然了,我们也可以启动多个线程,比如说一个线程进行 IO 读写,一个线程进行加减乘除计算,这样就可以充分发挥多核 CPU 的优势,因为 IO 读写相对 CPU 计算来说慢得多。线程是 CPU 分配资源的基本单位。

三分恶面渣逆袭:进程与线程关系

一个进程中可以有多个线程,多个线程共用进程的堆和方法区(Java 虚拟机规范中的一个定义,JDK 8 以后的实现为元空间)资源,但是每个线程都会有自己的程序计数器和栈。

进程的调度方式

进程调度是操作系统中的核心功能之一,它负责决定哪些进程在何时使用 CPU。这一决定基于系统中的进程调度算法。

三分恶面渣逆袭:进程调度算法

①、先来先服务

这是最简单的调度算法,也称为先进先出(FIFO)。进程按照请求 CPU 的顺序进行调度。这种方式易于实现,但可能会导致较短的进程等待较长进程执行完成,从而产生“饥饿”现象。

三分恶面渣逆袭:先来先服务

②、短作业优先

选择预计运行时间最短的进程优先执行。这种方式可以减少平均等待时间和响应时间,但缺点是很难准确预知进程的执行时间,并且可能因为短作业一直在执行,导致长作业持续被推迟执行。

三分恶面渣逆袭:短作业优先

③、优先级调度

在这种调度方式中,每个进程都被分配一个优先级。CPU 首先分配给优先级最高的进程。优先级调度可以是非抢占式的或抢占式的。在非抢占式优先级调度中,进程一旦开始执行将一直运行直到完成;在抢占式优先级调度中,更高优先级的进程可以中断正在执行的低优先级进程。

三分恶面渣逆袭:优先级调度

④、时间片轮转

时间片轮转调度为每个进程分配一个固定的时间段,称为时间片,进程可以在这个时间片内运行。如果进程在时间片结束时还没有完成,它将被放回队列的末尾。时间片轮转是公平的调度方式,可以保证所有进程得到公平的 CPU 时间,适用于共享系统。

三分恶面渣逆袭:时间片轮转

⑤、最短剩余时间优先

这是短作业优先的一种改进形式,它是抢占式的。即如果一个新进程的预计执行时间比当前运行进程的剩余时间短,调度器将暂停当前的进程,并切换到新进程。这种方法也可以最小化平均等待时间,但同样面临预测执行时间的困难。

参考链接

  • 三分恶的面渣逆袭:https://javabetter.cn/sidebar/sanfene/nixi.html
  • 二哥的 Java 进阶之路:https://javabetter.cn

ending

一个人可以走得很快,但一群人才能走得更远。二哥的编程星球已经有 5100 多名球友加入了,如果你也需要一个良好的学习环境,戳链接 🔗 加入我们吧。这是一个编程学习指南 + Java 项目实战 + LeetCode 刷题的私密圈子,你可以阅读星球专栏、向二哥提问、帮你制定学习计划、和球友一起打卡成长。

两个置顶帖「球友必看」和「知识图谱」里已经沉淀了非常多优质的学习资源,相信能帮助你走的更快、更稳、更远

欢迎点击左下角阅读原文了解二哥的编程星球,这可能是你学习求职路上最有含金量的一次点击。

最后,把二哥的座右铭送给大家:没有什么使我停留——除了目的,纵然岸旁有玫瑰、有绿荫、有宁静的港湾,我是不系之舟。共勉 💪。

浏览 123
10点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
10点赞
评论
收藏
分享

手机扫一扫分享

举报