解锁Java后台启动的奥秘

2024-12-18 09:12:33

Java 后台启动基本原理

图片7.jpg

Web 服务器与 Servlet 容器协作原理

在 Java 后台启动过程中,Web 服务器与 Servlet 容器的协作起着关键作用。当 Web 服务器接收到一个 HTTP 请求后,它会首先判断请求的内容是静态数据还是动态数据。对于静态页面数据,比如一些纯 HTML、CSS、JavaScript 文件等,Web 服务器可以自行处理,直接将对应的静态资源返回给客户端。而如果接收到的是动态数据请求,这时就需要 Servlet 容器介入了。Servlet 容器充当 Web 服务器和 Servlet 之间的 “中间人” 角色,Web 服务器会把被请求的 Servlet 的 URI 以及 request 对象转交给 Servlet 容器。随后,Servlet 容器负责调用相应的 Servlet 程序来处理该请求,并将 Servlet 处理后的请求结果再返回给 Web 服务器,最终由 Web 服务器传递给客户端。以常见的 Tomcat 作为 Servlet 容器举例来说,它会接收来自如 Apache 等 Web 服务器转发过来的请求。Servlet 容器与 Servlet 的交互主要是通过 request 和 response 对象来完成的,Servlet 容器负责创建这些对象并传递给 Servlet 程序,Servlet 程序则使用这些对象,并调用它们的方法来与 Servlet 容器进行通信。再说说 Servlet 的生命周期各阶段的具体情况,这也是协作过程中的重要部分。首先是加载和实例化阶段,容器负责加载和实例化一个 Servlet,这个过程可以发生在引擎启动的时候,也可能推迟到容器需要该 Servlet 为客户请求服务的时候。例如,当客户端第一次请求某个 Servlet 时,Servlet 容器将会根据 web.xml 配置文件实例化这个 Servlet 类,并将其贮存于内存中,后续再有新的客户端请求该 Servlet 时,一般就不会再重新实例化这个 Servlet 类了,而是多个线程使用这同一个实例。接着是初始化阶段,init () 方法用于初始化操作,该方法在 Servlet 的整个生命周期中只被调用一次,主要是读取永久的配置信息,以及执行其他仅仅需要执行一次的任务,像加载一些初始化参数等。然后是处理请求阶段,service () 方法由 Servlet 容器调用,以允许 Servlet 响应一个请求,Servlet 容器会传递 javax.servlet.ServletRequest 对象和 javax.servlet.ServletResponse 对象过来,ServletRequest 对象包含客户端 HTTP 请求信息,ServletrResponse 则封装 Servlet 响应。在 service () 方法内部,还会根据 HTTP 请求种类的不同,进一步调用像 doGet () 或 doPost () 等方法处理相应的请求。最后是移除实例阶段,当服务器决定删除已经加载的 Servlet 实例之前,会调用 Servlet 的 destroy () 方法,来释放占用的资源。而且,Servlet 容器通过采用多线程等机制,能够有效提高执行效率、降低服务器负担。比如针对同一个 Servlet,容器在第一次收到 HTTP 请求时建立一个 Servlet 实例,后续再收到 http 请求后,无需创建相同 Servlet,仅开启新的线程来处理请求,避免了为每个请求都创建新的实例对象,减少了资源消耗,提升了整体的运行效率。

Servlet 对象创建时机解析

