深入Java内存泄漏问题

翻译:吴嘉俊,叩丁狼高级讲师。

经验不足的开发人员经常会认为Java的自动垃圾回收机制会让他们彻底的摆脱内存管理的困扰。这是一个常见的错觉,即使垃圾收集器尽了最大的努力,即使是最好的程序员,也可能成为内存泄漏的牺牲品。容我慢慢道来。

内存泄漏出现在当对象已经不需要了,但是对象仍然被异常的引用。这种泄漏会带来严重后果,随意举一例,你的应用会持续的要求更多的资源,而导致对你的服务器造成不必要的压力。更糟糕的是,检测这种溢出是非常困难的:静态分析常常难以精确的识别这些冗余的引用,现有的内存诊断工具产生的针对独立对象的细粒度的诊断报告,也难以理解,并且缺乏精度。

换言之,内存泄漏要么太难识别,要么使用起来过于专业。

内存的问题可以分为4种类型,这四种类型的错误很相似,并且症状也有相似点,但是产生的原因和解决的方式完全不一样。

  • 性能相关: 通常出现在大量的对象创建和删除,长时间的垃圾回收延迟,大量的操作系统内存页交换等情况。
  • 资源限制: 常常出现在内存不足或内存碎片太大而无法分配大对象时,常常发生在native memory,或者heap memory中;
  • Java堆泄漏: 最经典的内存泄漏场景,出现在Java对象持续被创建,但是并没有被及时释放。常常因为潜在的对象引用导致。
  • Native memory泄漏: 与Java Heap memory外的持续增长的内存利用率相关,例如JNI代码、驱动程序或JVM分配所分配的。

在这篇文章中,我主要会聚焦在Java堆泄漏,并给出一种可行的方法去诊断这类内存问题,这种方法基于JVM报告,并利用可视化工具在应用运行中进行分析。

在介绍如何避免和排查内存泄漏之前,你必须先理解为什么内存泄漏会发生。

内存泄漏:入门

对于初学者来说,可以这样理解,内存泄漏你可以理解为一种疾病,而类似Java的OutOfMemoryError(一般简称为OOM)看成一种症状。但是不是所有的OOM都意味着内存泄漏:假如创建了巨大量级的本地变量,也会导致OOM,但这并不是内存泄漏。另一方面,并不是所有的内存泄漏都会导致OOM异常,特别是在桌面应用或者客户端应用中(运行时间不会很长,就会重启,所以可能出现内存泄漏,但是不会表现为OOM)。

为什么内存泄漏如此惹人厌恶?抛开其他的不说,内存泄漏会让系统随着时间的延长,性能直线降低。因为系统的物理内存一旦使用耗尽,就会导致物理内存交换。最终,应用可能耗尽分配的虚拟内存,导致OOM产生。

破解 OutOfMemoryError

上面已经说到,OOM是一个常见的内存泄漏的表现。大部分情况下,该错误是由于没有足够的空间分配给一个新的对象的时候抛出的,Java会尽力去尝试,但是垃圾回收器仍然无法清理出足够的空间,堆也没法继续扩展,错误就抛出了,并给你一个stack trace。

要理解OOM,第一步是明白OOM异常真正的意思。好像是句废话,但实际上,没有多少人能说出OOM到底有多少种。举个例子:一个OOM产生,是因为heap memory满了,还是因为native heap memory满了?为了帮助你回答类似的问题,我们先来看一下OOM可能出现的几种错误信息:

  • java.lang.OutOfMemoryError: Java heap space
  • java.lang.OutOfMemoryError: PermGen space
  • java.lang.OutOfMemoryError: Requested array size exceeds VM limit
  • java.lang.OutOfMemoryError: request <size> bytes for <reason>. Out of swap space?
  • java.lang.OutOfMemoryError: <reason> <stack trace> (Native method)

Java heap space

这个错误信息不一定意味着就是内存泄漏,事实上,这个错误类似配置错误,是很简答的问题。

举个例子,有一个应用经常出现这种类型的OOM异常,要我来分析这个问题。经过检查,我发现出问题的原因在于应用中有段代码会实例化一个占用内存空间较大的数组;在这种情况下,这并不是应用程序的错误,而是在应用服务器配置中,使用了默认的heap memory大小,这太小了。我仅仅只是通过调整Xms就解决了。

另外一种情况,特别是针对运行了较长时间的应用,这个信息可能就意味着应用中有可能存在未释放引用的对象,导致垃圾回收器无法及时回收空间。这是Java语言中最标准的内存泄漏(在API中的定义描述为:unintentionally holding object references)。

