Java虚拟机

前面我们提到了、方法区以及常量池的概念,相信第一次看的朋友们一定是云里雾里,此篇特来梳理一下完整版,填坑:

JVM可以总结6部分内存模型、垃圾回收、类加载、执行模式、性能调优、编译器优化。 其中3部分是最常提到的,也会重点

  1. 内存模型:包含堆、方法区、栈、本地方法栈、程序计数器:

堆: 年轻代(s0区,s1区,eden区)、老年代

(1).堆空间数据线程共享的,主要存储对象实例、数组、成员变量、初始化的对象;

(2).对象是GC隐式释放并回收内存,垃圾回收主要在堆中进行。

(3).TLAB:堆为每个线程初始化对象时会分配独立空间(TLAB),避免加锁同步来高效率,堆分配的TLAB区在eden区上即eden既有TLAB这样的私有区又有本身eden减去TLAB的剩余公有区。

(4). TLAB区的对象创建是私有的,但读取对象垃圾回收都是线程共享行为。

(5).大对象无法TLAB中分配,可能在eden剩余空间或老年代(共享空间)中分配,则需同步控制,效率降低。

(6).一个线程TLAB空间不大,当创建的对象将要占满TLAB,剩余空间不足创建新对象时,有最大浪费空间策略:例如某线程的TLAB空间为100KB,用了80KB剩下20KB,设置最大浪费阈值25KB,新对象占用空间>25KB则直接共享空间分配;如果<=25KB,则重新分配一个TLAB 空间供新对象用。

(7).年轻代空间占比:eden:s0:s1=8:1:1, eden的空间用于创建对象,s0或s1每次回收都交换状态,1个存上次回收存活的对象,1个为空;每次垃圾回收eden区+s0/s1(有存活对象的1个),回收后的存活对象再放入s0/s1(空那1个),每次回收后分代年龄+1这就是复制算法。

(8).对象晋升规则:一般对象先进入eden区,大对象连续对象直接进老年代,分代年龄超过15的对象进入老年代,空间分配担保(当年轻代中相同分代年龄的对象数量占总空间的50%以上时,大于等于此年龄的对象都进入老年代)。

(9)年轻代与年老代的空间占比为1:2,如果年轻代过大,对象就会过早进入老年代容易被占满;如果年轻代空间过小复制算法操作会变频繁。

(10).有年轻代防止对象全进入年老代,回收,效率低;年轻代中对象大多生命短,使用复制法成本低。

方法区:(1.7版方法区的永久代替代为1.8内存的元空间,各种常量池也由堆划分)

(1).方法区为线程共享,其存储程序中唯一的数据(class、static变量、类结构等)。

(2).1.8版主要存储:静态变量、静态常量、对象类型数据、字节码/变量符

(3).运行时常量池(1.8版堆中):是类或接口的字节码文件中常量池的运行形式,主要包含2种:编译时已知数字字面量值、需要运行时解析的方法或变量引用。

(4).1.7方法区中永久代改为1.8版的本地内存的元空间,原因简述:

永久代存放多种类型的数据不易分类且回收复杂;字符串也存在永久代中有内存溢出问题以及性能问题;永久代空间大小制定难,空间太小容易溢出,空间太大会内存紧张。

元空间简化Full GC,并且每一个回收器有专门的元数据迭代器

程序计数器:

(1).线程私有,任意时刻一线程只能执行一个方法代码,线程切换时,要切换到程序计数器记录的位置。

栈:

(1).线程私有,调方法存入栈中,栈中存放的每个数据叫栈帧。

(2).调用方法创建帧入栈,正常完成或抛出异常会销毁帧出栈,每个线程都有方法栈。

(3).帧结构:局部变量表(大小根据帧大小提前确定)、操作数栈(传参与接返回值给父级,存储JVM正在执行指令)、动态链接(将方法引用符号转为运行时常量池的具体方法引用),除这3种外还有一些其他,主要是这3部分。

(4).局部变量表:存储8大基础值、地址值;32bit一个单位,存一个类型的值,long和double为64bit占2单位,读时从前32bit开始,但写时从后32bit写可能数据错乱,32位CPU写可能会有Long/Double异常、64位CPU写不会异常。

(5).静态帧(静态方法)的局部变量表索引从0开始;普通帧的局部变量表索引从1开始,因为0位存除的是this引用。

本地方法栈:

(1).native方法不是Java所写,JVM不加载,也无需提供对应的方法栈

(2).本地方法:Java调用非Java代码的接口,方法非Java实现。

  1. 垃圾回收:

(1).栈、本地方法栈、方法区、程序计数器都不需GC回收,GC主要在堆中进行。

(2).finalize方法:年轻内存充足时不会执行、其主要用于回收-JNI调用程序(C或C++)如本地方法。

(3).垃圾识别方式:引用计数(存在相互引用的问题); 可达路径分析(判断有无gcRoot对象的引用),无gcRoot引用,初次执行finalize方法,若其中添加gcRoot引用则不回收,若初次执行finalize方法未添加gcRoot引用,第一次也不回收,第二次回收时不再执行finalize方法,直接进行垃圾回收。

