关于面试题java内存泄漏想到的(2)

本文深入探讨了Java内存管理及内存泄漏的问题,列举了多种可能导致内存泄漏的情形,并提供了相应的解决方案,包括合理使用缓存和监听器等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Java资料下载地址:

http://blog.sina.com.cn/s/blog_67ba07d60100lolk.html

2java书籍找齐,一本一本查找关于内存管理的介绍,以及内存泄漏的介绍:

1、JavaLanguage Specification, Third Edition,此书编写风格有些类似于C++他爹写的The C++Programming Language,书中可运行的例子较少,不像java编程思想那么容易理解的例子,基本是能省略就省略,语言非常简介,层次非常清楚,内容非常全面详细无论大小知识,都很规范的给出说明,毕竟是规范嘛。关于内存泄漏没有说明,只有java垃圾回收机制:12.6小结(Finalization of Class Instances)

2、EffectiveJava , Second Edition第六条,消除过期引用的对象,很清楚的说明了这个问题,百度的面试题,以及JAVA高级工程师的面试题应该是源于此书

3、JavaConcurrency in Practice,简历上面写着精通java多线程编程,我只能说我做过java多线程的例子,不看此书不要说精通多线程编程。示例丰富,讲解详细。

4、JavaPuzzles: Traps, Pitfalls and Corner Cases也是非常适合出笔试题的一本书籍,java细节性的东西,稍不注意,编程就忽略掉的编程细节,只是不知道有没有关于内存泄漏的章节,大致看了一下,似乎没有,但是这是一本对java用编程经验的水平人的非常值得看的书籍,不看这些书籍,我想就是你再写十万行代码也是无用。

 

5、java编程思想,大家公认的java经典书籍,刚毕业那会儿一直看不懂,一直以为是很经典的书籍,现在看来,真的是入门级的书籍。不过是Java Language Specification的扩大版本,Java LanguageSpecification里面通常只有理论,没有示例,Thinking in java里面配上了示例,配上了浅显的语言。4.3节Cleanup: finalization and garbage collection有介绍java垃圾回收机制的

6Better,faster, lighter Java没有下载到能打开的格式,本人电脑chm格式的坏掉。

7Core Javathinking injava更深入些,但是内容不想后者那么全面,没有介绍内存有垃圾回收的章节

8The JavaVirtual Machine Specification  SUN公司的官方介绍java虚拟机的,自然也有垃圾回收,内存分配的事情,可惜没有下载到中文版,1.5 Garbage Collected Heap,垃圾回收堆,然后就几句话(总书也就84页)The Java heapis the runtime data area from which class instances (objects) are allocated.The Java language is designed to be garbage collected — it does not give theprogrammer the ability to deallocate objects explicitly.

Java does notpresuppose any particular kind of garbage collection; various algorithms may beused depending on system requirements

翻译一下吧:JAVA 的堆是运行时数据区域,类实例(对象)从这些堆中被收集。JAVA语言被设计为垃圾回收的机制,不给程序员明确分配对象的权力。Java不预定任何特别形式的垃圾集合,各种垃圾回收算法依赖于具体系统需求。翻译完了,得不到什么答案。

9、ExceptionHandling, Testing, and Debugging看名字应该是一本不错的书籍,但是没有下载到电子版

10、Java CodeConvention java官网的说明,基本都是变量怎么命名,类怎么命名之类的常规介绍

11、网上搜到一本书籍《深入理解Java虚拟机JVM高级特性与最佳实践》,感觉就是一本从网上东拉西扯的一本书,作者竟然用著,不过毕竟是中国人编写的书,语言还算容易理解,结合java编程思想看,还不错,里面就是把jvm之类的文章要点给整理了一下。

可以参考的书目:EffectiveJava,java编程思想,深入理解Java虚拟机JVM高级特性与最佳实践》

 

针对问题而来的书籍Effective Java

第二章 第六条Eliminate obsoleteobject references(消除过期的对象的引用)

内容不多,摘抄原文,并翻译一下:

When you switchfrom a language with manual memory management, such as C

or C++, to agarbage-collected language, your job as a programmer is made much

easier by thefact that your objects are automatically reclaimed when you’re

through withthem. It seems almost like magic when you first experience it. It can

easily lead tothe impression that you don’t have to think about memory management, but thisisn’t quite true.

Consider thefollowing simple stack implementation:

// Can you spot the"memory leak"?

 

当你从手动管理内存的语言例如,C,C++,转移到垃圾回收机制的语言上面,你作为程序员的工作由于以下的这个事情变的更容易,就是你的对象在你使用完之后,会自动的被回收利用。第一次这么使用的体验,让你觉得这是多么的神奇。这种体验很容易是你产生一个想法,“你再也不用管理内存了的”,这是个相当错误的想法。

