简单理解JVM优化

几个基本概念

GCRoots对象都有哪些

所有正在运行的线程的栈上的引用变量。所有的全局变量。所有ClassLoader。。。

1.System Class .2.JNI Local 3.JNI Global 4.Thread Block 5.Busy Monitor 6.Java Local 7.Native Stack 8.Unfinalized 9.Unreachable 10.Java Stack Frame 11.Unknown


栈帧的解释

Java虚拟机栈(Java Virtual Machine Stacks)是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

简单地说,栈帧就是一个方法,里面有输入输出参数,局部变量表,返回值等信息,第一个参数一定是this


方法区说明

与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

举例:有一个HelloWorld的类如下

import java.text.SimpleDateFormat;
import java.util.Date;
import org.apache.log4j.Logger;
public class HelloWorld {
private static Logger LOGGER = Logger.getLogger(HelloWorld.class.getName());
public void sayHello(String message) {
SimpleDateFormat formatter = new SimpleDateFormat("dd.MM.YYYY");
String today = formatter.format(new Date());
LOGGER.info(today + ": " + message);
}
}

堆区、方法区、栈区存放的东西如下:

说明:堆区存放的是对象信息,方法区存放的是类信息、常量、静态变量、即时编译后的代码等数据;栈区存放的是线程、参数、变量、行号


JIT优化

JIT优化指的是即时编译器优化(Just In Time)

常规优化

1、禁用System.gc

因为System.gc会触发full GC,非常耗系统性能,所以要禁用

参数设置

-XX:-DisableExplicitGC,禁用了System.gc()的显示调用

2、逃逸分析与标量替换

分析对象动态作用域:当一个对象在方法中被定义后,它可能被外部方法所引用,例如作为调用参数传递到其他方法中,称为方法逃逸。 甚至还有可能被外部线程访问到,譬如赋值给类变量或可以在其他线程中访问的实例变量,称为线程逃逸。 栈上分配(Stack Allocation):如果确定一个对象不会逃逸出方法之外,那让这个对象在栈上分配内存将会是一个很不错的主意。由于HotSpot虚拟机目前的实现方式导致栈上分配实现起来比较复杂,因此在HotSpot中暂时还没有做这项优化。 同步消除(Synchronization Elimination):线程同步本身是一个相对耗时的过程,如果逃逸分析能够确定一个变量不会逃逸出线程,无法被其他线程访问,那这个变量的读写肯定就不会有竞争,对这个变量实施的同步措施也就可以消除掉。 标量替换(Scalar Replacement):标量(Scalar)是指一个数据已经无法再分解成更小的数据来表示了,Java虚拟机中的原始数据类型(int、 long等数值类型以及reference类型等)都不能再进一步分解,它们就可以称为标量。 相对的,如果一个数据可以继续分解,那它就称作聚合量(Aggregate),Java中的对象就是最典型的聚合量。 如果把一个Java对象拆散,根据程序访问的情况,将其使用到的成员变量恢复原始类型来访问就叫做标量替换。 如果逃逸分析证明一个对象不会被外部访问,并且这个对象可以被拆散的话,那程序真正执行的时候将可能不创建这个对象,而改为直接创建它的若干个被这个方法使用到的成员变量来代替。

标量替换的示例:

有一个类A
public class A{

     public int a=1;
     public int b=2


} 

方法getAB使用类A里面的a,b
private void getAB(){

     A x = new A();
     x.a;
     x.b;


}

JVM在编译的时候会直接编译成
private void getAB(){

     a = 1;
     b = 2;


}
这就是标量替换

参数设置:

逃逸分析默认是启用的,-XX:+DoEscapeAnalysis。后续有三种优化会进行:栈内分配,同步消除,标量替换


3、关闭偏向锁优化

偏向锁的概念:一把锁被使用之后不主动释放,保留给当前的使用者,预判等下一个进程来获取的时候再释放出来,

参数设置:

偏向锁关闭: -XX:-UseBiasedLocking

-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0


4、指针压缩

参数设置:

-XX:+UseCompressedOops


5、getter方法优化

指内联函数的优化,何为内联函数呢,即一个方法里面调用了另外一个方法,JVM在编译的时候把被调用的方法合入到调用的方法里面,这样就能减少栈帧的创建(因为每一个方法执行时都会创建一个栈帧),节约内存

使用示例:

方法1:
private void getA(){

    getB()


}

方法2:
private void getB(){

    system.out.print("getB");

}

如果配置了getter方法的优化参数,JVM在编译的时候会编译成如下形式

private void getA(){

    system.out.print("getB");


}

参数设置:

getter方法优化,-XX:UseFastAccessorMethods


JIT优化

1、开启服务端模式