另一个潜在的导致Java heap space OOM的原因是使用了finalizer。如果一个类有finalize方法,那么这个类型的对象不会在垃圾回收时间收回对应的空间,而会等到垃圾回收结束之后,对象进入finalize队列排队,等待finalize执行。在Sun公司的JVM实现中,finalizer是由一个守护线程执行的。如果这个finalizer线程被终止了,而等待finalize的对象仍然在finalize队列中,那么这些对象就无法被正常回收,这个时候OOM就有可能发生了。

PermGen space

这个错误信息意味着永久代空间被占满了[注:Java8中已经去掉永久代]。永久代空间是用来存储class对象和method对象的堆空间。如果一个应用需要加载大量的类,则需要通过调整-XX:MaxPermSize参数来扩大永久代空间大小。

驻留的字符串对象(Intered String Object)也是存储在永久代中的。由java.lang.String类持有一个string对象的字符串常量池。当String的intern方法被调用,这个方法会首先在字符串常量池中检查是否有相等的字符串存在,如果是,这个字符串常量池中的string对象会被intern方法返回,如果不存在,这个string会被添加到字符串常量池中。使用专业术语来说,java.lang.String.intern方法返回一个字符串的不可变形式。如果一个应用中有大量的驻留字符串,你也需要增加永久代空间的大小。

提示:你可以使用jmap -permgen命令输出针对永久代的统计信息,包含驻留字符串相关的信息。[注:Java8中没有这个选项,直接通过jmap -heap pid就能看到intern字符串信息]

Requested array size exceeds VM limit

这个错误出现在应用(或者应用调用的API)尝试在堆上分配一个大于堆空间的数组。举个例子,如果应用尝试分配一个512MB的数组,但是最大的heap大小设置为256MB,于是一个Requested array size exceeds VM limit的OOM错误就会被抛出。在大部分情况下,这个问题就是一个配置问题,或者检查应用中是否有尝试分配巨大的数组的情况。

Request \<size> bytes for \<reason>. Out of swap space?

这个错误也是OOM的一种。在HotSpot VM中,当native heap分配空间失败,并且native heap空间趋近于衰竭的时候,会抛出这个异常。这个错误信息中包含了请求分配失败的空间大小(size)和失败的原因(reason)。大部分情况下,原因会显示出尝试分配空间失败的代码名称。

如果这个类型的OOM异常抛出,你可能需要使用针对你的操作系统的专门的诊断工具来分析问题。分析这个问题的时候,不仅仅只是去检查你的程序那么简单。比如,在下面几种情况下,你也可以看到这个问题:

  • 操作系统分配的内存交换空间不足。
  • 操作系统中的另一个进程占满了系统所有的内存资源。

这个错误也有可能是本地内存泄漏(native leak)造成的(比如一个应用或者代码库持续的要求分配空间,但是在操作系统回收内存的时候失败了)

\<reason> \<stack trace> (Native method)

如果你看到了这个错误信息,并且在当前栈顶显示的是一个native方法,那么意味着这个native方法在申请一个内存分配的时候失败了。这个错误和上一个错误的区别在于,内存分配错误是发生在JNI或者native方法上,还是Java VM代码上,如果是前者,抛出的Native method错误信息,如果是后者,抛出的是上面那个错误。

同样,如果出现了这种OOM,你需要使用针对你的操作系统的专门的诊断工具来分析问题。

非OOM导致的应用崩溃

偶然的,如果出现native heap空间分配失败,会立刻导致应用的崩溃。这种情况出现在native方法的代码没有检查和处理内存分配的异常。比如,当系统使用malloc 命令请求分配内存,但是返回NULL 代表无空间可分配。如果调用malloc 方法但是没有检查和处理这个NULL结果,这个应用会尝试访问不存在的内存地址,这会立刻导致应用崩溃。类似这样的问题,是非常难以定位和处理的。

在某些情况下,可以借助系统的致命错误日志(fatal error log)或者故障快照(crash dump)来分析。如果经过分析,确实是因为未处理内存分配异常导致的应用崩溃,你必须要找到分配失败的原因。导致分配失败的原因,和native heap空间分配失败的原因相似,可能是因为系统设置的内存交换空间不足,也有可能是另一个进程消耗完了系统的内存资源。

诊断泄漏

在大多数情况下,要想正确诊断出内存泄漏的原因,需要对应用有非常细节的了解。这个过程可能非常漫长,而且往往是通过迭代完成。

我们诊断内存泄漏的策略非常简洁明了:

  1. 确定征兆
  2. 开启垃圾回收器日志
  3. 开启profile
  4. 分析跟踪信息

1. 确定征兆

