Java面试专栏
文章平均质量分 90
Java面试专栏
TimeFriends
这里没有天赋异禀,也没有天资聪颖,只有每天的陪伴。万物瞬息万变,但唯一不变的只有变化。抓住变化的根本,以时间为伍,以坚持为伴,做时间的朋友。
展开
-
后台服务出现明显“变慢”,谈谈你的诊断思路?
在日常工作中,应用或者系统出现性能问题往往是不可避免的,除了在有一定规模的 IT 企业或者专注于特定性能领域的企业,可能大多数工程师并不会成为专职的性能工程师,但是掌握基本的性能知识和技能,往往是日常工作的需要,并且也是工程师进阶的必要条件之一,能否定位和解决性能问题也是对你知识、技能和能力的检验。讲到这里,如果你对系统性能非常感兴趣,我建议参考Brendan Gregg(https://www.brendangregg.com/linuxperf.html)提供的完整图谱,我所介绍的只能算是九牛一毛。原创 2023-03-30 15:29:36 · 508 阅读 · 2 评论 -
如何写出安全的Java代码?
很多安全问题,在特定的上下文,存在着不同的定义,尽管本质是相似或一致的,这是由于 Java 平台自身的特性所带来特有的问题。我认为,从 Java 语言的角度,更加需要重视的是程序级别的攻击,也就是利用 Java、JVM 或应用程序的瑕疵,进行低成本的 DoS 攻击,这也是想要写出安全的 Java 代码所必须考虑的。攻击和防守是不对称的,只要有一个严重漏洞,对于攻击者就足够了,所以,不能对黑盒形式的部署心存侥幸,这并不能保证系统的安全,攻击者可以利用对软件设计的猜测,结合一系列手段,探测出漏洞。原创 2023-03-28 14:49:37 · 522 阅读 · 1 评论 -
你了解Java应用开发中的注入攻击吗?
但是,要达到攻击的目的,未必都需要绕过权限限制。比如利用哈希碰撞发起拒绝服务攻击(DOS,Denial-Of-Service attack),常见的场景是,攻击者可以事先构造大量相同哈希值的数据,然后以 JSON 数据的形式发送给服务器端,服务器端在将其构建成为 Java 对象过程中,通常以 Hastable 或 HashMap 等形式存储,哈希碰撞将导致哈希表发生严重退化,算法复杂度可能上升一个数量级(HashMap 后续进行了改进,我在专栏第 9 讲介绍了树化机制),进而耗费大量 CPU 资源。原创 2023-03-27 09:32:47 · 655 阅读 · 0 评论 -
Java程序运行在Docker等容器环境有哪些新问题?
第二,namespace 对于容器内的应用细节增加了一些微妙的差异,比如 jcmd、jstack 等工具会依赖于“/proc//”下面提供的部分信息,但是 Docker 的设计改变了这部分信息的原有结构,我们需要对原有工具进行修改(https://bugs.openjdk.java.net/browse/JDK-8179498)以适应这种变化。容器虽然省略了虚拟操作系统的开销,实现了轻量级的目标,但也带来了额外复杂性,它限制对于应用不是透明的,需要用户理解 Docker 的新行为。原创 2023-03-24 16:34:48 · 440 阅读 · 0 评论 -
Java内存模型中的happen-before是什么?
但是,显然问题的复杂度被低估了,随着 Java 被运行在越来越多的平台上,人们发现,过于泛泛的内存模型定义,存在很多模棱两可之处,对 synchronized 或 volatile 等,类似指令重排序时的行为,并没有提供清晰规范。Java 语言在设计之初就引入了线程的概念,以充分利用现代处理器的计算能力,这既带来了强大、灵活的多线程机制,也带来了线程安全等令人混淆的问题,而 Java 内存模型(Java Memory Model,JMM)为我们提供了一个在纷乱之中达成一致的指导准则。原创 2023-03-23 09:13:17 · 181 阅读 · 0 评论 -
谈谈你的GC调优思路
我发现,目前不少外部资料对 G1 的介绍大多还停留在 JDK 7 或更早期的实现,很多结论已经存在较大偏差,甚至一些过去的 GC 选项已经不再推荐使用。所以,今天我会选取新版 JDK 中的默认 G1 GC 作为重点进行详解,并且我会从调优实践的角度,分析典型场景和调优思路。下面我们一起来更新下这方面的知识。今天我要问你的问题是,谈谈你的 GC 调优思路?典型回答谈到调优,这一定是针对特定场景、特定目的的事情, 对于 GC 调优来说,首先就需要清楚调优的目标是什么?从性能的角度看,通常关注三个方面,内存占用(原创 2023-03-22 15:37:14 · 314 阅读 · 0 评论 -
如何监控和诊断JVM堆内和堆外内存使用?
这是 JVM 为每个线程分配的一个私有缓存区域,否则,多线程同时分配内存时,为避免操作同一地址,可能需要使用加锁等机制,进而影响分配速度,你可以参考下面的示意图。JVM 问题千奇百怪,如果你能快速将问题缩小,大致就能清楚问题可能出在哪里,例如如果定位到问题可能是堆内存泄漏,往往就已经有非常清晰的思路和工具(https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/memleaks004.html#CIHIEEFH)可以去解决了。原创 2023-03-20 09:08:10 · 1039 阅读 · 0 评论 -
有哪些方法可以在运行时动态生成一个Java类?
更进一步,我们来看看 JDK dynamic proxy 的实现代码(http://hg.openjdk.java.net/jdk/jdk/file/29169633327c/src/java.base/share/classes/java/lang/reflect/Proxy.java)。你可以参考下面的示例代码。试想,假如我们有这样一个需求,需要添加某个功能,例如对某类型资源如网络通信的消耗进行统计,重点要求是,不开启时必须是零开销,而不是低开销,可以利用我们今天谈到的或者相关的技术实现吗?原创 2023-03-14 10:10:11 · 753 阅读 · 1 评论 -
请介绍类加载过程,什么是双亲委派模型?
但不是所有类加载都遵守这个模型,有的时候,启动类加载器所加载的类型,是可能要加载用户代码的,比如 JDK 内部的 ServiceProvider/ServiceLoader机制,用户可以在标准 API 框架上,提供自己的实现,JDK 也需要提供些默认的参考实现。最后是初始化阶段(initialization),这一步真正去执行类初始化的代码逻辑,包括静态字段赋值的动作,以及执行类定义中的静态初始化块内的逻辑,编译器在编译阶段就会把这部分逻辑整理好,父类型的初始化逻辑优先于当前类型的逻辑。原创 2023-03-10 13:58:36 · 419 阅读 · 0 评论 -
AtomicInteger底层实现原理是什么?如何在自己的产品代码中应用CAS操作?
这里就是我们所说的,利用 FIFO 队列,实现线程间对锁的竞争的部分,算是是 AQS 的核心逻辑。今天的问题有点偏向于 Java 并发机制的底层了,虽然我们在开发中未必会涉及 CAS 的实现层面,但是理解其机制,掌握如何在 Java 中运用该技术,还是十分有必要的,尤其是这也是个并发编程的面试热点。以非公平的 tryAcquire 为例,其内部实现了如何配合状态与 CAS 获取锁,注意,对比公平版本的 tryAcquire,它在锁无人占有时,并不检查是否有其他等待者,这里体现了非公平的语义。原创 2023-03-08 10:01:17 · 145 阅读 · 0 评论 -
Java并发类库提供的线程池有哪几种? 分别有什么特点?
前面我说过 newFixedThreadPool 是创建指定数目的线程,但是其工作队列是无界的,如果工作线程数目太少,导致处理跟不上入队的速度,这就很有可能占用大量系统内存,甚至是出现 OOM。往往是不能的,如果线程太多,反倒可能导致大量的上下文切换开销。newSingleThreadExecutor(),它的特点在于工作线程数目被限制为 1,操作一个无界的工作队列,所以它保证了所有任务的都是被顺序执行,最多会有一个任务处于活动状态,并且不允许使用者改动线程池实例,因此可以避免其改变线程数目。原创 2023-03-07 15:28:08 · 335 阅读 · 0 评论 -
并发包中的ConcurrentLinkedQueue和LinkedBlockingQueue有什么区别?
今天的内容侧重于 Java 自身的角度,面试官也可能从算法的角度来考察,所以今天留给你的思考题是,指定某种结构,比如栈,用它实现一个 BlockingQueue,实现思路是怎样的呢?今天的内容侧重于 Java 自身的角度,面试官也可能从算法的角度来考察,所以今天留给你的思考题是,指定某种结构,比如栈,用它实现一个 BlockingQueue,实现思路是怎样的呢?SynchronousQueue,这是一个非常奇葩的队列实现,每个删除操作都要等待插入操作,反之每个插入操作也都要等待删除动作。原创 2023-03-06 14:44:21 · 647 阅读 · 0 评论 -
Java并发包提供了哪些并发工具类?
注意,上面的代码,更侧重的是演示 Semaphore 的功能以及局限性,其实有很多线程编程中的反实践,比如使用了 sleep 来协调任务执行,而且使用轮询调用 availalePermits 来检测信号量获取情况,这都是很低效并且脆弱的,通常只是用在测试或者诊断场景。但是,从具体节奏来看,其实并不符合我们前面场景的需求,因为本例中 Semaphore 的用法实际是保证,一直有 5 个人可以试图乘车,如果有 1 个人出发了,立即就有排队的人获得许可,而这并不完全符合我们前面的要求。原创 2023-03-02 10:21:30 · 538 阅读 · 1 评论 -
什么情况下Java程序会产生死锁?如何定位、修复?
除了典型应用中的死锁场景,其实还有一些更令人头疼的死锁,比如类加载过程发生的死锁,尤其是在框架大量使用自定义类加载时,因为往往不是在应用本身的代码库中,jstack 等工具也不见得能够显示全部锁信息,所以处理起来比较棘手。通常来说,我们大多是聚焦在多线程场景中的死锁,指两个或多个线程之间,由于互相持有对方需要的锁,而永久处于阻塞的状态。今天,我从样例程序出发,介绍了死锁产生原因,并帮你熟悉了排查死锁基本工具的使用和典型思路,最后结合实例介绍了实际场景中的死锁分析方法与预防措施,希望对你有所帮助。原创 2023-03-01 13:55:35 · 580 阅读 · 0 评论 -
一个线程两次调用start()方法会出现什么情况?
从操作系统的角度,可以简单认为,线程是系统调度的最小单元,一个进程可以包含多个线程,作为任务的真正运作者,有自己的栈(Stack)、寄存器(Register)、本地存储(Thread Local)等,但是会和进程内其他线程共享文件描述符、虚拟地址空间等。尤其是在多核 CPU 的系统中,线程等待存在一种可能,就是在没有任何线程广播或者发出信号的情况下,线程就被唤醒,如果处理不当就可能出现诡异的并发问题,所以我们在等待条件过程中,建议采用下面模式来书写。我们可以直接扩展 Thread 类,然后实例化。原创 2023-02-28 11:28:09 · 1563 阅读 · 0 评论 -
synchronized底层如何实现?什么是锁的升级、降级?
另外,如果你仔细查看synchronizer.cpp(https://hg.openjdk.java.net/jdk/jdk/file/896e80158d35/src/hotspot/share/runtime/synchronizer.cpp)里,会发现不仅仅是 synchronized 的逻辑,包括从本地代码,也就是 JNI,触发的 Monitor 动作,全都可以在里面找到(jni_enter/jni_exit)。今天的问题主要是考察你对 Java 内置锁实现的掌握,也是并发的经典题目。原创 2023-02-27 11:25:56 · 821 阅读 · 0 评论 -
synchronized和ReentrantLock有什么区别呢?
按照其中的定义,线程安全是一个多线程环境下正确性的概念,也就是保证多线程环境下共享的、可修改的状态的正确性,这里的状态反映在程序中其实可以看作是数据。你可能好奇什么是再入?在 Java 5 以前,synchronized 是仅有的同步手段,在代码中, synchronized 可以用来修饰方法,也可以使用在特定的代码块儿上,本质上 synchronized 方法等同于把方法全部语句用 synchronized 块包起来。这里所谓的公平性是指在竞争场景中,当公平性为真时,会倾向于将锁赋予等待时间最久的线程。原创 2023-02-23 09:45:52 · 205 阅读 · 0 评论 -
谈谈你知道的设计模式?
结构型模式,是针对软件设计结构的总结,关注于类、对象继承、组合方式的实践经验。今天,我与你回顾了设计模式的分类和主要类型,并从 Java 核心类库、开源框架等不同角度分析了其采用的模式,并结合单例的不同实现,分析了如何实现符合线程安全等需求的单例,希望可以对你的工程实践有所帮助。使用构建器模式,可以比较优雅地解决构建复杂对象的麻烦,这里的“复杂”是指类似需要输入的参数组合较多,如果用构造函数,我们往往需要为每一种可能的输入参数组合实现相应的构造函数,一系列复杂的构造函数会让代码阅读性和可维护性变得很差。原创 2023-02-22 09:45:39 · 238 阅读 · 0 评论 -
谈谈接口和抽象类有什么区别?
接口分离(Interface Segregation),我们在进行类和接口设计时,如果在一个接口里定义了太多方法,其子类很可能面临两难,就是只有部分方法对它是有意义的,这就破坏了程序的内聚性。这段代码的一个问题是,业务逻辑集中在一起,当出现新的用户类型时,比如,大数据发现了我们是肥羊,需要去收获一下, 这就需要直接去修改服务方法代码实现,这可能会意外影响不相关的某个用户类型逻辑。设想,为接口添加任何抽象方法,相应的所有实现了这个接口的类,也必须实现新增方法,否则会出现编译错误。原创 2023-02-21 11:42:42 · 263 阅读 · 0 评论 -
Java有几种文件拷贝方式?哪一种最高效?
我在上一讲提到 Buffer 是 NIO 操作数据的基本工具,Java 为每种原始数据类型都提供了相应的 Buffer 实现(布尔除外),所以掌握和使用 Buffer 是十分必要的,尤其是涉及 Direct Buffer 等使用,因为其在垃圾收集等方面的特殊性,更要重点掌握。今天我分析了 Java IO/NIO 底层文件操作数据的机制,以及如何实现零拷贝的高性能操作,梳理了 Buffer 的使用和类型,并针对 Direct Buffer 的生命周期管理和诊断进行了较详细的分析。原创 2023-02-20 11:17:51 · 503 阅读 · 0 评论 -
Java提供了哪些IO方式? NIO如何实现多路复用?
可以看到,在前面两个样例中,IO 都是同步阻塞模式,所以需要多线程以实现多任务处理。而 NIO 则是利用了单线程轮询事件的机制,通过高效地定位就绪的 Channel,来决定做什么,仅仅 select 阶段是阻塞的,可以有效避免大量客户端连接时,频繁线程切换带来的问题,应用的扩展能力有了非常大的提高。大家知道 Java 语言目前的线程实现是比较重量级的,启动或者销毁一个线程是有明显开销的,每个线程都有单独的线程栈等结构,需要占用非常明显的内存,所以,每一个 Client 启动一个线程似乎都有些浪费。原创 2023-02-17 09:43:39 · 884 阅读 · 0 评论 -
如何保证集合是线程安全的 ConcurrentHashMap如何实现高效地线程安全?
在传统集合框架内部,除了 Hashtable 等同步容器,还提供了所谓的同步包装器(Synchronized Wrapper),我们可以调用 Collections 工具类提供的包装方法,来获取一个同步的包装容器(如 Collections.synchronizedMap),但是它们都是利用非常粗粒度的同步方式,在高并发情况下,性能比较低下。试想,如果不进行同步,简单的计算所有 Segment 的总值,可能会因为并发 put,导致结果不准确,但是直接锁定所有 Segment 进行计算,就会变得非常昂贵。原创 2023-02-16 11:10:00 · 671 阅读 · 0 评论 -
对比Hashtable、HashMap、TreeMap有什么不同?
我在上一讲留给你的思考题提到了,构建一个具有优先级的调度系统的问题,其本质就是个典型的优先队列场景,Java 标准库提供了基于二叉堆实现的 PriorityQueue,它们都是依赖于同一种排序机制,当然也包括 TreeMap 的马甲 TreeSet。上面的回答,只是对一些基本特征的简单总结,针对 Map 相关可以扩展的问题很多,从各种数据结构、典型应用场景,到程序设计实现的技术考量,尤其是在 Java 8 里,HashMap 本身发生了非常大的变化,这些都是经常考察的方面。从代码里,你可以看出什么呢?原创 2023-02-15 13:53:13 · 770 阅读 · 0 评论 -
对比Vector、ArrayList、LinkedList有何区别?
另外,Java 8 引入了并行排序算法(直接使用 parallelSort 方法),这是为了充分利用现代多核处理器的计算能力,底层实现基于 fork-join 框架(专栏后面会对 fork-join 进行相对详细的介绍),当处理的数据集比较小的时候,差距不明显,甚至还表现差一点;单纯从面试的角度,很多朋友推荐使用一些算法网站如 LeetCode 等,帮助复习和准备面试,但坦白说我并没有刷过这些算法题,这也是仁者见仁智者见智的事情,招聘时我更倾向于考察面试者自身最擅长的东西,免得招到纯面试高手。原创 2023-02-14 11:04:59 · 551 阅读 · 0 评论 -
int和Integer有什么区别?
对于应用移植,虽然存在一些底层实现的差异,比如 64 位 HotSpot JVM 里的对象要比 32 位 HotSpot JVM 大(具体区别取决于不同 JVM 实现的选择),但是总体来说,并没有行为差异,应用移植还是可以做到宣称的“一次书写,到处执行”,应用开发者更多需要考虑的是容量、能力等方面的差异。我们知道 Java 的对象都是引用类型,如果是一个原始数据类型数组,它在内存里是一段连续的内存,而对象数组则不然,数据存储的是引用,对象往往是分散地存储在堆的不同位置。比如,对象头的结构。原创 2023-02-13 11:14:39 · 947 阅读 · 0 评论 -
动态代理是基于什么原理?
功能才是目的,实现的方法有很多。因为反射机制使用广泛,根据社区讨论,目前,Java 9 仍然保留了兼容 Java 8 的行为,但是很有可能在未来版本,完全启用前面提到的针对 setAccessible 的限制,即只有当被反射操作的模块和指定的包对反射调用者模块 Open,才能使用 setAccessible,我们可以使用下面参数显式设置。今天我简要回顾了反射机制,谈了反射在 Java 语言演进中正在发生的变化,并且进一步探讨了动态代理机制和相关的切面编程,分析了其解决的问题,并探讨了生产实践中的选择考量。原创 2023-02-10 15:40:29 · 600 阅读 · 0 评论 -
String、StringBuffer、StringBuilder有什么区别?
前面说到的几个方面,只是 Java 底层对字符串各种优化的一角,在运行时,字符串的一些基础操作会直接利用 JVM 内部的 Intrinsic 机制,往往运行的就是特殊优化的本地代码,而根本就不是 Java 代码生成的字节码。StringBuffer 本质是一个线程安全的可修改字符序列,它保证了线程安全,也随之带来了额外的性能开销,所以除非有线程安全的需要,不然还是推荐使用它的后继者,也就是 StringBuilder。当然,在极端情况下,字符串也出现了一些能力退化,比如最大字符串的大小。原创 2023-02-09 09:46:45 · 406 阅读 · 0 评论 -
强引用、软引用、弱引用、幻象引用有什么区别?
考虑一下这样的场景,按照 Java 语言规范,如果一个对象没有指向强引用,就符合垃圾收集的标准,有些时候,对象本身并没有强引用,但是也许它的部分属性还在被使用,这样就导致诡异的问题,所以我们需要一个方法,在没有强引用情况下,通知 JVM 对象是在被使用的。幻象引用仅仅是提供了一种确保对象被 finalize 以后,做某些事情的机制,比如,通常用来做所谓的 Post-Mortem 清理机制,我在专栏上一讲中介绍的 Java 平台自身 Cleaner 机制等,也有人利用幻象引用监控对象的创建和销毁。原创 2023-02-08 14:27:54 · 309 阅读 · 0 评论 -
谈谈final、finally、 finalize有什么不同?
类似的,final 字段对性能的影响,大部分情况下,并没有考虑的必要。final 变量产生了某种程度的不可变(immutable)的效果,所以,可以用于保护只读数据,尤其是在并发编程中,因为明确地不能再赋值 final 变量,有利于减少额外的同步开销,也可以省去一些防御性拷贝的必要。final 可以用来修饰类、方法、变量,分别有不同的意义,final 修饰的 class 代表不可以继承扩展,final 的变量是不可以修改的,而 final 的方法也是不可以重写的(override)。原创 2023-02-07 10:06:54 · 254 阅读 · 0 评论 -
第2讲 Exception和Error有什么区别?
可以思考一个问题,对于异常处理编程,不同的编程范式也会影响到异常处理策略,比如,现在非常火热的反应式编程(Reactive Stream),因为其本身是异步、基于事件机制的,所以出现异常情况,决不能简单抛出去;问题是,实际上可能吗?这是因为在日常的开发和合作中,我们读代码的机会往往超过写代码,软件工程是门协作的艺术,所以我们有义务让自己的代码能够直观地体现出尽量多的信息,而泛泛的 Exception 之类,恰恰隐藏了我们的目的。今天,我从一个常见的异常处理概念问题,简单总结了 Java 异常处理的机制。原创 2023-02-06 09:15:16 · 496 阅读 · 0 评论 -
第1讲 谈谈你对Java平台的理解?
除了我们日常最常见的 Java 使用模式,其实还有一种新的编译方式,即所谓的 AOT(Ahead-of-Time Compilation),直接将字节码编译成机器代码,这样就避免了 JIT 预热等各方面的开销,比如 Oracle JDK 9 就引入了实验性的 AOT 特性,并且增加了新的 jaotc 工具。这里说的 Java 的编译和 C/C++ 是有着不同的意义的,Javac 的编译,编译 Java 源码生成“.class”文件里面实际是字节码,而不是可以直接执行的机器码。其实这个问题,问得有点笼统。原创 2023-02-03 11:33:36 · 252 阅读 · 1 评论 -
以面试题为切入点,有效提升你的Java内功
由于 Java 组工作任务的特点,我非常注重面试者的计算机科学基础和编程语言的理解深度,我甚至不要求面试者非要精通 Java,如果对 C/C++ 等其他语言能够掌握得非常系统和深入,也是符合需求的。这几年我从业务系统或产品开发,切换到 Java 平台自身,接触了更多 Java 领域的核心技术,我相信我的分享能够提供一些独到的内容,而不是简单的人云亦云。万丈高楼平地起,愿我这个 Java 老兵,能与你一道,逐个击破大厂 Java 面试考点,直击 Java 技术核心要点,构建你的 Java 知识体系。原创 2023-02-02 15:43:44 · 270 阅读 · 0 评论