会话对象是否占用我的应用程序中的资源?垃圾收集器不会删除它们吗?
Do session objects take resources in my application? Won't garbage collector remove them?
我在 Head First JSP 和 Servlets page:241 中看到了一部分,它说我们必须摆脱 sessions 如图所示:
稍后,他们提出了方法 invalidate()
和 setMaxInactiveInterval()
,用于减少我们服务器中陈旧的 sessions 数量。看完之后有点懵
首先,我使用 HttpSession s = request.getSession()
在 Servlet 代码中获取 session 对象,然后进行一些操作。知道 一个请求 将为该 Servlet 创建 一个线程,这意味着变量 s
将具有 scope 仅适用于给定的线程。一旦线程完成,变量 s
将不存在。这进一步意味着 session 对象在 heap 中不会有来自 s
= 的活动 reference 已收集垃圾。
所以如果没有新的请求,不应该有任何 session 对象占用我的资源,对吧?那么为什么这本书告诉我必须摆脱它们呢?垃圾收集器不应该单独完成它的工作吗?
有人可以指正我写错的地方吗? session 对象真的存储在堆中吗?因为我想不出他们可能在的任何其他地方。
这个 question 及其接受的答案很好地说明了 HTTP 会话机制的目的。
如果您想在请求之间保持状态,那么该状态需要存储在某个地方。我相信大多数应用程序服务器都会默认使用堆。你可以认为它是以用户会话 ID 为键的 Map,以包含存储数据的对象作为其值。
除非您采取措施从中删除对象,否则此地图将永远增长,方法是在用户注销时使会话对象无效,或者设置某种不活动限制,让应用程序服务器自己在之后清理旧条目一段时间。
这里要解开的东西不多,让我们一一解开。
会话和 cookie
HTTP 是一种无状态协议。这意味着,对于服务器而言,每个 HTTP 请求都被视为独立于其他 HTTP 请求。因此,如果您向同一个服务器发出多个请求,服务器实际上并不关心它们是否来自同一个客户端:接收到一个请求并生成一个响应,接收到另一个请求并生成另一个响应,依此类推上。
但是在某些情况下,您需要将来自同一用户的一堆请求识别为与服务器进行更长时间的交互,而不仅仅是孤立的请求。这就是会话和 cookie 发挥作用的地方。
会话识别同一用户与服务器的多次交互,并允许您维护用户身份和一些有用的数据,这些数据的生命周期可以跨越所有请求。这意味着会话是 stateful as opposed to stateless.
会话基本上是服务器保存在内存中的一个对象,它充当您要在请求之间保存的任何数据的容器。该对象也可以保存在磁盘或数据库中(例如,当您重新启动服务器并且不想丢失活动会话时),但为了简单起见,只需将其视为内存中的对象。是的,它存储在 HEAP 中。
因此,如果您的应用程序需要在请求之间存储状态,您可以在服务器上创建一个会话对象。但是,如何从不属于该会话的其他请求中识别属于该会话的请求呢?答案是饼干。
当用户发出他们的第一个请求时,服务器可以创建一个会话并返回一个 SESSION_ID 并添加到响应中。当用户随后发出另一个请求时,SESSION_ID 被发送回服务器,现在该请求被识别为更大交互的一部分,会话的一部分。哪个会话?用 SESSION_ID 标识。因此,会话是存储在服务器上的对象,属于该会话交互的任何请求都需要用 SESSION_ID.
标识
垃圾收集会话
由于会话对象是 Java 存在于 HEAP 上的对象,因此可以对其进行垃圾回收。然而,事情并没有那么简单。
例如比较下面的代码片段。这个:
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// ...
Object s = new Object();
// ...
}
有了这个:
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// ...
HttpSession s = request.getSession();
// ...
}
在第一个示例中,您创建了一个存储在 HEAP 上的对象。一旦 doGet
方法结束,此对象就有资格进行垃圾回收,因为除了 s
之外不再有对该对象的引用,当方法 returns.[=22 时超出范围=]
这里的关键部分是“不再引用”。当无法再从 JVM 中存在的任何实时引用访问对象时,该对象就有资格进行垃圾回收。当 doGet
方法结束时,s
消失了,所以没有任何东西指向您创建的对象。 HttpSession
情况不同。
在第二段代码中,你没有创建会话对象,你要求服务器“给你”一个会话对象。想象一个由服务器保留的 Map,它包含会话对象作为值,SESSION_IDs 是访问它们的键。当您要求服务器为您提供 HttpSession s = request.getSession()
的会话时,它所做的是查看请求中的 SESSION_ID cookie 以查找与该请求关联的会话,并为您提供对会话对象的引用.现在您有两个对会话对象的引用,一个由服务器保存在该会话映射中,另一个存储在 s
中。当 doGet
方法结束时,s
引用消失了,但服务器仍然持有对会话对象的引用(因为它需要它来处理可能作为更大交互的一部分到达的进一步请求)。在这种情况下,会话对象不符合垃圾回收条件,因为它可以通过 JVM 中的实时引用访问,即服务器持有的引用。
因此,如果您不删除会话,服务器将无法知道该会话是有用还是无用,因为它不知道稍后是否会发出另一个请求来请求它或不。所以会话对象永远留在服务器中。由于服务器能够 运行 数月或数年而无需重新启动或关闭,会话对象可以累积并消耗所有内存。垃圾收集器不会删除它们,因为服务器持有对它们的引用。您最终会遇到 OutOfMemory 错误并且服务器崩溃。
会话超时
当然,您不希望服务器崩溃。所以你需要使会话无效并告诉服务器“嘿,我不再需要那个会话对象了。你可以摆脱它”。在那种情况下,服务器将它从其 Map 中删除,并且没有任何实时引用,它现在可以被垃圾收集。
但是由于所有这些交互都是通过 HTTP 网络进行的,就像书中提到的示例一样,浏览器可能会崩溃,用户的计算机可能会崩溃,用户可以直接离开。所以你可能没有机会使会话无效并告诉服务器可以处理它,所以它会永远留在那里。
这就是会话超时的用武之地。当你构建你的应用程序时,你还配置了一个会话超时来告诉服务器“嘿,如果这个会话有 X 分钟不活动,你可以摆脱它”。所以现在,如果客户端只是在没有使会话无效的情况下离开,服务器可以有一个故障安全机制来摆脱过期的会话,这样它们就不会永远留在内存中。
我在 Head First JSP 和 Servlets page:241 中看到了一部分,它说我们必须摆脱 sessions 如图所示:
稍后,他们提出了方法 invalidate()
和 setMaxInactiveInterval()
,用于减少我们服务器中陈旧的 sessions 数量。看完之后有点懵
首先,我使用 HttpSession s = request.getSession()
在 Servlet 代码中获取 session 对象,然后进行一些操作。知道 一个请求 将为该 Servlet 创建 一个线程,这意味着变量 s
将具有 scope 仅适用于给定的线程。一旦线程完成,变量 s
将不存在。这进一步意味着 session 对象在 heap 中不会有来自 s
= 的活动 reference 已收集垃圾。
所以如果没有新的请求,不应该有任何 session 对象占用我的资源,对吧?那么为什么这本书告诉我必须摆脱它们呢?垃圾收集器不应该单独完成它的工作吗?
有人可以指正我写错的地方吗? session 对象真的存储在堆中吗?因为我想不出他们可能在的任何其他地方。
这个 question 及其接受的答案很好地说明了 HTTP 会话机制的目的。
如果您想在请求之间保持状态,那么该状态需要存储在某个地方。我相信大多数应用程序服务器都会默认使用堆。你可以认为它是以用户会话 ID 为键的 Map,以包含存储数据的对象作为其值。
除非您采取措施从中删除对象,否则此地图将永远增长,方法是在用户注销时使会话对象无效,或者设置某种不活动限制,让应用程序服务器自己在之后清理旧条目一段时间。
这里要解开的东西不多,让我们一一解开。
会话和 cookie
HTTP 是一种无状态协议。这意味着,对于服务器而言,每个 HTTP 请求都被视为独立于其他 HTTP 请求。因此,如果您向同一个服务器发出多个请求,服务器实际上并不关心它们是否来自同一个客户端:接收到一个请求并生成一个响应,接收到另一个请求并生成另一个响应,依此类推上。
但是在某些情况下,您需要将来自同一用户的一堆请求识别为与服务器进行更长时间的交互,而不仅仅是孤立的请求。这就是会话和 cookie 发挥作用的地方。
会话识别同一用户与服务器的多次交互,并允许您维护用户身份和一些有用的数据,这些数据的生命周期可以跨越所有请求。这意味着会话是 stateful as opposed to stateless.
会话基本上是服务器保存在内存中的一个对象,它充当您要在请求之间保存的任何数据的容器。该对象也可以保存在磁盘或数据库中(例如,当您重新启动服务器并且不想丢失活动会话时),但为了简单起见,只需将其视为内存中的对象。是的,它存储在 HEAP 中。
因此,如果您的应用程序需要在请求之间存储状态,您可以在服务器上创建一个会话对象。但是,如何从不属于该会话的其他请求中识别属于该会话的请求呢?答案是饼干。
当用户发出他们的第一个请求时,服务器可以创建一个会话并返回一个 SESSION_ID 并添加到响应中。当用户随后发出另一个请求时,SESSION_ID 被发送回服务器,现在该请求被识别为更大交互的一部分,会话的一部分。哪个会话?用 SESSION_ID 标识。因此,会话是存储在服务器上的对象,属于该会话交互的任何请求都需要用 SESSION_ID.
标识垃圾收集会话
由于会话对象是 Java 存在于 HEAP 上的对象,因此可以对其进行垃圾回收。然而,事情并没有那么简单。
例如比较下面的代码片段。这个:
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// ...
Object s = new Object();
// ...
}
有了这个:
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// ...
HttpSession s = request.getSession();
// ...
}
在第一个示例中,您创建了一个存储在 HEAP 上的对象。一旦 doGet
方法结束,此对象就有资格进行垃圾回收,因为除了 s
之外不再有对该对象的引用,当方法 returns.[=22 时超出范围=]
这里的关键部分是“不再引用”。当无法再从 JVM 中存在的任何实时引用访问对象时,该对象就有资格进行垃圾回收。当 doGet
方法结束时,s
消失了,所以没有任何东西指向您创建的对象。 HttpSession
情况不同。
在第二段代码中,你没有创建会话对象,你要求服务器“给你”一个会话对象。想象一个由服务器保留的 Map,它包含会话对象作为值,SESSION_IDs 是访问它们的键。当您要求服务器为您提供 HttpSession s = request.getSession()
的会话时,它所做的是查看请求中的 SESSION_ID cookie 以查找与该请求关联的会话,并为您提供对会话对象的引用.现在您有两个对会话对象的引用,一个由服务器保存在该会话映射中,另一个存储在 s
中。当 doGet
方法结束时,s
引用消失了,但服务器仍然持有对会话对象的引用(因为它需要它来处理可能作为更大交互的一部分到达的进一步请求)。在这种情况下,会话对象不符合垃圾回收条件,因为它可以通过 JVM 中的实时引用访问,即服务器持有的引用。
因此,如果您不删除会话,服务器将无法知道该会话是有用还是无用,因为它不知道稍后是否会发出另一个请求来请求它或不。所以会话对象永远留在服务器中。由于服务器能够 运行 数月或数年而无需重新启动或关闭,会话对象可以累积并消耗所有内存。垃圾收集器不会删除它们,因为服务器持有对它们的引用。您最终会遇到 OutOfMemory 错误并且服务器崩溃。
会话超时
当然,您不希望服务器崩溃。所以你需要使会话无效并告诉服务器“嘿,我不再需要那个会话对象了。你可以摆脱它”。在那种情况下,服务器将它从其 Map 中删除,并且没有任何实时引用,它现在可以被垃圾收集。
但是由于所有这些交互都是通过 HTTP 网络进行的,就像书中提到的示例一样,浏览器可能会崩溃,用户的计算机可能会崩溃,用户可以直接离开。所以你可能没有机会使会话无效并告诉服务器可以处理它,所以它会永远留在那里。
这就是会话超时的用武之地。当你构建你的应用程序时,你还配置了一个会话超时来告诉服务器“嘿,如果这个会话有 X 分钟不活动,你可以摆脱它”。所以现在,如果客户端只是在没有使会话无效的情况下离开,服务器可以有一个故障安全机制来摆脱过期的会话,这样它们就不会永远留在内存中。