Servlet 对象的创建时机分为两种情况,一是在 Servlet 容器启动时创建,二是在 Servlet 容器启动后创建。在 Servlet 容器启动时创建 Servlet 对象,这种方式适合那些需要在应用加载时就做一些初始化操作的场景,例如初始化操作比较耗时,像要访问数据库来加载一些数据用于后续处理的情况。我们可以通过在配置文件 web.xml 中进行设置来实现容器启动时创建 Servlet,使用<load-on-startup>标签,里面设置正整数就代表服务器加载时创建,并且数字还有含义,代表启动的顺序,数字越小、优先级越高,不过要注意 1 已经被默认的 servlet 占用了,所以我们一般从 2 开始起设置。比如配置<load-on-startup>2</load-on-startup>,就可以让对应的 Servlet 在服务器启动时就被加载创建。而 Servlet 容器启动后创建 Servlet 对象,默认情况下就是当第一次访问 servlet(在浏览器中访问)的时候去创建。这种方式的优势在于可以减少对服务器内存的浪费,提高服务器启动的效率;但弊端就是如果有一些要在应用加载时就做的初始化操作,就没办法完成了。配置文件对 Servlet 对象创建时机的影响很大,通过在 web.xml 里不同的配置,可以灵活决定 Servlet 是在容器启动时就创建还是首次访问时创建。并且,同一类型的 Servlet 对象在容器中是以单例的形式存在的,也就是不管有多少个客户端请求这个 Servlet,容器中只会存在一个该类型的 Servlet 实例对象,多个线程会共用这一个实例来处理各自的请求,不过这也需要我们在编写 Servlet 代码时注意线程安全的问题,避免因多个线程同时访问同一资源而导致数据不一致等情况出现。例如,如果在 Servlet 方法中采用全局变量,并且以该变量的运算结果作为下一步操作的判断依据,就可能出现线程不安全的情况,像下面这种伪代码:所以要谨慎使用成员变量,或者采用一些线程安全的处理方式,比如使用synchronized关键字来同步对共享数据点的操作,保证一次只有一个线程可以访问被保护的区段,以此来避免出现类似的线程安全问题。

Java 后台启动常见方式

Linux 系统中的方式

在 Linux 系统里启动 java 项目有以下几种常见命令及对应的使用场景与特点:java -jar xxx.jar特点:当前 ssh 窗口被锁定,可按 CTRL + C 打断程序运行,或者直接关闭窗口时,程序就会退出。例如我们运行一个简单的 Java 项目,执行java -jar myproject.jar命令后,就只能在该窗口等待程序运行结束或者手动按 CTRL + C 打断它,如果关闭了这个终端窗口,那 Java 程序也就随之停止运行了。这种方式比较适合用于在开发过程中简单测试 Java 项目,并且需要随时查看程序输出信息以及手动控制程序停止的情况。java -jar xxx.jar &特定:当前 ssh 窗口不被锁定,意味着可以继续在该窗口执行其他命令操作,不过当窗口关闭时,程序会中止运行。比如在执行java -jar anotherproject.jar &后,我们还能在同一个终端窗口里输入像ls查看文件列表等其他 Linux 命令,但要是关闭了这个终端窗口,对应的 Java 程序就会停止。它适用于一些不需要长时间持续运行,在终端操作过程中临时启动 Java 项目查看效果等场景。nohup java -jar xxxx.jar &nohup意思是不挂断运行命令,当账户退出或终端关闭时,程序仍然运行。当用nohup命令执行作业时,缺省情况下该作业的所有输出被重定向到nohup.out的文件中,除非另外指定了输出文件。例如我们执行nohup java -jar longrunningproject.jar &,就算我们退出了当前登录的账户或者关闭了对应的终端窗口,这个 Java 项目依然会在后台持续运行,对于需要长时间稳定运行的 Java 后台服务程序,采用这种方式启动就很合适。nohup java -jar xxxx.jar >temp.txt &command >out.file是将command的输出重定向到out.file文件,即输出内容不打印到屏幕上,而是输出到out.file文件中。采用这种命令方式,不仅具备nohup命令使程序在终端关闭或账户退出后仍能运行的特点,还能将原本输出到控制台的内容按照我们的要求存放到指定的文件中,方便后续查看程序运行过程中的日志等输出信息。例如可以将程序运行过程中的报错信息、关键数据输出等保存下来用于分析排查问题。另外,在 Linux 系统中,还可以通过jobs命令查看后台运行任务,它会列出所有后台执行的作业,并且每个作业前面都有个编号。如果想将某个作业调回前台控制,只需要使用fg + 编号即可,比如fg 23就可以把编号为 23 的后台任务调回前台进行操作。

Windows 系统中的方式

