java跨平台的实现

引入Java语言虚拟机后,Java语言在不同平台上运行时不需要重新编译。 Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编
译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。
Java虚拟机在执行字节码时,把字节码解释成具体平台上的机器指令执行。这就是Java的能够“一次编译,到处运行”的原因。

JVM内存区域

JVM

程序计数器

程序计数器是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。在虚拟机中,字节码解释器工作时就是通过改变
计数器值来选取下一条执行的指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
程序计数器是线程私有的内存。

虚拟机栈

每个方法在执行时都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口 等信息。一个方法从调用到执行完成就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。 虚拟机栈中的局部变量表存放着编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double),对象引用(reference类型),returnAddress类型(指向了一条字节码指令的地址)。是线程私有的内存。

本地方法栈

支撑Native方法的调用、执行和退出。Native Method就是一个java调用非java代码的接口,他是由非java语言实现的。类似于C++的extend C. 用native method主要是为了跟外部的环境交互、效率或者用java实现不方便的方面。

方法区

方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。 在Hotspot虚拟机上,很多人更愿意把方法区称为“永久代”。这区域的内存回收目标主要是针对常量池的回收和对类型的卸载。而在jdk8中,已经彻底没有了永久代,将方法区直接放在一个与堆不相连的本地内存区域,这个区域叫元空间。

元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制。类的元数据放入 native memory, 字符串池和类的静态变量放入java堆中. 这样可以加载多少类的元数据就不再由MaxPermSize控制, 而由系统的实际可用空间来控制.

Java堆(Java Heap)是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。堆的唯一目的就是存放对象实例。

Java堆是垃圾收集器管理的主要区域,因此很多时候也被称做“GC堆”。现在收集器基本都采用分代收集算法,所以Java堆中还可以细分为:新生代和老年代;再细致一点的有Eden空间、From Survivor空间、To Survivor空间等。

堆内存回收机制

堆内存分代方式

新生代

主要是用来存放新生的对象。一般占据堆的1/3空间。由于频繁创建对象,所以新生代会频繁触发MinorGC进行垃圾回收。

         新生代又分为 Eden区、ServivorFrom、ServivorTo三个区。

         Eden区:Java新对象的出生地(如果新创建的对象占用内存很大,则直接分配到老年代)。当Eden区内存不够的时候就会触发MinorGC,对新生代区进行一次垃圾回收。

         ServivorTo:保留了一次MinorGC过程中的幸存者。

         ServivorFrom:上一次GC的幸存者,作为这一次GC的被扫描者。

         MinorGC的过程:MinorGC采用复制算法。首先,把Eden和ServivorFrom区域中存活的对象复制到ServicorTo区域(如果有对象的年龄以及达到了老年的标准,则赋值到老年代区),同时把这些对象的年龄+1(如果ServicorTo不够位置了就放到老年区);然后,清空Eden和ServicorFrom        中的对象;最后,ServicorTo和ServicorFrom互换,原ServicorTo成为下一次GC时的ServicorFrom区

老年代

主要存放应用程序中生命周期长的内存对象。
老年代的对象比较稳定,所以MajorGC不会频繁执行。在进行MajorGC前一般都先进行了一次MinorGC,使得有新生代的对象晋身入老年代,导致
空间不够用时才触发。当无法找到足够大的连续空间分配(即创建大内存容易触发fullGC)给新创建的较大对象时也会提前触发一次MajorGC进行垃圾回收腾出
空间。
MajorGC采用标记—清除算法:首先扫描一次所有老年代,标记出存活的对象,然后回收没有标记的对象。MajorGC的耗时比较长,因为要扫描
再回收。MajorGC会产生内存碎片,为了减少内存损耗,我们一般需要进行合并或者标记出来方便下次直接分配。
当老年代也满了装不下的时候,就会抛出OOM(Out of Memory)异常。

G1

JVM