如上讨论,在大部分时候,Java进程抛出一个OOM异常,这本身就是一个非常名确的信号,内存资源已经被耗尽。在这种情况下,你必须首先要区别这是正常的内存资源耗尽还是内存泄漏。我们需要分析OOM抛出的信息,根据上面我们讨论的信息含义来确定大致的问题可能的方向。

常常的,如果一个java应用需要的存储空间超过运行时堆所有提供的,这往往归结于不合理的设计。举个例子,如果一个应用会创建一个图片的多个备份,然后放到一个数组里面,或者加载多个文件放到一个数组中,在这种情况下,如果一个图片或者这个文件本身就很大,就很容易造成资源的不足,这是一种常见的资源耗尽的的情况。但是,如果程序是正常的在处理相同的数据,但是内存的消耗却在稳步的增加,这就极有可能是内存溢出了。

2. 开启垃圾回收器日志

确定是否出现内存泄漏的一个最快的方式,就是开启垃圾回收器日志信息。内存约束问题通常可以通过-verbose:gc 的输出模式来快速确定。

在启动应用的时候,使用-verbose:gc参数,允许你在每次开始执行GC的时候,生成跟踪信息。意思是,当内存被回收的时候,在标准控制台会输出摘要信息,给出一个明确的数据,展示当前内存的管理情况。

下面是一个典型的使用-verbose:gc参数的输出:

image.png

在这个GC跟踪中,每一个块按照标记的数字升序排列。要明白这个跟踪日志的意思,主要要关注标记为连续分配失效的行(Allocation Failure[AF]),然后关注释放的内存(释放的空间大小和释放的比例),可以看到,每次释放的大小在减少,但是总的内存空间在增长,这就是一个典型的内存耗减的信号。

3. 开启Profiling

不同的JVM提供了不同的方法去生成trace文件来反应堆内存活动的情况,这些文件中一般都包含了堆中对象的详细类型和数量。这种方式就叫做heap profiling,比如jmap -histo[:live]

4. 分析跟踪文件

这篇文章关注点在JVM生成的trace上。trace信息可以由不同的内存泄漏分析工具生成,生成的展示格式也不一样,但是在这些数据之后的道理是一致的:在堆中找到一组本不应该存在的对象,并持续检查对象是否是在持续增长而非释放。特别关注的是那些在应用中一个定时的时间会触发相关事件,然后创建一定量的瞬时对象的情况。如果这种在内存中理应少量存在的对象却出现了较多的实例,那么可能就意味着bug的存在了。

最后,处理内存泄漏,需要整体的对代码进行复查。熟悉内存泄漏的类型对于加快debug是很有帮助的。

GC的是如何工作的?

在我们正式开始分析应用的内存泄漏问题之前,我们先来看看在JVM中,GC是如何工作的。

JVM使用跟踪搜集器来完成垃圾的回收。这种回收器标记出所有的root对象(即直接被活动线程引用的而对象),并跟随对象的引用,把路径上所有的对象标记出来,这些对象就是活动对象。

Java基于代际假设(generational hypothesis assumption)实现了分代垃圾回收器。分代垃圾收集器假设大部分对象都会在短时间内成为垃圾,而经过了一定时间依然存活的对象往往拥有较长的寿命。寿命长的对象更容易存活下来,寿命短的对象则会被很快的废弃。所以,为了缩短垃圾回收的时间,只需要重点扫描新创建的对象。

基于这个假设,Java把不同的对象放到不同的分代空间中进行管理。下面是一个模拟图。

image.png

  • 新生代 - 这是对象开始的地方。它有两个子代:
    • Eden Space:对象从这里开始。大部分的对象在Eden空间中创建并销毁。在这里面,GC执行小回收(Minor GC),来优化垃圾回收。当执行小回收的时候,如果对象仍然需要(即存活),那么这些对象会被移动到任何一个survivors空间(S0或者S1)。
    • Survivor Space (S0 and S1):从Eden Space存活下的对象会移动到这里。有两个Survivor Space,在一个给定时间,只会有一个处于激活状态(除非发生严重的内存泄漏)。一个被设置为空,一个是存活的,每一次GC周期会执行轮换。
  • 年老代(Tenured Generation):也叫做old generation (old space), 这个区域存放存活较久的对象(从survivor空间移动过来,已经存活了足够久的时间)。当这个空间被填满,会触发一次完整GC(full GC),完整GC会小号较多的性能。如果这个空间持续的增长,JVM就会抛出一个OOM-Java heap space。
  • 永久代(Permanent Generation):靠近老年代的第三个代,永久代是一个特殊的空间,在这里面存储的是虚拟机需要使用的类的描述数据。比如,对象的类描述信息和方法描述信息,都是存在永久代中。

