java----hash冲突和内存溢出

一、hash冲突

1.什么是hash冲突?

由于哈希算法被计算的数据是无限的,而计算后的结果范围有限,因此总会存在不同的数据经过计算后得到的值相同,这就是哈希冲突。(两个不同的数据计算后的结果一样)

2.解决哈希冲突的方式

有四种方法:
1. 开放定址法(再散列法)
线行探查法
平方探查法
双散列函数探查法
2. 链地址法(拉链法)
3. 再哈希法
4. 建立公共溢出区

3.开放定址法

发生冲突的那个单元起,按照一定的次序,从哈希表中找到一个空闲的单元,然后把发生冲突的元素存入到该单元的一种方法。

开发定址法需要的表长度要大于等于所需要存放的元素

在开发定址法中解决冲突的方法有:
1. 线行探查法
2. 平方探查法
3. 双散列探查法
开放定址法缺点:
删除元素的时候不能真的删除,否则会引起查找错误。
只能做一个标记,直到有下个元素插入才能真正删除该元素。

3.1线行探查法

线行探查法是开放定址法中最简单的冲突处理方法,它从发生冲突的单元起,依次判断下一个单元是否为空,当达到最后一个单元时,再从表首依次判断。直到碰到空闲的单元或者探查完全部单元为止。

3.2平方探查法

平方探查法即是发生冲突时,用发生冲突的单元d[i], 加上 1²、 2²等。即d[i] + 1²,d[i] + 2², d[i] + 3²…直到找到空闲单元。
在实际操作中,平方探查法不能探查到全部剩余的单元。不过在实际应用中,能探查到一半单元也就可以了。若探查到一半单元仍找不到一个空闲单元,表明此散列表太满,应该重新建立。

3.3双散列函数探查法

这种方法使用两个散列函数hl和h2。其中hl和前面的h一样,以关键字为自变量,产生一个0至m—l之间的数作为散列地址;h2也以关键字为自变量,产生一个l至m—1之间的、并和m互素的数(即m不能被该数整除)作为探查序列的地址增量(即步长),探查序列的步长值是固定值l;对于平方探查法,探查序列的步长值是探查次数i的两倍减l;对于双散列函数探查法,其探查序列的步长值是同一关键字的另一散列函数的值。

4.再哈希法

就是同时构造多个不同的哈希函数:
Hi = RHi(key) i= 1,2,3 … k;
当H1 = RH1(key) 发生冲突时,再用H2 = RH2(key) 进行计算,直到冲突不再产生,这种方法不易产生聚集,但是增加了计算时间。

5.建立公共溢出区

将哈希表分为公共表和溢出表,当溢出发生时,将所有溢出数据统一放到溢出区。

6.链地址法

链接地址法的思路是将哈希值相同的元素构成一个同义词的单链表,并将单链表的头指针存放在哈希表的第i个单元中,查找、插入和删除主要在同义词链表中进行。链表法适用于经常进行插入和删除的情况。

二、内存溢出

1.什么是内存溢出?

内存溢出:(out of memory)通俗理解就是内存不够,指程序要求的内存超出了系统所能分配的范围,通常在运行大型软件或游戏时,软件或游戏所需要的内存远远超出了你主机内安装的内存所承受大小,就叫内存溢出。 比如申请一个int类型,但给了它一个int才能存放的数,就会出现内存溢出,或者是创建一个大的对象,而堆内存放不下这个对象,这也是内存溢出。

2.JVM内存模型

根据 JVM8 规范,JVM 运行时内存共分为虚拟机栈、堆、元空间、程序计数器、本地方法栈五个部分。还有一部分内存叫直接内存,属于操作系统的本地内存,也是可以直接操作的。

在这里插入图片描述

3.内存溢出

3.1堆内存溢出

当出现java.lang.OutOfMemoryError:Java heap space异常时,就是堆内存溢出了。
1.问题描述

