jvm优化_day01

jvm的三种运行参数
jvm的优化的作用:
	让程序能更好更快的跑起来,解决生产环境下出现的bug

jvm的三种运行参数:
1. 标准参数: 不会随着jdk版本的改变而变化
-version         #jdk版本
-showversion     #输出jdk版本信息,并继续执行后面的命令,在调试时非常有用
-help			#检索出所有的标准参数
-D			    #设置jvm的属性参数,eg: -Dstr=hello
-server			#以更多的初始堆内存来启动服务,使用并行垃圾回收器,启动速度较慢,只支持64位的操作系统
-client			#以较少的初始堆内存来启动服务,使用串行垃圾回收器,启动速度快,3264位都支持

2. -X参数:非标准参数,在不同的jdk版本下,命令可能不同
-X			    #查看当前jdk版本所有的-X参数
-Xms<size>       #设置初始Java堆大小,eg: java -Xms10m TestJVM 
-Xmx<size>       #设置最大Java堆大小,eg: java -Xmx20m TestJvm

3. -XX参数:非标准参数,主要用于jvm调优和debug操作,-XX参数的使用有2种方式,一种是boolean类型,一种是非boolean类型:
* boolean类型
  - 格式:-XX:[+-]<name> 表示启用或禁用<name>属性
  - 如:-XX:+DisableExplicitGC 表示启用禁止手动调用gc操作,也就是说调用System.gc()无效
*boolean类型
  - 格式:-XX:<name>=<value>  表示<name>属性的值为<value>
  - 如:-XX:NewRatio=1 表示新生代和老年代的比值
  

查看jvm运行参数:
第一种,运行java命令时打印出运行参数:
	运行java命令时打印参数,需要添加-XX:+PrintFlagsFinal参数即可
	eg: java -XX:+PrintFlagsFinal -version
参数有boolean类型和数字类型,值的操作符是=:=,分别代表默认值和被修改的值。
第二种,查看正在运行的java进程的参数:
	首先使用jps -l查看正在运行的java程序的pid,再用jinfo -flags <进程pid>查看正在运行的java进程的参数
jdk1.7堆内存模型
* Young 年轻区(代)
  Young区被划分为三部分,Eden区和两个大小严格相同的Survivor区,其中,Survivor区间中,某一时刻只有其中一个是被使用的,另外一个留做垃圾收集时复制对象用,在Eden区间变满的时候, GC就会将存活的对象移到空闲的Survivor区间中,根据JVM的策略,在经过几次垃圾收集后,任然存活于Survivor的对象将被移动到Tenured区间。
* Tenured 年老区
  Tenured区主要保存生命周期长的对象,一般是一些老的对象,当一些对象在Young复制转移一定的次数以后,对象就会被转移到Tenured区,一般如果系统中用了application级别的缓存,缓存中的对象往往会被转移到这一区间。
* Perm 永久区
  Perm代主要保存class,method,filed对象,这部份的空间一般不会溢出,除非一次性加载了很多的类,不过在涉及到热部署的应用服务器的时候,有时候会遇到java.lang.OutOfMemoryError : PermGen space 的错误,造成这个错误的很大原因就有可能是每次都重新部署,但是重新部署后,类的class没有被卸载掉,这样就造成了大量的class对象保存在了perm中,这种情况下,一般重新启动应用服务器可以解决问题。
* Virtual区:
  最大内存和初始内存的差值,就是Virtual区。

在这里插入图片描述

jdk1.8堆内存模型
jdk1.8的内存模型是由2部分组成,年轻代 + 年老代。
年轻代:Eden + 2*Survivor
年老代:OldGen
在jdk1.8中变化最大的Perm区,用Metaspace(元数据空间)进行了替换。
需要特别说明的是:Metaspace所占用的内存空间不是在虚拟机内部,而是在本地内存空间中,这也是与1.7的永久代最大的区别所在。 

在生产环境下,由于永久代内存经常不够用或发生内存泄露,爆出异常java.lang.OutOfMemoryError: PermGen。
基于此,将永久区废弃,而改用元空间,改为了使用本地内存空间。

在这里插入图片描述

堆内存的维护
1. 通过jstat命令进行查看堆内存使用情况(统计分析)
jstat [-命令选项] [vmid] [间隔时间/毫秒] [查询次数]
1.1 查看class加载统计
   	jstat -class <进程pid>
* 查询结果参数:
    - Loaded:加载class的数量
    - Bytes:所占用空间大小
    - Unloaded:未加载数量
    - Bytes:未加载占用空间
    - Time:时间

1.2 查看编译统计
	jstat -compiler <进程pid>
* 查询结果参数:
    - Compiled:编译数量。
    - Failed:失败数量
    - Invalid:不可用数量
    - Time:时间
    - FailedType:失败类型
    - FailedMethod:失败的方法
    
1.3 垃圾回收统计
	jstat -gc <进程pid>
* 查询结果参数:
    - S0C:第一个Survivor区的大小(KB)
    - S1C:第二个Survivor区的大小(KB)
    - S0U:第一个Survivor区的使用大小(KB)
    - S1U:第二个Survivor区的使用大小(KB)
    - EC:Eden区的大小(KB)
    - EU:Eden区的使用大小(KB)
    - OC:Old区大小(KB)
    - OU:Old使用大小(KB)
    - MC:方法区大小(KB)
    - MU:方法区使用大小(KB)
    - CCSC:压缩类空间大小(KB)
    - CCSU:压缩类空间使用大小(KB)
    - YGC:年轻代垃圾回收次数
    - YGCT:年轻代垃圾回收消耗时间
    - FGC:老年代垃圾回收次数
    - FGCT:老年代垃圾回收消耗时间
    - GCT:垃圾回收消耗总时间