考虑下面的小例子的栈实现。

你能找出里面的内存泄漏吗?

 

public classStack {

private Object[]elements;

private int size= 0;

private staticfinal int DEFAULT_INITIAL_CAPACITY = 16;

public Stack() {

elements = newObject[DEFAULT_INITIAL_CAPACITY];

}

public voidpush(Object e) {

ensureCapacity();

elements[size++]= e;

}

public Objectpop() {

if (size == 0)

throw new EmptyStackException();

returnelements[--size];

}

/**

* Ensure spacefor at least one more element, roughly

* doubling thecapacity each time the array needs to grow.

*/

private voidensureCapacity() {

if(elements.length == size)

elements =Arrays.copyOf(elements, 2 * size + 1);

}

}

 

拷贝程序运行,导入包

importjava.util.Arrays;

importjava.util.EmptyStackException;

size表示栈指针,默认大小16,构造函数按照默认值构造数组。入栈时候检查栈顶指针是不是最大了,如果是,则利用Arrays的copyOf,重新内存分配。

测试方法

    @Test

    public void testPop() {

       Stack stack = new Stack();

       String str = "fanjf";

       for (int i = 0; i < 20; i++) {

           stack.push(str + i);

       }

 

       for (int i = 0; i < 20; i++) {

 

           String strPop = (String) stack.pop();

           System.out.println(strPop);

       }

    }

 

There’s nothingobviously wrong with this program (but see Item 26 for a

genericversion). You could test it exhaustively, and it would pass every test with

flying colors,but there’s a problem lurking. Loosely speaking, the program has a

“memory leak,” whichcan silently manifest itself as reduced performance due to

increasedgarbage collector activity or increased memory footprint. In extreme

cases, suchmemory leaks can cause disk paging and even program failure with an

OutOfMemoryError,but such failures are relatively rare.

这个没有明显错误的程序(但是请看第26条的常见泛型的版,此处比较第26条的泛型版本只有pop 函数有差异elements[size] = null;对象弹出后进行了置空)。你不能耗尽一切的测试它,它总能带着胜利的旗帜通过你的测试,但是它真的有潜在问题。坦率的说,这个程序存在内存泄漏。随着垃圾回收活动增加或者内存占用的增加,这个程序会慢慢的展示出性能下降的情况。极端情况下,这种内存泄漏,会引起内存分页调度,甚至程序因为内存溢出而导致程序失败,尽管这种失败不常见。疑问1:怎么查出它是内存泄漏的?

 

So where is thememory leak? If a stack grows and then shrinks, the objects

that were poppedoff the stack will not be garbage collected, even if the program

using the stackhas no more references to them. This is because the stack maintains obsoletereferences to these objects. An obsolete reference is simply a reference thatwill never be dereferenced again. In this case, any references outside of the“active portion” of the element array are obsolete. The active portion consistsof the elements whose index is less than size.

然而哪里有内存泄漏呢?如果栈增加或者然后收缩,出栈的元素不会被垃圾回收器回收,甚至用栈的程序不再引用这些对象。因为栈维持者这些对象的旧的引用。一个过期的引用是指那些再也不被间接引用不会被解除的简单引用。这个例子中,任何数组元素有效(活动)部分之外的引用都是过期的。有效部分包含那些索引小于栈大小的元素

Memory leaks ingarbage-collected languages (more properly known as unintentional object retentions)are insidious. If an object reference is unintentionally retained, not only isthat object excluded from garbage collection, but so too are any objectsreferenced by that object, and so on. Even if only a few object references areunintentionally retained, many, many objects may be prevented from  being garbage collected, with potentiallylarge effects on performance.

内存泄漏在有垃圾回收机制的语言中是不明显的(更恰当的应该说是无意的对象保留),

如果对象被无意中保留了,那么不仅仅这些对象超出垃圾回收机制,这些对象引用的对象也超出垃圾回收机制了。甚至只是很少的对象引用被无意保留,但是越来越多的对象超越垃圾回收机制,最终会导致系统性能下降,最终对系统性能造成潜在的大影响。

The fix for thissort of problem is simple: null out references once they become obsolete. Inthe case of our Stackclass, the reference to an item becomes obsolete as soonas it’s popped off the stack.  Thecorrected version of the pop

处理这种问题是简单的。一旦引用过期,指向null就可以了,在我们的Stack类示例中个,当一个条目的引用当出栈之后就会过期。

当前版本的出栈代码如下

method lookslike this:

public Objectpop() {

if (size == 0)

throw newEmptyStackException();

Object result =elements[--size];

elements[size] =null; // Eliminate obsolete reference

return result;

}

An added benefitof nulling out obsolete references is that, if they are subsequentlydereferenced by mistake, the program will immediately fail with a NullPointerException,rather than quietly doing the wrong thing. It is always beneficial to detectprogrammingerrors as quickly as possible.

过期引用的null指向的清空过期引用另外一个好处是,如果以后他们被错误的解除引用,随之而来的错误指向,程序会立即报空指针异常,而不是做错的事情。这对尽可能快的发现程序错误是有利的。

When programmersare first stung by this problem, they may overcompensate by nulling out everyobject reference as soon as the program is finished using it.This is neithernecessary nor desirable, as it clutters up the program unnecessarily.Nullingout object references should be the exception rather than the norm.The best wayto eliminate an obsolete reference is to let the variable that contained thereference fall out of scope. This occurs naturally if you define each variablein the narrowest possible scope (Item 45).

当程序员第一次遇到这问题的时候,或许会过度的清空当程序使用完之后的每个对象的的引用。这是不必要的也是不合适的,因为这会使程序不必要的混乱起来。为什么?清空对象的引用不是java语言的规范,是例外情况。清楚过期引用的最好方式是让包含这个引用的变量放弃指向的区域。这个当你在定义每个变量在必须的最小的区域时候自然而然的就发生。

So when shouldyou null out a reference? What aspect of the Stack class makes it susceptibleto memory leaks? Simply put, it manages its own memory. The storage pool consistsof the elements of the elements array (the object reference cells, not theobjects themselves). The elements in the active portion of the array (asdefined earlier) are allocated, and those in the remainder of the array are free.The garbage collector has no way of knowing this; to the garbage collector,allof the object references in the elements array  are equally valid. Only the programmer knowsthat the inactive portion of the array is unimportant. The programmereffectively communicates this fact to the garbage collector by manually nullingout array elements as soon as they become part of the inactive portion.

然而,什么时候你应该置空一个引用呢?Stack类的哪些方面让它更容易内存泄漏呢?简单的入栈,它自己管理自己的内存。存储池包含着元素数组中的元素(是对象的引用不是对象本身)。在数组有效区域的元素(如早期定义的)是被分配的,数组其余的部分则是自由的。但是垃圾回收器并不知道这些情况,对于垃圾回收器数组中所有对象的引用都是同样有效的。只有程序员知道无效的数组区域是不重要的。当数组元素变到无效部分,程序员需要通知这个事情给垃圾回收器。

Generallyspeaking, whenever a class manages its own memory, the programmer should bealert for memory leaks. Whenever an element is freed, any object referencescontained in the element should be nulled out.

一般而言,无论什么时候一个类管理自己的内存,程序员都应该注意内存泄漏。无论什么时候,一个元素被释放,包含在元素中的任何对象引用应该被清空。

Another commonsource of memory leaks is caches.Once you put an object reference into a cache,it’s easy to forget that it’s there and leave it in the cache long after itbecomes irrelevant . There are several solutions to this problem. If you’relucky enough to implement a cache for which an entry is relevant exactly solong as there are references to its key outside of the cache, represent thecache as a Weak HashMap; entries will be removed automatically after theybecome obsolete. Remember that Weak HashMap is useful only if the desiredlifetime of cache

entries isdetermined by external references to the key, not the value.

另外一个内存泄漏的公共代码是缓存。一旦你放一个对象的引用到缓存中个,很容易忘记它,当不用它时候,就会长时间把它放在缓存。关于此问题有一些解决方案。如果你足够幸运去实现一个缓存,入口恰好相当重要的一个缓存,只要有key的引用在缓存之外,表现那个缓存是弱HashMap.入口将会在使用完之后自动被移除。记住了,弱HashMap是有用的,仅仅在期望的缓存生命周期,入口被外部引用的key所决定,而不是值。

内存泄漏另一个常见的来源是缓存。一旦你把对象引用放到缓存中,就很容易遗忘它,从而使得他再无用之后很长一段时间留在缓存中。对于这个问题有几种可能的解决方案。如果你正好要实现这样的缓存:只要在缓存之外存在对某项的键的引用个,该项就有意义,那就可以用Weak HaspMap(什么东西?)代表缓存;当缓存项过期之后,他们就自动被删除,记住只有当索要的缓存项的生命周期是有该键的外部引用而不是由值决定时,weakHashmap才有用处。

