Tomcat

Tomcat

1.JVM是啥

JVM是Java Virtual Machine(Java虚拟机)的缩写

是运行Java程序的抽象计算机,是Java语言的运行环境,Java虚拟机本质是就是一个程序,当它在命令行上启动的时候,就开始执行保存在某字节码文件中的指令。Java语言的可移植性正是建立在Java虚拟机的基础上。任何平台只要装有针对于该平台的Java虚拟机,字节码文件(.class)就可以在该平台上运行。实现"一次编译,多次运行"。

jvm java虚拟机

jre java运行环境

jdk java开发工具包

jdk包含jre,jre包含jvm虚拟机


常用的是jdk8

jdk下载地址:https://www.oracle.com/java/technologies/downloads/?er=221886#java8

其他版本jdk下载地址:https://www.oracle.com/java/technologies/downloads/archive/


2.Tomcat

Tomcat和 Nginx 类似,也是一个Web服务器。

tomcat是一个java编写的web服务器,需要java运行环境,运行java。

Nginx默认仅支持处理静态资源,而Tomcat则支持Java开发的 jsp 动态资源和静态资源。

Nginx适合做前端负载均衡,而Tomcat适合做后端应用服务处理。

通常情况下会将 Nginx+tomcat 结合使用,由Nginx处理静态资源,Tomcat处理动态资源。


tomcat下载地址:https://mirrors.tuna.tsinghua.edu.cn/apache/tomcat/

将下载好的jdk包传到服务器上

安装jdk
rpm -ivh jdk-8u411-linux-x64.rpm

安装启动tomcat

mkdir app -p
tar xf apache-tomcat-9.0.90.tar.gz -C /app
/app/apache-tomcat-9.0.90/bin/startup.sh 

安装方法二:

#适合debian和ubuntu等其他linux发行版
tar xf jdk-8u60-linux-x64.tar.gz -C /app/
ln -s /app/jdk1.8.0_60 /app/jdk
sed -i.ori '$a export JAVA_HOME=/app/jdk\nexport PATH=$JAVA_HOME/bin:$JAVA_HOME/jre/bin:$PATH\nexport CLASSPATH=.$CLASSPATH:$JAVA_HOME/lib:$JAVA_HOME/jre/lib:$JAVA_HOME/lib/tools.jar' /etc/profile
source /etc/profile
mkdir /app/
tar xf apache-tomcat-8.0.27.tar.gz -C /app
/app/apache-tomcat-8.0.27/bin/startup.sh 

tomcat启动慢解决方案

可以在/apache-tomcat-9.0.90/logs/catalina.out中看到启动用时

优化方法:
vim /usr/java/jdk1.8.0_102/jre/lib/security/java.security

将:securerandom.source=file:/dev/random
改为:securerandom.source=file:/dev/urandom

启动后可以看到java常占用的端口


3.tomcat目录结构介绍
bin

主要包含启动、关闭tomcat脚本和脚本依赖文件 非常重要

以启动脚本为例

执行脚本时,会先找到脚本本身的目录,然后去执行catalina.sh脚本,然后让tomcat在后台启动。

在需要时,也可以让tomcat在前台启动

先停掉tomcat,再执行./catalina.sh run 让它前台启动


conf

tomcat配置文件目录,重要

tomcat的主配置文件server.xml


lib

tomcat运行需要加载的jar包,重要

jar包相当于扩展模块,tomcat在运行过程中,会运行不同的组件,不同的组件对应的就是不同的jar包。想实现某些功能,就将对应的jar包放到lib目录下。

logs

在运行过程中产生的日志文件,重要

catalina.out中主要记录一些报错输出等内容

access访问日志中主要记录访问日志


webapps

站点目录,重要

访问tomcat时,默认访问的是ROOT目录下的内容

要访问其他的目录在url中加入/对应的目录名称即可


work

tomcat运行时产生的缓存文件


temp

temp 存放临时文件


RELEASE-NOTES

版本特性,不重要


RUNNING.txt

帮助文件,不重要


4.tomcat主配置文件

主配置文件:

/app/apache-tomcat-8.x.xx/conf/server.xml

用户访问tomcat,会先访问到proxy模块,监听的8080端口。

proxy会将请求抛向server内部,

请求抛给谁处理,就会找对应的连接器,

例如是http请求,就会抛给http连接器,

