tomcat(十一)了解JVM

By | 2019年 11月 23日

JVM

Java目前是最流行的编程语言。
目前主要应用在企业级WEB开发和大数据领域。

使用Java语言编写.java Source Code文件,通过javac编译成.class Byte Code文件。

class loader类加载器:将所需所有类加载到内存,必要时将类实例化成实例。

Jvm运行时区域,由下面几部分构成

  1. 方法区:所有线程共享的内存空间,存放已加载的类信息、常量和静态变量。
  2. heap堆:所有线程共享的内存空间,存放创建的所有对象。堆是靠GC垃圾回收器管理的。
  3. Java栈:每个线程会分配一个栈,存放线程用的本地变量、方法参数和返回值等。
  4. PC寄存器:PC, 即Program Counter,每一个线程用于记录当前线程正在执行的字节码指令地址。因为线程需要切换,当一个线程被切换回来需要执行的时候,知道执行到哪里了。
  5. 本地方法栈:为本地方法执行构建的内存空间,存放本地方法执行时的局部变量、操作数等。

所谓本地方法,简单的说是非Java实现的方法,例如操作系统的C编写的库提供的本地方法,Java调用这些本地方法接口执行。但是要注意,本地方法应该避免直接编程使用,因为Java可能跨平台使用,如果用了Windows API,换到了Linux平台部署就有了问题。

虚拟机

目前Oracle官方使用的是HotSpot, 它最早由一家名为"Longview Technologies"公司设计,使用了很多优秀的设计理念和出色的性能,1997年该公司被SUN公司收购。后来随着JDK一起发布了HotSpot VM。目前HotSpot是最主要的VM。

安卓程序需要运行在JVM上,而安卓平台使用了Google自研的Java虚拟机——Dalvid,适合于内存、处理器能力有限系统。

GC垃圾收集器

堆内存里面经常创建、销毁对象,内存也是被使用、被释放。如果不妥善处理,一个使用频繁的进程,将可能有内存容量,但是无法分配出可用内存空间,因为没有连续成片的内存了,内存全是碎片化的数据。

在这里插入图片描述

回收基本算法
1. 引用计数
每一个堆内对象上都与一个私有引用计数器,记录着被引用的次数,引用计数清零,该对象所占用堆内存就可以被回收。循环引用的对象都无法引用计数归零,就无法清除。
2. 标记-清除 Mark-SweepMarkSweep
分垃圾标记阶段和内存释放阶段。标记阶段,找到所有可访问对象打个标记。清理阶段,遍历整个堆,对未标记对象清理。

由图可知,标记-清除最大的问题会造成内存碎片。

  1. 复制 CopyingCopying
    先将可用内存分为大小相同两块区域A和B,每次只用其中一块,比如A。当A用完后,则将A中存活的对象复制到B。复制到B的时候连续的使用内存,最后将A一次性清除干净。缺点是比较浪费内存,能使用原来一半的内存,因为内存对半划分了,复制过程毕竟也是有代价。好处是没有碎片,复制过程中保证对象使用连续空间。
  2. 标记-压缩 Mark-CompactMarkCompact
    分垃圾标记阶段和内存整理阶段。标记阶段,找到所有可访问对象打个标记。内存清理阶段时,整理时将对象向内存一端移动,整理后存活对象连续的集中在内存一端。

由上图可知,标记-压缩算法好处是整理后内存空间连续分配,有大段的连续内存可分配,没有内存碎片。缺点是内存整理过程有消耗。
5. 分代收集算法
既然上述垃圾回收算法都有优缺点,能不能对不同数据进行区分管理,不同分区对数据实施不同回收策略,分而治之。

1.7及以前,堆内存分为新生代、老年代、持久代。
1.8开始,持久代没有了,取而代之MetaSpace。

STW

对于大多数垃圾回收算法而言,GC线程工作时,停止所有工作的线程,称为Stop The World。GC完成时,恢复其他工作线程运行。这也是JVM运行中最头疼的问题。

分代堆内存

在这里插入图片描述

Heap堆内存分为
+ 新生代:刚刚创建的对象
- 伊甸园区
- 存活区Servivor Space:有2个存活区,一个是from区,一个是to区。它们大小相等、地位相同、可互换。
+ to指的是本次复制数据的目标区
+ 老年代:长时间存活的对象
+ 持久代:JVM的类和方法