g1使用mark compact算法,依然有young和old,但不严格区分。
把堆内存划分成一个各region,每个region占用一块连续内存空间,每个region可能是old或young,g1根据情况动态调整每个region的类型。
region数量是1024的倍数,初始1024,后面扩展成2048或更多。
每个region大小可以通过-XX:G1HeapRegionSize=16m设置,若有很多大对象可以加大region。
humongous区域,若对象超过region空间50%,一般是byte[]或char[],存储大对象。逻辑上属于old区域。一旦大对象没有引用,ygc快速回收。
g1 ygc执行次数略多,但速度很快,基本不影响业务,可以PrintGCDetails打印在日志查看。
g1分为young gc, mixed gc和full gc。
young gc使用parallel复制,原理相同,会导致stw。
mixed gc发生在堆空间占有率达到阈值(可以用-XX:InitiatingHeapOccupancyPercent=45调整)触发。mixed gc先并发mark,不会stw,后面回收也会stw。
full gc回收整个堆空间,g1尽量避免full gc。
full gc发生原因:

  1. 大对象从young移动到old,old没有连续大空间放下。
  2. old区空间利用率达到阈值。
  3. metaspace/permanent空间不够。
  4. 代码调用System.gc()


无论哪种gc都会导致stw,只是停顿时间长短区别。serial, parallel gc会stw,cms和g1只在并发mark阶段不终止应用,后面回收阶段一样会终止应用,但时间短。

GC方式

1.serial gc,单线程,stop the world(stw,应用暂停),client模式下默认。young

用法:-XX:+UseSerialGC

2.par new gc:young parallel gc,通常配合old cms。

用法:-XX:+UseConcMarkSweepGC -XX:+UseParNewGC

3.parallel gc,多线程,jdk7以前默认,server模式默认,吞吐量优先。提供暂停时间或吞吐量目标。

用法:-XX:+UseParallelGC -XX:MaxGCPauseMillis=200 -XX:GCTimeRatio=9

4.concurrent mark sweep(cms),并发减少暂停时间,响应时间优先。使用mark sweep算法可能导致碎片,长时间运行有full gc。并发会和用户线程抢占资源。jdk9已deprecated。

用法:-XX:+UseConcMarkSweepGC

5.garbage first(G1),主流方式,兼顾吞吐量和响应时间,jdk7新版开始支持,jdk8阶段不断完善,jdk9后成熟成为默认。young gc次数少速度快,避免full gc。下面详细介绍。

用法:-XX:+UseG1GC -XX:MaxGCPauseMillis=200

6.zgc,jdk11引入,暂停时间不超过10ms。

GC调优

jvm优化要点

1.垃圾回收频率

无论young gc还是full gc,发生频率越少越好,增大heap是个简单方法。但是若Xmx指定过大,因为回收对象过多,反而会导致gc时间增大,应用暂停时间长,所以不要设置过大的Xmx。

2.应用暂停时间

暂停时0.间越短越好。使用concurrent并发收集器如g1,由于mark阶段是并发,不暂停应用,回收速度快。g1可以使用-XX:MaxGCPauseMillis=200设置。

3.吞吐量

系统运行时间=应用时间+gc时间,若应用运行99s,gc 1s,吞吐量=99%。g1可以使用-XX:GCTimeRatio=n来表示吞吐量目标,gc时间=1/(1+n)。比如n=9,gc时间=10%,吞吐量90%。

优化思路

  1. 尽量使用高版本jdk,g1算法更加成熟。
  2. 开启g1,即使使用默认,在吞吐量和响应时间上满足最优。
  3. 若内存充足,且应用没有内存泄漏,不需要设置Xmx,让jvm自动扩展。
  4. 若想看gc过程,打开gc日志。

优化参数

