SpringBoot项目部署中遇到的问题

为什么要从tomcat中移出来?

因为一个tomcat下部署多个应用,一旦某个应用要发布,就得全部引用都重启一次,特别麻烦。

没有设置context导致url访问有误的问题

之前应用(比如名为infomine)部署在tomcat中时,页面访问地址是这样的:

http://www.zhouchangju.com/infomine/controller/action/

这一级应用名,是通过tomcat的conf/server.xml配置的,配置信息大致如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<Service name="infomine">
<Connector port="10002" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
<Connector port="8012" protocol="AJP/1.3" redirectPort="8443" />
<Engine name="infomine" defaultHost="localhost">
<Realm className="org.apache.catalina.realm.LockOutRealm">
<Realm className="org.apache.catalina.realm.UserDatabaseRealm"
resourceName="UserDatabase"/>
</Realm>
<Host name="localhost" appBase="webapps/infomine"
unpackWARs="true" autoDeploy="true">
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="infomine_access_log" suffix=".txt"
pattern="%h %l %u %t &quot;%r&quot; %s %b" />
</Host>
</Engine>
</Service>

当项目独立出来,直接通过命令行启动jar包的形式运行时,是没有infomine这一级的,需要在项目的application.yml中增加如下配置:

1
2
server:
context-path: /infomine

Controller里面的Thymeleaf模板的路径问题

之前部署在tomcat中时,Controller里面Thymeleaf的模板是这样设置的:

1
return "/hype/index";

应用通过命令启动jar包的形式运行后,需要将其改为这样(去掉第一个斜杠):

1
return "hype/index";

否则会提示找不到模板文件。

这里总结下Thymeleaf各个路径要不要加斜杠:

return后面不要加斜杠
redirect后面加斜杠
form表单的action加斜杠 th:action=”@{/hype/index}”

限制程序的内存大小

应用发布的时候,直接用的是这样的命令:

1
nohup java -Dspring.profiles.active=production -jar /var/www/infomine/target/infomine.jar > /tmp/logs/springboot/infomine.console.log &

程序本身很小,但是程序跑起来后,耗费内存很高。比如通过PS或者top查看:

1
2
ps aux|grep infomine
root 30492 0.8 12.0 7244544 946572 pts/4 Sl Sep27 8:19 java -Dspring.profiles.active=production -jar /var/www/infomine/target/infomine.jar
1
2
3
top -p xxx
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
30492 root 20 0 7074m 924m 13m S 0.3 12.0 8:19.95 java

分配的虚拟内存有7074m,实际占用的内存有924m

这台机器的配置是8核7.5G内存,看起来如果不做限制,SpringBoot进程默认分配的虚拟内存就是整机的内存大小;实际占用的内存等于整机内存/核心数

应该加上限制内存的参数:

1
nohup java -Xms64m -Xmx256m -XX:PermSize=128M -XX:MaxPermSize=256M -Dspring.profiles.active=production -jar /var/www/infomine/target/infomine.jar > /tmp/logs/springboot/infomine.console.log &

说明:
1、堆内存:最小64M,最大256M。(对象使用的内存)
2、永久内存:最小128M,最大256M。(类使用的内存,PermGen)

这个不一定合理,还需要我测试下。

参数验证

为了验证这些参数,我转到了测试环境(4核4G)进行测试。

添加内存参数前:

java -Dspring.profiles.active=testing -jar /var/www/infomine/target/infomine.jar

PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
6175 root 20 0 4539m 548m 11m S 0.7 14.3 0:45.60 java

添加内存参数后:

java -Xms128m -Xmx256m -XX:PermSize=128M -XX:MaxPermSize=256M -Dspring.profiles.active=testing -jar /var/www/infomine/target/infomine.jar

PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
8503 root 20 0 3781m 331m 13m S 0.7 8.7 0:44.25 java

发现有所下降,但是并没有在设置的参数范围之内

为什么Java程序占用的内存可能会大于Xmx

很多人认为Xmx和-Xms参数指定的就是Java程序将会占用的内存,但是这实际上只是Java堆对象将会占用的内存。堆只是影响Java程序占用内存数量的一个因素。
除了堆,影响Java程序所占用内存的因素还包括:永生代JVM本身NIO中的DirectBuffer等。
假设Xmx为1024m,MaxPermSize为256m,Xss为512k,有100个线程。考虑到socket缓冲区、JNI等,一般大约是jvm内存的5%左右。
则Java程序占用的最大内存可能为:1024m + 256m + 100*512k + (0.05 * 1330m) = 1396.5m
DirectBuffer占用的空间是不包含在JVM的空间中的,如果使用了DirectBuffer,除了JVM还需要监控DirectBuffer占用的空间大小,为操作系统留下足够的内存。

