什么JVM优化不熟悉?回去等消息吧

jvm 内存结构

前言

对于中级程序猿或者高级程序猿,可以说jvm这块知识是必须掌握的了吧,毕竟在跳槽找工作面试时,都会提问关于jvm的相关知识,与其死记硬背,不如静下心来主动捅破这层窗户,深入了解下关于jvm的知识点;

jvm 内存结构

jvm的内存结构主要分为 4部分,其中主要分析的是运行时数据区。运行时数据区包含 程序计数器,堆,栈,本地方法栈以及方法区,其中堆和方法区是线程共享的,其他的3种是线程私有的。jvm的优化主要发生在 堆和方法区
jvm内存结构

程序计数器

程序计数器线程私有的,他是一块比较小的内存空间,可以将它看成是当前线程执行字节码的行号显示器。更加确切的说,一个线程的执行,是通过字节码解释器来改变当前线程的计数器的值,来获取到下一条需要执行字节码的指令,从而保证线程的正常执行;

如果执行的是java程序,那么显示的是正在执行的虚拟机字节码指令地址,如果执行的是Native 程序,那么展示的是undefined ;

虚拟机栈

什么是栈

了解虚拟机栈之前,首先了解下什么是栈?
栈 是一种仅能在表头进行插入和删除的线性表结构,及所说的栈是一种先进后出的;
栈的数据结构
线程私有的,它的生命周期是和线程绑定在一起的,线程执行前,都会分配一个独立的栈空间;

栈中主要存储了什么

的元素是栈帧,每个方法在执行时都会创建一个栈帧,栈帧里主要存储了局部变量表,操作数栈,方法出口以及动态连接等信息;其实每个方法在调用执行的时候,就对应了一个入栈和出栈的过程。

局部变量表

在栈帧中,是由局部变量表进行存储数据的,那么局部变量表主要存储了基本数据类型的局部变量,对象的引用,但是局部变量表中并不存储对象的内容。局部变量表在程序进行编译时,进行内存空间的分配。

操作数栈

操作数栈的元素是java 中的任意数据类型,在编译时,操作数栈是空的,在方法执行的过程中,通过字节码指令完成操作数栈的入栈和出栈过程。通常在进行算术运算的时候是通过操作数栈完成的。所以可以说操作数栈是用于计算的临时储存区

栈的异常

栈的异常主要有两种:

  1. StackOverflowError:栈溢出错误
    线程所需要的栈内存大小> 配置允许的栈内存大小,那么java虚拟机会抛出 栈内存溢出溢出

  2. OutOfMemoryError:内存不足
    栈内存在进行扩展时,申请的内存大于剩余的内存时,会抛出内存不足的异常;

  3. 在面试过程中,会提问到,为什么使用递归会造成栈溢出? 原因是,递归每次调用时,都会创建一个栈帧,这样会导致,递归层数越深,所需要的栈的大小超过jvm设置的栈大小,所以会抛出StackOverflowError的异常;

什么是堆?

堆是完全二叉树,也就是说除了树的最后一层节点不需要是满的,其他的每一层从左到右都必须是满的。
堆结构

java堆

在java中,是java 虚拟机管理最大的内存区域,它是线程共享的,主要存放了new 关键字创建的对象。所有对象的实例都是在堆上进行分配的,它也是发生垃圾回收的主要区域;