g1相关参数:
-XX:+UseG1GC,开启g1
-XX:MaxGCPauseMillis=200,最大暂停时间200ms
-XX:GCTimeRatio=9,吞吐量1-1/(1+9)=90%
-XX:ParallelGCThreads=4,并行gc线程数,等同cpu核心数
-XX:ConcGCThreads=1,并发mark线程数,cpu核心数/4。
-XX:HeapDumpPath=%os%/logs/dump/jvm-oom.log
-XX:+HeapDumpOnOutOfMemoryError
perm/metaspace参数:
jdk7
-XX:PermSize=64m -XX:MaxPermSize=384m
jdk8
-XX:MetaspaceSize=64m -XX:MaxMetaspaceSize=384m
打印gc日志参数:
-XX:+PrintGC 输出 GC 日志
-XX:+PrintGCDetails 输出 GC 的详细日志
-XX:+PrintGCTimeStamps 输出 GC 的时间戳(以基准时间的形式)
-XX:+PrintGCDateStamps 输出 GC 的时间戳(以日期的形式,如 2013-05-04T21:53:59.234+0800)
-XX:+PrintHeapAtGC 在进行 GC 的前后打印出堆的信息
-XX:+PrintAdaptiveSizePolicy 打印g1 ergonomics信息,方便诊断。
-Xloggc:../logs/gc.log 日志文件的输出路径
G1参数组合,注意对暂停时间和吞吐量不要过分苛刻。
对于dubbo和springboot jar:
java -server -XX:+UseG1GC -XX:MaxGCPauseMillis=300 -XX:GCTimeRatio=9 -XX:PermSize=64m -XX:MaxPermSize=384m -XX:ParallelGCThreads=4 -XX:ConcGCThreads=1 ....jar

-XX:MetaspaceSize=128m (元空间默认大小,jdk1.8存放在内存中,不放在jvm中)
-XX:MaxMetaspaceSize=128m (元空间最大大小)
-Xms1024m (堆最大大小)
-Xmx1024m (堆默认大小)
-Xmn512m (新生代大小,堆-新生代 = 老年代,也可以用-XX:NewRatio=n:配置)
-Xss512k (棧最大深度大小)
-XX:SurvivorRatio=8 (Eden:ServivorTo:ServivorFrom=8:1:1)
-XX:+UseConcMarkSweepGC (指定使用的垃圾收集器,这里使用CMS收集器)
-XX:+PrintGCDetails (打印详细的GC日志)

G1调优

不推荐设置young区域大小(NewRatio, SurvivorRatio, Xmn),g1动态调整old和young的region数量。
-XX:MaxGCPauseMillis=200,最大gc暂停时间,g1尽量满足。
-XX:GCTimeRatio=9,吞吐量1-1/(1+9)=90%
-XX:ConcGCThreads,并发mark线程数,通常是cpu核心数。
-XX:G1HeapRegionSize:每个region大小。

常见异常

1.java.lang.OutOfMemoryError: PermGen Space

jdk7永久代空间不够
-XX:PermSize=64m -XX:MaxPermSize=384m

2. java.lang.OutOfMemoryError: Metaspace

jdk8 metaspace空间不够
-XX:MetaspaceSize=64m -XX:MaxMetaspaceSize=384m

3.java.lang.OutOfMemoryError:Java heap space

堆空间不够,使用Xmx加大或不加此参数

4.StackOverFlowError

递归死循环,导致不断压入线程vm stack。

常用工具

1. 本机java进程
jps -mlv
2. 查看java进程参数
jinfo pid
3. 导出线程栈,常用于判断线程栈。
jstack pid > pid.txt
4. gc次数,5000是间隔时间,5是打印5次。
jstat -gc pid 5000 5
5. 堆内存详情
jmap -heap pid
6. 导出内存快照
jmap -dump:format=b,file=heap.hprof pid

7.windows下图形化工具监控 jvisualjvm

8. 内存快照分析
https://www.gceasy.io/index.jsp,在线分析
jprofile, eclipse mat,离线本地分析

常用命令

1.jmap -heap pid 查看堆信息

2.jstat -gccapacity pid 堆内存使用情况统计

JVM

3. jstat -gcutil pid 查看堆占用百分比

优化命令示例

java -Xms1024m -Xmx1024m -Xmn640m -Xss1m -XX:SurvivorRatio=8 -XX:+UseConcMarkSweepGC -jar /huance/xmky-cloud/huance-cloud.jar
解释: 堆最大最小值1024mb,等值防止jvm自动扩容影响效率。-xmn新生代640mb, -xss1m栈1mb,-XX:SurvivorRatio=8表示Eden:survisorTo:
survisorFrom=8:1:1; -XX:+UseConcMarkSweepGC指定收集器为CMS收集器。后续若要优化可用该配置或在该配置基础上进行修改。


Follow your heart ~!