Java 的 GC
java garbage collection pdf
内存管理
两个流派:
-
手动内存管理(C/C++)
-
自动内存管理(Java/Python...)
- 引用计数(Reference Counting)
-
问题:循环引用
- 引用计数的缺点
- 标记清除(Mark and Sweep)
-
可达性分析
-
线程对应方法栈
-
方法对应栈帧
内存碎片化
- 长期工作的内存会出现碎片化
- 总可用空间仍然足够,但是无法分配大对象
- 垃圾回收 = 回收可用空间 + 压缩内存
引用的类型
- 强引用 Strong Reference:用到的都是强引用
- 软引用 Soft Reference:内存不够的时候会回收他们
- 弱引用 Weak Reference:一GC就回收他们
- 影子引用 Phantom Reference:只能拿到影子,拿不到引用本身
GC基本原理
1. 哪些东西是GC Roots?
- 活的线程(java.lang.Thread)
- 类的静态成员
- 线程方法栈所引用的对象
- JNI(Java Native Interface)引用的对象
- 分代GC时其他代的对象
2. GC发生了什么?
1.
- Stop the world (STW)
- 清除垃圾
- 压紧内存
2.
- 拷贝
对象的分代假设
1. 对象的分代假设
- 内存分代
- 年轻代(Young Generation)
- 老年代(Old Generation/Tenured)
- 永久代(Peramanent Generation)
-XX:SurvivorRatio=n //Edon区和Survivor区的占比(n/10)
-XX:MaxTenuringThreshold=0 //设置垃圾最大年龄
2. 年轻代
- Eden (伊甸园)
- 可以分为多个Thread Local Allocation Buffer
-
Survivor (S0/S1或者from/to)
- Young GC发生时,整个年轻代进入其中一个Survivor区
-
足够老的对象被提升到老年代(Tenured)
3. 老年代
- 老年代相对较大
- 存储足够年老的对象
- 发生GC的频率较低
- 清除垃圾
- 压紧内存
永久代/元空间
- Java 8之前:永久代
- 堆的一部分
- 存储类数据、字符串常量
- OOM(OutOfMemory):Permgen space
- 自定义Classloader
- 动态字节码生成
- Java 8+:元空间
- 不是堆的一部分(-Xmx)
- 除非特殊指定,否则没有上限
- -XX:MaxMetaspaceSize/OOM:metaspace
GC的种类
- Minor GC/Young GC
- 总是发生在Eden满的时候
- 会发生STW
- Major GC / Full GC - 这两个概念并没有明确的定义,这里阐述一般理解 - Major GC 清除老年代 - Full GC 清除整个堆
Minor GC 的过早提升:有个对象占用空间非常大,但是年龄又没到 ,却过早提升到老年代 => 需要调整
垃圾回收算法(Java 8)
垃圾回收过程
- -XX:+PrintGCDetails
- -XX:+PrintGCDateStamps
- -XX:+PrintGCTimeStamp,
-XX:+DisableExplicitGC
-XX:+PrintGCDetails
-XX:+PrintGCApplicationStoppedTime
-XX:+PrintGCApplicationConcurrentTime
-XX:+PrintGCDateStamps
-Xloggc:gclog.log
-XX:+UseGCLogFileRotation
-XX:NumberOfGCLogFiles=5
-XX:GCLogFileSize=2000k
>#### 让我们了解每个选项的目的。这些选项可能会因OS / JVM供应商/ JAVA版本而异。
>
>- DisableExplicitGC:默认情况下禁用此选项。有时,开发人员可能通过调用System.gc()或Runtime.getRuntime()。gc()实用地调用了垃圾回收。不建议这样做。因此,在生产系统中,我们启用此选项。这样就禁止了实用的垃圾回收调用。
>
>- PrintGCDetails:默认情况下禁用此选项。如果启用此选项,则JVM将在每个垃圾收集中打印更多详细信息。
>
>- PrintGCApplicationStoppedTime:默认情况下禁用此选项。如果启用,它将在垃圾回收期间在应用程序暂停时打印信息。
>
>- PrintGCApplicationConcurrentTime:默认情况下,此选项处于禁用状态。如果启用,它将在GC期间打印有关应用程序运行时间的信息。
>
>- PrintGCDateStamps:默认情况下禁用此选项。如果启用,它将在每个GC上打印日期和时间详细信息。
>
>- loggc:这是一个字符串选项。我们必须传递gc日志文件名。在此文件中,我们将获取所有GC日志信息。
>
>- UseGCLogFileRotation:如果文件大小达到指定大小,则此选项指示JVM旋转日志文件。
>
>- NumberOfGCLogFiles:默认值为1。这设置轮换日志时要使用的文件数。
>
>- GCLogFileSize: 默认值为8KB。日志文件的大小,此时将旋转日志。
>
>通过设置以上选项,我们准备获取GC日志。要调整JVM,最好在负载测试期间启用这些选项,并在不同负载上获取GC日志以进行分析。从GC日志中,我们必须观察以下参数。
>
>#### 1. Young GC和Full GC多久发生一次?
>GC事件之间应该有几分钟的时间间隔。如果Young GC发生的频率更高,那么我们可能需要查看分配的Young Gen空间。如果Full GC发生的频率更高,那么我们需要查看分配的堆大小。
>#### 2. 每个GC事件需要多少时间才能完成?
>理想情况下,Young GC将花费几毫秒,而Full GC将花费几毫秒至几秒。在任何情况下,如果GC需要花费几秒钟到几分钟,那么我们必须查看堆大小的调整。如果GC线程花费更多时间来完成垃圾回收,则应用程序线程将处于等待状态(GC停止了world事件)。这会影响用户体验。
<br><br><br><br><br>
# 题外话
## Heap Dump
Heap dump文件是一个二进制文件,它保存了某一时刻JVM堆中对象使用情况。Heap Dump文件是指定时刻的Java堆栈的快照,是一种镜像文件。Heap Dump一般都包含了一个堆中的Java Objects, Class等基本信息。同时,当你在执行一个转储操作时,往往会触发一次GC,所以你转储得到的文件里包含的信息通常是有效的内容(包含比较少,或没有垃圾对象了) 。我们可以这么理解:heap dump记录内存信息的,thread dump是记录CPU信息的。
## Heap Dump 包含的信息
- 所有的对象信息
对象的类信息、字段信息、原生值(int, long等)及引用值
- 所有的类信息
类加载器、类名、超类及静态字段
- 垃圾回收的根对象
根对象是指那些可以直接被虚拟机触及的对象
- 线程栈及局部变量
包含了转储时刻的线程调用栈信息和栈帧中的局部变量信息
## Heap Dump 获取方式
使用 jmap 命令生成 dump 文件
```jmap -dump:live,format=b,file=c:dumpheap.hprof <pid>```