首页 > 基础资料 博客日记
Java全栈解密:从JVM内存管理到Spring框架,揭秘垃圾回收、类加载机制与Web开发精髓的全方位旅程
2024-08-14 14:00:06基础资料围观130次
JVM内存划分
在JVM中,每个线程有自己的虚拟机栈,而整个JVM实例共享一些内存区域。JVM的内存划分主要包括四个部分:程序计数器、虚拟机栈、堆区和方法区(元数据区)。
-
程序计数器:程序计数器用于存储当前线程所执行的字节码指令的地址。在程序执行过程中,程序计数器会随着指令的执行而递增,如果遇到条件分支、循环等控制结构时,程序计数器的值也会发生变化。
-
虚拟机栈:每个线程都有自己的虚拟机栈,栈内存储的是栈帧。栈帧包含方法的局部变量表、操作数栈、动态链接、方法出口等信息。每当一个方法被调用时,JVM都会在虚拟机栈中创建一个新的栈帧。当方法执行结束时,栈帧会被弹出。
-
堆区:堆区是JVM内存中最大的一块区域,用于存储通过
new
关键字创建的对象和数组。所有线程共享堆区,是垃圾回收器管理的主要区域。 -
方法区(元数据区):方法区用于存储类的元数据、常量、静态变量和即时编译后的代码。虽然方法区是堆的一部分,但它有一个别名叫做“非堆”。
需要注意的是,程序计数器和虚拟机栈是线程私有的,而堆区和方法区是所有线程共享的。
class Student {
// 类的定义
}
public class People {
public static void main(String[] args) {
Student s = new Student();
// s变量是一个局部变量,位于虚拟机栈中
// s指向的Student对象位于堆区
}
}
在上述代码中,s
变量是一个局部变量,位于虚拟机栈中。s
指向的Student
对象实际存储在堆区中。
类加载
Java代码需要解析为.class文件,并通过JVM读取.class文件创建类对象,将其加载到方法区。类加载可以细分为以下五个步骤:加载、验证、准备、解析和初始化。
-
加载:将.class文件中的二进制数据加载到JVM中,将这些数据映射为方法区中的类对象。这一步通常包括从文件系统、网络或其他源读取.class文件。
-
验证:验证.class文件的字节码是否符合JVM的规范,确保没有安全威胁或格式错误。这一步的主要目的是保护虚拟机免受恶意代码的攻击。
-
准备:为类的静态变量分配内存,并将其初始化为默认值(通常为零或null)。此时只分配内存,不进行任何赋值操作。
-
解析:将常量池中的符号引用转换为直接引用。符号引用是用来表示类、接口、字段和方法的符号表项,通过解析将其转换为具体的内存地址或偏移量。符号引用在类加载时并不会直接指向具体内存,解析步骤将其替换为可以直接访问的内存地址或偏移量。
-
初始化:对类的静态变量赋予初始值,并执行静态初始化块。如果类中定义了静态变量且有初始值,则在此阶段赋予变量这些初始值。随后,执行类的静态代码块(如果有),以完成类的初始化工作。
通过上述五个步骤,JVM能够确保.class文件中的字节码被正确加载并转换为可以执行的Java类,确保代码的安全性和正确性。
双亲委派模型
双亲委派模型是Java虚拟机(JVM)类加载机制中使用的一种模式。这种模型有助于保证核心类的安全性,防止类的重复加载,并且实现分层管理。
-
保证核心类的安全性:类加载过程会优先由父类加载器进行。根类加载器(Bootstrap ClassLoader)会首先尝试加载Java的核心类库,这样可以确保这些核心类不会被其他恶意类加载器篡改。
-
防止类的重复加载:当一个类加载器尝试加载某个类时,它会首先检查缓存区是否已经加载过该类。如果缓存中不存在该类,类加载请求会逐级向上委派给父类加载器,直到到达根类加载器(Bootstrap ClassLoader)。如果根类加载器已经加载过该类,它将直接返回该类的引用。否则,类加载过程会返回一个ClassNotFoundException,通知子类加载器继续尝试加载。
-
分层管理:通过使用双亲委派模型,可以实现类加载的分层管理。自定义类和核心类等会被不同的类加载器加载,避免了相互干扰。核心类由根类加载器加载,而自定义类通常由应用程序类加载器或自定义类加载器加载。
具体过程如下:类加载请求首先会被委派给父类加载器,直至到达根类加载器(Bootstrap ClassLoader)。如果根类加载器能够找到对应的类,则进行加载并返回。如果根类加载器找不到该类,会抛出ClassNotFoundException,并将异常传递给子类加载器,子类加载器依次尝试加载该类,直到由自定义类加载器进行处理。
通过这种方式,双亲委派模型有效地确保了Java应用程序中类加载的安全性、可靠性和灵活性。
垃圾回收机制
JVM的垃圾回收机制(GC)是用来管理内存的分配和释放的。当GC发现不再被使用的对象(垃圾)时,会自动释放它们占用的内存空间。
对象扫描起点
JVM会从GC Roots(例如静态变量、栈中的引用、本地方法栈中的引用等)开始,对所有对象进行扫描,以确定哪些对象是可达的(仍被使用的),哪些对象是不可达的(垃圾)。
JVM堆的划分
JVM的堆分为以下几部分:
-
新生代(Young Generation):
- 伊甸区(Eden Space):存放新创建的对象。
- 存活区(Survivor Spaces):用来存放从Eden区复制和存活下来的对象,分为两个部分——From Survivor和To Survivor。存活的对象被复制到To Survivor,然后清空From Survivor。
-
老年代(Old Generation):存放从新生代中筛选出来的、多次GC后仍未被清除的长生命周期对象。
-
元空间(Metaspace,JDK 8及以后):存放类的元数据,如类定义、方法、静态数据等信息。JDK 8之前称为永久代(Permanent Generation)。
清除方式
-
标记-清除(Mark-Sweep):
- 标记阶段:从GC Roots开始,标记所有可达对象。
- 清除阶段:清除所有未标记的对象,释放它们的内存。
- 缺点:容易产生内存碎片,导致内存难以管理和分配。
-
复制算法(Copying):
- 过程:将内存划分为两个等大小的区域。首先将所有对象分配到其中一个区域,当该区域内存用尽时,对该区域进行扫描,将所有存活的对象复制到另一个区域,然后清空第一个区域。
- 优点:消除了内存碎片。
- 缺点:需要两倍的内存空间,浪费了一部分内存。
-
标记-整理(Mark-Compact):
- 标记阶段:从GC Roots开始,标记所有可达对象。
- 整理阶段:将所有存活的对象移动到内存的一端,压缩内存空间,消除碎片。
- 优点:解决了内存碎片问题,内存利用率更高。
通过这些方式,JVM的垃圾回收机制能够有效管理内存,自动释放不再使用的对象,确保内存资源的合理利用。不同的GC算法和策略有其适用场景和优缺点,开发者可以根据具体应用需求选择合适的GC策略。
this关键字
this关键字的含义
this关键字在Java中用于引用当前对象。它有几个不同的用法,具体取决于上下文:
- 在构造方法中:this表示正在创建的对象。
- 在成员方法中:this表示调用该方法的当前对象。
this关键字的作用
this关键字主要用于以下几个方面:
-
区分局部变量和成员变量: 在方法或构造方法中,局部变量和成员变量如果同名,可以使用this关键字来区分它们。
-
调用类的其他构造方法: 在一个构造方法中,可以通过this关键字调用同一类中的其他构造方法,以减少代码重复。
-
返回当前对象: this关键字还可以用来返回当前对象的引用,通常用于链式调用。
总之,this关键字在Java中是一个非常重要的概念,它的正确使用可以使代码更加清晰和易于维护。
匿名对象
匿名对象是指没有被任何变量引用的对象。这种对象在创建后立即使用,并在使用完毕后被垃圾回收。
匿名对象的作用
-
使用匿名对象调用方法: 匿名对象可以直接用于调用方法,而无需先声明一个变量来引用该对象。
-
使用匿名对象作为参数传入方法: 匿名对象可以直接作为参数传递给方法,这在需要临时对象时非常有用。
-
匿名对象作为方法的返回值: 方法可以返回一个匿名对象,在需要立即使用返回对象而不需要长期保存引用时非常方便。
匿名对象的使用使得代码更加简洁,特别是在仅需要临时对象的场景中。由于这些对象没有被任何变量引用,它们在使用后会很快被垃圾回收,从而提高内存利用效率。
Spring框架
Spring是面向企业级的Java开发框架,其主要特点是IOC(控制反转)、DI(依赖注入)和面向切面的编程。由于基于Java的特性,Spring具有广泛的部署平台。
- IOC(控制反转):将对象的创建和管理交给Spring框架,降低对象之间的耦合性。
- 事务处理:Spring框架能够将系统和事务进行分离,使得事务处理适合在多平台上进行。
- DI(依赖注入):通过依赖注入机制,Spring可以方便地读取和管理对象,以进行业务处理。
Spring 常见的注解:
-
@Component:用于标记一个类,使其能够被 Spring 容器管理。所有其他的 Spring 注解(例如 @Service、@Controller 等)都基于 @Component,因此它是这些注解的基础。
-
@Controller:用于标记一个类,使其能够被 Spring MVC 框架管理,通常用于定义控制器类。
-
@Service:用于标记一个类,表示该类是处理业务逻辑的服务组件。
-
@Repository:用于标记一个类,表示该类是数据访问组件,通常用于与数据库交互。
-
@PathVariable:用于从 URL 路径中动态获取变量值,用于 RESTful 风格的接口中。
-
@Bean:用于方法上,表示该方法的返回值应该注册为 Spring 容器中的一个 Bean,以便于依赖注入。
Spring 框架采用了 Inversion of Control(IoC)模式来实现对象的创建和管理,并通过 Dependency Injection(DI)来进行对象的依赖注入。利用 Aspect-Oriented Programming(AOP)技术,Spring 使得业务逻辑和系统服务(如事务管理、日志记录等)能够分离,从而简化了业务处理的实现。
Spring 集成了许多流行的框架,例如 MyBatis,提供了更强大的数据持久化功能和更灵活的 SQL 映射配置。
此外,Spring 还提供了便捷的测试方案,使得开发者能够更方便地编写单元测试和集成测试,提高了代码的可维护性和可靠性。
JavaWeb
Servlet生命周期
Servlet的生命周期主要包含三个阶段:初始化、服务和销毁。以下是对每个阶段的详细说明:
1. 初始化
- 触发时机:
- Servlet加载到内存时
- 可通过
web.xml
中的<load-on-startup>
元素配置加载方式:- 正数:预加载,数字越小优先级越高
- 负数、0或不配置:懒加载(首次请求时加载)
- 执行过程:
- 调用
init()
方法 - 在整个生命周期中只执行一次
- 调用
2. 服务
- 功能:处理客户端的各种请求
- 过程:
- 接收请求(如HTTP请求)
- 调用相应的处理方法(如
doGet()
、doPost()
等)
- 特点:每收到一个新请求就会执行一次
3. 销毁
- 触发时机:
- 手动调用销毁方法
- 服务器关闭
- 执行过程:
- 调用
destroy()
方法 - 在整个生命周期中只执行一次
- 调用
生命周期流程
- 用户首次发送请求或服务器启动
- Servlet初始化
- 用户持续发送请求,Servlet处理请求(服务阶段)
- Servlet不再使用或服务器关闭时,Servlet被销毁
通过这个生命周期,Servlet能够高效地处理客户端请求,并合理管理资源。
forward()和redirect()
Forward和Redirect都用于在服务器处理后将请求或响应转发到另一个资源。
Forward方法
- 在服务器内部进行,URL不变
- 速度较快(服务器内部传输)
- 请求和响应数据共享,修改会影响接收方
Redirect方法
- 客户端接收含新URL的响应,发起新请求
- 速度较慢(涉及客户端与服务器交互)
- 不保留原请求数据
应用场景
- Forward示例: 未登录用户浏览商品 → 转发到登录页面 (保留浏览信息,便于登录后继续操作)
- Redirect示例: 浏览商品 → 重定向到订单确认页面 (URL变化,便于使用浏览器返回功能)
Request.getParameter() 与 Request.getAttribute() 的比较
Request.getParameter()
- 用途:用于获取客户端发送的参数。
- 场景:例如在登录界面,获取用户输入的账号和密码。
- 特点:直接获取HTTP请求中的参数值。
Request.getAttribute()
- 用途:用于获取请求范围内的数据。
- 场景:在请求处理过程中,在不同阶段之间传递数据。
- 特点:
- 需要先使用
setAttribute()
设置数据。 - 然后可以在后续的处理阶段或JSP页面中使用
getAttribute()
获取数据。
- 需要先使用
主要区别
- 数据来源:
getParameter()
: 获取客户端发送的数据。getAttribute()
: 获取服务器端设置的数据。
- 使用目的:
getParameter()
: 用于接收客户端输入。getAttribute()
: 用于服务器端不同处理阶段之间的数据共享。
- 数据生命周期:
getParameter()
: 仅在当前请求中有效。getAttribute()
: 可以在整个请求处理过程中共享。
使用示例
假设有一个登录功能:
- 客户端发送登录请求,包含用户名和密码。
- 服务器端处理:
// 获取客户端参数 String username = request.getParameter("username"); String password = request.getParameter("password"); // 验证逻辑... // 设置属性以供后续使用 request.setAttribute("user", authenticatedUser);
- 在后续的处理中(如转发到欢迎页面):
// 获取之前设置的属性 User user = (User) request.getAttribute("user");
这样,getAttribute()
允许我们在整个请求处理过程中传递和共享数据,而不仅限于初始的客户端参数。
JSP的包含
JSP包含分为两种:静态包含(编译时包含)和动态包含(运行时包含)。它们在工作方式和执行时间上有所不同。
- 静态包含:
- 使用JSP指令: <%@ include file="file.jsp" %>
- 在编译时将被包含的页面合并到主页面中
- 作为整体一起编译,运行时不会发生变化
- 适用于内容相对固定的页面部分,如网站的头部和尾部
- 动态包含:
- 使用JSP动作: <jsp:include page="file.jsp" />
- 在请求处理期间(运行时)包含目标页面
- 每次请求都会重新编译被包含的页面,确保获取最新内容
- 适用于需要实时更新的内容,如天气信息或动态新闻
示例应用:
- 网页布局: 使用静态包含处理固定的页面结构(如头部、导航栏、尾部)
- 动态内容: 使用动态包含处理需要频繁更新的内容区域(如天气预报、实时新闻)
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:jacktools123@163.com进行投诉反馈,一经查实,立即删除!
标签: