JVM主要有堆、方法区、虚拟机栈、本地方法栈、程序计数器组成
线程私有区域:
程序计数器:字节码执行的都有一个编号,就是程序计数器,解释器按这个计数器执行字码,将字节码转换为机器码给cpu运行。其二就是多线程中,也是时间片用完(会用程序计数器记录当前执行到的位置),因为有程序计数器,所以下次再获取时间片时,就可以继续接下去的操作
虚拟机栈:为虚拟机执行Java方法服务。
本地方法栈: 为虚拟机使用到的Native方法服务(本地方法一般就是Native关键字修饰,库里本来就有的方法,有的实现类是用c++实现的,直接调用方法使用就行.总结:java调用非java代码的接口)
线程共有区域(线程共享):
堆:通过new关键字创建的对象都会使用堆内存(它是线程共享的,堆中的对象都要考虑线程安全问题,有垃圾回收机制)
方法区:类信息,类加载器、运行时常量池(当虚拟机要使用一个类时,它需要读取并解析 Class 文件获取相关信息,再将信息存入到方法区。方法区会存储已被虚拟机加载的 类信息、字段信息、方法信息、常量、静态变量、即时编译器编译后的代码缓存等数据。)(以前有永久代、现在有元空间)
操作系统内存
直接内存(常见于NIO操作,用于数据缓冲区。分配回收成本较高,但读写性能高。不受JVM内存回收管理)
一、线程运行诊断
案例一:cpu占用过多
定位
(1)用top命令,定位哪个进程cpu的占用过高
(2)ps H -eo pid,tid,%cpu | grep 进程id(进一步定位那个线程引起的cpu占用过高)
(3)jstack 进程id (将线程转换为16进制,jstack命令得到结果的 nid,找到占用cpu的线程),进一步定位到问题代码的源码行数
案例二:程序运行很长时间没有结果
可能发生死锁
(1)使用jstack 进程id 查看
(2)看到有(Found one Java-level deadlock),定位到相应的源码行数
二、堆内存溢出(OutOfMemoryError:Java heap space)
显示指定堆内存(设置堆内存大小和最大堆内存大小)
-Xms
-Xmx
例如-Xms2G 、-Xmx8G
堆内存诊断
1、jps工具
查看当前系统中有哪些java进程
2、jmap工具
查看堆内存占用情况
3、jconsole工具
图形界面的、多功能的监测工具,可以连续检测
4、jvisualvm工具
也是图形化界面
jmap heap 进程ip(查看进程的堆内存信息,占用情况)
jconsole(命令行执行,就会弹出一个图形化界面,里面会有相应的监控信息)
案例 垃圾回收后,内存占用任然很高
使用jconsole执行垃圾回收(发现堆内存还是很高)
jvisualvm(弹出可视化界面)
这样就能查找到占用内存高的对象
三、元空间内存溢出OutOfMemoryError:Metaspace(1.8以前是叫永久代,1.8以后叫元空间,是方法区内的,类信息占用内存大于最大值会溢出)
永久代:
-XX:MaxPermSize=8m
元空间:
-XX:MaxMetaspaceSize=8m
三、String Table垃圾回收(intern()方法能进常量池)
-Xmx10m -XX:+PrintStringTableStatics -XX:+PrintGCDetails -verbose:gc(显示垃圾回收的信息)
StringTable 性能调优:
1、调整-XX:StringTableSize=桶个数
-Xms500m -Xmx500m -XX:+PrintStringTableStatistics -XX:StringTableSize=1009(如果读取字符串常量比较多, -XX:StringTableSize=1009把这个池调大,减少哈希冲突,使得性能提升)
2、考虑将字符串对象是否入池
四、直接内存释放原理
1、使用unsafe.allocateMemory() 释放直接内存
2、ByteBuffer的实现类内部,使用Cleaner(虚引用)来监测ByteBuffer对象,一旦ByteBuffer对象被垃圾回收,那么就会由ReferenceHandler线程通过Cleaner的clean方法调用freeMemory来释放直接内存
-XX:+DisableExplicicitGC 显示回收垃圾无效
System.gc() 执行这个是执行一次Full GC,使用上面那个参数,会无效
五、判断垃圾
1、引用计数法:每被引用一次,就计数+1,引用0就可回收
2、可达性分析:(根对象是那种不能被当成垃圾回收的对象,看对象有没有被根对象直接或间接引用,若有就不能当成垃圾回收,否则就可以被垃圾回收)
下载Memory Analyzer(MAT)工具
步骤,控制台输入:
jps 获得进程id
jmap -dump:format=b,live,file=要存储的文件名.bin 进程id
将bin文件在Mat工具中打开
即可查看哪些对象是根对象
JAVA中的四种引用
1、强引用(A1对象)
只要沿着GC Root被引用,那就是强引用不会被回收
2、弱引用(通过SoftReference),如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。
(代码示例如下)
-Xmx20m -XX:+PrintGCDetails -verbose:gc 打印查看垃圾回收过程
3、弱引用的特点是不管内存是否足够,只要发生 GC,都会被回收。
4、虚引用
必须配合引用队列使用,主要配合ByteBuffer使用,被引用对象回收时,会将虚引用入队,由Reference Handler线程调用虚引用相关方法(Unafe.freeMemory)释放直接内存
5、终结器引用
无需手动编码,但其内部配合引用队列使用,在垃圾回收时,终结器引用入队(被引用对象暂时没有被回收),再由Finalizer线程通过终结器引用找到被引用对象并调用它的finalize方法,第二次GC才能回收被引用对象
六、垃圾回收算法
1、标记清除(缺点:会产生垃圾碎片)
2、标记整理
3、复制算法(缺点,内存只能有一半被使用)
七、 垃圾回收器
1、串行垃圾回收器(只有一个线程进行GC)
-XX:+UseSerialGC=Serial + SerialOld (新生代用复制算法,老年代用标记-整理算法)
2、吞吐量优先
并行垃圾回收器(多个线程一起进行垃圾回收)
-XX:+UseParallelGC -XX:+UseParallelOldGC (只要开启其中一个,就会开启另一个)
参数:自适应调整新生代大小、调整垃圾回收时间与总时间比例、最大暂停毫秒数、线程数
3、响应时间优先
-XX:+UseConcMarkSweepGC (用户工作线程与垃圾线程一起并行,工作在老年代的垃圾回收器)
与-XX:+UseParNewGC~SerialOld 一起使用的(当垃圾回收器出现问题,它就会退化为SerialOld)
4、G1垃圾回收器(JDK9被设为默认回收器)
适用场景
同时注重吞吐量和低延迟,默认暂停目标是200ms
超大堆内存,会将堆划分为多个大小相等的Region
整体上是标记+整理算法,两个区域之间是复制算法
-XX:+UseG1GC
垃圾回收过程
复制到幸存区
七、GC调优
或者用命令行查看
(1)调优领域
1、内存
2、锁竞争
3、cpu占用
4、io
(2)确定目标
1、低延迟还是高吞吐量,选择合适的回收器
2、CMS、G1、ZGC
3、ParallelGC
(3)最快的GC是不发生GC
1、查看FullGC前后的内存占用,考虑下面几个问题
数据是不是太多
数据表示是否太臃肿
是否存在内存泄漏
八、新生代调优
新生代
内存不是占越大越好,差不多25%-50%之间
幸存区
1、幸存区要大到能保留(当前活跃对象+需要晋升的对象)
2、(因为幸存区是复制算法,需要复制来复制去)
老年代调优
案例
案例1 Full GC 和 Minor GC频繁
新生代内存不足, 当业务高峰期来临,大量对象被创建,幸存区不够,大量对象被放到老年区,导致老年区也满了触发FullGC。
所以增大新生代内存,幸存区内存调大,阈值调高。
案例2 请求高峰期发送FullGC,单次暂停时间特别长(CMS)
重新标记前先对垃圾进行清理
案例3 老年代充裕情况下,发生Full GC(CMS jdk1.7)
1.7及以前 永久代空间不足也会导致Full GC
1.8 元空间不足也会导致Full GC