java heap space 什么意思_java内存溢出之Java heap space

java heap space 什么意思_java内存溢出之Java heap space作为一个java程序员,大家都应该认识JVM。JVM作为java的核心,实在太重要了。而内存溢出又是程序员常遇到的错误之一,如果你对JVM的原理足够了解,那么解决这样的问题就不在是一件困难的事情。关于内存溢出,一般有下面这八个症状,本文将说明引发特定错误的原因,提供了可能导致此类错误的代码示例,并提供了解决方案的修复准则,希望对做开发的小伙伴能有一定的帮助。本篇是第1小篇。OutOfMemoryE…

大家好,又见面了,我是你们的朋友全栈君。

作为一个java程序员,大家都应该认识JVM。JVM作为java的核心,实在太重要了。而内存溢出又是程序员常遇到的错误之一,如果你对JVM的原理足够了解,那么解决这样的问题就不在是一件困难的事情。

关于内存溢出,一般有下面这八个症状,本文将说明引发特定错误的原因,提供了可能导致此类错误的代码示例,并提供了解决方案的修复准则,希望对做开发的小伙伴能有一定的帮助。

本篇是第1小篇。

OutOfMemoryError之Java heap space

OutOfMemoryError之GC overhead limit exceeded

OutOfMemoryError之Permgen space

OutOfMemoryError之Metaspace

OutOfMemoryError之Unable to create new native thread

OutOfMemoryError之Out of swap space?

OutOfMemoryError之Requested array size exceeds VM limit

OutOfMemoryError之Kill process or sacrifice child

每个Java程序都只能使用一定量的内存, 这种限制是由JVM的启动参数决定的。而更复杂的情况在于, Java程序的内存分为两部分: 堆内存(Heap space)和 永久代(Permanent Generation, 简称 Permgen):

4454216b4022

java内存溢出之Java heap space(1/8)

这两个区域的最大内存大小, 由JVM启动参数 -Xmx 和 -XX:MaxPermSize 指定. 如果没有明确指定, 则根据平台类型(OS版本+ JVM版本)和物理内存的大小来确定。

假如在创建新的对象时, 堆内存中的空间不足以存放新创建的对象, 就会引发java.lang.OutOfMemoryError: Java heap space 错误。

不管机器上还没有空闲的物理内存, 只要堆内存使用量达到最大内存限制,就会抛出 java.lang.OutOfMemoryError: Java heap space 错误。

原因分析

产生 java.lang.OutOfMemoryError: Java heap space 错误的原因, 很多时候, 就类似于将 XXL 号的对象,往 S 号的 Java heap space 里面塞。其实清楚了原因, 就很容易解决对不对? 只要增加堆内存的大小, 程序就能正常运行. 另外还有一些比较复杂的情况, 主要是由代码问题导致的:

超出预期的访问量/数据量。 应用系统设计时,一般是有 “容量” 定义的, 部署这么多机器, 用来处理一定量的数据/业务。 如果访问量突然飙升, 超过预期的阈值, 类似于时间坐标系中针尖形状的图谱, 那么在峰值所在的时间段, 程序很可能就会卡死、并触发 java.lang.OutOfMemoryError: Java heap space 错误。

内存泄露(Memory leak). 这也是一种经常出现的情形。由于代码中的某些错误, 导致系统占用的内存越来越多. 如果某个方法/某段代码存在内存泄漏的, 每执行一次, 就会(有更多的垃圾对象)占用更多的内存. 随着运行时间的推移, 泄漏的对象耗光了堆中的所有内存, 那么 java.lang.OutOfMemoryError: Java heap space 错误就爆发了。

具体示例

一个非常简单的示例

以下代码非常简单, 程序试图分配容量为 2M 的 int 数组. 如果指定启动参数 -Xmx12m, 那么就会发生 java.lang.OutOfMemoryError: Java heap space 错误。而只要将参数稍微修改一下, 变成 -Xmx13m, 错误就不再发生。

public class OOM {

static final int SIZE=2*1024*1024;

public static void main(String[] a) {

int[] i = new int[SIZE];

}

}

内存泄漏示例

这个示例更真实一些。在Java中, 创建一个新对象时, 例如 Integer num = new Integer(5); , 并不需要手动分配内存。因为 JVM 自动封装并处理了内存分配. 在程序执行过程中, JVM 会在必要时检查内存中还有哪些对象仍在使用, 而不再使用的那些对象则会被丢弃, 并将其占用的内存回收和重用。这个过程称为 垃圾收集. JVM中负责垃圾回收的模块叫做 垃圾收集器(GC)。