一个tomcat实例一个server

一个server中包含多个连接器(Connector),Connector的主要功能是接受、响应用户请求。

service的作用是:将connector关联至engine(catalina引擎),引擎下,对应的就是站点

一个host就是一个站点,类似于nginx的多站点

context类似于nginx中location的概念


打开server.xml文件,可以看到8005端口可以用来远程关闭tomcat服务

server和service之间就是通过这些connector连接器连接的

tomcat核心的引擎是catalina

引擎下,对应的就是站点,一个Host就对应一个站点,类似于nginx的多站点

nginx支持多域名多站点,同样tomcat (host)也支持多域名多站点

默认配置的站点是localhost站点,站点目录是webapps,

访问日志格式:

className="org.apache.catalina.valves.AccessLogValve" 日志的方法
directory="logs" 日志所在目录
prefix="localhost_access_log" 日志前缀
suffix=".txt" 日志后缀
中间加上时间戳

5.tomcat部署java项目示例:zrlog

修改主配置文件server.xml,添加一个站点,就是添加一个Host

例:

www.tct.com 为站点名 记得做域名解析
/html 为站点目录
将下载好的项目war包放到站点目录/html下会自动解压,自动部署。

修改后,需要重启服务才会生效

创建站点目录后,将war包放入站点目录,会自动解压自动部署

过一会访问

配置数据库

yum install mariadb-server -y
systemctl start mariadb.service
systemctl enable mariadb.service


站点部署成功



6.配置tomcat basic认证

有了basic认证使网站多了一层安全保障

nginx同样也有basic认证,参考nginx常用功能模块nginx通过账户登陆实现访问控制


测试basic认证时

点击这三个按钮后提示403

解决方法:

将以下两个文件中的这两行注释掉

/app/apache-tomcat-9.0.90/webapps/manager/META-INF/context.xml
/app/apache-tomcat-9.0.90/webapps/host-manager/META-INF/context.xml

然后在
/app/apache-tomcat-9.0.90/conf/tomcat-users.xml

加入

<role rolename="manager-gui"/> #声明角色
<role rolename="admin-gui"/>
<user username="tomcat" password="s3cret" roles="admin-gui,manager-gui"/>  #一个用户可以是多个角色

配置过后需要重启tomcat

/app/apache-tomcat-9.0.90/bin/shutdown.sh
/app/apache-tomcat-9.0.90/bin/startup.sh

重启后再试即可认证


如果是在部署的项目上开启basic认证时,需要在配置的站点目录下找到

ROOT/WEB-INF/web.xml

例如站点目录是/html,就编辑/html/ROOT/WEB-INF/web.xml文件

在文件倒数第二行加上basic认证

 <security-constraint>
        <web-resource-collection>
            <web-resource-name>test</web-resource-name>
            <url-pattern>/做basic认证的URL,当访问这个URL时才做basic认证</url-pattern>
        </web-resource-collection>
        
        <auth-constraint>
            <role-name>角色名</role-name>
        </auth-constraint>
    </security-constraint> 
    <login-config>
        <auth-method>BASIC</auth-method>
        <realm-name>Default</realm-name>
    </login-config>

配置过后重启tomcat

/app/apache-tomcat-9.0.90/bin/shutdown.sh
/app/apache-tomcat-9.0.90/bin/startup.sh

7.Nginx+Tomcat集群架构

大概架构

在之前的基础上,部署一台配置相同的web服务器,jdk+tomcat+zrlog

再部署一台nginx负载均衡服务器,配置文件如下

cat proxy_java.conf

upstream tomcat {    #定义资源池
 	server 192.168.xx.1:8080;  #tomcat01
 	server 192.168.xx.2:8080;  #tomcat02
 }

server {
 	listen 80;
 	server_name www.tct.com;
 	location / {
 		proxy_pass http://tomcat;  #调⽤upstream资源池定义的名称
		proxy_set_header Host $host;    #修改请求头中的Host为实际请求的主机名
		proxy_set_header X-Real-IP $remote_addr;  #传递客户端的真实IP地址
 		include proxy_params;
 	}
 }

proxy_set_header Host $host;

修改由Nginx传递给代理后端的请求头,修改请求头中的Host,

不然会保留原始的Host,返回的页面是tomcat默认页面,

并非实际请求页面,$host的值就是请求的主机名。