java 堆又分为年轻代(Young Generation) ,年老代(Old Gereration,且年轻代和年老代的比例是1:2 的关系。在年轻代中又分为 Eden , to Survivor ,from Survivor 空间,他们的关系是8:1:1 的关系。
堆得内存结构
年轻代中主要存储的是“新生对象”,当年轻代满了之后,会出发minor gc 来清理年轻代内存;

年老代中主要存储的是长期存活的对象或者大对象,所以在新生代多次未清理掉的对象会进入年老代中。当年老代满了之后,会出发full gc

需要注意的是,full gc不单单是只清理年老代,而是清理整个内存空间,包含年轻代和年老代。如果full gc 清理之后,还是无法存储对象,那么就会发生我们常见的内存溢出;
堆内存分配

方法区

什么是方法区

方法区是同java 的堆一样,是线程共享的,主要用来存储已经被加载的类的信息,常量,静态变量,以及及时编译器编译后的代码。可以更形象的说 静态变量,常量,类信息,常量池都是存在方法区中的;

需要注意的是,在jdk1.8 之后,取消了方法区,而取而代之的是MetaSpace 元空间,元空间不是存在jvm中的,而是存在于本地内存中的;
方法区

java 垃圾回收

垃圾判断

既然要进行垃圾回收,首先需要判断哪些是垃圾。在java 中,判断是否是垃圾有两种方式,分别是 引用计数法,可达性分析

引用计数法

引用计数法很简单,就是一个对象被引用时加一,去除引用时减一,这样我们就可以进行判断引用计数是否为零 来判断当前对象是否是垃圾了。

已用计数方法虽然简单,但是存在一个问题,在进行循环引用时,无法判断是否为垃圾对象。例如:a ,b两个对象,a引用了 b, b引用了a ,但是a ,b对象未被其他对象引用,理论上来讲,a ,b 两个对象是属于垃圾回收范围内的,但是他们的计数器并不为0 ,所以无法将a ,b 两个对象进行垃圾回收;

可达性分析

可达性分析的思路是通过 “GC Roots ” 的对象作为起点,从这个起点向下搜索,所搜索的路径称为“引用链” ,当一个对象没有任何引用链的时候,那么证明这个对象是不可用的,则会被垃圾回收掉;
可达性分析
在java 中,可以作为 “GC Roots” 的对象主要有几下几种:

  1. 虚拟机栈中的引用对象
  2. 方法区中类静态引用对象
  3. 方法区中常量引用对象

垃圾回收算法

垃圾回收算法主要有: 标记-清除,标记-整理,复制 ,分代收集算法。

标记-清除

标记清除算法主要包含了标记清除两个阶段。标记阶段是由GC Roots 触发的可达对象。清除阶段是清除未被标记的对象。

缺点: 标记清除会产生内存碎片问题,如果内存碎片过多,会导致内存地址不连续,在后续内存分配是效率会降低;
标记清除

标记-整理

标记整理和标记清除很相像,只不过标记整理是在标记完之后,会将存活的对象向一边移动,然后清理未标记的对象
缺点:整理时比较耗时

标记整理

复制

复制算法是每次都将内存划分两块,每次只使用其中的一块,当这块用完时,会将存活的对象复制到另外一块内存上,然后将已经使用过的进行一次内存清理。
缺点: 标记占用内存,当对象存活率较高时,需要频繁的去复制,效率变低;
复制算法

分代收集

分代收集算法是针对不同的分代,采取不同的收集方法,在“新生代” 中以为对象存活率低,所以采用“复制算法” 进行收集,在“年老代” 中,因为对象存活率高,所以采用“标记-清除”,“标记-整理”算法。
新生代分为Eden 和两个大小相同的Survivor 区,所有新创建的对象都存放下Eden区,当Eden 区满了之后,会触发Minor Gc,然后会将仍然存活的对象移到其中一个Survivor 区,并始终保证一个Survivor 为空。如果在Survivor 区存放不了,那么Gc 收集器会将这些对象直接存放在Old 区,如果在Old 区也满了之后,就会触发full gc 了。full gc 会进行触发清理整个堆内存;

垃圾收集器

新生代的回收器主要有:Serial、ParNew、Parallel Scavenge(这三种算法都采用了标记-复制算法)
老年代回收器主要有:CMS、Serial Old、Parallel Old
分代收集:G1
垃圾回收器

CMS,G1

CMS收集的步骤为:
初始标记–>并发标记–>重新标记–>并发清除
初始标记:需要停止运行程序的,初始标记仅仅只是标记一下GC Roots能关联到的对象。
并发标记:进行GC Roots Tracing 的过程,就是追踪与GC Roots关联的对象是否还在使用中。
重新标记:在上一步并发标记时,程序是在运行的,会产生新的垃圾对象,对象地址也有所变动,所以需要重新标记一下,但是这样也会导致程序暂停。
并发清除:这个阶段就是清除垃圾对象,因为是并行运行的,所以这时也会产生新的垃圾,但是这些垃圾是清理不掉的,因为没有被标记,所以只有等下次GC回收。
CMS清除算法有2个缺点:

  1. 因为使用的是标记-清除算法,所以会产生内存碎片,无法回收并发清除阶段产生的垃圾, 对CPU资源很敏感。
  2. 无法处理浮动垃圾,可能出现"Concurrent Mode Failure"失败(浮动垃圾:新线程产生的垃圾)

G1回收算法(慢慢要替代CMS回收算法)

为解决CMS算法产生空间碎片和其它一系列的问题缺陷.G1中提供了三种模式垃圾回收模式,young gc、mixed gc 和 full gc,在不同的条件下被触发。
发生在年轻代的GC算法,一般对象(除了巨型对象)都是在eden region中分配内存,当所有eden region被耗尽无法申请内存时,就会触发一次young gc.
当越来越多的对象晋升到老年代old region时,为了避免堆内存被耗尽,虚拟机会触发一个混合的垃圾收集器
缺点:G1垃圾回收器对内存的要求较高,一般建议4G以上使用

JDK 性能监控

  1. 在生产环境上多多少少会碰到过jvm 内存溢出情况,这个时候需要分析溢出原因,就需要一些工具和命令进行监控。
    在jdk 中提供的非常强大的可视化工具jvisualvm.exe
    jvisulvm
  2. 另外可以使用命令可以进行对jdk 的使用情况进行监控,以下是常用命令;

查看虚拟机进程: jps 命令

jps 命令可以列出所有的java 进程。
除此之外,还有一些参数可以自定义输出:
-q 指定jps只输出进程ID
-m 输出传递给Java进程的参数
-l 输出主函数的完整路径
-v 显示传递给Java虚拟机的参数

虚拟机统计信息:jstat 命令

jstat 用于观察 Java 堆信息的详细情况
命令: jstat -option 进程id

参数含义
-class监视类装载、卸载数量、总空间以及类装载所耗费的时间
-gc监视Java堆状况,包括Eden区、两个Survivor区、老年代、永久代等的容量、已用空间、GC时间合计等信息
-gccapacity监视内容与-gc基本相同,但输出主要关注Java堆各个区域使用到的最大、最小空间
-gcutil监视内容与-gc基本相同,但输出主要关注已使用空间占总空间的百分比
-gccause与-gcutil功能一样,但是会额外输出导致上一次GC产生的原因
-gcnew监视新生代GC状况
-gcnewcapacity监视内容与-gcnew基本相同,输出主要关注使用到的最大、最小空间

例:
jstat -gcutil 2365

S0S1EOPYGCYGCTFGCFGCTGCT
0.000.0012.050.0014.7100.00000.000.00

其中每个选项的意义如下:

S0、S1 表示Survivor0、Survivor1,还未使用。
E 表示Eden区使用了12.05%的空间。
O 表示老年代还未使用。
P 表示永久代使用了14.17%的空间
YUC、YGCT 表示从程序运行以来一共发生了0次Minor GC(YGC,Young GC),总共耗时0秒。
FGC、FGCT 表示从程序运行以来一共发生了0次Full GC(FGC,Full GC),总共耗时0秒。

查看虚拟机参数:jinfo 命令

命令: jinfo -option pid

导出堆到文件:jmap 命令

jmap 是一个多功能命令,可以生成 Java 程序的 Dump 文件,也可以查看堆内对象实例的统计信息、查看 ClassLoader 的信息以及 finalizer 队列。
命令: jmap -option pid

堆分析工具:jhat 命令

jhat 命令用于分析 Java 应用的对快照内存。Sun JDK 提供了 jhat 命令与 jmap 搭配使用,来分析 jmap 生成的堆转储快照。jhat 内置了一个微型的 HTTP/HTML 服务器,生成 dump 文件的分析结果后,可以在浏览器中查看( http://localhost:7000)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值