开启服务端模式以后就有即时编译器和解释器两种执行引擎,执行效率最高的是即时编译器,所以我们做JIT优化的目的是尽量使代码使用即时编译器

参数设置:

-server


2、增加内联函数的可能性

增加函数内联的可能性能减少栈帧的创建,节约内存空间

参数设置:

使用final修饰函数向编译器建议可以内联,启动参数不宜设置,注意只是建议,具体是否内联看JVM决定


3、提高使用即时编译器的可能性

小方法:写方法时尽量不要写得太大,让JVM尽可能使用即时编译器编译代码

在启动项配置参数-XX:CompileThreshold=10000,使得一个方法被调用超过10000次以后使用即时编译器编译为机器码

OSR编译阈值

A、调用计数器,即方法被调用的次数,CompileThreshold,该值是指当方法被调用多少次后,就编译为机器码,client模式默认为1500次,server模式默认为1万次,可以在启动时添加-XX:CompileThreshold=10000来设置该值。 B、回边计数器,即方法中循环执行部分代码的执行次数,OnStackReplacePercentage,该值用于/参与计算是否触发OSR编译的阈值,client默认为933,sever默认为140,可以通过-XX:OnStackReplacePercentage=140来设置。 client模式下的计算规则为 CompileThreshold*OnStackReplacePercentage/100, server模式下计算规则为 CompileThreshold*(OnStackReplacePercentage-InterpreterProfilePercentage)/100。 InterpreterProfilePercentage,默认为33。


4、降低线程优先级

Linux不能设置,需要root权限


5、热度衰减与半衰周期


内存优化

1、将新对象预留在年轻代

参数设置:

-XX:TargetSurvivorRatio=90

90表示让新生代的from区的利用率为90%,这样新对象进来就会优先在里面


2、让大对象进入年老代

参数设置:

-XX:PetenureSizeThreshold=1000000,1M

大小为1M的对象为大对象


3、设置对象进入年老代的年龄

参数设置:

-XX:MaxTenuringThreshold=31

表示在新生代经过31次回收以后还存活的对象移到老年代,默认值是15,设置31的目的是让对象尽可能的在新生代就被回收,避免进入老年代触发full GC


4、稳定的 Java 堆

参数设置:

Xmx与Xms相同

最小堆内存和最大堆内存设置为一样的目的是避免频繁的向操作系统申请内存占用系统资源


5、增大吞吐量提升系统性能

指设置合理的垃圾回收器

参数设置:

– X X : + U s e P a r a l l e l G C :年轻代使用多线程的收集器

–XX:+UseParallelOldGC:老年代使用多线程的垃圾收集器

–XX:ParallelGC-Threads(CPU核心数相等):设置垃圾回收时使用的线程数


6、使用非占有的垃圾回收器

参数设置:

–XX:+UseConcMarkSweepGC:使用CMS垃圾回收器


监控及工具

1. jps:虚拟机进程状况工具

它的功能也和ps命令类似:可以列出正在运行的虚拟机进程,并显示虚拟机执行主类(Main Class,main()函数所在的类)名称以及这些进程的本地虚拟机唯一ID(Local Virtual Machine Identifier,LVMID)。

jps可以通过RMI协议查询开启了RMI服务的远程虚拟机进程状态,hostid为RMI注册表中注册的主机名。

简单地说:jps可以用来查看java进程的id

参数选项:


2. jstat:虚拟机统计信息监视工具

用于监视虚拟机各种运行状态信息的命令行工具。它可以显示本地或者远程虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据,在没有GUI图形界面,只提供了纯文本控制台环境的服务器上,它将是运行期定位虚拟机性能问题的首选工具

参数选项:


3. jmap:Java内存映像工具

jmap的作用并不仅仅是为了获取dump文件,它还可以查询finalize执行队列、Java堆和永久代的详细信息,如空间使用率、当前用的是哪种收集器等。

参数选项:


4. 其他工具

jinfo:Java配置信息工具 作用是实时地查看和调整虚拟机各项参数。使用-sysprops选项把虚拟机进程System.getProperties()的内容打印出来。 jhat:虚拟机堆转储快照分析工具,和jmap配合使用 jhat内置了一个微型的HTTP/HTML服务器,jmap生成dump文件的分析结果后,可以在浏览器中查看。 HSDIS:JIT生成代码反汇编 HSDIS是一个HotSpot虚拟机JIT编译代码的反汇编插件,它包含在HotSpot虚拟机的源码之中,但没有提供编译后的程序。


5. 可视化工具

JConsole:Java监视与管理控制台

JConsole(Java Monitoring and Management Console)是一种基于JMX的可视化监视、管理工具。它管理部分的功能是针对JMX MBean进行管理,由于MBean可以使用代码、中间件服务器的管理控制台或者所有符合JMX规范的软件进行访问。

