当前位置:   article > 正文

Java全栈解密:从JVM内存管理到Spring框架,揭秘垃圾回收、类加载机制与Web开发精髓的全方位旅程_jvm spring

jvm spring

JVM内存划分

在JVM中,每个线程有自己的虚拟机栈,而整个JVM实例共享一些内存区域。JVM的内存划分主要包括四个部分:程序计数器、虚拟机栈、堆区和方法区(元数据区)。

  1. 程序计数器:程序计数器用于存储当前线程所执行的字节码指令的地址。在程序执行过程中,程序计数器会随着指令的执行而递增,如果遇到条件分支、循环等控制结构时,程序计数器的值也会发生变化。

  2. 虚拟机栈:每个线程都有自己的虚拟机栈,栈内存储的是栈帧。栈帧包含方法的局部变量表、操作数栈、动态链接、方法出口等信息。每当一个方法被调用时,JVM都会在虚拟机栈中创建一个新的栈帧。当方法执行结束时,栈帧会被弹出。

  3. 堆区:堆区是JVM内存中最大的一块区域,用于存储通过new关键字创建的对象和数组。所有线程共享堆区,是垃圾回收器管理的主要区域。

  4. 方法区(元数据区):方法区用于存储类的元数据、常量、静态变量和即时编译后的代码。虽然方法区是堆的一部分,但它有一个别名叫做“非堆”。

需要注意的是,程序计数器和虚拟机栈是线程私有的,而堆区和方法区是所有线程共享的。

  1. class Student {
  2. // 类的定义
  3. }
  4. public class People {
  5. public static void main(String[] args) {
  6. Student s = new Student();
  7. // s变量是一个局部变量,位于虚拟机栈中
  8. // s指向的Student对象位于堆区
  9. }
  10. }

在上述代码中,s变量是一个局部变量,位于虚拟机栈中。s指向的Student对象实际存储在堆区中。

类加载

Java代码需要解析为.class文件,并通过JVM读取.class文件创建类对象,将其加载到方法区。类加载可以细分为以下五个步骤:加载、验证、准备、解析和初始化。

  1. 加载:将.class文件中的二进制数据加载到JVM中,将这些数据映射为方法区中的类对象。这一步通常包括从文件系统、网络或其他源读取.class文件。

  2. 验证:验证.class文件的字节码是否符合JVM的规范,确保没有安全威胁或格式错误。这一步的主要目的是保护虚拟机免受恶意代码的攻击。

  3. 准备:为类的静态变量分配内存,并将其初始化为默认值(通常为零或null)。此时只分配内存,不进行任何赋值操作。

  4. 解析:将常量池中的符号引用转换为直接引用。符号引用是用来表示类、接口、字段和方法的符号表项,通过解析将其转换为具体的内存地址或偏移量。符号引用在类加载时并不会直接指向具体内存,解析步骤将其替换为可以直接访问的内存地址或偏移量。

  5. 初始化:对类的静态变量赋予初始值,并执行静态初始化块。如果类中定义了静态变量且有初始值,则在此阶段赋予变量这些初始值。随后,执行类的静态代码块(如果有),以完成类的初始化工作。

通过上述五个步骤,JVM能够确保.class文件中的字节码被正确加载并转换为可以执行的Java类,确保代码的安全性和正确性。

双亲委派模型

双亲委派模型是Java虚拟机(JVM)类加载机制中使用的一种模式。这种模型有助于保证核心类的安全性,防止类的重复加载,并且实现分层管理。

  1. 保证核心类的安全性:类加载过程会优先由父类加载器进行。根类加载器(Bootstrap ClassLoader)会首先尝试加载Java的核心类库,这样可以确保这些核心类不会被其他恶意类加载器篡改。

  2. 防止类的重复加载:当一个类加载器尝试加载某个类时,它会首先检查缓存区是否已经加载过该类。如果缓存中不存在该类,类加载请求会逐级向上委派给父类加载器,直到到达根类加载器(Bootstrap ClassLoader)。如果根类加载器已经加载过该类,它将直接返回该类的引用。否则,类加载过程会返回一个ClassNotFoundException,通知子类加载器继续尝试加载。

  3. 分层管理:通过使用双亲委派模型,可以实现类加载的分层管理。自定义类和核心类等会被不同的类加载器加载,避免了相互干扰。核心类由根类加载器加载,而自定义类通常由应用程序类加载器或自定义类加载器加载。