Java的自动内存管理依赖 GC, GC会一遍又一遍地扫描内存区域, 将不使用的对象删除. 简单来说, Java中的内存泄漏, 就是那些逻辑上不再使用的对象, 却没有被 垃圾收集程序 ****给****干掉. 从而导致垃圾对象继续占用堆内存中, 逐渐堆积, 最后造成 java.lang.OutOfMemoryError: Java heap space 错误。

很容易写个BUG程序, 来模拟内存泄漏:

import java.util.*;

public class KeylessEntry {

static class Key {

Integer id;

Key(Integer id) {

this.id = id;

}

@Override

public int hashCode() {

return id.hashCode();

}

}

public static void main(String[] args) {

Map m = new HashMap();

while (true){

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

if (!m.containsKey(new Key(i))){

m.put(new Key(i), “Number:” + i);

}

}

System.out.println(“m.size()=” + m.size());

}

}

}

粗略一看, 可能觉得没什么问题, 因为这最多缓存 10000 个元素嘛! 但仔细审查就会发现, Key 这个类只重写了 hashCode() 方法, 却没有重写 equals() 方法, 于是就会一直往 HashMap 中添加更多的 Key。

随着时间推移, “cached” 的对象会越来越多. 当泄漏的对象占满了所有的堆内存, GC 又清理不了, 就会抛出 java.lang.OutOfMemoryError:Java heap space 错误。

解决办法很简单, 在 Key 类中恰当地实现 equals() 方法即可:

@Override

public boolean equals(Object o) {

boolean response = false;

if (o instanceof Key) {

response = (((Key)o).id).equals(this.id);

}

return response;

}

说实话, 在寻找真正的内存泄漏原因时, 你可能会死掉很多很多的脑细胞。

一个SpringMVC中的场景

译者曾经碰到过这样一种场景:

为了轻易地兼容从 Struts2 迁移到 SpringMVC 的代码, 在 Controller 中直接获取 request.

所以在 ControllerBase 类中通过 ThreadLocal 缓存了当前线程所持有的 request 对象:

public abstract class ControllerBase {

private static ThreadLocal requestThreadLocal = new ThreadLocal();

public static HttpServletRequest getRequest(){

return requestThreadLocal.get();

}

public static void setRequest(HttpServletRequest request){

if(null == request){

requestThreadLocal.remove();

return;

}

requestThreadLocal.set(request);

}

}

然后在 SpringMVC的拦截器(Interceptor)实现类中, 在 preHandle 方法里, 将 request 对象保存到 ThreadLocal 中:

/**

* 登录拦截器

*/

public class LoginCheckInterceptor implements HandlerInterceptor {

private List excludeList = new ArrayList();

public void setExcludeList(List excludeList) {

this.excludeList = excludeList;

}

private boolean validURI(HttpServletRequest request){

// 如果在排除列表中

String uri = request.getRequestURI();

Iterator iterator = excludeList.iterator();

while (iterator.hasNext()) {

String exURI = iterator.next();

if(null != exURI && uri.contains(exURI)){

return true;

}

}

// 可以进行登录和权限之类的判断

LoginUser user = ControllerBase.getLoginUser(request);

if(null != user){

return true;

}

// 未登录,不允许

return false;

}

private void initRequestThreadLocal(HttpServletRequest request){

ControllerBase.setRequest(request);

request.setAttribute(“basePath”, ControllerBase.basePathLessSlash(request));

}

private void removeRequestThreadLocal(){

ControllerBase.setRequest(null);

}

@Override

public boolean preHandle(HttpServletRequest request,

HttpServletResponse response, Object handler) throws Exception {

initRequestThreadLocal(request);

// 如果不允许操作,则返回false即可

if (false == validURI(request)) {

// 此处抛出异常,允许进行异常统一处理

throw new NeedLoginException();

}

return true;

}

@Override

public void postHandle(HttpServletRequest request,

HttpServletResponse response, Object handler, ModelAndView modelAndView)

throws Exception {

removeRequestThreadLocal();

}

@Override

public void afterCompletion(HttpServletRequest request,

HttpServletResponse response, Object handler, Exception ex)

throws Exception {

removeRequestThreadLocal();

}

}