More commonly,the useful lifetime of a cache entry is less well defined, with entriesbecoming less valuable over time. Under these circumstances, the cache shouldoccasionally be cleansed of entries that have fallen into disuse. This can be doneby a background thread (perhaps a Timeror Scheduled Thread Pool Executor) or asa side effect of adding new entries to the cache. The LinkedHashMap classfacilitates the latter approach with its remove Eldest Entry method. For

moresophisticated caches, you may need to use java.lang.ref directly.

更常见的是:缓存项的生命周期是否有用是无法确定的,随着时间的推移,缓存项越来越无用。这种情况下,缓存应偶尔的清理废弃不用的项。这能通过一个后台线程(最好是定时器,或者是日程线程池执行者),或者另外一种方-添加新的项目到缓存中。LinkedHashMap类实现了后者促进后者逼近它移除边界的方法。更多复杂缓存,你可能需要直接使用java.lang.ref

java.lang.ref?

 A third common source of memory leaks islisteners and other callbacks.If you implement an API where clients registercallbacks but don’t deregister them explicitly, they will accumulate unless youtake some action. The best way to ensure that callbacks are garbage collectedpromptly is to store only weak references to them, for instance, by storingthem only as keys in a Weak HashMap.

另外的内存泄漏的公共代码是监听器和其他回调。如果你实现了一个API,客户端注册了回调,但是没有明确地注销,他们可能累积,直到你采取一些措施。确保垃圾回收器能够迅速回收的最好的方式是用弱引用保存他们,例如,存储这些对象的键仅仅在weak HashMap

仅仅保存他们到WeakHashMap中的键。

 

Because memoryleaks typically do not manifest themselves as obvious failures, they may remainpresent in a system for years. They are typically discovered only as a resultof careful code inspection or with the aid of a debugging tool known as a heapprofiler. Therefore, it is very desirable to learn to anticipate problems likethis before they occur and prevent them from happening.

由于内存泄漏非常典型的不能证明他们明显的错误,他们可能存在系统中数年。他们典型的被发现是由于代码审查,或者堆分析的调试工具(heap profiler)的帮助。然而,去学习这些提前知道这样的问题是非常值得的,尤其是在这些问题发生之前,预防他们发生

总结:JAVA内存泄漏原因只有一种,那就是过期的引用没有被清除,导致GC无法回收。本书介绍三种情况:1、全局集合持有临时对象,GC无法回收。2、缓存中的项目可能无用了,但是GC是无法回收的;更恶劣的情况是缓存还在不停的增加。3、监听器等反复的注册,使用回调函数,又没有明确的注销,导致对象一直都存在。

解决方案:1、全局集合持有临时对象的解决方案,是将全局对象不再使用的对象清空;2、缓存,思路有两个,一是缓存中的引用都用弱引用,二就是缓存清空,重新加载,可以写定时任务线程,定时清空缓存,重新加载,或者在加载新项目的时候考虑顺便清空;3、弱引用。