(4).gcRoot对象: 可以是栈或本地栈或方法区静态量以及方法区常量所引用的对象。

垃圾回收算法:

(1).复制:将存活的对象复制到另一分区,无内存碎片,但总闲置一半空间。

(2).标记清除:可达性判断标记,清除后产生内存碎片,无法存连续的对象。

(3).标记整理:可达性判断标记,移动至尾端,再清除,但频繁移动效率低。

(4).分代回收:年轻代-复制,年老代-标记清除或标记整理,依据垃圾回收器而不同。

(5).FullGC回收年轻和年老代造成STW性能消耗大,MinorGC也造成STW但消耗小。

(6).StopTheWorld(STW):除垃圾回收线程工作,其他工作线程挂起避免边回收边生产。

(7).SafePoint(安全点):此时没有线程工作,此时是GC垃圾回收线程切入的时机。

(8).SafePoint规则:太少造成GC时间长停顿长、太多造成GC较频繁负荷高。

垃圾回收器:

(1).Serial:单线程GC回收器适于Client模式虚拟机,年轻代默认复制算法。

(2).SerialOld:单线程GC回收器,年老代标记整理算法,适用于单核CPU。

(3).PerNew:多GC线程版Serial,适于Server模式,响应快且STW时间短,复制算法。

(4).CMS:多线程版,适于Server模式,年老代标记清除,以STW时间最短为目标,用标记清除节省整理内存的时间消耗。分为四步:初始标记、并发标记、重新标记、并发清除;初始标记和重新标记均STW,重新标记是因并发标记期有用户线程可能产新垃圾,缺点是吞吐量低(并发标记/并发清除可能有用户线程工作,默认启GC线程(CPU数+3)/4个)、浮动垃圾(并发标记/并发清除可能有用户线程产生垃圾,只能下次执行回收)、标记清除(内存碎片化,容易造成FullGC); CMS有2种机制回收触发:主动触发(内存不足)、被动触发(周期性2s执行回收一次)。

(5).ParallelScavenge:多线程、年轻代复制算法,关注吞吐量控制而非响应时间,适和用户交互少的后台运算,吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)

(6).ParallelOld:多线程、适于Server、年老代标记整理,吞吐量优先为目标。

(7).G1:多线程、适于Server、年轻年老同时用标记整理,支持用户线程并发且吞吐大,GC停顿时间可预测调整,优先回收大对象来防FullGC的STW时间长;缺点是需要记录新老代关系会占内存大、执行效率低、维护难;也是分为四步:初始标记、并发标记(有用户线程)、最终标记、筛选回收;通过记忆集记录新老对象跨代的引用关系,避免回收有跨代引用的同时不必全扫描查找。记忆集存储跨代引用指针的集合,根据记录精度分3种:字长精度(记录机器字长)、对象精度(记录对象)、卡精度(记录1块内存区);G1用卡精度:卡表将内存切为多个部分,1个部分中只要有一个新老关系引用,此部分标记为扫描,以此代替全空间扫描,卡精度的新老两者关系类似复制算法。

(8).ZGC:支持TB级内存、停顿时间10ms内、程序吞吐小于15%,无新老代区分。标记/复制/迁移过程均是并发,比G1更好的处理大对象,高吞吐低延迟,但也有浮动垃圾。内存分三类容量:小型容量2MB空间(存<256KB的对象)、中型容量32MB空间(存>256K且<4MB的对象)、大型容量动态变化(必需2MB的整数倍)存>4MB的对象。

GC类型:因为需要判断栈上数据的类型则有以下类型GC

(1).保守式GC:据一些条件猜测,不符则回收,符合的不一定都是不回收的,但都保留。

(2).半保守式GC:在对象上记录类型信息,知晓对象所包含信息,不完全用条件猜测。

(3).准确式GC:准确知道对象的类型、栈上的引用的类型,映射表(OopMap)记录此类型对象的偏移量和对应的类型数据,形成(映射表)OopMap供GC使用。

(4).安全点介入时机:循环尾部(非counted循环)、方法返回前/call指令后、异常位置。

(5).增量式GC:应用线程执行中,穿插执行GC可抽象为三色标记(代表GC中对象状态):

三色标记算法:白代表未搜索到的对象、灰代表未搜索完的对象、黑代表已搜索完的对象;GC执行前,对象均为白色,有gcRoot引用的对象压栈并标为灰色,搜索其子对象标为灰入栈,子对象均为灰入栈后,根对象标黑结束,黑根的子级再搜索,重复上述过程。GC结束后,没有灰色,只有黑色存活,白色回收掉,下次GC重置黑色为白色,再计算。

(6).多标问题:黑对象的父引用被删,黑无可达根,但为黑则需下次GC回收,可接受。

(7).漏标问题:使用的对象被当垃圾回收,问题严重不可接受;2种情况对应2种解决:1-插入黑对白的引用,当黑不再搜索时则白也不被搜索、2-删灰对白的引用,白未被搜索就不会标灰。解决漏标:1-增量更新(写屏障-将白标灰)、2-STAB(写屏障-保留要删的引用-将白标灰)

