iis应用程序池内存上限_简述Java中的内存泄漏

摘要

Java 的核心优点之一就是有一个内置垃圾收集器(简称 GC),能够帮助实现内存管理的自动化。 GC 隐式地负责内存的分配和释放,因此能够处理大多数内存泄漏问题。

虽然 GC 可以有效地处理很大一部分内存,但它不能保证有一个万无一失的解决内存泄漏的办法。 GC 非常聪明,但并非完美无缺。 即使是一个有经验的开发人员,也会不知不觉地让自己开发的程序发生内存泄漏。

也有可能出现,应用程序生成大量多余对象的情况,从而耗尽宝贵的内存资源,有时导致整个应用程序失败。内存泄漏是 Java 中一个一直存在的问题。 在本教程中,我们将看到内存泄漏的潜在原因是什么,如何在运行时识别它们,以及如何在应用程序中处理它们

什么是内存泄漏

内存泄漏就是堆中存在不再使用的对象,但是垃圾收集器无法从内存中移出它们。内存泄漏是糟糕的,因为它会占用内存资源,并随着时间的推移降低系统性能。 如果不处理,应用程序最终将耗尽其资源,导致OOM。

堆内存中存在着两种不同类型的对象: 引用的和未引用的。 被引用的对象是那些在应用程序中仍然有活动引用的对象,而未被引用的对象没有任何其他的对象引用。垃圾收集器定期删除未引用的对象,但是它从不收集仍然被引用的对象。 这就是内存泄漏发生的原因:

7a15c32f207f8833a0f20373af6eab70.png

堆内存中的对象

内存泄漏的几种现象:

  • 当应用程序长时间连续运行时,性能会严重下降
  • 应用程序发生OOM
  • 应用程序自发的和奇怪的崩溃
  • 应用程序有时会用完连接(connection)对象

让我们仔细看看其中的一些场景以及如何处理它们。

Java 中内存泄漏的类型

在任何应用程序中,内存泄漏都有可能发生,并且发生的原因也是多种多样的。在这里我们就简述几种常见的内存泄漏的情况:

  • 通过 static 字段发生内存泄漏

第一个可能导致内存泄漏的场景是大量使用静态变量。在 Java 中,静态字段的生命周期通常与正在运行的应用程序的整个生命周期相匹配(除非 ClassLoader 符合垃圾收集的条件)。

让我们创建一个简单的 Java 程序来,在该程序中有一个静态 List:

public class StaticTest {    private static Logger Log = LoggerFactory.getLogger(StaticTest.class);    public static List list = new ArrayList<>();    public void populateList() {        for (int i = 0; i < 10000000; i++) {            list.add(Math.random());        }        Log.info("Debug Point 2");    }    public static void main(String[] args) {        Log.info("Debug Point 1");        new StaticTest().populateList();        Log.info("Debug Point 3");    }}

现在,如果我们在程序执行期间分析堆内存,那么我们将看到在Debug Point 1和2之间,堆内存正如预期的那样增加了。但是当我们将 populateList ()方法留在调试点3时,堆内存还没有被垃圾收集,正如我们在 VisualVM 响应中看到的:

97424ebb4a4766bc82ea243c5a690c6c.png

堆内存有static字段的状态

然而,在上面的程序中,如果我们只是删除关键字 static,那么它将会给内存使用带来剧烈的变化,这个 Visual VM 响应显示:

676bc9b369d9e6899b5ed1fb7c8e7085.png

堆内存没有static字段的状态

直到Debug Point 1与我们在静态情况下得到的结果几乎相同。 但是这一次,在我们离开 populateList ()方法之后,列表的所有内存都被垃圾收集了,因为我们没有对它的任何引用。

因此我们需要密切关注静态变量的使用。如果集合或大型对象被声明为静态的,那么它们将在应用程序的整个生命周期中保留在内存中,从而阻塞本来可以在其他地方使用的重要内存。

如何预防?

  1. 尽量减少使用static的变量
  2. 当使用单例时,延迟加载对象而不是饿汉式的加载对象
  • 通过未关闭的资源导致的内存泄漏