proxy_set_header X-Real-IP $remote_addr;

传递客户端的真实IP地址,当 Nginx做负载均衡时,后端服务器看到的客户端 IP 是负载均衡的 IP,而不是真实用户的 IP。

同时需要将两台tomcat服务器的日志格式修改

vim /app/apache-tomcat-8.0.27/conf/server.xml

pattern="%{X-Real-IP}i %l %u %t &quot;%r&quot; %s %b" /> 

参数含义:

  %i 表示请求头
  %l 表示'-'
  %u 远程用户身份验证,应该就是前面的basic认证
  %t 日期时间
  &quot 表示空格
  %r 第一行的请求(主要包含请求方法和请求的URI)
  %s 响应的HTTP状态码
  %b 发送的字节数

修改后重启tomcat

/app/apache-tomcat-8.0.27/bin/shutdown.sh
/app/apache-tomcat-8.0.27/bin/startup.sh

然后后端看到的IP就是真实的客户端IP了。


8.tomcat+nfs实现文件共享

大概架构

为了方便我就直接在数据库服务器上部署NFS服务了

安装配置nfs服务端

1.安装
yum install nfs-utils -y

2.配置
vim /etc/exports

/data 192.168.51.1/24(rw,sync,no_root_squash,no_all_squash)

3.根据配置进行初始化操作
mkdir -p /Data

4.启动

systemctl start nfs
#systemctl enable nfs
客户端配置nfs

在两台tomcat服务器上都要执行以下操作

安装nfs
yum install nfs-utils -y

查看某主机提供的nfs服务
showmount -e 192.168.xx.xxx

因为zrlog的图片存放路径在 /站点目录/zrlog/attached下,所以创建对应目录
mkdir /html/zrlog/attached

挂载

将192.168.xx.xxx:/Data 挂载到本地的/html/zrlog/attached目录
mount -t nfs 192.168.xx.xxx:/Data /html/zrlog/attached

df -h 查看是否挂载上

如果遇到客户端权限不足的情况参考:
NFS客户端权限不足

接下来在任意一台服务器上传图片,另外一台服务器同样可以读到该图片


9.nginx做静态资源缓存
缓存服务器配置

nginx配置文件如下

vim /etc/nginx/conf.d/proxy.conf

proxy_cache_path /opt/nginx/cache levels=1:2 keys_zone=one:10m;
upstream tomcat {
	server 192.168.xx.xx1:8080;
 	server 192.168.xx.xx2:8080;
}

server {
 	listen 80;
 	server_name www.tct.com;
 	
	location / {
 		proxy_pass http://tomcat;
		proxy_set_header Host $host;
		proxy_set_header X-Real-IP $remote_addr;
 		include proxy_params;
 	}
 	
	location ~ .*\.(gif|jpg|png|css|js|woff|flv|ico|swf)(.*) {
                proxy_cache one;
                proxy_cache_key $uri;
                proxy_cache_valid 200 302 1h;
                proxy_cache_valid 301 1d;
                proxy_cache_valid any 1m;
                expires 30d;
                add_header     Nginx-Cache   "$upstream_cache_status";
                proxy_pass http://tomcat;
				proxy_set_header Host $host;
				proxy_set_header X-Real-IP $remote_addr;
 			 	include proxy_params;
 }
}

/opt/nginx/cache 目录需要提前创建,并且授权给nginx,
chown -R nginx:nginx /opt/nginx/cache

因为nginx需要往该目录中写缓存

参数介绍

/opt/nginx/cache 指定缓存目录

levels=1:2 表示缓存目录的层级 ,1:2表示生成的目录是两级目录

keys_zone=one:10m key是索引,每个缓存都会生成唯一的标识符来索引,下次请求缓存的时候,会先去索引key,有请求资源的话直接返回缓存,没有的话生成新的缓存。one是给key区域的起的名。 10m指的是索引的大小。

gif|jpg|png|css|js|woff|flv|ico|swf 指定格式

proxy_cache one; 缓存到哪个区域,对应前面创建的one

proxy_cache_key $uri; 指定uri作为key的索引

proxy_cache_valid 200 302 1h; 200 302状态码的缓存一小时

proxy_cache_valid 301 1d; 301状态码的缓存一天

proxy_cache_valid any 1m; 其他状态码的缓存一分钟

expires 30d; 过期时间为30天