设置的jvm内存太小,对象所需内存太大,创建对象时分配空间,就会抛出这个异常。
流量/数据峰值,应用程序自身的处理存在一定的限额,比如一定数量的用户或一定数量的数据。而当用户数量或数据量突然激增并超过预期的阈值时,那么就会峰值停止前正常运行的操作将停止并触发java . lang.OutOfMemoryError:Java堆空间错误

2.示例代码
编译以下代码,执行时jvm参数设置为-Xms20m -Xmx20m

package 内存溢出;

import java.util.ArrayList;

import java.util.List;

public class HeapOomError {
	public static void main(String[] args) {
		List<byte[]> list = new ArrayList<>();
		int i = 0;
		while(true) {
			list.add(new byte[5 * 1024 * 1024]);
			System.out.println("count is:"+(++i));
		}
	}
}

运行结果:
在这里插入图片描述
以上这个示例,如果一次请求只分配一次5m的内存的话,请求量很少垃圾回收正常就不会出错,但是一旦并发上来就会超出最大内存值,就会抛出内存溢出。
3.解决方法
首先,如果代码没有什么问题的情况下,可以适当调整-Xms和-Xmx两个jvm参数,使用压力测试来调整这两个参数达到最优值。
其次,尽量避免大的对象的申请,像文件上传,大批量从数据库中获取,这是需要避免的,尽量分块或者分批处理,有助于系统的正常稳定的执行。
最后,尽量提高一次请求的执行速度,垃圾回收越早越好,否则,大量的并发来了的时候,再来新的请求就无法分配内存了,就容易造成系统的雪崩。

3.2虚拟机栈和本地方法栈溢出

HotSpot虚拟机中不区分虚拟机栈和本地方法栈。栈容量用-Xss参数设定。Java虚拟机规范中描述了两种异常:
如果线程请求的栈深度大于虚拟机锁允许的最大深度,将抛出StackOverflowError异常。
如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常。

1.StackOverflowError异常

package com.lindaxuan.outofmemory;

/**
 * VM Args:-Xss128k
 * Error: Could not create the Java Virtual Machine.

 Error: A fatal exception has occurred. Program will exit.
 The stack size specified is too small, Specify at least 160k

 VM Args:-Xss256k
 * @author linxuan
 */
public class JavaVMStackSOF {

    private int stackLength = 1;

    public void stackLeak() {
        stackLength++;
        stackLeak();
    }

    public static void main(String[] args) throws Throwable {
        JavaVMStackSOF oom = new JavaVMStackSOF();
        try {
            oom.stackLeak();
        } catch (Throwable e) {
            System.out.println("stack length:" + oom.stackLength);
            throw e;
        }
    }
}

/*
result:
stack length:1868
Exception in thread "main" java.lang.StackOverflowError
   at com.lindaxuan.outofmemory.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:18)
   at com.lindaxuan.outofmemory.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:19)
   at com.lindaxuan.outofmemory.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:19)
   at com.lindaxuan.outofmemory.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:19)
   at com.lindaxuan.outofmemory.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:19)
   at com.lindaxuan.outofmemory.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:19)
   at com.lindaxuan.outofmemory.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:19)
   at com.lindaxuan.outofmemory.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:19)
   at com.lindaxuan.outofmemory.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:19)
   ...
 */

当单个线程下,无论是由于栈帧太大还是虚拟机栈容量太小,当内存无法分配的时候,虚拟机抛出的都是StackOverflowError异常。
2 OutOfMemoryError异常

package com.lindaxuan.outofmemory;

/**
 * VM Args:-Xss2M (这时候不妨设大些)
 * @author linxuan
 */
public class JavaVMStackOOM {

    private void dontStop() {
        while (true) {
        }
    }

    public void stackLeakByThread() {
        while (true) {
            Thread thread = new Thread(new Runnable() {
                public void run() {
                    dontStop();
                }
            });
            thread.start();
        }
    }