无论何时建立新连接或打开流,JVM 都会为这些资源分配内存。例如数据库连接、input stream和session对象。忘记关闭这些资源可能会阻塞内存,从而使它们无法被 GC 访问。无论哪种情况,未关闭的资源连接都会消耗内存,如果我们不处理它们,它们都会降低性能,甚至可能导致 OutOfMemoryError 错误。

如何预防?

  1. 永远使用finally 来关闭资源
  2. 在finally块中的关闭资源的代码不要发生任何异常
  3. 当使用jdk1.7以上的版本时,尽量使用try-with语句
  • 不适当的equals和hashcode导致的内存泄漏

在定义新类时,一个常见的疏忽是没有重写 equals()和 hashCode()方法。Hashset 和 HashMap 在许多操作中使用这些方法,如果没有正确地覆盖它们,那么它们可能成为潜在的内存泄漏问题的根源。

让我们以一个普通的 Person 类为例,把它作为 HashMap 中的一个键:

public class Person {    public String name;         public Person(String name) {        this.name = name;    }}

现在我们将重复的 Person 对象插入到使用此键的 Map 中。

// 注意此处我们没有实现Person的equals和hashcode方法// Map不能包含重复的键@Testpublic void testInsertMap() {    Map map = new HashMap<>();    for(int i=0; i<100; i++) {        map.put(new Person("jon"), 1);    }    Assert.assertFalse(map.size() == 1);}

在这里我们使用 Person 作为一个key。 因为 Map 不允许重复的键,所以此处插入大量重复的 Person 对象不会增加内存。但是由于我们没有定义正确的 equals ()方法,重复的对象增加了内存,这就是为什么我们在内存中看到不止一个对象的原因。 下面是 VisualVM 中的堆内存:

6d47b092b1abf58bd2d207882a3757a9.png

未实现equals和hashcode方法

当实现hashcode和equals方法时:

public class Person {    public String name;         public Person(String name) {        this.name = name;    }         @Override    public boolean equals(Object o) {        if (o == this) return true;        if (!(o instanceof Person)) {            return false;        }        Person person = (Person) o;        return person.name.equals(name);    }         @Override    public int hashCode() {        int result = 17;        result = 31 * result + name.hashCode();        return result;    }}

此时的堆内存使用情况如下:

781ff2b1a301e13d79019659482c4860.png

实现equals和hashCode方法

另一个例子是当我们使用Hibernate时,它使用equals和hashcode方法来分析对象并把它们保存在缓存中。如果这些方法没有被重写,那么内存泄漏的可能性相当高,因为 Hibernate 将无法比较对象,并且会将重复的对象填充到缓存中。

如何预防?

  1. 定义新类时,一直重写equals和hashCode方法
  2. 仅仅重写是不够的,还要以最佳的方式重写。有好多第三方工具类,这里就不赘述。
  • 引用外部类的内部类导致的内存泄漏

对于非静态的匿名内部类,如果我们想要实例化,必须先实例化包含它的类。因此,当我们即使不使用外部类,它也不会被垃圾回收器回收。

如何预防?

  1. 如果内部类不需要使用外部类的任何属性,考虑将它变成静态内部类。
  • 错误的使用finalize()方法导致的内存泄漏

使用finalize()是潜在的内存泄漏问题的另一个来源。 每当重写一个类的 finalize ()方法时,那么该类的对象就不会立即被垃圾收集。 相反,GC 将对它们进行排队,以便在稍后的时间点进行收集。

此外,如果 finalize ()方法中编写的代码不是最优的,并且如果finalize队列无法和 Java 垃圾收集器保持一致,那么迟早,我们的应用程序将遇到 OutOfMemoryError。

如何预防?

  1. 永远避免重写finalize方法
  • 错误的使用字符串的intern方法导致的内存泄漏

当 Java String 池从 PermGen(永久代) 转移到 HeapSpace(堆内存) 时,它在 java7中经历了一个重大的变化。 但是对于在版本6及以下操作的应用程序,我们在处理大字符串时应该注意。

如果我们读取一个巨大的大型 String 对象,并在该对象上调用 intern () ,那么它将进入位于 PermGen (永久内存)中的字符串池,并在应用程序运行期间一直停留在那里。 这会阻塞内存,并在应用程序中造成严重的内存泄漏。