3. 类加载:

(1).前置流程:java代码文件、经过编译器(词法分析,语法分析,语义分析,字节码生成器)后变为.class文件、进入JVM执行类加载。

(2).类加载:.class文件验证(判断名称合法性)、准备(开辟存储空间)、解析(符号转为直接引用)、初始化、销毁(GC回收)。

(3).进入系统:类加载后执行字节码校验与翻译和编译执行JIT,最后进入操作系统。

(4).双亲委派机制:启动类加载器、扩展类加载器、系统类加载器、自定义类加载器;上级可以创建则上级创建,避免类重复加载和java核心Api被篡改风险。

(5).跨平台性:JVM通过加载.class文件(字节码文件),实现不同平台安装JVM能运行。

(6).两个对象只有全路径名相同并且类加载器也相同时,确定为同一对象。

4. 性能调优

(1).jps指令:显示指定系统所有虚拟机进程。

(2).jstat指令:监控虚拟机运行状态,显示类加载、内存、垃圾回收等运行数据。

(3).jmap指令:生成heap dump文件。

(4).jhat指令:分析生成的dump文件 可在浏览器中查看。

(5).jstack指令:生成虚拟机当前线程快照。

(6).jinfo指令:实时查看和调整虚拟机运行参数。

(7).jconsole工具:监控JVM的内存、线程、类等。

(8).jvisualvm工具:分析内存和线程快照,监控内存和GC变化。

(9).MAT工具:查找内存泄漏和减少内存消耗。

(10).GChisto工具:分析GC日志工具。

(11).调优目标: GC低停顿、GC低频率、低内存占吞吐量。

(12).JVM调优:分析GC和dump(判断是否需优化),确定瓶颈点、调优量化标。

5. 执行模式

(1).解释执行模式:代码全解释执行不做JIT编译,立即执行,节省时间和内存。

(2).编译执行模式:代码全JIT编译执行,代码编译为本地代码,执行效率高。

(3).混合执行模式:部分解释/部分编译-执行,高频调的函数-编译执行,默认混合模式

6. 编译器优化:编译器优化技术:

(1).公共子表达式消除(即公共部分已计算不必再计算)。

(2).数组边界检查消除(确定访问的数组位不超过范围时不进行边界检查)。

(3).方法内联(剔除无用方法,没必要的方法跳转放入发起调用的方法中)。

(4).逃逸分析(定义的对象,外部方法访问-方法逃逸,外部线程访问-线程逃逸),无逃逸的对象可优化 (栈上分配空间、消除线程同步、对象不拆解-整体使用)。

以上就是个人的总结,一些知识点还是没说到,篇幅有限多多包涵,后记一些操作笔记吧:

JVM调优前工作:

配置jstatd的远程RMI服务(远程服务器上看JAVA程序GC情况),可JVM工具查看使用情况

grant codebase "file:${java.home}/../lib/tools.jar" { permission java.security.AllPermission;  };

代码存为文件jstatd.all.policy,放JAVA_HOME/bin中,执行which java查JAVA_HOME目录

执行命令jstatd -J-Djava.security.policy=jstatd.all.policy -J-Djava.rmi.server.hostname =服务器ip地址 & 执行C:\glassfish4\jdk7\bin\jvisualvm.exe 打开JVM控制台。

工具--插件--中找到Visual GC插件进行安装.

对要执行java程序进行调优,在该jar包所在目录下建立一个start.sh文件,文件内容如下

java

-server

-Xms4G

-Xmx4G

-Xmn2G

-XX:SurvivorRatio=1

-XX:+UseConcMarkSweepGC

-Dcom.sun.management.jmxremote

-Dcom.sun.management.jmxremote.port=1100

-Dcom.sun.management.jmxremote.authenticate=false

-Dcom.sun.management.jmxremote.ssl=false

-jar c1000k.jar&

通过配置,使用JVM控制台即可查看JVM(CPU/内存)及垃圾回收的情况

控制台配置   打开\jvisualvm.exe,远程---添加远程主机---输入远程IP----添加JMX连接

JVM调优核心:调整年轻代、年老代的内存空间大小、GC回收器的类型等,分析start.sh内容

S0C是:Survivor0区的分配空间

S0U是:Survivor1区的已经使用的空间

EC是:Eden区所使用的空间

EU是:Eden区当前使用的空间

OC是:老年代分配的空间

OU是:老年代当前使用的空间

PC是:永久代分配的空间

PU是:永久代当前使用的空间

YGC是:年轻代发生的次数,这里是354次

YGCT是:年轻代发送的总时长,这里是54.272秒,因此每次年轻代发生GC,即平均每次stop-the-world的时长为54.272/354=0.153秒。

FGC是:年老代回收的次数,或者成为FullGC的次数。

FGCT是:年老代发生回收的总时长。

GCT是:包括年轻代YGC及年老代FGC的总时间长。

MAC电脑上可以通过jconsole调出图形化分析工具。

声明:因文章是个人笔记,顾偏向于知识总结,文中提到的相关知识还需细化学习,因篇幅有限无法逐一讲解,最后谢谢支持与指正。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值