    public static void main(String[] args) throws Throwable {
        JavaVMStackOOM oom = new JavaVMStackOOM();
        oom.stackLeakByThread();
    }
}
/*
my result:
run too long
 */

3.3.垃圾回收超时内存溢出

1、问题描述

当应用程序耗尽所有可用内存时,GC开销限制超过了错误,而GC多次未能清除它,这时便会引发java.lang.OutOfMemoryError。当JVM花费大量的时间执行GC,而收效甚微,而一旦整个GC的过程超过限制便会触发错误(默认的jvm配置GC的时间超过98%,回收堆内存低于2%)。

2.示例代码
在这里插入图片描述

3.解决方法

要减少对象生命周期,尽量能快速的进行垃圾回收。

3.4.Metaspace内存溢出

1.问题描述

元空间的溢出,系统会抛出java.lang.OutOfMemoryError: Metaspace。出现这个异常的问题的原因是系统的代码非常多或引用的第三方包非常多或者通过动态代码生成类加载等方法,导致元空间的内存占用很大。

2.示例代码
以下是用循环动态生成class的方式来模拟元空间的内存溢出的。
在这里插入图片描述

3.解决办法

默认情况下,元空间的大小仅受本地内存限制。但是为了整机的性能,尽量还是要对该项进行设置,以免造成整机的服务停机。

1)优化参数配置,避免影响其他JVM进程
-XX:MetaspaceSize,初始空间大小,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。
-XX:MaxMetaspaceSize,最大空间,默认是没有限制的。
除了上面两个指定大小的选项以外,还有两个与 GC 相关的属性:
-XX:MinMetaspaceFreeRatio,在GC之后,最小的Metaspace剩余空间容量的百分比,减少为分配空间所导致的垃圾收集 。
-XX:MaxMetaspaceFreeRatio,在GC之后,最大的Metaspace剩余空间容量的百分比,减少为释放空间所导致的垃圾收集。
2)慎重引用第三方包
对第三方包,一定要慎重选择,不需要的包就去掉。这样既有助于提高编译打包的速度,也有助于提高远程部署的速度。
3)关注动态生成类的框架
对于使用大量动态生成类的框架,要做好压力测试,验证动态生成的类是否超出内存的需求会抛出异常。

3.5.直接内存内存溢出

1.问题描述

在使用ByteBuffer中的allocateDirect()的时候会用到,很多javaNIO(像netty)的框架中被封装为其他的方法,出现该问题时会抛出java.lang.OutOfMemoryError: Direct buffer memory异常。
如果你在直接或间接使用了ByteBuffer中的allocateDirect方法的时候,而不做clear的时候就会出现类似的问题。

2.示例代码
在这里插入图片描述

3.解决办法

如果经常有类似的操作,可以考虑设置参数:-XX:MaxDirectMemorySize,并及时clear内存。

3.6.栈内存溢出

1.问题描述

当一个线程执行一个Java方法时,JVM将创建一个新的栈帧并且把它push到栈顶。此时新的栈帧就变成了当前栈帧,方法执行时,使用栈帧来存储参数、局部变量、中间指令以及其他数据。
当一个方法递归调用自己时,新的方法所产生的数据(也可以理解为新的栈帧)将会被push到栈顶,方法每次调用自己时,会拷贝一份当前方法的数据并push到栈中。因此,递归的每层调用都需要创建一个新的栈帧。这样的结果是,栈中越来越多的内存将随着递归调用而被消耗,如果递归调用自己一百万次,那么将会产生一百万个栈帧。这样就会造成栈的内存溢出。

2.示例代码
在这里插入图片描述

3.解决办法

如果程序中确实有递归调用,出现栈溢出时,可以调高-Xss大小,就可以解决栈内存溢出的问题了。递归调用防止形成死循环,否则就会出现栈内存溢出。

3.7.创建本地线程内存溢出

1.问题描述