add_header Nginx-Cache "$upstream_cache_status"; 判断是否命中缓存 ,在http 的响应头中添加一个缓存的命中状态。HIT表示命中缓存,MISS表示没有命中缓存。


缓存结果验证

查看缓存目录,可以看到缓存目录层级是按照我们指定的两层进行缓存的

查看响应头,可以看到 png图片资源、js文件 都命中了缓存


10.Nginx+Tomcat实现Https

其实就是给nginx配一个证书

nginx配置如下:

proxy_cache_path /opt/nginx/cache levels=1:2 keys_zone=one:10m;
upstream tomcat {
	server 192.168.xx.xx1:8080;
 	server 192.168.xx.xx2:8080;
}

server {  #主要是证书配置这一部分,其他的和之前的变化不大
 		listen 443 ssl;
   		server_name closeai.xyz;
    	ssl_certificate /opt/closeai.xyz_nginx/closeai.xyz.pem;
    	ssl_certificate_key /opt/closeai.xyz_nginx/closeai.xyz.key;
    	ssl_session_timeout 5m;
   		ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    	ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;

	location / {
 		proxy_pass http://tomcat;
		proxy_set_header Host $host;
		proxy_set_header X-Real-IP $remote_addr;
 		include proxy_params;
 	}

	location ~ .*\.(gif|jpg|png|css|js|woff|flv|ico|swf)(.*) {
                proxy_cache one;
                proxy_cache_key $uri;
                proxy_cache_valid 200 302 1h;
                proxy_cache_valid 301 1d;
                proxy_cache_valid any 1m;
                expires 30d;
                add_header     Nginx-Cache   "$upstream_cache_status";
		proxy_pass http://tomcat;
                proxy_set_header Host $host;
                proxy_set_header X-Real-IP $remote_addr;
                include proxy_params;
 }
}

server {    #做个协议层面的跳转,请求http时,自动跳转到https
        listen 80;
        server_name closeai.xyz;
        return 302 https://$server_name$request_uri;       #$server_name:请求的域名,$request_uri:用户请求的URI

}

修改完配置记得重载一下nginx服务

nginx -t
systemctl reload nginx

两台tomcat的配置文件中的站点也要记得修改成同样的域名

vim /app/apache-tomcat-8.0.27/conf/server.xml

修改完记得重启tomcat

/app/apache-tomcat-8.0.27/bin/shutdown.sh
/app/apache-tomcat-8.0.27/bin/startup.sh

结果验证

通过https的方式访问站点,可以成功打开

因为我用的证书是有效的,如果是自己生成的证书,浏览器会报证书无效的错,但原理都一样,花钱买个域名,再把有效的证书换上就可以了。


11.使用maven编译java程序
maven安装配置

下载maven
wget https://mirrors.tuna.tsinghua.edu.cn/apache/maven/maven-3/3.8.8/binaries/apache-maven-3.8.8-bin.tar.gz

解压maven
tar xf apache-maven-3.8.8-bin.tar.gz -C /usr/local/

创建软链接
ln -s /usr/local/apache-maven-3.8.8 /usr/local/maven

配置maven环境变量

vim /etc/profile

#文件结尾添加两行
export M2_HOME=/usr/local/maven
export PATH=${M2_HOME}/bin:$PATH

source /etc/profile

验证maven
mvn -v

配置maven仓库
vim /usr/local/maven/conf/settings.xml

	<mirror>
     <id>nexus-aliyun</id>
     <mirrorOf>central</mirrorOf>
     <name>Nexus aliyun</name>
     <url>http://maven.aliyun.com/nexus/content/groups/public</url>
    </mirror>

编译java程序

下载好测试用的包:https://github.com/efsavage/hello-world-war

解压后进入项目目录

unzip hello-world-war-master.zip

cd hello-world-war-master

编译
mvn clean package

编译后war包在target目录下


配置tomcat站点

vim /app/apache-tomcat-8.0.27/conf/server.xml

再添加一个Host

记得创建站点目录,再将之前的war包放到站点目录下名为ROOT.war

tomcat会自动解压

mkdir /hello
mv /opt/hello-world-war-master/target/hello-world-war-1.0.0.war /hello/ROOT.war

记得做域名解析

记得重启tomcat

/app/apache-tomcat-8.0.27/bin/shutdown.sh
/app/apache-tomcat-8.0.27/bin/startup.sh