新生代回收

起始时,所有新建对象都出生在eden,当eden满了,启动GC。这个称为Young GC,Minor GC。

先标记eden存活对象,然后将存活对象复制到s0(假设本次是s0,也可以是s1,它们可以调换),eden剩余所有空间都“清空”。GC完成。

继续新建对象,当eden满了,启动GC。

先标记eden和s0中存活对象,然后将存活对象复制到s1。将eden和s0“清空”。

继续新建对象,当eden满了,启动GC。

先标记eden和s1中存活对象,然后将存活对象复制到s0。将eden和s1“清空”。

以后就重复上面的步骤。

大多数对象都不会存活很久,而且创建活动非常多,新生代就需要频繁垃圾回收。

但是,如果一个对象一直存活,它最后就在from、to来回复制,如果from区中对象复制次数达到阈值,就直接复制到老年代。

老年代回收

进入老年代的数据较少,所以老年代区被占满的速度较慢,所以垃圾回收也不频繁。老年代GC称为Old GC,Major GC。

由于老年代对象一般来说存活次数较长,所有较常采用标记-压缩算法。

Full GC:对所有“代”的内存进行垃圾回收
Minor GC比较频繁,Major GC较少。但一般Major GC时,由于老年大对象也可以引用新生代对象,所以先进行一次Minor GC,然后在Major GC会提高效率。可以认为回收老年代的时候完成了一次Full GC。

GC触发条件
Minor GC触发条件:当eden区满了触发
Full GC触发条件:
+ 老年代满了
+ 新生代搬向老年代,老年代空间不够
+ 持久代满了
+ System.gc()手动调用。不推荐

调整策略
减少STW时长,串行变并行
减少GC次数,调整内存大小

对JVM调整策略应用极广
+ 在WEB领域中Tomcat等
+ 在大数据领域Hadoop生态各组件
+ 在消息中间件领域的Kafka等
+ 在搜索引擎领域的ElasticSearch、Solr等

在不同领域对JVM需要不同的调整策略

垃圾收集器类型

按回收线程数:
+ 串行垃圾回收器:一个GC线程完成回收工作
+ 并行垃圾回收器:多个GC线程同时一起完成回收工作,充分利用CPU资源

指的是GC线程是否串并行

按工作模式不同:
+ 并发垃圾回收器:让GC线程垃圾回收某些阶段可以和工作线程一起进行。
+ 独占垃圾回收器:只有GC在工作,STW一直进行到回收完毕,工作线程才能继续执行。

在这里插入图片描述

指的是GC线程和工作线程是否一起运行
一般情况下,我们大概可以使用以下原则:
客户端或较小程序,内存使用量不大,可以使用串行回收;
对于服务端大型计算,可以使用并行回收;
大型WEB应用,用户端不愿意等,尽量少的STW,可以使用并发回收;

  • -X选项 稳定的选项
  • -XX:选项名称 beta选项,但是有些就一直保留下来了