2.通过jmap命令查看堆内存的详细数据
2.1 查看堆内存的详细使用情况
jmap -heap <进程pid>
2.2 参看堆内存中对象的详细情况
jmap -histo <进程pid>		     #查看所有活跃和非活跃的对象
jmap -histo:live <进程pid> 	 #查看所有活跃的对象
    #对象说明
    B  byte
    C  char
    D  double
    F  float
    I  int
    J  long
    Z  boolean
    [  数组,如[I表示int[]
    [L+类名 其他对象
2.3 将堆内存的详细数据dump(快照)到文件中,最终得到的是二进制的dat文件
jmap -dump:format=b,file=dumpFileName <pid>
eg: jmap -dump:format=b,file=d:/test.dat 8088

3.查看dump文件
3.1 使用jhat查看dump文件
jhat -port <port> <file>
eg: jhat -port 9999 d:/test.dat 
在浏览器输入: http://localhost:9999 查看dump文件

3.2 使用MAT工具查看dump文件
MAT(Memory Analyzer Tool,一个基于Eclipse的内存分析工具,是一个快速、功能丰富的JAVA heap分析工具,官网地址:https://www.eclipse.org/mat/

3.3 使用VisualVM工具查看dump文件
VisualVM是jdk自带的工具,可以查看几乎所有的jdk的命令,可以参看堆内存的使用情况,也可以查看线程的使用情况,在jdk/bin目录下,jvisualvm.exe
线程的维护
1. 线程快照命令
jstack <pid>      #将正在运行的jvm的线程情况进行快照,并且打印出来

2.线程的6中状态
在Java中线程的状态一共被分成6种:
- 初始态(NEW)
  - 创建一个Thread对象,但还未调用start()启动线程时,线程处于初始态。
- 运行态(RUNNABLE),在Java中,运行态包括 就绪态 和 运行态。
  - 就绪态
    - 该状态下的线程已经获得执行所需的所有资源,只要CPU分配执行权就能运行。
    - 所有就绪态的线程存放在就绪队列中。
  - 运行态
    - 获得CPU执行权,正在执行的线程。
    - 由于一个CPU同一时刻只能执行一条线程,因此每个CPU每个时刻只有一条运行态的线程。
- 阻塞态(BLOCKED)
  - 当一条正在执行的线程请求某一资源失败时,就会进入阻塞态。
  - 而在Java中,阻塞态专指请求锁失败时进入的状态。
  - 由一个阻塞队列存放所有阻塞态的线程。
  - 处于阻塞态的线程会不断请求资源,一旦请求成功,就会进入就绪队列,等待执行。
- 等待态(WAITING)
  - 当前线程中调用wait、join、park函数时,当前线程就会进入等待态。
  - 也有一个等待队列存放所有等待态的线程。
  - 线程处于等待态表示它需要等待其他线程的指示才能继续运行。
  - 进入等待态的线程会释放CPU执行权,并释放资源(如:锁)
- 计时等待态(TIMED_WAITING)
  - 当运行中的线程调用sleep(time)、wait、join、parkNanos、parkUntil时,就会进入该状态;
  - 它和等待态一样,并不是因为请求不到资源,而是主动进入,并且进入后需要其他线程唤醒;
  - 进入该状态后释放CPU执行权 和 占有的资源。
  - 与等待态的区别:到了超时时间后自动进入阻塞队列,开始竞争锁。
- 终止态(TERMINATED)
  - 线程执行结束后的状态。
  
3.产生死锁的4个必要条件:
互斥条件: 一个资源一次只能被一个线程持有
请求与保持条件: 一个线程获取到资源后,会继续请求别的资源,而且会保持之前的资源
不可剥夺条件: 已持久的资源是私有财产,神圣不可侵犯
循环等待条件: 每个线程都在等待循环等待别的线程先释放资源

4.死锁的解决:
使用jstack命令可以快速查找产生死锁的代码
	

在这里插入图片描述

死锁的经典代码
public class TestDeadLock {

    private static Object obj1 = new Object();
    private static Object obj2 = new Object();
    
    public static void main(String[] args) {
        new Thread(new Thread1()).start();
        new Thread(new Thread2()).start();
    }
    private static class Thread1 implements Runnable{
        @Override
        public void run() {
            synchronized (obj1){
                System.out.println("Thread1 拿到了 obj1 的锁!");
                try {
                    // 停顿2秒的意义在于,让Thread2线程拿到obj2的锁
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (obj2){
                    System.out.println("Thread1 拿到了 obj2 的锁!");
                }
            }
        }
    }
    private static class Thread2 implements Runnable{
        @Override
        public void run() {
            synchronized (obj2){
                System.out.println("Thread2 拿到了 obj2 的锁!");
                try {
                    // 停顿2秒的意义在于,让Thread1线程拿到obj1的锁
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                synchronized (obj1){
                    System.out.println("Thread2 拿到了 obj1 的锁!");
                }
            }
        }
    }
}

猜你喜欢

转载自blog.csdn.net/qq_42514129/article/details/85392229