在 Windows 系统上利用任务计划程序实现 Java 程序后台启动,可按照以下详细步骤进行操作:编写 Java 程序首先创建一个简单的 Java 程序示例,比如以下代码:将上述代码保存在HelloWorld.java文件中,这就是一个基础的 Java 程序代码,当然实际应用中要根据具体的业务逻辑编写更为复杂的代码内容。编译 Java 程序使用javac命令来编译 Java 程序,在命令提示符或者 PowerShell 等命令行工具中进入到保存HelloWorld.java文件的目录下,执行命令javac HelloWorld.java,如果编译成功,会生成一个名为HelloWorld.class的字节码文件,这是 Java 程序能够被运行的基础。打包成 JAR 文件在项目目录下,生成一个 JAR 文件,可以使用命令jar cvfm HelloWorld.jar Manifest.txt HelloWorld.class(这里假设已经有符合要求的Manifest.txt文件,它可以包含如Manifest-Version: 1.0、Main-Class: HelloWorld等关键信息用于指定 JAR 文件的一些属性以及启动类等内容)。这样就把 Java 程序打包成了一个可执行的 JAR 文件,方便后续统一进行部署和启动操作。配置任务计划程序打开 Windows 的任务计划程序,选择 “创建基本任务”,并设置任务名称和描述。在 “触发器” 中选择合适的触发条件,例如可以选择 “在系统启动时” 自动启动 Java 程序,或者按照特定的时间计划来触发运行。接着在 “操作” 中,选择 “启动程序”,然后找到java.exe的路径(一般在 Java 安装目录下的bin文件夹中,比如C:\Program Files\Java\jdk1.8.0_251\bin\java.exe),后面加上参数-jar [你的JAR文件路径](例如-jar C:\path\to\HelloWorld.jar)。完成以上配置后,保存这个任务设置,之后系统就会按照设定的条件来启动对应的 Java 程序,使其在后台运行了。通过这样一套流程,就可以实现在 Windows 系统上让 Java 程序在后台自动启动运行,满足如定时任务执行、后台服务持续运行等各种实际开发和应用的需求。

Java 后台启动注意事项

资源相关注意点

在后台运行 Java 程序时,有几个资源相关的方面需要我们着重留意。首先是程序日志输出,这是非常关键的一点。合理的日志输出能够帮助我们后续排查问题、了解程序运行状态等。但如果不注意规范,也会带来不少麻烦。一方面,要避免日志输出过多的情况,有些人可能为了方便问题排查,在短短 50 行代码里有一半都在打印日志,要知道打印日志是存在一定性能消耗的,比如一个原本 100ms 就能执行完的方法,可能其中 50ms 都花费在了打印日志上,而且还增加了日志的存储成本。像在循环体内大量打印日志这种批量打印的情况,就应该尽量避免另外,也要防止日志重复打印的问题,比如方法的调用方和被调用方都打印了相同的内容,或者可以合为一条打印的却分成多次打印等情况。同时,还需按照正确的日志级别进行输出,像 debug、info、warn、error 这些级别要区分清楚,error 级别只记录系统逻辑出错、异常或者重要的错误信息等,并且建议使用占位符的方式输出日志,这样比使用 String 拼接要高效,还要尽量使用英文输出日志,能有效减少日志存储大小,也更符合国际化标准。在日志的保存方面,阿里开发手册建议日志文件至少保留 15 天,对于涉及敏感信息、安全等相关记录的,根据国家法律要求至少保留六个月,所以要合理规划日志的存储和清理策略,防止磁盘空间被日志占满影响系统稳定性,比如可以采用滚动日志并且定时清除旧文件的方式。其次是错误信息处理,当程序出现错误时,要确保错误信息能够准确地被记录下来,方便我们快速定位问题根源,知晓是程序逻辑出错、存在隐含风险还是其他方面的原因,进而采取相应的解决措施。再者就是要及时释放资源,避免内存泄漏的情况发生。内存泄漏是指分配的内存无法正确释放,可能导致性能下降和程序崩溃。比如在使用像文件输入流(FileInputStream)等资源时,如果没有在使用完后及时关闭它那么在程序执行后,文件输入流仍然保持打开状态,占用内存资源,从而导致内存泄漏。所以我们要使用 finally 块或 try-with-resources 语句来确保在使用后关闭资源。还有像避免长生命周期的对象持有短生命周期对象的引用,若一个长生命周期的对象持有了一个短生命周期对象的引用,即使短生命周期对象已经不再需要,由于长生命周期对象的存在,垃圾回收器也无法回收短生命周期对象的内存。另外,要慎重使用静态变量,因为静态变量的生命周期与应用程序一样长,使用不当可能意外地阻止对象的回收,也要小心避免循环引用等情况,防止对象无法被正常垃圾回收。同时,我们还可以借助一些内存泄漏检测工具,如 MAT 和 VisualVM 等来分析堆内存中的对象,查找潜在的内存泄漏问题。