具体过程如下:类加载请求首先会被委派给父类加载器,直至到达根类加载器(Bootstrap ClassLoader)。如果根类加载器能够找到对应的类,则进行加载并返回。如果根类加载器找不到该类,会抛出ClassNotFoundException,并将异常传递给子类加载器,子类加载器依次尝试加载该类,直到由自定义类加载器进行处理。

通过这种方式,双亲委派模型有效地确保了Java应用程序中类加载的安全性、可靠性和灵活性。

垃圾回收机制

JVM的垃圾回收机制(GC)是用来管理内存的分配和释放的。当GC发现不再被使用的对象(垃圾)时,会自动释放它们占用的内存空间。

对象扫描起点

JVM会从GC Roots(例如静态变量、栈中的引用、本地方法栈中的引用等)开始,对所有对象进行扫描,以确定哪些对象是可达的(仍被使用的),哪些对象是不可达的(垃圾)。

JVM堆的划分

JVM的堆分为以下几部分:

  1. 新生代(Young Generation)

    • 伊甸区(Eden Space):存放新创建的对象。
    • 存活区(Survivor Spaces):用来存放从Eden区复制和存活下来的对象,分为两个部分——From Survivor和To Survivor。存活的对象被复制到To Survivor,然后清空From Survivor。
  2. 老年代(Old Generation):存放从新生代中筛选出来的、多次GC后仍未被清除的长生命周期对象。

  3. 元空间(Metaspace,JDK 8及以后):存放类的元数据,如类定义、方法、静态数据等信息。JDK 8之前称为永久代(Permanent Generation)。

清除方式

  1. 标记-清除(Mark-Sweep)

    • 标记阶段:从GC Roots开始,标记所有可达对象。
    • 清除阶段:清除所有未标记的对象,释放它们的内存。
    • 缺点:容易产生内存碎片,导致内存难以管理和分配。
  2. 复制算法(Copying)

    • 过程:将内存划分为两个等大小的区域。首先将所有对象分配到其中一个区域,当该区域内存用尽时,对该区域进行扫描,将所有存活的对象复制到另一个区域,然后清空第一个区域。
    • 优点:消除了内存碎片。
    • 缺点:需要两倍的内存空间,浪费了一部分内存。
  3. 标记-整理(Mark-Compact)

    • 标记阶段:从GC Roots开始,标记所有可达对象。
    • 整理阶段:将所有存活的对象移动到内存的一端,压缩内存空间,消除碎片。
    • 优点:解决了内存碎片问题,内存利用率更高。

通过这些方式,JVM的垃圾回收机制能够有效管理内存,自动释放不再使用的对象,确保内存资源的合理利用。不同的GC算法和策略有其适用场景和优缺点,开发者可以根据具体应用需求选择合适的GC策略。

this关键字

this关键字的含义

this关键字在Java中用于引用当前对象。它有几个不同的用法,具体取决于上下文:

  1. 在构造方法中:this表示正在创建的对象。
  2. 在成员方法中:this表示调用该方法的当前对象。

this关键字的作用

this关键字主要用于以下几个方面:

  1. 区分局部变量和成员变量: 在方法或构造方法中,局部变量和成员变量如果同名,可以使用this关键字来区分它们。

  2. 调用类的其他构造方法: 在一个构造方法中,可以通过this关键字调用同一类中的其他构造方法,以减少代码重复。

  3. 返回当前对象: this关键字还可以用来返回当前对象的引用,通常用于链式调用。

总之,this关键字在Java中是一个非常重要的概念,它的正确使用可以使代码更加清晰和易于维护。

匿名对象

匿名对象是指没有被任何变量引用的对象。这种对象在创建后立即使用,并在使用完毕后被垃圾回收。

匿名对象的作用

  1. 使用匿名对象调用方法: 匿名对象可以直接用于调用方法,而无需先声明一个变量来引用该对象。

  2. 使用匿名对象作为参数传入方法: 匿名对象可以直接作为参数传递给方法,这在需要临时对象时非常有用。

  3. 匿名对象作为方法的返回值: 方法可以返回一个匿名对象,在需要立即使用返回对象而不需要长期保存引用时非常方便。