然后访问站点即可


12.使用redisson实现session共享

存在的问题:将相同业务的后端节点加入负载均衡后,在客户端进行登录操作的时候,会发现不管在哪个节点都无法正常登陆,原因是session没有共享。

解决方案:做会话保持

1.粘性session: ip_hash 始终定向⾄某⼀个后端web节点。

2.session共享: 将session通过存储到指定⼀个位置,⽽不是存储⾄本地,后端所有节点都通过这个位置进行验证 ( redis )


方法一:ip_hash 做会话保持

在负载均衡的配置文件的地址池中加入ip_hash

解决了无法登陆的问题,但是只能访问固定的一个web节点,无法轮询

upstream web {
	ip_hash;    #做会话保持
	server 192.168.xx.xx1:8080;
	server 192.168.xx.xx2:8080;

}

方法二:使用redisson实现session共享
redis部署

yum install redis -y

启动redis

systemctl start redis
systemctl enable redis

修改redis配置文件
vim /etc/redis.conf

添加redis服务器的IP地址

重启redis
systemctl restart redis


配置redisson

在每台tomcat服务器上都执行以下操作

redisson项目地址
https://github.com/redisson/redisson

基于 Redis 的 Tomcat 会话管理器
https://github.com/redisson/redisson/tree/master/redisson-tomcat

官方使用教程:

1.添加会话管理器

vim /app/apache-tomcat-8.0.27/conf/context.xml

添加以下内容

<Manager className="org.redisson.tomcat.RedissonSessionManager"
  configPath="${catalina.base}/conf/redisson.conf" 
  readMode="REDIS" updateMode="DEFAULT" broadcastSessionEvents="false"
  keyPrefix=""/>

然后将redisson.conf文件放到上面配置的目录下

/app/apache-tomcat-8.0.27/conf/目录下

这个配置文件是用来连接redis的

redisson.conf文件内容如下

{
   "singleServerConfig":{
      "idleConnectionTimeout":10000,
      "connectTimeout":10000,
      "timeout":3000,
      "retryAttempts":3,
      "retryInterval":1500,
      "password":null,
      "subscriptionsPerConnection":5,
      "clientName":null,
      "address": "redis://你的redis地址:6379",  #这里记得改成你部署的redis的地址
      "subscriptionConnectionMinimumIdleSize":1,
      "subscriptionConnectionPoolSize":50,
      "connectionMinimumIdleSize":32,
      "connectionPoolSize":64,
      "database":0,
      "dnsMonitoringInterval":5000
   },
   "threads":0,
   "nettyThreads":0,
   "codec":{
      "class":"org.redisson.codec.JsonJacksonCodec"
   },
   "transportMode":"NIO"
}

2.将下载好的jar包放到指定目录

将下载好的jar包放到官方指定的tomcat根目录下的lib目录中

/app/apache-tomcat-8.0.27/lib/

重启tomcat
/app/apache-tomcat-8.0.27/bin/shutdown.sh
/app/apache-tomcat-8.0.27/bin/startup.sh

每台tomcat服务器都配置完毕后,session已经共享,再去登陆就可以成功登陆了

可以查看到在redis中存的session

redis-cli

KEYS *

13.使用JConsole 监控jvm
在被监控的tomcat服务器上开启监控接口

vim /app/apache-tomcat-8.0.27/bin/catalina.sh

CATALINA_OPTS="$CATALINA_OPTS
-Dcom.sun.management.jmxremote
-Djava.rmi.server.hostname=192.168.xx.xxx
-Dcom.sun.management.jmxremote.port=12345
-Dcom.sun.management.jmxremote.ssl=false
-Dcom.sun.management.jmxremote.authenticate=false"

重启tomcat

/app/apache-tomcat-8.0.27/bin/shutdown.sh
/app/apache-tomcat-8.0.27/bin/startup.sh 

验证


使用JConsole 监控

在一台Windows机器上安装jdk

jdk下载地址:
https://www.oracle.com/cn/java/technologies/downloads/#java11-windows

安装jdk

按照他默认的安装路径即可

点关闭

在刚才的安装路径下可以看到jdk-11


环境变量配置

右键此电脑,属性

高级系统设置

点击环境变量

浏览目录找到刚才的安装路径,然后点确定

找到Path编辑,点击新建输入%Java_Home%\bin ,然后确定

