@SneakyThrows
public static void main(String[] args) {
HashMap hashMap = new HashMap();
ArrayList arrayList = new ArrayList();
Byte[] e = new Byte[1024];
while (true) {
Thread.sleep(10);
arrayList.add(e);
}
}
java执行代码
C:\WINDOWS\System32>jps -l
26576 org/netbeans/Main
5156 org.netbeans.lib.profiler.server.ProfilerServer
19272 sun.tools.jps.Jps
26312
8232 com.jesse.log.JVMTest
24204 org.jetbrains.jps.cmdline.Launcher
jps -l 显示java pid
C:\WINDOWS\System32>jmap -dump:live,format=b,file=d:/a.dump 8232
Dumping heap to D:\a.dump ...
File exists
使用jmap导出dump
-dump:live 会执行一次fullgc, 导出存活的对象
format=b 二进制格式, 不填默认也是二进制格式
file=d:/1.dump 导出文件的路径
8232 pid
说白了, 调优主要看fullGC垃圾回收的频率, 和垃圾回收的时间.和回收的垃圾多少
如果垃圾回收频率高, 回收时间短. 那要看回收的垃圾多不多, 一般这种情况很少, 有没有可能把老年代的空间设置太小了. 正常老年代的空间都是比较大的, 即使回收的垃圾少, 他的执行时间也不会短的.
如果垃圾回收时间长, 那就要调优了. 即使频率低, 执行时间长也是要调优的, 对于并发执行的垃圾回收器, 主要要STW执行的时间长短. 如何执行长, 会直接影响到客户端体验. 可能原因是不是设置老年代的堆内存多大, 如果4-8G, 最好是用G1垃圾回收器. 如果回收频率很高, 回收的内存也很多, 及要看一下对象是不是过早晋升到老年代了. 除了看代码, 还要看新生代的内存是不是设置的太小了. 如果回收的内存不多, 就要看下老年代是不是设置的太小, 还是代码有问题. 本身没有用的对象却常驻内存.
-Xms 512M jvm初始化内存
-Xmx 512M jvm最大内存
-Xmn512M 新生代的内存
-Xss512K 栈的大小
-XX:+UseConcMarkSweepGC 使用CMS
-XX:+PrintGCDetails 打印GC详情
-XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps 打印GC的时间
-XX:+PrintGCHeapAtGC 打印GC的堆信息
-Xloggc:d:/a.log 指定GC日志路径
-Xms512M
-Xmx512M
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-XX:+PrintGCDateStamps
-XX:+PrintHeapAtGC
-Xloggc:d:/a.log
运行参数
Connected to the target VM, address: '127.0.0.1:12967', transport: 'socket'
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at com.jesse.log.JVMTest.main(JVMTest.java:19)
Disconnected from the target VM, address: '127.0.0.1:12967', transport: 'socket'
Process finished with exit code 1
java.lang.OutOfMemoryError: Java heap space java堆空间, java内存溢出
Java HotSpot(TM) 64-Bit Server VM (25.101-b13) for windows-amd64 JRE (1.8.0_101-b13), built on Jun 22 2016 01:21:29 by "java_re" with MS VC++ 10.0 (VS2010)
Memory: 4k page, physical 12446092k(5781004k free), swap 21079212k(10336556k free)
CommandLine flags: -XX:InitialHeapSize=536870912 -XX:MaxHeapSize=536870912 -XX:+PrintGC -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintHeapAtGC -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
{Heap before GC invocations=1 (full 0):
PSYoungGen total 153088K, used 129019K [0x00000000f5580000, 0x0000000100000000, 0x0000000100000000)
eden space 131584K, 98% used [0x00000000f5580000,0x00000000fd37ee30,0x00000000fd600000)
from space 21504K, 0% used [0x00000000feb00000,0x00000000feb00000,0x0000000100000000)
to space 21504K, 0% used [0x00000000fd600000,0x00000000fd600000,0x00000000feb00000)
ParOldGen total 349696K, used 0K [0x00000000e0000000, 0x00000000f5580000, 0x00000000f5580000)
object space 349696K, 0% used [0x00000000e0000000,0x00000000e0000000,0x00000000f5580000)
Metaspace used 7257K, capacity 7292K, committed 7552K, reserved 1056768K
class space used 849K, capacity 856K, committed 896K, reserved 1048576K
2023-07-27T14:53:32.453+0800: 8.662: [GC (Allocation Failure) [PSYoungGen: 129019K->21480K(153088K)] 129019K->112507K(502784K), 0.0292881 secs] [Times: user=0.13 sys=0.02, real=0.03 secs]
Heap after GC invocations=1 (full 0):
PSYoungGen total 153088K, used 21480K [0x00000000f5580000, 0x0000000100000000, 0x0000000100000000)
eden space 131584K, 0% used [0x00000000f5580000,0x00000000f5580000,0x00000000fd600000)
from space 21504K, 99% used [0x00000000fd600000,0x00000000feafa070,0x00000000feb00000)
to space 21504K, 0% used [0x00000000feb00000,0x00000000feb00000,0x0000000100000000)
ParOldGen total 349696K, used 91027K [0x00000000e0000000, 0x00000000f5580000, 0x00000000f5580000)
object space 349696K, 26% used [0x00000000e0000000,0x00000000e58e4ef8,0x00000000f5580000)
Metaspace used 7257K, capacity 7292K, committed 7552K, reserved 1056768K
class space used 849K, capacity 856K, committed 896K, reserved 1048576K
}
查看a.log日志信息
Java HotSpot(TM) 64-Bit Server VM (25.101-b13) for windows-amd64 JRE (1.8.0_101-b13), built on Jun 22 2016 01:21:29 by "java_re" with MS VC++ 10.0 (VS2010) = JVM版本, java版本
Memory: 4k page, physical 12446092k(5781004k free), swap 21079212k(10336556k free)
= 一页4k, 物理内存约12G, 约6G空闲, swap约21G, 约10G空闲
PSYoungGen total 153088K, used 129019K = 年轻代150M使用130M
eden space 131584K, 98% used = eden空间130M, 已使用98%
from space 21504K, 0% used = from空间21M, 已使用0%
to space 21504K, 0% used = to空间21M, 已使用0%
ParOldGen total 349696K, used 0K = 老年代350M, 使用...
object space 349696K, 0% used = 对象空间350M 已使用0%
Metaspace used 7257K, = 元空间 已使用7M
class space used 849K = class空间 已使用849k
太麻烦了, 可以使用工具gceasy
文件名称和GC总共花费的时间
新生代 150M , 老年代350M, 元空间 1G, 公共1.5G
Allocated 分配的内存
Peak 最高峰的内存数值
吐出量94%, 延长 GC平均时间148ms, 最大的GC时间410ms
每个时间间隔GC个数的比例
1-100ms 占36%
100-200ms 占36%
GC Duration 可以看YGC和FullGC的次数和持续时间
Heap after GC GC之后的堆内存
Heap before GC GC之前的堆内存
Reclaimed bytes GC回收的字节大小
Young GC YGC回收内存前后的内存变化
old GC fullGC回收奴才能前后的内存变化
Meta Space 元空间回收内存的情况
Reclaimed Bytes YGC和FullGC是否内存的多少
GC cumulative time YGC累计时间和FullGC累计时间的比例
GC Average Time YGC和FullGC平均时间
Full GC Count 总的GC次数
Full GC reclaimed GC释放内存的大小
Full GC total time FullGC总共花的时间
Full GC avg time FullGC平均时间
Full GC avg time std dev FullGC平均时间标准偏差
Full GC min/max time FullGC花费最小和最大的时间
Full GC Interval avg FullGC的平均时间间隔
报告分享
G1的配置参数说明
-XX:+UseG1GC 使用G1垃圾收集器
-XX:+G1HeapRegionSize=32M Region的大小
-XX:+G1NewsizePercent=20 新生代初始化占用20%比例的内存
-XX:+G1MaxNewsizePercent=50 新生代最大占用50%的堆内存
-XX:+G1OldcsetRegionThresholdPercent=10 老年代初始化10内存
-XX:+ParallelPCThread=8 并发收集线程, 最多8个
-XX:+ConcthreadCount=2 并发标记线程数, 一般设置为ParallelPCThread的1/4
-XX:+InitiatingOccupancyPencent=45 内存超过45%就会执行fullGC. 不配置默认是45%
-XX:+MaxGCPauseMillis 最大的GC停顿时间
-XX:+HeapDumpOnoutofMemoryError 内存溢出导出堆文件
-XX:HeapDumpPath=d:/g1.hprof 堆文件快照导出文件路径
-XX:+PrintGC 打印GC信息
-XX:+PrintGCDetails 打印GC详情
-XX:+PrintGCTimeStamps 打印GC时间
-XX:+PrintGCDateStamps 打印GC时间
-Xloggc=d:/g1.log 打印GC文件路径
-XX:+UseTLAB 使用功能TLAB, 叫做指针碰撞, 就是线程会向主内存申请一定大小的内存. 通过CAS的方式获取
-XX:+PrintTLAB 打印TLAB
-XX:+TLABSize TLAB的大小
-XX:+DisableExplicitGC 禁用System.gc()
CMS配置参数说明
-XX:+ConcMarkSweepGC 老年代使用CMS
-XX:+ParallelGCThreads 并发收集线程数
-XX:+CMSInitiatingOccupancyFraction=68 达到多少执行CMS, 默认是达到68%
-XX:+UseCMSCompactAtFullCollection 在fullGC的时候进行压缩
-XX:+CMSFullGCCsBeforeCompaction 在fullGC多少次之后压缩
-XX:+CMSClassUnloadingEnabled 垃圾回收的时候, 启动类卸载, 默认是开启的
-XX:+CMSInitiatingPermOccupancyFraction 达到多少回收Perm, java中不推荐
-XX:+GCTimeRatio 设置GC占用户运行时间比, 不推荐
-XX:+MaxGCPauseMillis 最大GC暂停时间, 是一个建议的时间
-Xms512M
-Xmx512M
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-XX:+PrintGCDateStamps
-XX:+PrintHeapAtGC
-Xloggc:d:/a.log
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=d:/ps.hprof
内存溢出导出堆文件
D:\>jhat ps.hprof
Reading from ps.hprof...
Dump file created Thu Jul 27 16:39:34 CST 2023
Snapshot read, resolving...
Resolving 38143 objects...
Chasing references, expect 7 dots.......
Eliminating duplicate references.......
Snapshot resolved.
Started HTTP server on port 7000
Server is ready.
jhat ps.hprof 之后, 通过localhost:7000访问
不好用, 放弃...
使用VisualVM 打开ps.hprof
char, String 两个占用比较高
G1可以设定暂停时间, 默认是200ms.收集过程分为初始标记, 并发标记, 重新标记, 清除. 除了并发标记可以和用户线程一起运行之外, 其他都需要STW.
并发标记的又可能出现的情况是
一 是垃圾的但是没有标记, 这种问题不大, 大不了下次垃圾回收的时候回收掉.
二 不是垃圾, 但是标记为垃圾. 这个问题就很大. 出现这个情况的可能原因是某个rootA对象引用A对象, 另外一个用户线程B拿到对象A的地址. 用户线程A让rootA对象不引用对象A. 此时垃圾回收线程扫描rootA, 发现没有引用A对象, 扫描rootB没有引用A对象, 这是垃圾回收线程标记A对象不可达, 为垃圾, 然后用户线程B让rootB引用A对象. A不应该是垃圾, 却被回收了, 这是不可以的.
如果解决这个问题呢? 我们看到rootA删除了A对象, rootB添加了A对象, 才会导致这样的问题.
我们发现
只删除A对象, 不添加A对象, 就不会出现这个的问题.
G1使用的三色标记法
黑色 回收器已经访问该节点, 并访问的这个节点指向的所有节点
灰色 回收器已经访问这个节点, 他指向的节点还有访问完
白色 回收器还没有访问的节点
用户线程1删除黑色 没有问题, 垃圾回收器这次没有回收, 下次回收会判断它是不是垃圾
用户线程1删除灰色 也没有问题, 因为垃圾回收线程已经获取灰色节点的指针, 可以继续访问下去. 也会在下次回收判断是不是垃圾
用户线程1删除白色A 有问题, 因为白色A垃圾回收器没有访问过, 当它遍历完之后, 发现白色不可达, 会判断A是垃圾, 会被回收. 如果存在这种情况, 在用户线程1删除白色A的时候, 用户线程B已经获取白色A的地址, 在垃圾回收器标记A为垃圾的时候, 之后用户线程B把对象A赋值给某个变量. 然后垃圾回收器回收了A对象, 用户线程B使用对象A就会出现空指针的情况.
那么如何处理这种情况.
我们发现, 不允许删除白色节点, 不靠谱. 那就在新增节点下手.
用户线程2对白色节点新增节点A 没有问题, 垃圾回收器会遍历这个节点, 即使之前用户线程1删除节点A.
用户线程2对灰色节点新增节点A 也没有问题, 只要新增之后垃圾回收器扫描这个节点就可以了
用户线程2对黑色节点新增节点A 有问题, 因为黑色节点表明垃圾回收器已经扫描完了. 不会在标记A, 如果之前标记了A是垃圾的话, 就会有问题.
所以我针对第三种情况, 如果用户线程在黑色节点添加一个节点, 那么就会把这个黑色的节点变成灰色, 把这个灰色的节点记录下来, 在接下来的重新标记, 重新标记是STW的. 会从这个灰色的节点扫描下去.
CMS是从新增节点入手, 叫做增量更新
G1是总删除节点入手, 叫做原始快照, 解决办法是删除的这个节点这个垃圾回收不回收这个节点, 等下次垃圾回收判断它是否是垃圾. 只有用户线程删除灰色到白色这样的节点才不回收.
java1.6, java1.7, java1.8默认是使用PS, PO垃圾收集器
一般用parallelNew+CMS
java1.9默认是使用G1垃圾回收器
垃圾回收算法有标记清除, 标记复制, 标记整理.
G1设置有Region的概念, 可以设置大小, Region可以eden1区, servious区, old区, 还有大对象区. YGC的时候会把eden和s1的存活对象复制到s2, fullGC的时候会把eden和s1的存活的对象复制s2, 还有标记整理老年代的对象.
新增对象会存在这几种情况
新增对象的时候, 被老年代引用.那这样的对象在YGC的时候是不可以回收的. 为了判断它是不是垃圾, 重新扫描一遍Old区是不可以行的. 所以需要一个记录表, 叫记忆集. 主要记录老年代对新生代的引用, 和这个新生代做好映射关系. 主要是回收这个新生代的内存的时候, 查这个记录表. 因为G1有多个Region, 所以会记录这个对象被哪些Region引用. 如果以对象映射记录的话, 如果对象很小, 如果存在大量的对象, 那就要不少的数据空间存放. 所以想到了把内存划分成多个区域, 一个区域的空间是16k, 如果这个区域的对象被其他地方的对象引用, 就会变为1. 垃圾回收的时候就会回收这个区域. 这样的方法叫卡表. 那当这个区域的对象不用的时候, 什么时候变回0呢
G1会进行三种GC
YoungGC 会拷贝eden和s1的存活对象到s2
MixedGC 对新生代和老年代进行回收, 使用的复制算法, 通过InitiatingOccupancy参数控制, 如果剩余的内存不足装下存活的对象, 会进行fullGC
fullGC 这个过程是非常耗时的.
CMS和G1的区别
CMS的是并发标记清除, G1的标记整理, 是需要STW
CMS并发标记到重新标记使用的增量更新, G1是用的原始快照
CMS有新生代和老年代, G1没有物理的新生代和老年代, 只有逻辑的新生代和老年代