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

CSDN 2024-08-13 12:05:02 阅读 99

JVM内存划分

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

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

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

堆区:堆区是JVM内存中最大的一块区域,用于存储通过<code>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" />在请求处理期间(运行时)包含目标页面每次请求都会重新编译被包含的页面,确保获取最新内容适用于需要实时更新的内容,如天气信息或动态新闻

示例应用:

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



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。