然后都点确定关闭页面

同时按下 win + R 输入 cmd 回车

输入java -version 查看版本

输入javac 会输出以下信息

jdk配置完成


启动jconsole.exe

C:\Program Files\Java\jdk-11\bin下找到jconsole.exe

因为我要监控的是远程的tomcat服务器,

所以在远程进程下输入远程服务器的IP地址:JMX自定义的端口,然后点连接

然后点 不安全的连接 (前面没开SSL连接)

稍等一会,就可以看到监控页面了

可以切换查看不同的信息


14.Tomcat性能优化

‌Tomcat的性能优化可以通过多个方面进行,

主要包括:内存优化、并发优化、‌数据库连接池优化、线程优化、缓存优化等。

内存优化:通过调整JVM启动参数如-Xms和-Xmx来设置JVM的初始堆大小和最大堆大小。
例如,可以设置JAVA_OPTS='-Xms1024m -Xmx2048m'来确保JVM有足够的内存资源。‌

并发优化:调整Tomcat的连接器配置文件server.xml中的参数,
如minProcessors、maxProcessors、acceptCount和maxThreads,
以优化并发处理能力。例如,增加acceptCount的值可以扩大连接请求队列,避免连接请求被拒绝。‌

数据库连接池优化:选择合适的数据库连接池
(如Apache Commons DBCP、Tomcat JDBC Pool、HikariCP等),
并合理配置连接池参数,如连接数、最大等待时间、最大空闲连接数等,
以提高数据库连接的利用率和性能。‌

线程优化:根据服务器的CPU核数和负载情况调整maxThreads参数,
以及考虑使用非阻塞模式的连接协议(如NIO或APR)来提高处理能力。‌

缓存和压缩优化:开启gzip压缩来减小网络传输大小,同时考虑使用缓存来减少对后端Tomcat的访问。
例如,可以在Tomcat中配置压缩相关的参数,如compression="on"和设置压缩的最小内容长度等。‌

Tomcat的性能优化是一个多方面的过程,
需要综合考虑内存、并发、数据库连接池、线程以及缓存和压缩等多个方面的设置和调整。

15.JVM内存分代

内存主要包括堆内存非堆内存

JVM将内存划分为三大部分:

年轻代Young Generation
老年代Old Generation
永久代Permanent Generation

年轻代老年代属于堆内存

堆内存会从JVM启动参数(-Xmx:3G)指定的内存中分配

年轻代和老年代将根据默认的比例(1:4)分配堆内存

永久代不属于堆内存,虚拟机直接分配,

但可以通过(-XX:PermSize -XX:MaxPermSize)等参数调整其大小。


年轻代:用来存放JVM刚分配的Java对象

老年代:年轻代中经过垃圾回收没有被回收掉的对象将被Copy到年老代

永久代:永久代存放Class、Method元信息,其大小跟项目的规模、类、方法的量有关,一般设置为128M就足够,设置原则是预留30%的空间。


年轻代

年轻代用来存放JVM刚分配的Java对象。

由于频繁创建对象,所以新生代会频繁触发MinorGC进行垃圾回收。

年轻代又分为 :EdenSurvivorFromSurvivorTo三个区。

Eden区:存放JVM刚分配的Java对象

(如果新对象占用内存很大,则直接分配到老年代),

当Eden区内存不够的时候就会触发MinorGC(局部的GC),对年轻代区进行一次垃圾回收。

SurvivorFrom(s0):保留了一次MinorGC过程中的幸存者。

SurvivorTo(s1):上一次GC的幸存者,作为这一次GC的被扫描者。

两个Survivor空间一样大,当Eden中的对象经过垃圾回收没有被回收掉时,会在两个Survivor之间来回Copy,当满足某个条件,比如对象的年龄,就会被Copy到Tenured。显然,Survivor只是增加了对象在年轻代中的逗留时间,增加了被垃圾回收的可能性。


年轻代垃圾回收算法(复制算法)

首先把Eden和SurvivorFrom区域中存活的对象复制到SurvivorTo区域

同时把这些对象的年龄+1(如果ServicorTo不够位置了就放到老年区)

(如果有对象的年龄已经达到了老年的标准,一般是15,则复制到老年代区)

然后,清空Eden和SurvivorFrom中的对象;

最后把SurvivorTo和SurvivorFrom互换,原SurvivorTo成为下一次GC时的SurvivorFrom区。