Java针对不同的代空间采用了足够聪明的垃圾回收方法。在新生代中,使用了跟踪,拷贝回收算法,叫做并行GC(Parallel New Collector)。所谓Copying算法就是扫描出存活的对象,并复制到一块新的完全未使用的空间中。新生代采用空闲指针的方式来控制GC触发,指针保持最后一个分配的对象在新生代区间的位置,当有新的对象要分配内存时,用于检查空间是否足够,不够就触发GC。

想要对JVM各个代空间有更细致的理解,请访问:Memory Management in the Java HotSpot™ Virtual Machine

检测内存溢出

为了找到内存泄漏并排除错误,你需要使用合适的内存泄漏检查工具。是时候介绍Java VisualVM。

使用Java VisualVM远程profiling堆信息

VisualVM提供了可视化界面,能够提供对基于java的运行着的应用的数据进行细节的数据展示。

使用VisualVM,你可以监控本地的应用,也可以监控远端服务器上的应用。你可以获取JVM实例的相关数据,并且将数据保存到本地系统。

为了获取Java VisualVM的所有功能,请运行在JavaSE6以上。

连接远端JVM

在生产环境,直接连接到远程服务器是不安全的,也不方便,远程连接并profile是一个不错的选择。

首先,我们需要授权我们的JVM能够连接上远端的目标机器。创建一个文件叫做jstatd.all.policy,加入以下内容:

grant codebase "file:${java.home}/../lib/tools.jar" {

   permission java.security.AllPermission;

};

一旦这个文件创建好,我们需要使用jstatd-Virtual Machine jstat Daemon工具连接远端VM,使用以下命令:

jstatd -p <PORT_NUMBER> -J-Djava.security.policy=<PATH_TO_POLICY_FILE>

比如:

jstatd -p 1234 -J-Djava.security.policy=D:\jstatd.all.policy

当jstatd在目标VM上运行之后,我们就可以连接上目标机器,开启远端profile,排查内存泄漏问题。

连接远端服务

在本地打开一个命令窗口,并输入jvisualvm开启VisualVM工具。

下一步,我们需要在VisualVM中添加一个远程主机,前提是我们已经在远程主机上允许从另一台机器远程连接。我们启动Java VisualVM工具,并连接这台服务器,一旦连接成功,我们就能看到远端运行的那台JVM了。

image.png

要开启内存profiler,我们需要在左边的面板中双击目标服务器。

到这里,我们已经为我们的内存分析做好了基本的准备。

内存泄漏

在Java中要模拟一个内存泄漏有多重方法,最简单的方式是我们定义一个类,但不要覆盖equals()和hashcode()方法,并将这个类作为一个HashMap的key。HashMap要求作为key的类需要实现equals()和hashcode()方法。没有这两个方法,就无法产生一个正确的key。如果没有定义equals()和hashcode()方法,当我们将相同的key重复的添加到HashMap中,我们本意是执行替换操作,但实际上不会,因为无法正确对比key值,HashMap会持续的增长,直到抛出一个OOM。

下面是定义的MemLeak类:

package com.post.memory.leak;
import java.util.Map;

public class MemLeak {

    public final String key;
    public MemLeak(String key) {
        this.key =key;
    }

    public static void main(String args[]) {
        try {
            Map map = System.getProperties();
            for(;;) { // line 14
                map.put(new MemLeak("key"), "value");
            }
        } catch(Exception e) {
            e.printStackTrace();
        }
    }
}

注意,内存泄漏不是出现在14行的无限循环;无限循环可能会导致资源的耗竭,但是不会出现内存溢出。如果我们正确提供了equals()和hashcode()方法,就算使用无限循环,map中也只可能出现一个元素。

可以访问:http://stackoverflow.com/questions/6470651/creating-a-memory-leak-with-java 找到更多的内存泄漏模拟方法。

使用Java VisualVM

使用Java VisualVM,我们可以监控Java 堆信息,并且识别出是否存在内存泄漏。

下面是初始化完成之后,我们的MemLeak的Java 堆信息示意图:

image.png

差不多30秒之后,老年代基本上被占满,注意,即使是执行了一次Full GC,老年代仍然在持续增长,这就是明显的内存泄漏的标志。

为了排查这次内存溢出的原因,一种方式就是使用Java VisualVM生成heap dump信息。在图中我们可以看到,heap中50%的对象都是Hashtable%Entry,第二行指向了我们的MemLeak类。那么可以得出结论,这次的内存泄漏在于MemLeak类作为了hash table的key。

image.png

最后,可以注意到,当抛出OOM的时候,新生代和老年代全部都满了。

