再看 JVM(2)
文章太长了,分2篇写吧,上一篇:再看 JVM(1)
堆内存
想必大家对堆内存都是耳熟能详了,自己都去仔细研究过了,但是这里还是要重点说明,尽量力争全面,有些细节点的点还是有很多人不知道的 φ(≧ω≦*)♪
堆内存特点
几乎
所有的对象实例都在堆空间分配内存- 方法结束后,堆中的对象不会马上被移除,仅仅在垃圾回收时才会移除
- 栈上保存对象引用,对象本身还是储存在堆内存中的
TLAB 线程私有缓冲区
这个很多人不知道哦 ヽ(✿゚▽゚)ノ
因为堆是进程内线程间共享的,那么自然并发争强数据是少不了的,为了减少线程之间因并带来的性能损失,推出了 TLAB
这么个技术
TLAB:线程私有缓冲区,堆里面分出一小部分空间来,再分成好几块,每个线程占一块,那就是每个线程独有一下块,每一个小块称为 TLAB 并发性更好,没有竞争
堆内存结构
大部分现代垃圾收集器都是基于分带收集理论设计,而决定堆空间的又是GC,所以 JVM 采用哪种类型的垃圾收集器,堆空间的结构就趋近哪种构型设计
我们以 JDK1.8 Hotspot 虚拟机为准,目前开发绝大部分都是 JDK1.8 的,因为之后就收费了 o( ̄ヘ ̄o#)
堆内存 JVM 参数
JVM 有2个参数:
Xms
- 堆内存初始值,默认=物理内存的 1/64Xmx
- 堆内存最大值,默认=物理内存的 1/4Xms500m
- 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 28763S0C 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:
S0S1C/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:575Xmx:575
因为这里少了一个 s1 的大小,堆内存中真正能存数据的就是 Ednt+s0或者s1其中的一个,s0、s1 是为了相互赋值的,一个时刻只有一个用来存储对象数据,另一个留着准备给GC复制对象用
jvisualvm 截图看下,S0、S1 的大小是25M
实际上 -XX:+PrintGCDetails
打印出来的新生代的 total 也是575,也是不算 S1 的
Xms:575Xmx: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 出一张内存快照出来,工具有很多
- 先看是不是有不合理代码生成了大量对象并且这些对象内存泄露了,占用了大量内存出去
- 再看内存泄露,一般单单内存泄露不会 OOM,但是可以优化内存使用
- 增加屋里内存
- 看看是不是某些大体积对象声明周期过长,比如 bitmap
接口的匿名实现类实际上是被作为一种类型来使用的,在每一个匿名实现类在方法区都会占据一块 class 空间
StringTable
谁有error,谁有gc
以上是 再看 JVM(2) 的全部内容, 来源链接: utcz.com/a/29683.html