线程基本只占用heap以外的内存区域,也就是这个错误说明除了heap以外的区域,无法为线程分配一块内存区域了,这个要么是内存本身就不够,要么heap的空间设置得太大了,导致了剩余的内存已经不多了,而由于线程本身要占用内存,所以就不够用了。

2.示例代码
在这里插入图片描述

3.解决方法

首先检查操作系统是否有线程数的限制,使用shell也无法创建线程,如果是这个问题就需要调整系统的最大可支持的文件数。
日常开发中尽量保证线程最大数的可控制的,不要随意使用线程池。不能无限制的增长下去。

3.8.超出交换区内存溢出

1.问题描述

在Java应用程序启动过程中,可以通过-Xmx和其他类似的启动参数限制指定的所需的内存。而当JVM所请求的总内存大于可用物理内存的情况下,操作系统开始将内容从内存转换为硬盘。
一般来说JVM会抛出Out of swap space错误,代表应用程序向JVM native heap请求分配内存失败并且native heap也即将耗尽时,错误消息中包含分配失败的大小(以字节为单位)和请求失败的原因。

2.解决办法

增加系统交换区的大小,我个人认为,如果使用了交换区,性能会大大降低,不建议采用这种方式,生产环境尽量避免最大内存超过系统的物理内存。其次,去掉系统交换区,只使用系统的内存,保证应用的性能。

3.9.数组超限内存溢出

1.问题描述

有的时候会碰到这种内存溢出的描述Requested array size exceeds VM limit,一般来说java对应用程序所能分配数组最大大小是有限制的,只不过不同的平台限制有所不同,但通常在1到21亿个元素之间。当Requested array size exceeds VM limit错误出现时,意味着应用程序试图分配大于Java虚拟机可以支持的数组。JVM在为数组分配内存之前,会执行特定平台的检查:分配的数据结构是否在此平台是可寻址的。

2.示例代码
以下就是代码就是数组超出了最大限制。
在这里插入图片描述

3.解决方法

因此数组长度要在平台允许的长度范围之内。不过这个错误一般少见的,主要是由于Java数组的索引是int类型。 Java中的最大正整数为2 ^ 31 - 1 = 2,147,483,647。 并且平台特定的限制可以非常接近这个数字,例如:我的环境上(64位macOS,运行Jdk1.8)可以初始化数组的长度高达2,147,483,645(Integer.MAX_VALUE-2)。若是在将数组的长度再增加1达到nteger.MAX_VALUE-1会出现的OutOfMemoryError。

3.10.系统杀死进程内存溢出

1.问题概述

在描述该问题之前,先熟悉一点操作系统的知识:操作系统是建立在进程的概念之上,这些进程在内核中作业,其中有一个非常特殊的进程,称为“内存杀手(Out of memory killer)”。当内核检测到系统内存不足时,OOM killer被激活,检查当前谁占用内存最多然后将该进程杀掉。
一般Out of memory:Kill process or sacrifice child错会在当可用虚拟虚拟内存(包括交换空间)消耗到让整个操作系统面临风险时,会被触发。在这种情况下,OOM Killer会选择“流氓进程”并杀死它。
2.示例代码
在这里插入图片描述

3.解决方法
虽然增加交换空间的方式可以缓解Java heap space异常,还是建议最好的方案就是升级系统内存,让java应用有足够的内存可用,就不会出现这种问题。

内存溢出原因分析
分析堆内存溢出的原因可能如下:
使用了大量的递归或无限递归(递归中用到了大量的新建的对象)
使用了大量循环或死循环(循环中用到了大量的新建的对象)
类中和引用变量过多使用了Static修饰 如 public staitc Student s;在类中的属性中使用 static修饰的最好只用基本类型或字符串。如public static int i = 0; //public static String str;
数组,List,Map中存放的是对象的引用而不是对象,因为这些引用会让对应的对象不能被释放,会大量存储在内存中。
分析栈内存溢出的原因可能如下:
使用了大量的递归或无限递归
使用了大量循环或死循环(如循环中不停调用方法)
list,map,数组等长度过大等。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值