VisualVM:多合一故障处理工具 VisualVM(All-in-One Java Troubleshooting Tool)是到目前为止随JDK发布的功能最强大的运行监视和故障处理程序。VisualVM的还有一个很大的优点:不需要被监视的程序基于特殊Agent运行,因此它对应用程序的实际性能的影响很小,使得它可以直接应用在生产环境

JMC,Oracle Java Mission Control 是一个用于对 Java 应用程序进行管理、监视、概要分析和故障排除的工具套件。首次安装时,Java Mission Control 包括 JMX 控制台和 Java 飞行记录器。从 Mission Control 中可以轻松安装更多插件


6. 即时编译器监控工具JITWatch

安装:

git clone git@github.com:AdoptOpenJDK/jitwatch.git cd jitwatch mvn clean install -DskipTests=true 运行:launchUI.bat 使用:XX:+UnlockDiagnosticVMOptions -XX:+TraceClassLoading -XX:+LogCompilation - XX:+PrintAssembly 查看结果。


JVM优化配置示例

服务器:8 cpu, 8G mem e.g. java -Xmx3550m -Xms3550m -Xss128k -XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:MaxPermSize=16m -XX:MaxTenuringThreshold=0 调优方案: -Xmx5g:设置JVM最大可用内存为5G。 -Xms5g:设置JVM初始内存为5G。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。 -Xmn2g:设置年轻代大小为2G。整个堆内存大小 = 年轻代大小 + 年老代大小 + 持久代大小 。持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。 -XX:+UseParNewGC:设置年轻代为并行收集。可与CMS收集同时使用。JDK5.0以上,JVM会根据系统配置自行设置,所以无需再设置此值。 -XX:ParallelGCThreads=8:配置并行收集器的线程数,即:同时多少个线程一起进行垃圾回收。此值最好配置与处理器数目相等。 -XX:SurvivorRatio=6:设置年轻代中Eden区与Survivor区的大小比值。根据经验设置为6,则两个Survivor区与一个Eden区的比值为2:6,一个Survivor区占整个年轻代的1/8。 -XX:MaxTenuringThreshold=30: 设置垃圾最大年龄(次数)。如果设置为0的话,则年轻代对象不经过Survivor区直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概率。设置为30表示 一个对象如果在Survivor空间移动30次还没有被回收就放入年老代。 -XX:+UseConcMarkSweepGC:设置年老代为并发收集。测试配置这个参数以后,参数-XX:NewRatio=4就失效了,所以,此时年轻代大小最好用-Xmn设置,因此这个参数不建议使用


原文发布于微信公众号 - JAVA烂猪皮(gp1106701116)

原文发表时间:2018-09-30

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏与神兽党一起成长

解析XML和JSON内容的一点技巧

在没有统一标准的情况下,一个系统对接多个外部系统往往会遇到请求接口响应数据异构的情况,有可能返回的是XML,也有可能返回 JSON。除了返回类型不同,内容结构也...

2132
来自专栏IT可乐

mybatis源码解读(三)——数据源的配置

1393
来自专栏安恒网络空间安全讲武堂

从零基础到成功解题之0ctf-ezdoor

2174
来自专栏鹅厂少年的奇妙之旅

Go内存模型

Go语言中内存分配大致有3种模式:Stack、Heap、Fixed Size Segment。

7565
来自专栏眯眯眼猫头鹰的小树杈

Dubbo AbstractRegistry源码阅读

最近因为工作需要在学习Dubbo的各种机制。其中深入学习了一下AbstractRegistry的实现机制。在此根据Dubbo源码对其实现进行一个总结。

1852
来自专栏令仔很忙

OutOfMemoryError异常----Java堆溢出

在Java虚拟机规范的描述中,除了程序计数器外,虚拟机内存的其他几个运行时区域都有发生OutOfMemoryError(下面都叫OOM)异常的肯能,下面就通过...

1152
来自专栏MySQL

JVM难学?那是因为你没认真看完这篇文章

JAVA程序运行与虚拟机之上,运行时需要内存空间。虚拟机执行JAVA程序的过程中会把它管理的内存划分为不同的数据区域方便管理。

1180
来自专栏武培轩的专栏

迅雷面经汇总

实现多态的技术称为 :动态绑定,是指在执行期间判断所引用对象的实际类型,根据其实际的类型调用其相应的方法。

1361
来自专栏JAVA烂猪皮

JVM难学?那是因为你没认真看完这篇文章

JAVA程序运行与虚拟机之上,运行时需要内存空间。虚拟机执行JAVA程序的过程中会把它管理的内存划分为不同的数据区域方便管理。

1064
来自专栏码云1024

MFC多线程

4736

扫码关注云+社区

领取腾讯云代金券