再看 JVM(2)

文章太长了,分2篇写吧,上一篇:再看 JVM(1)

堆内存

想必大家对堆内存都是耳熟能详了,自己都去仔细研究过了,但是这里还是要重点说明,尽量力争全面,有些细节点的点还是有很多人不知道的 φ(≧ω≦*)♪

堆内存特点

  • 几乎所有的对象实例都在堆空间分配内存

  • 方法结束后,堆中的对象不会马上被移除,仅仅在垃圾回收时才会移除
  • 栈上保存对象引用,对象本身还是储存在堆内存中的

TLAB 线程私有缓冲区

这个很多人不知道哦 ヽ(✿゚▽゚)ノ

因为堆是进程内线程间共享的,那么自然并发争强数据是少不了的,为了减少线程之间因并带来的性能损失,推出了 TLAB 这么个技术

TLAB:线程私有缓冲区,堆里面分出一小部分空间来,再分成好几块,每个线程占一块,那就是每个线程独有一下块,每一个小块称为 TLAB 并发性更好,没有竞争

堆内存结构

大部分现代垃圾收集器都是基于分带收集理论设计,而决定堆空间的又是GC,所以 JVM 采用哪种类型的垃圾收集器,堆空间的结构就趋近哪种构型设计

我们以 JDK1.8 Hotspot 虚拟机为准,目前开发绝大部分都是 JDK1.8 的,因为之后就收费了 o( ̄ヘ ̄o#)

堆内存 JVM 参数

JVM 有2个参数:

  • Xms - 堆内存初始值,默认=物理内存的 1/64

  • Xmx - 堆内存最大值,默认=物理内存的 1/4

  • Xms500m - VM options 这么写

通过 Runtime 对象可以获取这2个参数

long Xms = Runtime.getRuntime().totalMemory();

long Xmx = Runtime.getRuntime().maxMemory();

一般情况下我们把 Xms、Xmx 设置成相等的,为的是减少系统压力。他俩要是不等的话,堆内存在需求增长的情况下会不停的去申请内存,新申请的内存和原来内存是不连续的,内存碎片化会降低内存读写性能。内存需求减少的情况下,系统会回收堆内存不用的空间,这样频繁的来来回回申请、回收内存会极大的系统压力,更何况GC本身就很耗费性能还会阻塞用户进程,GC之后我们再来这么一下系统性能压力就更大了 (๑•̀ㅂ•́)و✧

打印堆内存有2个方式:

  • jsts -gc 进程ID: 这是命令行的,随时都能能用

  • -XX:+PrintGCDetails: 这是配置到 VM options 里面的,只有进程结束时才能代印出数据,前面章节介绍过了

看下命令行打印出来的数据,认识下参数:

➜  ~ jstat -gc 28763

S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT

25600.0 25600.0 0.0 0.0 153600.0 61443.3 409600.0 0.0 4480.0 774.4 384.0 75.9 0 0.000 0 0.000 0.000

  • C结尾的是总数,U结尾的是使用量

  • EC/EU: 新生代

  • OC/OU: 老年代

  • S0C/S0U: S0

  • S1C/S1U: S1

不过这里有个点要知道,我们用代码把堆内存打印出来,参数我们设置的是:-Xms600m -Xmx600m

publicstaticvoidmain(String[] args){

long Xms = Runtime.getRuntime().totalMemory() / 1024 / 1024;

long Xmx = Runtime.getRuntime().maxMemory() / 1024 / 1024;

System.out.println("Xms:" + Xms);

System.out.println("Xmx:" + Xmx);

}

实际打印出来的是 575,为啥???

Xms:575

Xmx:575

因为这里少了一个 s1 的大小,堆内存中真正能存数据的就是 Ednt+s0或者s1其中的一个,s0、s1 是为了相互赋值的,一个时刻只有一个用来存储对象数据,另一个留着准备给GC复制对象用

jvisualvm 截图看下,S0、S1 的大小是25M

实际上 -XX:+PrintGCDetails 打印出来的新生代的 total 也是575,也是不算 S1 的

Xms:575

Xmx:575

Heap

PSYoungGen total 179200K, used 12288K [0x00000007b3800000, 0x00000007c0000000, 0x00000007c0000000)

eden space 153600K, 8% used [0x00000007b3800000,0x00000007b44001b8,0x00000007bce00000)

from space 25600K, 0% used [0x00000007be700000,0x00000007be700000,0x00000007c0000000)

to space 25600K, 0% used [0x00000007bce00000,0x00000007bce00000,0x00000007be700000)

ParOldGen total 409600K, used 0K [0x000000079a800000, 0x00000007b3800000, 0x00000007b3800000)

object space 409600K, 0% used [0x000000079a800000,0x000000079a800000,0x00000007b3800000)

Metaspace used 3387K, capacity 4496K, committed 4864K, reserved 1056768K

class space used 376K, capacity 388K, committed 512K, reserved 1048576K


逃逸分析:栈上分配,标量替换

OOM

Full GC中,元数据指向元数据的那些指针都不用再扫描了。很多复杂的元数据扫描的代码(尤其是CMS里面的那些)都删除了。

元空间只有少量的指针指向Java堆。这包括:类的元数据中指向java/lang/Class实例的指针;数组类的元数据中,指向java/lang/Class集合的指针。

没有元数据压缩的开销

减少了根对象的扫描(不再扫描虚拟机里面的已加载类的字典以及其它的内部哈希表)

减少了Full GC的时间

G1回收器中,并发标记阶段完成后可以进行类的卸载

遇到 OOM 呢, 第一时间看内存分布,用工具 dump 出一张内存快照出来,工具有很多

  1. 先看是不是有不合理代码生成了大量对象并且这些对象内存泄露了,占用了大量内存出去
  2. 再看内存泄露,一般单单内存泄露不会 OOM,但是可以优化内存使用
  3. 增加屋里内存
  4. 看看是不是某些大体积对象声明周期过长,比如 bitmap

接口的匿名实现类实际上是被作为一种类型来使用的,在每一个匿名实现类在方法区都会占据一块 class 空间

StringTable

谁有error,谁有gc

以上是 再看 JVM(2) 的全部内容, 来源链接: utcz.com/a/29683.html

回到顶部