老年代

老年代的对象比较稳定,所以不会频繁执行GC。

老年代在进行GC前一般年轻代都先进行了一次GC,使得年轻代的对象晋升入老年代,

导致空间不够用时才触发。当无法找到足够大的连续空间分配给新创建的较大对象时也会提前触发一次GC进行垃圾回收腾出空间。


老年代垃圾回收算法 (标记清除算法)

首先扫描一次所有老年代,标记出存活的对象,然后回收没有标记的对象。

老年代的GC的耗时比较长,因为要扫描再回收。

GC会产生内存碎片,为了减少内存损耗,一般需要进行合并或者标记出来方便下次直接分配。

当老年代也满了装不下的时候,就会抛出OOM(Out of Memory)异常。


永久代

主要存放Class和Meta(元数据)的信息。

Class在被加载的时候放入永久区域。

它和其他区域不同,GC不会在主程序运行期对永久区域进行清理。所以这也导致了永久代的区域会随着加载的Class的增多而胀满,最终抛出OOM异常。

在Java8中,永久代已经被移除,被"元数据区"(元空间)的区域所取代。

元空间与永久代之间区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制。


垃圾回收何时进行

当年轻代内存满时,会引发一次普通的局部GC,该GC仅回收年轻代。

需要强调的是,年轻代满是指Eden代满,Survivor满不会引发GC。

当老年代满时会引发全局GC(Full GC)将会同时回收年轻代、老年代。

当永久代满时也会引发全局GC(Full GC),会导致Class、Method元信息的卸载。

GC垃圾回收: 回收次数越少越好,回收时间越短越好


系统崩溃前的一些现象:

每次垃圾回收的时间越来越长,由之前的10ms延长到50ms左右,

FullGC的时间也有之前的0.5s延长到4、5s。

FullGC的次数越来越多,最频繁时隔不到1分钟就进行一次FullGC,

老年代的内存越来越大并且每次FullGC后老年代没有内存被释放。

之后系统会无法响应新的请求,逐渐到达OutOfMemoryError的临界值。

dump文件

通过JMX的MBean生成当前的Heap(堆栈)信息,大小为一个3G(整个堆的大小)的hprof文件,如果没有启动JMX可以通过Java的jmap命令来生成该文件。然后选用Eclipse专门的静态内存分析工具:Mat分析该文件。


Tomcat内存调整(JVM内存调整)

Tomcat内存优化主要是对 tomcat 启动参数优化,我们可以在 tomcat 的启动脚本 catalina.sh 中设置 JAVA_OPTS参数。

例:

JAVA_OPTS="$JAVA_OPTS -server -Xms512m -Xmx512m -Xss256k  
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/heapdump 
-XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:/tmp/heap_trace.txt 
-XX:NewSize=128m -XX:MaxNewSize=128m"
参数 含义
-Xms 初始堆大小
-Xmx 最大堆大小
-Xss 线程栈的大小,这个选项对性能的影响比较大,建议使用256K的大小。
-XX:NewRatio 年轻代和老年代的堆内存比例
-XX:+HeapDumpOnOutOfMemoryError 控制JVM 在 OutOfMemoryError 时打印堆的信息
-XX:HeapDumpPath 指定堆信息文件的存储路径
-XX:+PrintGCDetails 输出详细的GC收集日志的信息
-XX:+PrintGCTimeStamps 打印CG发生的时间戳
-Xloggc 设置GC打印的日志名称
-XX:NewSize 设置年轻代大小
-XX:MaxNewSize 设置年轻代大小,通常会把-XX:newSize -XX:MaxNewSize设置为同样大小
-XX:SurvivorRatio 年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5
-XX:MaxPermSize 设置永久代大小
-XX:PermSize 设置永久代大小
-XX:+UseParalledlOldGC 垃圾回收时,设置并行年老代收集器回收,在配置较好的机器上(比如多核、大内存),可以为年老代选择并行收集算法: -XX:+UseParallelOldGC ,默认为Serial收集
-XX:+UseSerialGC 垃圾回收时,设置串行收集器回收
-XX:+UseParallelGC 垃圾回收时,设置并行收集器回收
-XX:+UseConcMarkSweepGC 垃圾回收时,设置并发收集器回收

年轻代和老年代设置多大才合理