image.png

小节

内存泄漏算JAVA应用中最难解决的一个问题了,症状变化多端,并且难以重现。这篇文章中,我们一步一步的介绍了内存泄漏的症状,产生的原因,如何定位和识别。最后,注意识别OOM的错误信息,仔细的分析错误栈,不是所有的内存溢出问题,都如我们这篇文章中举得MemLeak例子这么明显。

原文:https://www.toptal.com/java/hunting-memory-leaks-in-java

 

相关推荐

运行时数据区

本文作者:李敏,叩丁狼高级讲师。原创文章,转载请注明出处。前言为什么要了解虚拟机如何操作内存?java与c/c++之间有一堵由内存动态分配和垃圾收集技术所围成的"高墙",墙外面的人想进去,墙里面的人却想出来.对于java程序员来说,在虚拟机自动内存管理机制的帮助下,不再需要为每一个new操作去写配对的delete/free代码,不容易出现内存泄漏和内存溢出.有虚拟机管理内存,这一切看起...

wolfcode_cn的博客 1015

使用Mycat实现数据库读写分离

本文作者:蓝雄威,叩丁狼高级讲师。原创文章,转载请注明出处。01前言在上一篇文章中,我们基于CentOS7实现了MySQL的主从复制,我们需要把DML操作放在MySQL的主节点执行,需要把DQL操作放在MySQL的从节点执行.那在应用程序中我们怎么控制这个逻辑呢?下面是一种可行的方案:1.在项目中配置两个数据源(一主一从),分别是A(主)和B(从)。2.在程序中判断是不是select...

wolfcode_cn的博客 1342

线程基础(三十三)

接上篇,本篇讲解线程另外一个设计模式:Producer-Consumer Pattern.概念Producer是生产,生产者的意思, 指生产数据的线程, Consumer 则是消费,消费者的意思. 指使用数据的线程.Producer-Consumer 模式主要目标:生产者生产数据能安全得交给消费者进行处理.当Producer 跟 Consumer 都为一个线程时, Producer-Con...

wolfcode_cn的博客 963

线程基础(三十二)

接上篇,本篇讲解线程另外一个设计模式:Balking Pattern.概念Balking 是停止并返回意思. 如果当前线程不适合(不满足守护条件)执行某个操作直接返回,这就是Balking 模式.Balking 模式跟之前的Guarded Suspension模式有点类似, 区别:Guarded Suspension模式不满足守护条件,线程需要挂起等待, 而Balking 模型不满足守...

wolfcode_cn的博客 804

线程基础(三十一)

本文作者:王一飞,叩丁狼高级讲师。原创文章,转载请注明出处。接上篇,本篇讲解线程另外一个设计模式:Guarded Suspension Pattern.概念Guarded是被守护,被保卫,被保护的意思, Suspension 则是暂停的意思. 如果不满足执行条件,先让当前处理的线程暂停.这就是Guarded Suspension模式.参与角色Guarded Suspension模式参...

wolfcode_cn的博客 1017

刚进职场的程序员,和工作了2、3年的程序员到底有什么不一样?

“老师,我们刚进职场,和工作了2、3年的程序员到底有什么不一样?”这是很多同学在进入职场后,问过我最多的问题。不妨让我来翻译一下:老师,我到底有哪些表现让我在职场显得特别“嫩”,没办法获得领导、同事更多的认可和信任?于是我总结分析了工作中常见的三个误区,如果在工作中有意识地规避这些误区,也许就能帮助你快速成为“职场老司机”——01 不敢争取【情景】公司有一个做大项目的机会,经理问你是否愿意参与,但你没有十足的把握,最后权衡来权衡去还是拒绝了。【解析】这里面有两个层次的问题。第一,还是自信.

wolfcode_cn的博客 4942

『面试篇』注意面试中常见的三大误区,大幅度提升你被录用的机会

​先分享一个真实案例:之前我发现有一个学员把简历上面的项目经验删除了三分之二,我问他为什么?他说因为在一场面试中HR说不相信这些项目是他做的,没有给他解释机会就结束了面试。于是他认为找不到工作是因为自己做的项目经验太多了,于是在接下来的面试中,不仅删除了自己做过的项目,还在面试环节装作不懂的样子。这是很明显的心态和认知都被面试官影响的例子。大家好,我是叩丁狼的就业老师刘梓华。这些年帮助过上千个学员找到工作,不管是学员的面试问题还是工作期间遇到的非技术问题,我都有一些想和大家分享的经验。针.

wolfcode_cn的博客 1121

初级程序员应该怎么选电脑?来自从业5年维修工程师的建议