可以参考这3个文章:

https://www.cnblogs.com/koik/p/4452029.html

https://www.cnblogs.com/mingforyou/archive/2012/03/03/2378143.html

https://www.cnblogs.com/dennyzhangdd/p/6770188.html

参数名 说明 默认大小 配置示例
-Xms JVM初始分配的堆内存 物理内存的1/64 -Xms128m
-Xmx JVM最大允许分配的堆内存,按需分配 物理内存的1/4 -Xmx512m
-XX:PermSize JVM初始分配的非堆内存 物理内存的1/64 -XX:PermSize=64M
-XX:MaxPermSize JVM最大允许分配的非堆内存,按需分配 物理内存的1/4 -XX:MaxPermSize=128M
-XX:MetaspaceSize 初始元空间 21M -XX:MetaspaceSize=64m
-XX:MaxMetaspaceSize 最大元空间 -XX:MaxMetaspaceSize=128m

注意:JDK1.8废弃了永久代(PermGen),改为用元空间(Metaspace)替代。因此-XX:PermSize和-XX:MaxPermSize在1.8之后已经不生效了。

1、-Xms :表示java虚拟机堆区内存初始内存分配的大小,通常为操作系统可用内存的1/64大小即可,但仍需按照实际情况进行分配。有可能真的按照这样的一个规则分配时,设计出的软件还没有能够运行得起来就挂了。
2、-Xmx: 表示java虚拟机堆区内存可被分配的最大上限,通常为操作系统可用内存的1/4大小。但是开发过程中,通常会将 -Xms 与 -Xmx两个参数的配置相同的值,其目的是为了能够在java垃圾回收机制清理完堆区后不需要重新分隔计算堆区的大小而浪费资源。

1、-XX:newSize:表示新生代初始内存的大小,应该小于 -Xms的值;
2、-XX:MaxnewSize:表示新生代可被分配的内存的最大上限;当然这个值应该小于 -Xmx的值;
3、-Xmn:至于这个参数则是对 -XX:newSize、-XX:MaxnewSize两个参数的同时配置,也就是说如果通过-Xmn来配置新生代的内存大小,那么-XX:newSize = -XX:MaxnewSize = -Xmn,虽然会很方便,但需要注意的是这个参数是在JDK1.4版本以后才使用的。

1、-XX:PermSize:表示非堆区初始内存分配大小,其缩写为permanent size(持久化内存)
2、-XX:MaxPermSize:表示对非堆区分配的内存的最大上限。

原来是我设置了非堆区内存参数(-XX:PermSize=128M -XX:MaxPermSize=256M)的原因。

默认的初始非堆区内存(PermSize),是物理内存的1/64,比如我的测试机是3830m内存,那么默认这个数值就是60m左右。然后我设置的堆区内存最大是256m,这样加起来就300m+了。

默认的最大非堆区内存(MaxPermSize),是物理内存的1/4,比如我的测试机是3830m内存,那么默认这个数值就是958m左右。

弄清楚参数的含义后,重新设置参数并启动程序,结果如下:

#不设置非堆区内存大小

java -Xms128m -Xmx256m -Dspring.profiles.active=testing -jar /var/www/infomine/target/infomine.jar

PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
3990 root 20 0 3780m 324m 13m S 0.7 8.5 0:41.78 java

#设置非堆区内存大小(故意把堆区内存也调小了)

java -Xms64m -Xmx128m -XX:PermSize=64M -XX:MaxPermSize=64M -Dspring.profiles.active=testing -jar /var/www/infomine/target/infomine.jar

PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
13405 root 20 0 3647m 276m 13m S 1.3 7.2 0:43.39 java

设置非堆区内存大小时的堆信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
jmap -heap  13405
Attaching to process ID 13405, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.122-b04

using thread-local object allocation.
Parallel GC with 4 thread(s)