参数说明举例
-Xms设置应用程序初始使用的堆内存大小(新生代+老年代)-Xms2g
-Xmx设置应用程序能获得的最大堆内存
早期JVM不建议超过32G,内存管理效率下降
-Xms4g
-XX:NewSize设置初始新生代大小
-XX:MaxNewSize设置最大新生代内存空间
-XX:NewRatio以比例方式设置新生代和老年代-XX:NewRatio=2
new/old=1/2
-XX:SurvivorRatio以比例方式设置eden和survivor-XX:SurvivorRatio=6
eden/survivor=6/1
survivor/new=1/8
-Xss设置线程的栈大小
$ java -Xms512m -Xmx1g HelloWorld
// 测试用java程序
// javac HelloWorld.java
// java -Xms512m -Xmx1g HelloWorld
public class HelloWorld extends Thread {
public static void main(String[] args) {
try {
while (true) {
Thread.sleep(2000);
System.out.println("hello magedu");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

Tomcat设置
默认不指定,-Xmx大约使用了1/4的内存,当前本机内存指定约为1G。
在bin/catalina.sh中增加

JAVA_OPTS="-server -Xmx512m -Xms128m -XX:NewSize=48m -XX:MaxNewSize=200m"

-server:VM运行在server模式,为在服务器端最大化程序运行速度而优化
-client:VM运行在Client模式,为客户端环境减少启动时间而优化
重启Tomcat,观察。

# ps aux | grep java
root 8980 2.7 12.3 3050788 123216 pts/0 Sl 21:53 0:05 /usr/java/default/bin/java -
Djava.util.logging.config.file=/usr/local/tomcat/conf/logging.properties -
Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Xmx512m -Xms128m -
XX:NewSize=48m -XX:MaxNewSize=200m -Djdk.tls.ephemeralDHKeySize=2048 -
Djava.protocol.handler.pkgs=org.apache.catalina.webresources -
Dorg.apache.catalina.security.SecurityListener.UMASK=0027 -Dignore.endorsed.dirs= -classpath
/usr/local/tomcat/bin/bootstrap.jar:/usr/local/tomcat/bin/tomcat-juli.jar -
Dcatalina.base=/usr/local/tomcat -Dcatalina.home=/usr/local/tomcat -
Djava.io.tmpdir=/usr/local/tomcat/temp org.apache.catalina.startup.Bootstrap start

垃圾回收器

新生代
+ 新生代串行收集器:单线程、独占式串行,回收算法标记-复制
+ 新生代并行收集器:将单线程的串行收集器变成了多线程并行、独占式
+ 新生代并行回收收集器:多线程并行、独占式,使用复制算法,关注调整吞吐量

老年代:
+ 老年代串行收集器:单线程、独占式串行,回收算法使用标记-压缩
+ 老年代并行回收收集器:多线程、独占式并行,回收算法使用标记-压缩,关注调整吞吐量
+ CMS收集器
- Concurrent Mark Sweep并发标记清除算法
- 在某些阶段尽量使用和工作线程一起运行,减少停顿时长。是互联网站点服务端BS系统上较佳的回收算法
- 分为4个阶段:初始标记、并发标记、重新标记、并发清除,在初始标记、重新标记时需要STW。
+ G1收集器
- Garbage First是最新垃圾回收器,从JDK1.6实验性提供,JDK1.7发布,其设计目标是在多处理器、大内存服务器端提供优于CMS收集器的吞吐量和停顿控制的回收器。建议JDK8再考虑它,目前生成环境慎用。
- 基于标记-压缩算法。+UseG1GC
- 分为4个阶段:初始标记、并发标记、最终标记、筛选回收。并发标记并发执行,其它阶段STW只有GC线程并行执行。

垃圾收集器设置

可以单独指定新生代、老年代的垃圾收集器
+ -XX:+UseSerialGC
- 运行在Client模式下,新生代、老年代都启用串行收集器
+ -XX:+UseParNewGC
- 新生代使用并行收集器,老年代使用串行收集器
+ -XX:+UseParallelGC
- 新生代使用并行回收收集器,老年代使用串行收集器
+ -XX:+UseParallelOldGC
- 新生代、老年代都是用并行回收收集器
- -XX:ParallelGCThreads=8,在关注吞吐量的场景使用它增加并行线程数
+ -XX:+UseConcMarkSweepGC
- 新生代使用并行收集器,老年代使用CMS收集器
- 响应时间要短,停顿短使用这个垃圾收集器
- -XX:CMSInitiatingOccupancyFraction=N,N为0-100整数表示达到老年代的大小的百分比多少触发回收
+ 默认68
- 由于CMS算法有碎片产生,还可以将-XX:+UseCMSCompactAtFullCollection开启,在CMS收集后,进行一次内存碎片整理。-XX:CMSFullGCsBeforeCompaction=N设定多少次CMS后,进行一次整理

-XX:+UseConcMarkSweepGC -XX:+UseCMSCompactAtFullCollection
-XX:CMSFullGCsBeforeCompaction=5

将参数加入到bin/catalina.sh中,重启观察Tomcat status。老年代已经使用CMS
+ 开启垃圾回收统计信息
- -XX:+PrintGC 输出GC信息
- -XX:+PrintGCDetails 输出GC详细信息
- -XX:+PrintGCTimeStamps 与前两个组合使用,在信息上加上一个时间戳
- -XX:+PrintHeapAtGC 生成更多信息供分析,日志会更大
- 以上调试完成后,请移除这些参数,否则有非常多的日志输出

工具

$JAVA_HOME/bin下

命令说明
jps 查看所有jvm进程
jinfo 查看进程的运行环境参数,主要是jvm命令行参数
jstat 对jvm应用程序的资源和性能进行实时监控
jstack 查看所有线程的运行状态
jmap 查看jvm占用物理内存的状态
jhat +UseParNew
jconsole
jvisualvm

jps:Java virutal machine Process Status tool,
jps [-q] [-mlvV] [<hostid>]
-q:静默模式;
-v:显示传递给jvm的命令行参数;
-m:输出传入main方法的参数;
-l:输出main类或jar完全限定名称;
-v:显示通过flag文件传递给jvm的参数;
[<hostid>]:主机id,默认为localhost;
详细列出当前Java进程信息
# ./jps -l -v
jinfo:输出给定的java进程的所有配置信息;
jinfo [option] <pid>
-flags:打印 VM flags
-sysprops:to print Java system properties
-flag <name>:to print the value of the named VM flag
先获得一个java进程ID,然后jinfo
# jps
# jinfo 6822
# jinfo -flags 6822
jstat:输出指定的java进程的统计信息
jstat -help|-options
jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]
[<interval> [<count>]]
interval:时间间隔,单位是毫秒;
count:显示的次数;
返回可用统计项列表
# jstat -options
-class:class loader
-compiler:JIT
-gc:gc
-gccapacity:统计堆中各代的容量
-gccause:
-gcmetacapacity
-gcnew:新生代
-gcnewcapacity
-gcold:老年代
-gcoldcapacity
-gcutil
-printcompilation
# jstat -gc 6822
YGC:新生代的垃圾回收次数;
YGCT:新生代垃圾回收消耗的时长;
FGC:Full GC的次数;
FGCT:Full GC消耗的时长;
GCT:GC消耗的总时长;
3次,一秒一次
# jstat -gcnew 6822 1000 3

程序员常用堆栈情况查看工具

jstack:查看指定的java进程的线程栈的相关信息;
jstack [-l] <pid>
jstack -F [-m] [-l] <pid>
-l:long listings,会显示额外的锁信息,因此,发生死锁时常用此选项;
-m:混合模式,既输出java堆栈信息,也输出C/C++堆栈信息;
-F:当使用“jstack -l PID"无响应,可以使用-F强制输出信息;
先获得一个java进程ID,然后jinfo
# jps
# jstack -l 6822
jmap:Memory Map, 用于查看堆内存的使用状态;
查看进程堆内存情况
# jmap -heap 6822
jhat:Java Heap Analysis Tool
jmap [option] <pid>
查看堆空间的详细信息:
jmap -heap <pid>
查看堆内存中的对象的数目:
jmap -histo[:live] <pid>
live:只统计活动对象;
保存堆内存数据至文件中,而后使用jvisualvm或jhat进行查看:
jmap -dump:<dump-options> <pid>
dump-options:
live dump only live objects; if not specified, all objects in the heap are
dumped.
format=b binary format
file=<file> dump heap to <file>

Tomcat常用配置

1、内存空间优化

JAVA_OPTS="-server -Xms32g -Xmx32g -XX:NewSize= -XX:MaxNewSize= "
-server:服务器模式
-Xms:堆内存初始化大小;
-Xmx:堆内存空间上限;
-XX:NewSize=:新生代空间初始化大小;
-XX:MaxNewSize=:新生代空间最大值;

2、线程池调整

<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />

常用属性:

  • maxThreads:最大线程数,默认200;
  • minSpareThreads:最小空闲线程数;
  • maxSpareThreads:最大空闲线程数;
  • acceptCount:当启动线程满了之后,等待队列的最大长度,默认100;
  • URIEncoding:URI地址编码格式,中文建议使用UTF-8;
  • enableLookups:是否启用客户端主机名的DNS反向解析,缺省禁用,建议禁用,就使用客户端IP就行;
  • compression:是否启用传输压缩机制,建议“on",CPU和流量的平衡;
    • compressionMinSize:启用压缩传输的数据流最小值,单位是字节;
    • compressableMimeType:定义启用压缩功能的MIME类型;
      text/html, text/xml, text/css, text/javascript

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注