大家好,我是叩丁狼的彬辉老师。因为工作原因,经我手修好的电脑没有一千也有八百,关于编程开发对硬件的要求,也是了然于心。不管你是什么级别的程序员,选配置也是一件头疼的事,这里我给大家整理一些简单的配置参考,希望能帮到大家——其实电脑配置远不止这些,还有散热、主板、处理器、电源等等,这次主要提到对电脑性能影响较大的四个核心参数,方便同学们更快更精确地挑选合适的电脑,如果你对电脑已经有一定的了解,欢迎来办公室找我交流~至于选择笔记本还是台式,完全看个人喜好:同等配置下,台式机性价比更高,可以定制化.

wolfcode_cn的博客 8286

互联网最大谣言:程序员35岁必淘汰?今天我就来击碎他!

朋友,只要你是程序员,你一定知道996和“程序员35岁必死”的言论。这两个话题在互联网上的讨论一次比一次激烈。996工作制,众所周知,每天早上9点到岗,一直待到晚上9点,每周工作6天,很多互联网公司在招聘前甚至就会私下告诉你公司实行996制度。尽管很多年轻程序员在工作之前已经做了一定的心理准备,而一旦真正直面这些问题,心里肯定会打怵:“我现在年轻,还可以接受加班,可等我成家了、年纪大了、身体吃不消怎么办?”今天我就从个人的角度跟大家分享一下我的看法。程序员最爱给自己挖坑本人从事过开发工作,现在

wolfcode_cn的博客 1万+

用了这么久开源社区,还搞不明白免费/自由/开源软件?

在开源社区中,有很多人经常把免费软件、自由软件、开源软件这三个概念混淆在一起。今天就来和大家谈谈这三个软件到底有什么不一样。免费软件用户可以免费使用该软件,但通常会有其他权限的限制:1.源码不一定会公开/不能修改软件源码;2.限制拷贝的代码数量和段落;3.不得用于商业用途、不能以该软件牟利、仅供个人使用、仅供学习交流使用等等,如果有人未经作者允许则随意散布、传播,作者有权进行追责;4.限制使用者研究、修改和再散布的权利;免费软件免费的目的无非出于:先提供免费版本软件扩大市场占有率,当

wolfcode_cn的博客 1635

中国开发者现状报告:最想学Java语言、金融业高收入比例大、仅2%的人不安排学习时间......

​在《2019-2020 年中国开发者调查报告》中,我们从「开发者现状」中有最新的发现:6成开发者在使用Java语言;在一线城市,30%开发者月薪超过1.7万元;互联网、软件、IT制造三个技术领域涵盖了国内84%以上的开发者;开发者学习热情高涨,31%接受了软件开发的在职培训…*以下数据报告来源:优快云软件开发工具使用特点分析01Java长盛:使用最多、最想学60%的开发者使用最多的编程语言是Java,Java、Python、Javascript是开发者最想学的三种语言。左图为开

wolfcode_cn的博客 1093

如何在百度中脱颖而出——前端与SEO

SEO:译为搜索引擎优化,搜索引擎优化技术现在随着百度,谷歌,搜狗等搜索引擎的兴起,越来越火爆,那今天,我们一起来看看,SEO和前端有着怎样千丝万缕的联系~SEO的由来:早在90年代国外便有了SEO的概念,曾经由于谷歌的SEO,导致很多媒体行业的不满。后来大约03年,由于一款游戏(传奇私服),各种私服产业恶性竞争,慢慢的,SEO在中国逐渐商业化。SEO分类:SEO主要分为白帽SEO和黑帽SEO,就像白帽黑客和黑帽黑客一样。白帽SEO:改良网站设计规范,使网站对搜索引擎和用户更加友好黑帽SEO:利

wolfcode_cn的博客 989

28岁程序员郭宇财务自由退休,都说他运气爆棚,但我看未必...

今年6月,一封退休告别信最近引爆了微博、知乎等各大平台,一位名叫郭宇的程序员走进了大众的视野。郭宇是谁?他的微博简介是这么写的:株式会社山月夜代表取缔役社长/住过150+个日本温泉乡/100万公里飞行进度86.5%/程序员/原字节跳动资深技术专家。说到程序员,大家都会想到996、过劳、无休止地加班、没有自由…而这位程序员颠覆了大众的认知,不仅在28岁成功实现财务自由从字节跳动退休,并在日本开了一家温泉旅馆,当起了作家。面对这一系列的“暴击”,让不少互联网行业人直呼:“我酸了!”01 28岁财

wolfcode_cn的博客 3901

疫情让程序员活得更难吗?2020年程序员薪资满意度、跳槽状况、裁员补偿数据大公开!