如何预防?

  1. 解决这个问题的最简单方法是升级到最新的 Java 版本,因为 String 池从 jdk 1.7开始移动到 堆内存中
  2. JDK1.7之前, 如何有大的字符串存在,试着增加永久代的大小
  • 使用ThreadLocal导致的内存泄漏

ThreadLocal能保证我们的线程安全。只要线程是alive的,使用ThreadLocal时,每个线程都会保存一个对 ThreadLocal 变量副本的隐式引用,并维护自己的副本,而不是在多个线程之间共享资源。

尽管有其优点,但是使用 ThreadLocal 变量是有争议的,因为如果使用不当,它们会引入内存泄漏,这是臭名昭著的。 Joshua Bloch 曾经评论过 thread local 的用法:

“Sloppy use of thread pools in combination with sloppy use of thread locals can cause unintended object retention, as has been noted in many places. But placing the blame on thread locals is unwarranted.”

一旦持有线程不再活动,ThreadLocals 就应该被垃圾回收。但是由于应用服务器中的线程池需要被重用,因此它们不会被垃圾收集,而是被重用来服务另一个请求。

现在,如果任何类创建了一个 ThreadLocal 变量,但是没有显式地删除它,那么即使在 web 应用程序停止之后,这个对象的副本仍然会保留在 Thread 中,从而防止对象被垃圾收集。

如何预防?

  1. 当不再使用ThreadLocal时,显示的调用remove方法进行删除
  2. 不要使用ThreadLocal.set(null) 来清除值
  3. 把ThreadLocal当作一个资源一样, 放在finally块中,进行remove

处理内存泄漏的其他策略

虽然在处理内存泄漏时没有一个普适的解决方案,但是有一些方法可以最小化这些泄漏。

  1. 启用Profiling

Java 分析器是通过应用程序监视和诊断内存泄漏的工具。它们分析应用程序内部发生的事情ーー例如,内存是如何分配的.使用分析器,我们可以比较不同的方法,并找到可以最佳利用资源的区域。常见的工具如Mission Control, JProfiler, YourKit, Java VisualVM, 和 the Netbeans Profiler.

  1. 通过verbose Garbage Collection
-verbose:gc

通过添加这个参数,我们可以看到 GC 内部发生的细节:

c3e02c00b9b97894c5154881aaa21734.png

启用verbose之后

总结

通俗地说,我们可以认为内存泄漏是一种通过阻塞重要内存资源降低应用程序性能的问题。 与所有其他问题一样,如果不能解决,随着时间的推移,它可能导致致命的应用程序崩溃。

解决内存泄漏是一个棘手的问题,找到它们需要掌握复杂的 Java 语言和命令。 在处理内存泄漏时,没有普适的解决方案,因为泄漏可能通过各种各样的事件发生。

但是,如果我们采用最佳实践,并经常执行严格的代码遍历和分析,那么我们就可以最小化应用程序中内存泄漏的风险。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
IIS应用程序回收是指IIS(Internet Information Services)在一定条件下自动终止和重新启动应用程序的过程。 应用程序IIS一个独立的进程,在运行网站时负责处理HTTP请求,并将静态或动态内容返回给客户端。由于长时间运行或资源占用过多,应用程序可能出现问题,导致网站响应缓慢甚至崩溃。 为了确保网站的性能和稳定性,IIS提供了应用程序回收功能。当满足以下条件之一时,IIS自动回收应用程序: 1. 配置更改:修改了应用程序的相关设置,例如CPU占用限制、内存限制等。 2. 定时回收:设定了应用程序的闲置时间或过期时间,超过指定时间没有请求访问应用程序时,IIS自动回收它。 3. 内存限制:当应用程序使用的物理内存超过了设定的限制时,IIS回收应用程序。 4. CPU限制:当应用程序的CPU使用率超过限制值时,IIS回收应用程序。 5. 请求失败:当应用程序连续多次失败或崩溃时,IIS回收它。 在回收过程IIS终止应用程序的运行的进程,并且清除内存的所有对象。然后,IIS重新启动应用程序,以确保网站继续正常运行。 应用程序回收是IIS一个关键的功能,它可以提高网站的性能和稳定性。但是,频繁的回收可能导致一定的延迟,因此需要根据实际情况进行合理的配置和调整。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值