在 postHandle 和 afterCompletion 方法中, 清理 ThreadLocal 中的 request 对象。

但在实际使用过程中, 业务开发人员将一个很大的对象(如占用内存200MB左右的List)设置为 request 的 Attributes, 传递到 JSP 中。

JSP代码中可能发生了异常, 则SpringMVC的postHandle 和 afterCompletion 方法不会被执行。

Tomcat 中的线程调度, 可能会一直调度不到那个抛出了异常的线程, 于是 ThreadLocal 一直 hold 住 request。 随着运行时间的推移,把可用内存占满, 一直在执行 Full GC, 系统直接卡死。

后续的修正: 通过 Filter, 在 finally 语句块中清理 ThreadLocal。

@WebFilter(value=”/*”, asyncSupported=true)

public class ClearRequestCacheFilter implements Filter{

@Override

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,

ServletException {

clearControllerBaseThreadLocal();

try {

chain.doFilter(request, response);

} finally {

clearControllerBaseThreadLocal();

}

}

private void clearControllerBaseThreadLocal() {

ControllerBase.setRequest(null);

}

@Override

public void init(FilterConfig filterConfig) throws ServletException {}

@Override

public void destroy() {}

}

教训是:可以使用 ThreadLocal, 但必须有受控制的释放措施、一般就是 try-finally 的代码形式。

说明: SpringMVC 的 Controller 中, 其实可以通过 @Autowired 注入 request, 实际注入的是一个 HttpServletRequestWrapper 对象, 执行时也是通过 ThreadLocal 机制调用当前的 request。

常规方式: 直接在controller方法中接收 request 参数即可。

解决方案

如果设置的最大内存不满足程序的正常运行, 只需要增大堆内存即可, 配置参数可以参考下文。

但很多情况下, 增加堆内存空间并不能解决问题。比如存在内存泄漏, 增加堆内存只会推迟 java.lang.OutOfMemoryError: Java heap space 错误的触发时间。

当然, 增大堆内存, 可能会增加 GC pauses 的时间, 从而影响程序的 吞吐量或延迟。

要从根本上解决问题, 则需要排查分配内存的代码. 简单来说, 需要解决这些问题:

哪类对象占用了最多内存?

这些对象是在哪部分代码中分配的。

要搞清这一点, 可能需要好几天时间。下面是大致的流程:

获得在生产服务器上执行堆转储(heap dump)的权限。“转储”(Dump)是堆内存的快照, 稍后可以用于内存分析. 这些快照中可能含有机密信息, 例如密码、信用卡账号等, 所以有时候, 由于企业的安全限制, 要获得生产环境的堆转储并不容易。

在适当的时间执行堆转储。一般来说,内存分析需要比对多个堆转储文件, 假如获取的时机不对, 那就可能是一个“废”的快照. 另外, 每次执行堆转储, 都会对JVM进行“冻结”, 所以生产环境中,也不能执行太多的Dump操作,否则系统缓慢或者卡死,你的麻烦就大了。

用另一台机器来加载Dump文件。一般来说, 如果出问题的JVM内存是8GB, 那么分析 Heap Dump 的机器内存需要大于 8GB. 打开转储分析软件(我们推荐Eclipse MAT , 当然你也可以使用其他工具)。

检测快照中占用内存最大的 GC roots。详情请参考: Solving OutOfMemoryError (part 6) – Dump is not a waste。 这对新手来说可能有点困难, 但这也会加深你对堆内存结构以及navigation机制的理解。

接下来, 找出可能会分配大量对象的代码. 如果对整个系统非常熟悉, 可能很快就能定位了。

打个广告, 我们推荐 Plumbr, the only Java monitoring solution with automatic root cause detection。 Plumbr 能捕获所有的 java.lang.OutOfMemoryError , 并找出其他的性能问题, 例如最消耗内存的数据结构等等。

Plumbr 在后台负责收集数据 —— 包括堆内存使用情况(只统计对象分布图, 不涉及实际数据),以及在堆转储中不容易发现的各种问题。 如果发生 java.lang.OutOfMemoryError , 还能在不停机的情况下, 做必要的数据处理. 下面是Plumbr 对一个 java.lang.OutOfMemoryError 的提醒:

4454216b4022

java内存溢出之Java heap space(1/8)

强大吧, 不需要其他工具和分析, 就能直接看到:

哪类对象占用了最多的内存(此处是 271 个 com.example.map.impl.PartitionContainer 实例, 消耗了 173MB 内存, 而堆内存只有 248MB)

这些对象在何处创建(大部分是在 MetricManagerImpl 类中,第304行处)

当前是谁在引用这些对象(从 GC root 开始的完整引用链)

得知这些信息, 就可以定位到问题的根源, 例如是当地精简数据结构/模型, 只占用必要的内存即可。

当然, 根据内存分析的结果, 以及Plumbr生成的报告, 如果发现对象占用的内存很合理, 也不需要修改源代码的话, 那就增大堆内存吧。在这种情况下,修改JVM启动参数, (按比例)增加下面的值:

-Xmx1024m

这里配置Java堆内存最大为 1024MB。可以使用 g/G 表示 GB, m/M 代表 MB, k/K 表示 KB.

下面的这些形式都是等价的, 设置Java堆的最大空间为 1GB:

# 等价形式: 最大1GB内存

java -Xmx1073741824 com.mycompany.MyClass

java -Xmx1048576k com.mycompany.MyClass

java -Xmx1024m com.mycompany.MyClass

java -Xmx1g com.mycompany.MyClass

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/156205.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • React项目的国际化

     最近做的react项目需要支持国际化,网上查了一下,发现一款很好的插件“react-intl-universal”,由阿里巴巴团队开发,这款插件是原先的国际化插件“react-intl”的升级版,“react-intl”因为一些“致命”缺陷现已被其取代,npm官网有罗列原因,有兴趣的可以去了解一下。下面具体介绍一下这款插件的使用方法。1.安装npminstallreact-i…

  • 数据库自动化运维平台–自助DML

    数据库自动化运维平台–自助DML今天介绍下最近开发的一个平台,自助DML。什么是DML,就是平常执行的增删改查数据库操作。有人有疑问这不是程序访问的操作,为什么还要做一个平台操作这些呢,其实这种操作主要是开发需要线下修复数据的一种操作,不只是增删改,还有建表,建索引,添加字段等,这些操作开发一般会提给DBA协助操作数据库。可能你会觉得这些活能有多少,其实这种活真不少,我上家公司是电商互联网公司,大概有七八百个实例,每天的这种操作有近百个。处理近百个这种需求,基本上一个人一天就不用干别的了。虽说现在的公司实例少点,但每天的工作量还是很大,关

  • PAT乙级_1007

    PAT乙级_1007问题描述:让我们定义dn为:dn=pn+1-pn,其中pi是第i个素数。显然有d1=1且对于n&gt;1有dn是偶数。“素数对猜想”认为“存在无穷多对相邻且差为2的素数”。现给定任意正整数N(&lt;105),请计算不超过N的满足猜想的素数对的个数。输入格式:每个测试输入包含1个测试用例,给出正整数N。输出格式:每个测试用例的输出占一行,不超过N的满足猜想的素数对的个数…

  • img图片加载失败默认图片「建议收藏」

    img图片加载失败默认图片「建议收藏」<img:src=”item.goods_pic”onerror=”javascript:this.src=’../static/images/default.png’;”>转载于:https://www.cnblogs.com/techliang666/p/8876842.html

  • mongodb服务启动失败_mongodb启动不了

    mongodb服务启动失败_mongodb启动不了在操作前需要启动mongodb数据库服务1.首先打开dos窗口,然后选择路径到你的安装路径下的bin目录(我的路径是的D:mongo\mongodb\bin)2.然后输入启动命令(D:mongo\data\db是我的数据库文件的目录前边两个–不能少)mongod–dbpathD:mongo\data\db3.回车dos界面出现12701的字样说明服务启动成功了如…

  • 一文解释清卷积神经网络中池化层的作用「建议收藏」

    池化层:池化层夹在连续的卷积层中间,用于压缩数据和参数的量,减小过拟合。简而言之,如果输入是图像的话,那么池化层的最主要作用就是压缩图像。池化层分为3类,平均池化,最大池化和随机池化。拿最大池化举个例子:上图的例子是按步幅2进行2X2的最大池化时的处理顺序。最大池化是获得最大值的运算,“2X2”表示目标区域的大小。如上图所示,从2X2的窗口的移动间隔为2个元素。另外,一般来说,池化的窗口大小会和步幅设定成相同的值。比如3X3的窗口的步幅会设为3,4X4的窗口的步幅会设为4等。而最大池化的优点是:

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号