自从网传程序员到了35岁之后必须要转行,现在又有人传言:“疫情之下,程序员今年要过苦日子了,降薪裁员是大趋势。”不是,我就不明白了,你们怎么就看不得程序员好呢?天天巴望着程序员降薪、转行、裁员…我们不可否认,从去年年底开始,疫情就导致了很多企业陷入危机,继而停发薪资、变相裁员,在如此“难搞”的大环境下,对程序员来说究竟意味着什么?在疫情期间,程序员客栈对北京、上海、广东、浙江等全国29个省、直辖市、特别行政区的近40万程序员进行了一次详细调查。我想这份详细调查数据足以吊打网络上传出莫名其妙的“程序

wolfcode_cn的博客 2173

程序员和码农有什么不同?从这三个单词就看得出来

码农这个词很早就有了,但我以为这只是程序员偶尔自黑的幽默,直到论坛、博客上越来越多人正儿八经地自称“码农”、“码畜”、“搬砖的”…有人认为“码农”、“程序员”、“工程师”等等这些称谓大可不必过于在意,名称和真实情况是两回事。不过我也比较介意别人叫我码农,可能本身并无恶意,但是感觉这个称呼实在配不上自己的工作创意与工作程度,就像我可以说自己是屌丝,但别人这么叫自己肯定不舒服。举个对比的例子:设计师VS美工艺人VS唱歌的心理咨询师VS路边算卦财务计算VS收银新媒体运营VS小编…程序员不是码

wolfcode_cn的博客 6986

CSS3绘制各种形状

本文为叩丁狼高级讲师原创文章,转载请注明出处。现在的CSS功能非常强大了,特别是CSS3的属性,例如转换属性,过渡属性,动画属性,能做的效果非常多。在网页开发中,会经常遇到一些小符号或者形状,在以前就只能通过切图来实现,切图这种方式是用起来方便,但是会增加请求。而现在浏览器对CSS3的兼容基本没有什么问题,所以在网页开发的时候遇到符号或者形状,能写的都是用CSS来书写了。而这篇文章就是收...

wolfcode_cn的博客 1114

强大而好用的选择器:focus-within

本文为叩丁狼高级讲师原创文章,转载请注明出处。伪类和伪元素在开发网页样式中,选择器必不可少,而且选择器也是在开发css中非常重要的内容,包括常用的类选择器,id选择,同时还有伪类,伪类选择器最大的特点就是冒号开头。平时也经常会有小伙伴问到,在使用的时候有伪类,也有伪元素,有些添加单冒号,有些添加双冒号,有没有什么区别?在网上总结的比较多的一个特点就是,其实在使用的时候,无论是伪类还是伪...

wolfcode_cn的博客 1046

JavaScript-DOM操作

本文为叩丁狼高级讲师原创文章,转载请注明出处。在之前的文章中,学习过了DOM的访问方式,其中也把DOM相关的概念进行了学习,其实DOM的内容还有很多,所以本文将会继续学习DOM操作相关的内容,DOM操作涉及到的操作有:增、删、改、查,而查这方面的内容其实就是上一次所写的“访问方式”。##DOM的增加DOM操作中增指的是增加节点,分为两部分:创建节点和插入节点。###创建节点创建节...

wolfcode_cn的博客 682

超简单自动编译sass扩展和工具

本文为叩丁狼高级讲师原创文章,转载请注明出处。在之前的文章中,学习过了DOM的访问方式,其中也把DOM相关的概念进行了学习,其实DOM的内容还有很多,所以本文将会继续学习DOM操作相关的内容,DOM操作涉及到的操作有:增、删、改、查,而查这方面的内容其实就是上一次所写的“访问方式”。##DOM的增加DOM操作中增指的是增加节点,分为两部分:创建节点和插入节点。###创建节点创建节...

wolfcode_cn的博客 684

Css、less和Sass(SCSS)

本文为叩丁狼高级讲师原创文章,转载请注明出处。背景CSS(层叠样式表)是一门非程序式语言,入门学习使用非常直观方便,但是队友一些比较复杂或者重用性比较强的项目来说,因为CSS没有变量、函数、SCOPE(作用域),需要书写大量看似没有逻辑的代码,不方便维护及扩展,不利于复用,尤其对于非前端开发工程师来讲,往往会因为缺少 CSS 编写经验而很难写出组织良好且易于维护的 CSS 代码。为了方便前...

wolfcode_cn的博客 1642

面试题:如何在10亿个随机整数中找出前1000个最大的数