Heap Configuration:
MinHeapFreeRatio = 0
MaxHeapFreeRatio = 100
MaxHeapSize = 134217728 (128.0MB)
NewSize = 22020096 (21.0MB)
MaxNewSize = 44564480 (42.5MB)
OldSize = 45088768 (43.0MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 17592186044415 MB
G1HeapRegionSize = 0 (0.0MB)

Heap Usage:
PS Young Generation
Eden Space:
capacity = 39321600 (37.5MB)
used = 38019624 (36.258338928222656MB)
free = 1301976 (1.2416610717773438MB)
96.68890380859375% used
From Space:
capacity = 2621440 (2.5MB)
used = 737328 (0.7031707763671875MB)
free = 1884112 (1.7968292236328125MB)
28.1268310546875% used
To Space:
capacity = 2621440 (2.5MB)
used = 0 (0.0MB)
free = 2621440 (2.5MB)
0.0% used
PS Old Generation
capacity = 57147392 (54.5MB)
used = 42520056 (40.55028533935547MB)
free = 14627336 (13.949714660644531MB)
74.40419328322105% used

26821 interned Strings occupying 3203216 bytes.

为什么实际内存消耗还是比设置的大呢?

未完待续。。。。。。

如何设置堆区与非堆区参数的大小

这个内容较多,专门另外写了一篇笔记,就不在这里写了。

线上程序的堆信息

这是208.118上面让运维先触发FullGC,然后打印出来的堆信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
Attaching to process ID 30492, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.171-b11

using thread-local object allocation.
Parallel GC with 7 thread(s)

Heap Configuration:
MinHeapFreeRatio = 0
MaxHeapFreeRatio = 100
MaxHeapSize = 2015363072 (1922.0MB)
NewSize = 42467328 (40.5MB)
MaxNewSize = 671612928 (640.5MB)
OldSize = 85458944 (81.5MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 17592186044415 MB
G1HeapRegionSize = 0 (0.0MB)

Heap Usage:
PS Young Generation
Eden Space:
capacity = 33554432 (32.0MB)
used = 576168 (0.5494766235351562MB)
free = 32978264 (31.450523376464844MB)
1.7171144485473633% used
From Space:
capacity = 524288 (0.5MB)
used = 0 (0.0MB)
free = 524288 (0.5MB)
0.0% used
To Space:
capacity = 524288 (0.5MB)
used = 0 (0.0MB)
free = 524288 (0.5MB)
0.0% used
PS Old Generation
capacity = 169869312 (162.0MB)
used = 34257200 (32.67021179199219MB)
free = 135612112 (129.3297882080078MB)
20.16679740246431% used

30488 interned Strings occupying 3506624 bytes.

根据这个信息,可以看到FullGC后,老年代的内存大小是33m,因此得出这个应用应该设置的堆大小是:

33 * 4 = 132m

那么启动参数可以设置为:

-Xms128m -Xmx128m

其他参数

根据docker运维的建议,下面这些参数也是必须添加的。

参数说明可以参考这个文章:https://blog.csdn.net/Thousa_Ho/article/details/77278656

Docker中运行的Java 9将能调整内存限制

-XX:+UnlockExperimentalVMOptions

开启实验性的参数。

有些时候当设置一个特定的JVM参数时,JVM会在输出“Unrecognized VM option”后终止。如果发生了这种情况,你应该首先检查你是否输错了参数。然而,如果参数输入是正确的,并且JVM并不识别,或许需要设置-XX:+UnlockExperimentalVMOptions 来解锁参数

-XX:+UseCGroupMemoryLimitForHeap

这个参数是为了让JVM的内存参数,在docker环境下也生效。因为这是一个实验性的参数,因此该参数必须依赖于上面的-XX:+UnlockExperimentalVMOptions配置。

完整的启动命令

完整的启动参数应该是类似这样的:

nohup java -Xms128m -Xmx128m -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -Dspring.profiles.active=production -jar /var/www/infomine/target/infomine.jar > /tmp/logs/springboot/infomine.console.log &

注意:UseCGroupMemoryLimitForHeap是Java9才支持的,如果你用的JDK是8的,请勿加上该参数,会导致启动报错。

一些基础知识

如何打包项目

idea右侧的Maven Projects中,选择标注为(root)的模块,点击下面的Lifecycle->package,即可打包,命令行也会输出具体执行的打包命令。

注意:可以通过点击上方的闪电图标img来跳过测试,否则会先把测试用例执行通过后,才会执行打包操作。

如何根据环境变量,读取不同的配置文件?

打包的时候是不用管的,把各种环境变量的配置都打进去即可。关键在于启动的时候,可以通过--spring.profiles.active指定环境变量,类似这样:

1
java -XX:+PrintGC -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -Xloggc:/logs/gc.log -jar -Xms512m -Xmx1024m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/logs/ XXX.jar --spring.profiles.active=prod > /dev/null 2>&1

而环境变量一般是服务器变量预设的,比如THS_TIER这种,可以根据该变量的值,确定--spring.profiles.active到底要设置成什么。

如何设置URI前缀?

可以通过配置server.servlet.context-path来设置URI前缀,比如这样:

1
2
3
4
5
6
7
server:
servlet:
context-path: /${spring.application.name}

spring:
application:
name: micro-video