更大的年轻代必然导致更小的老年代,大的年轻代会延长普通GC的周期,但会增加每次GC的时间;小的老年代会导致更频繁的Full GC,更小的年轻代必然导致更大老年代,小的年轻代会导致普通GC很频繁,但每次的GC时间会更短;大的老年代会减少Full GC的频率。

如何选择应该依赖应用程序对象生命周期的分布情况:如果应用存在大量的临时对象,应该选择更大的年轻代;如果存在相对较多的持久对象,老年代应该适当增大。但很多应用都没有这样明显的特性,在抉择时应该根据以下两点:

(A)本着Full GC尽量少的原则,让老年代尽量缓存常用对象,JVM的默认比例1:2也是这个道理。

(B)通过观察应用一段时间,看其他在峰值时老年代会占多少内存,在不影响Full GC的前提下,根据实际情况加大年轻代,比如可以把比例控制在1:4。但应该给老年代至少预留1/3的增长空间


调优方法

在调优之前,我们需要记住下面的原则:

1、多数的Java应用不需要在服务器上进行GC优化;
2、多数导致GC问题的Java应用,都不是因为我们参数设置错误,而是代码问题;
3、在应用上线之前,先考虑将机器的JVM参数设置到最优(最适合);
4、减少创建对象的数量;
5、减少使用全局变量和大对象;
6、GC优化是到最后不得已才采用的手段;
7、在实际使用中,分析GC情况优化代码比优化GC参数要多得多;

GC优化的目的有两个:

1、将转移到老年代的对象数量降低到最小;
2、减少full GC的执行时间;

为了达到上面的目的,一般地,你需要做的事情有:

1、减少使用全局变量和大对象;
2、调整年轻代的大小到最合适;
3、设置老年代的大小为最合适;
4、选择合适的GC收集器;

GC调优是建立在GC监控和调优上的,进行监控和调优的一般步骤为:

1、监控GC的状态
使用各种JVM工具,查看当前日志,分析当前JVM参数设置,并且分析当前堆内存快照和gc日志,根据实际的各区域内存划分和GC执行时间,觉得是否进行优化;

2、分析结果,判断是否需要优化
如果各项参数设置合理,系统没有超时日志出现,GC频率不高,GC耗时不高,那么没有必要进行GC优化;如果GC时间超过1-3秒,或者频繁GC,则必须优化;

注:如果满足下面的指标,则一般不需要进行GC:
Minor GC执行时间不到50ms;
Minor GC执行不频繁,约10秒一次;
Full GC执行时间不到1s;
Full GC执行频率不算频繁,不低于10分钟1次;

3、调整GC类型和内存分配
如果内存分配过大或过小,或者采用的GC收集器比较慢,则应该优先调整这些参数,并且先找1台或几台机器进行beta,然后比较优化过的机器和没有优化的机器的性能对比,并有针对性的做出最后选择;

4、不断的分析和调整
通过不断的试验和试错,分析并找到最合适的参数

5、全面应用参数
如果找到了最合适的参数,则将这些参数应用到所有服务器,并进行后续跟踪。

参考:https://oldqiang.com/archives/435.html

相关推荐
Dola_Pan10 分钟前
Linux文件IO(二)-文件操作使用详解
java·linux·服务器
wang_book12 分钟前
Gitlab学习(007 gitlab项目操作)
java·运维·git·学习·spring·gitlab
蜗牛^^O^1 小时前
Docker和K8S
java·docker·kubernetes
从心归零2 小时前
sshj使用代理连接服务器
java·服务器·sshj
IT毕设梦工厂3 小时前
计算机毕业设计选题推荐-在线拍卖系统-Java/Python项目实战
java·spring boot·python·django·毕业设计·源码·课程设计
Ylucius3 小时前
动态语言? 静态语言? ------区别何在?java,js,c,c++,python分给是静态or动态语言?
java·c语言·javascript·c++·python·学习
七夜zippoe4 小时前
分布式系统实战经验
java·分布式
是梦终空4 小时前
JAVA毕业设计176—基于Java+Springboot+vue3的交通旅游订票管理系统(源代码+数据库)
java·spring boot·vue·毕业设计·课程设计·源代码·交通订票
落落落sss4 小时前
sharding-jdbc分库分表
android·java·开发语言·数据库·servlet·oracle
码爸4 小时前
flink doris批量sink
java·前端·flink