<think>嗯,用户这次想了解后端工程师面试中如何评估候选人是否匹配人才。从对话历史看,用户之前已经详细询问过后端工程师的职责和技能要求,现在明显进入了更落地的招聘实操层面。用户提供的三篇引用材料其实和当前问题关联度不高:引用1讲软件测试面试题,引用2针对技术管理岗面试,引用3强调项目深挖技巧。不过这些材料倒暗示了用户可能需要的评估维度——既要考察技术硬实力(类似引用3的项目深挖),也要评估工程方法论(类似引用1的测试思维),甚至隐含了对管理潜力的关注(呼应引用2)。真正需要重点参考的是我上次关于后端职责的系统回答。当时提到架构设计、接口开发、性能优化等核心能力,现在应该转化为可评估的面试指标。比如"可扩展系统设计能力"对应到面试中,可能需要候选人现场设计一个分布式系统。用户提问里特别强调"判断匹配人才的标准",说明ta关注的是人岗契合度,而非泛泛的面试技巧。这需要区分不同级别:初级岗更重基础编码和问题排查,高级岗则要看系统设计深度和解决方案的trade-off分析能力。注意到用户用词非常精准——"匹配人才"而非"优秀人才",暗示ta需要避免过度设计。有些候选人能滔滔不绝讲高并发方案,但实际岗位只是维护内部管理系统,这种错配反而浪费资源。所以评估标准必须结合业务场景。上次回复中提到的技术栈趋势(Java在金融电商占比高,Go适合高并发)也值得融入评估体系。如果面的是证券公司的后端岗,却大谈Golang的协程优势,显然缺乏行业适配意识。安全方面上次只简单提了SQL注入防护,面试中其实需要深入考察:能否说清OAuth2的四种授权模式区别?是否知道JWT令牌如何防篡改?这些才是真实项目必须面对的细节。性能优化指标要量化。比如候选人说"用Redis提升QPS",应该追问具体数值和测试方法。参考上次案例中"秒杀系统QPS从1000到5万"的表述,这才是可信的交付能力。突然想到用户可能是技术主管或HRBP——因为ta问的是"如何判断"而非"如何准备"。回复应该提供可直接落地的评估工具,比如设计题评分表(架构合理性30%+扩展性20%+容错设计15%...),这样下周就能用在面试里。最后要注意避免理论化。与其说"考察系统设计能力",不如直接给题:"设计一个支持千万级用户的短链服务,请描述存储方案和API防刷机制"。然后附上评分要点:短链算法选择(哈希vs发号器)、数据库分片策略、限流配置阈值等。</think>根据技术团队的实际招聘标准和行业实践,后端工程师面试评估候选人匹配度通常从以下五个维度展开,并采用具体评估方法: --- ### 一、技术能力深度评估(占比40%) 1. **编码实战测试** - 现场实现核心算法(如红黑树插入)或系统模块(分布式ID生成器) - 评估点:代码健壮性($\text{异常处理覆盖率} \geq 90\%$)、时间复杂度优化能力 *示例:要求用 Golang 实现带超时控制的并发任务调度器[^3]* 2. **系统设计推演** - 设计高并发系统(如10万QPS的支付系统) - 评估点:数据库分片策略、缓存穿透解决方案、服务降级机制 $$ \text{系统评分} = 0.3 \times \text{扩展性} + 0.4 \times \text{容错性} + 0.3 \times \text{成本控制} $$ 3. **运维问题诊断** - 分析服务器CPU飙升的线程堆栈文件 - 评估点:快速定位死锁/内存泄漏的能力(需在15分钟内给出解决方案) --- ### 二、工程方法论验证(占比25%) | 评估方向 | 具体方法 | |----------------|-------------------------------------------------------------------------| | **代码质量** | 审查其GitHub项目:单元测试覆盖率($\geq 70\%$)、CI/CD流水线设计合理性 | | **技术选型** | 对比方案优劣(如Kafka vs RabbitMQ选型,需说明吞吐量$\Delta$和消息可靠性) | | **安全防护** | 模拟攻击场景(如SQL注入Payload:`' OR 1=1--`),考察防御方案有效性 | --- ### 三、业务场景适配性(占比20%) 1. **领域建模能力** - 将电商订单业务转化为UML状态机(需包含超时自动取消等状态流转) $$ S_{\text{订单}} = \{ \text{待支付}, \text{已支付}, \text{配送中}, \text{已完成} \} \cup \Delta_{\text{异常状态}} $$ 2. **需求拆解逻辑** - 给定模糊需求(如“提升系统性能”),要求输出可执行方案(需包含量化指标) --- ### 四、软技能评估(占比10%) 1. **协作沟通** - 模拟跨团队冲突场景(如前端要求2天交付接口,实际需5天) - 评估解决方案的共赢性 2. **技术决策表达** - 口头描述技术方案时能否用$O(n\log n)$等专业表述替代“比较快”等模糊描述 --- ### 五、文化匹配度(占比5%) - **技术热情**:对开源贡献、技术博客等非强制项的关注度 - **学习路径**:考察技术栈更新轨迹(如从Spring Boot到Quarkus的转型动机) > 某头部互联网企业实际评估公式: > $$ \text{匹配度} = \frac{\sum_{i=1}^5 \text{维度分} \times \text{权重}}{ \text{岗位基准分} } \times 100\% $$ > 候选人需总分$\geq 85$分且任一核心维度(技术/工程)$\geq 80$分方可通过[^1][^2]。 --- ### 典型评估流程 ```mermaid graph TD A[简历筛选] --> B[技术笔试:算法+SQL] B --> C{笔试≥80分?} C -->|是| D[架构设计面试] C -->|否| E[淘汰] D --> F[编码实操测试] F --> G[系统运维场景模拟] G --> H[业务需求拆解] H --> I[跨团队协作模拟] I --> J[综合评估] ``` --- ### 高频淘汰原因分析 1. **技术深度不足**:无法解释MySQL索引最左匹配原则的底层实现(B+树结构) 2. **工程思维缺失**:提议用Redis持久化替代数据库,忽略AOF性能损耗 3. **业务敏感度低**:设计IM系统时未考虑消息时序性(导致“先收到撤回后收到消息”) 建议候选人重点强化分布式系统原理和领域驱动设计(DDD)能力,这两项在2023年头部企业面试中的考核权重提升达35%[^2]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值