匿名对象的使用使得代码更加简洁,特别是在仅需要临时对象的场景中。由于这些对象没有被任何变量引用,它们在使用后会很快被垃圾回收,从而提高内存利用效率。

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()方法
    • 在整个生命周期中只执行一次

生命周期流程

  1. 用户首次发送请求或服务器启动
  2. Servlet初始化
  3. 用户持续发送请求,Servlet处理请求(服务阶段)
  4. Servlet不再使用或服务器关闭时,Servlet被销毁

通过这个生命周期,Servlet能够高效地处理客户端请求,并合理管理资源。

forward()和redirect()

Forward和Redirect都用于在服务器处理后将请求或响应转发到另一个资源。

Forward方法

  • 在服务器内部进行,URL不变
  • 速度较快(服务器内部传输)
  • 请求和响应数据共享,修改会影响接收方

Redirect方法

  • 客户端接收含新URL的响应,发起新请求
  • 速度较慢(涉及客户端与服务器交互)
  • 不保留原请求数据

应用场景

  1. Forward示例: 未登录用户浏览商品 → 转发到登录页面 (保留浏览信息,便于登录后继续操作)
  2. Redirect示例: 浏览商品 → 重定向到订单确认页面 (URL变化,便于使用浏览器返回功能)

Request.getParameter() 与 Request.getAttribute() 的比较

Request.getParameter()

  • 用途:用于获取客户端发送的参数。
  • 场景:例如在登录界面,获取用户输入的账号和密码。
  • 特点:直接获取HTTP请求中的参数值。

Request.getAttribute()

  • 用途:用于获取请求范围内的数据。
  • 场景:在请求处理过程中,在不同阶段之间传递数据。
  • 特点
    • 需要先使用setAttribute()设置数据。
    • 然后可以在后续的处理阶段或JSP页面中使用getAttribute()获取数据。

主要区别

  1. 数据来源
    • getParameter(): 获取客户端发送的数据。
    • getAttribute(): 获取服务器端设置的数据。
  2. 使用目的
    • getParameter(): 用于接收客户端输入。
    • getAttribute(): 用于服务器端不同处理阶段之间的数据共享。
  3. 数据生命周期
    • getParameter(): 仅在当前请求中有效。
    • getAttribute(): 可以在整个请求处理过程中共享。

使用示例

假设有一个登录功能:

  1. 客户端发送登录请求,包含用户名和密码。
  2. 服务器端处理:
    1. // 获取客户端参数
    2. String username = request.getParameter("username");
    3. String password = request.getParameter("password");
    4. // 验证逻辑...
    5. // 设置属性以供后续使用
    6. request.setAttribute("user", authenticatedUser);

  3. 在后续的处理中(如转发到欢迎页面):
    1. // 获取之前设置的属性
    2. User user = (User) request.getAttribute("user");

这样,getAttribute()允许我们在整个请求处理过程中传递和共享数据,而不仅限于初始的客户端参数。

JSP的包含

JSP包含分为两种:静态包含(编译时包含)和动态包含(运行时包含)。它们在工作方式和执行时间上有所不同。

  1. 静态包含:
    • 使用JSP指令: <%@ include file="file.jsp" %>
    • 在编译时将被包含的页面合并到主页面中
    • 作为整体一起编译,运行时不会发生变化
    • 适用于内容相对固定的页面部分,如网站的头部和尾部
  2. 动态包含:
    • 使用JSP动作: <jsp:include page="file.jsp" />
    • 在请求处理期间(运行时)包含目标页面
    • 每次请求都会重新编译被包含的页面,确保获取最新内容
    • 适用于需要实时更新的内容,如天气信息或动态新闻

示例应用:

  • 网页布局: 使用静态包含处理固定的页面结构(如头部、导航栏、尾部)
  • 动态内容: 使用动态包含处理需要频繁更新的内容区域(如天气预报、实时新闻)

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/天景科技苑/article/detail/1016631
推荐阅读
相关标签
  

闽ICP备14008679号