本文作者:梁开权,叩丁狼高级讲师。原创文章,转载请注明出处。我们知道排序算法有很多:冒泡算法:通过两层for循环,外层第一次循环找到数组中最大的元素放置在倒数第一个位置,第二次循环找到第二大的元素放置在倒数第二个位置。。。循环N次就可以找到TopN。缺点:冒泡排序内层循环需要大量交换元素。复杂度介于O(n)和O(n^2)之间。快速排序:选一个基准元素,每次排序可以将这个基准元素...

wolfcode_cn的博客 1631

MyBaits-Plus

本文作者:范佳龙,叩丁狼高级讲师。原创文章,转载请注明出处。MyBaits-PlusMyBatis的优点Mybatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Old ...

wolfcode_cn的博客 1217

线程基础(三十四)-Read-Write Lock Pattern

本文作者:王一飞,叩丁狼高级讲师。原创文章,转载请注明出处。接上篇,本篇讲解线程另外一个设计模式:Read-Write Lock Pattern.概念Read是读, 指获取/查询数据的线程, Write是写,指操作(增删改)数据的线程.Read-Write Lock 模式要求:1>在读模式时,多个线程可以同时执行读操作,但不允许写操作2>在写模式时,只允许一个线程执行写...

wolfcode_cn的博客 509

一起来看源代码-01TreeMap添加操作

本文作者:黄海燕,叩丁狼高级讲师。原创文章,转载请注明出处。##前言之前很多小伙伴问我怎么看源代码,还有就是越来越多的程序员都想要看源代码,搞懂底层原理,但是感觉源代码非常的晦涩难懂,不够直接和清晰,所以我希望这篇文章能够快速带同学们看懂java源码,更加深入的学习java,帮助小伙伴们节约学习的时间成本.##1.树的介绍什么是树结构?其实就是一个节点下面有多个子节点,我们称之为...

wolfcode_cn的博客 483

MongoDB With Jav

本文作者:杨龙,叩丁狼高级讲师。原创文章,转载请注明出处。准备工作要使用 Java 代码操作 MongoDB 数据库,创建 Maven 项目则先添加 MongoDB 的 Java 驱动依赖.<!-- MongoDB 的 Java 驱动 --><dependency> <groupId>org.mongodb</groupId>...

wolfcode_cn的博客 623

垃圾回收器和内存分配策略

本文作者:李敏,叩丁狼高级讲师。原创文章,转载请注明出处。4. 垃圾回收器和内存分配策略**GC(Garbage Collection)**的历史比java久远.1960年诞生于MIT的Lisp是第一门真正使用内存动态分配和垃圾收集技术的语言.GC一直致力于解决的问题:哪些内存需要回收(what)?什么时候回收(when)?如何回收(how)?在虚拟机运行时数据区中,最频繁使用的是...

wolfcode_cn的博客 419

Sharding JDBC-分库分表

本文作者:梁开权,叩丁狼高级讲师。原创文章,转载请注明出处。环境准备pom.xml<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <ver...

wolfcode_cn的博客 558

Sharding JDBC-读写分离

本文作者:梁开权,叩丁狼高级讲师。原创文章,转载请注明出处。读写分离接着上一篇的分表分库我们继续来说读写分离案例模型让数据库的读和写功能分开,其中master负责所有的写操作以及在特殊情况下负责少量的读操作,slave在任何情况下都负责所有的读操作,不负责任何写操作,同样完成这个操作我们需要至少需要2个连接池,具体选择哪个连接池来操作取决于内部的一个叫路由的组件,该组件具有SQL的识别...

wolfcode_cn的博客 466

线程基础(三十)

本文作者:王一飞,叩丁狼高级讲师。原创文章,转载请注明出处。接上篇,本篇讲解线程另外一个设计模式:Immutable Pattern. 不可变模式.概念Immutable Pattern:不可变模式, 设计思路非常简单, 要求共享资源是一个不可改变对象, 线程只能访问资源,而不允许对资源修改.并发环境下即使不使用加锁操作,也可保证线程操作安全.知识补充敞开讲述前,先来科普一下不可对象...

wolfcode_cn的博客 282

线程基础(二十八)

本文作者:王一飞,叩丁狼高级讲师。原创文章,转载请注明出处。#####List集合######SynchronizedList :一个由普通list转换而来的线程安全集合List list = Collections.synchronizedList(new LinkedList(…));######CopyOnWriteArrayList :一种读写安全的ArrayList集合Co...

wolfcode_cn的博客 279
上一篇: 使用七牛云存储图片案例
下一篇: 升级到JUnit5的7个理由
叩丁狼
叩丁狼 企业官方账号 企业官方账号
博客等级 码龄9年 914粉丝 349原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值