特殊报错及解决思路

在 Java 后台启动过程中,常常会遇到一些特殊报错,下面讲讲常见的问题及对应的解决办法或排查思路。

端口占用问题

当我们启动 Java 程序时,可能会遇到端口被占用的报错,比如出现类似 “Web server failed to start. Port 8080 was already in use.” 这样的提示,这意味着你尝试运行的 Java 应用所需的特定网络端口已经被其他进程占用了,从而导致 Java 程序无法正常启动。在 Linux 或 Mac OS 系统中,可以使用命令 “lsof -i : 端口号” 来检查某个特定端口的占用情况,例如查看 80 端口的使用情况,就执行 “lsof -i :80”。而在 Windows 系统中,则可以使用 “netstat -ano | findstr : 端口号” 这个命令,比如查看 80 端口的占用情况,就输入 “netstat -ano | findstr :80”,上述命令会列出占用该端口的进程 ID(PID)。找到占用端口的进程 ID 后,在 Linux 或 Mac OS 中,可以使用命令 “kill -9 进程 ID” 来杀掉该进程;在 Windows 系统中可以使用 “taskkill /F/PID 进程 ID” 命令,强制停止占用端口的进程,释放端口以供其他程序使用。另外,如果不想或无法结束占用端口的进程,还可以选择修改 Java 程序的端口,在代码中进行设置在上面的示例中,创建了一个新的 ServerSocket 对象并设置了一个新的端口(8080),如果这个端口已经被占用,程序将捕获异常并输出提示信息。还有一种情况,在某些情况下,系统防火墙可能会阻止 Java 应用程序访问特定端口,这时要确保在防火墙设置中允许你设置的端口(如 8080)进行数据传输。

服务启动失败报错

有时候会遇到服务启动失败的报错情况,比如在 Windows 系统中将.bat 文件注册成 windows 服务时,可能会出现 “服务启动失败:错误码 1053,服务没有及时响应启动或控制请求” 这样的问题,这是因为 windows 服务默认启动超时时间为 30s,但是有些服务的启动时间可能会超过 30s,这时就需要修改注册表来解决该问题,注册表地址为:HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Control/ServicesPipeTimeout,ServicesPipeTimeout 可能不存在,如果不存在需要自己手动添加,类型为 DWORD,单位是毫秒,不过修改这个设置需要重启服务器,要根据实际服务器情况来决定是否采用这种方式解决。再比如将 jar 包做成 windows 服务时,可能会遇到执行注册命令出现报错,像 “.exe 文件不是有效的 windows32 位应用程序” 这种情况,这时就需要仔细检查相关版本、文件等是否存在问题,也可以通过网上提供的一些解决方案,如查看是否是病毒原因,是否需要删除.exe 注册表等去尝试解决问题。总之,遇到服务启动失败报错时,要仔细查看报错提示信息,分析可能出现


声明:此篇为墨韵科技原创文章,转载请标明出处链接: https://360jidan.com/news/4538.html
  • 网站建设
  • SEO
  • 信息流
  • 短视频
合作伙伴
在线留言
服务热线

服务热线

15879069746

微信咨询
返回顶部
在线留言