纪要:
1. 堆与进程
- 一个进程只有一个JVM实例,一个JVM实例中就有一个运行时数据区,一个运行时数据区只有一个堆和一个方法区。
- 但是进程包含多个线程,他们是共享同一堆空间的。
在方法结束后,触发了GC的时候,堆才会进行回收
2. java7及其之前的堆和方法区的物理存储
Java7及以前版本的Hotspot中方法区位于永久代中
永久代和堆是相互隔离的,但它们使用的物理内存是连续的
永久代的垃圾收集是和老年代捆绑在一起的,因此无论谁满了,都会触发永久代和老年代的垃圾收集
3. java8中 堆和方法区的物理存储
1. 在Java8中,时代变了,Hotspot取消了永久代。永久代的参数-XX:PermSize和-XX:MaxPermSize也随之失效。
2. 对于Java8,HotSpots取消了永久代,那么是不是就没有方法区了呢?当然不是,方法区只是一个规范,只不过它的实现变了。
3. 在Java8中,元空间(Metaspace)登上舞台,方法区存在于元空间(Metaspace)。同时,元空间不再与堆连续,而且是存在于本地内存(Native memory)。
4. 本地内存(Native memory),是供JVM自身进程使用的。当Java Heap空间不足时会触发GC,但Native memory空间不够却不会触发GC。
5. 元空间存在于本地内存,意味着只要本地内存足够,它不会出现像永久代中“java.lang.OutOfMemoryError: PermGen space”这种错误
6. 更深层的原因还是要合并HotSpot和JRockit的代码,JRockit从来没有所谓的永久代,也不需要开发运维人员设置永久代的大小,但是运行良好。同时也不用担心运行性能问题了,在覆盖到的测试中, 程序启动和运行速度降低不超过1%,但是这点性能损失换来了更大的安全保障。
4. 设置堆内存大小和OOM
- Java堆区用于存储Java对象实例,那么堆的大小在JVM启动时就已经设定好了,大家可以通过选项”-Xms”和”-Xmx”来进行设置
- 一旦堆区中的内存大小超过“-Xmx”所指定的最大内存时,将会抛出OutofMemoryError异常。
- 通常会将-Xms和-Xmx两个参数配置相同的值
- 原因:假设两个不一样,初始内存小,最大内存大。在运行期间如果堆内存不够用了,会一直扩容直到最大内存。如果内存够用且多了,也会不断的缩容释放。频繁的扩容和释放造成不必要的压力,避免在GC之后调整堆内存给服务器带来压力。
- 如果两个设置一样的就少了频繁扩容和缩容的步骤。内存不够了就直接报OOM
5. 堆的比例大小
6. 内存分配逻辑
7. 逻辑分配特殊说明
8. GC分类
部分收集:不是完整收集整个Java堆的垃圾收集。其中又分为:
- 新生代收集(Minor GC/Young GC):只是新生代(Eden,s0,s1)的垃圾收集
- 老年代收集(Major GC/Old GC):只是老年代的圾收集。
- 注意,很多时候Major GC会和Full GC混淆使用,需要具体分辨是老年代回收还是整堆回收。
![]()
9. GC日志分析
public class TestMain1 {
public static void main(String[] args) {
int i = 0;
try {
List<String> list = new ArrayList<>();
String a = "atguigu.com";
while (true) {
list.add(a);
a = a + a;
i++;
}
} catch (Throwable t) {
t.printStackTrace();
System.out.println("遍历次数为:" + i);
}
}
}
[GC (Allocation Failure) [PSYoungGen: 2048K->512K(2560K)] 2048K->797K(9728K), 0.0008019 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 2254K->496K(2560K)] 2540K->1388K(9728K), 0.0015801 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 2316K->448K(2560K)] 3209K->2396K(9728K), 0.0012466 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[Full GC (Ergonomics) [PSYoungGen: 1943K->0K(2560K)] [ParOldGen: 6172K->4917K(7168K)] 8115K->4917K(9728K), [Metaspace: 3086K->3086K(1056768K)], 0.0042929 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] 4917K->4917K(9728K), 0.0011728 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] [ParOldGen: 4917K->4898K(7168K)] 4917K->4898K(9728K), [Metaspace: 3086K->3086K(1056768K)], 0.0044432 secs] [Times: user=0.03 sys=0.00, real=0.01 secs]
遍历次数为:16
Heap
PSYoungGen total 2560K, used 147K [0x00000007bfd00000, 0x00000007c0000000, 0x00000007c0000000)
eden space 2048K, 7% used [0x00000007bfd00000,0x00000007bfd24d08,0x00000007bff00000)
from space 512K, 0% used [0x00000007bff80000,0x00000007bff80000,0x00000007c0000000)
to space 512K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007bff80000)
ParOldGen total 7168K, used 4898K [0x00000007bf600000, 0x00000007bfd00000, 0x00000007bfd00000)
object space 7168K, 68% used [0x00000007bf600000,0x00000007bfac8bd0,0x00000007bfd00000)
Metaspace used 3147K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 347K, capacity 388K, committed 512K, reserved 1048576K
java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3332)
at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124)
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:448)
at java.lang.StringBuilder.append(StringBuilder.java:136)
at com.luoyu.aiyu.luo1.TestMain1.main(TestMain1.java:16)
[GC (Allocation Failure) [PSYoungGen: 2048K->512K(2560K)] 2048K->797K(9728K), 0.0008019 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
[PSYoungGen: 2048K->512K(2560K)]:年轻代总空间为 2560K ,当前占用 2048K ,经过垃圾回收后剩余512K
2048K->797K(9728K):堆内存总空间为 9728K ,当前占用2048K ,经过垃圾回收后剩余797K
9. Java堆空间分代思想
为什么要把Java堆分代?不分代就不能正常工作了吗?经研究,不同对象的生命周期不同。70%-99%的对象是临时对象。
10. TLAB
1. 为什么有TLAB?
堆是共享的。并发环境下从堆区中划分内存空间是线程不安全的;
JVM为每个线程分配了一个私有缓存区域,它包含在Eden空间内。
多线程同时分配内存时,使用TLAB可以避免一系列的非线程安全问题,称之为快速分配策略。
11. 堆相关参数
/**
* -Xms:初始堆空间内存 (默认为物理内存的1/64)
* -Xmx:最大堆空间内存(默认为物理内存的1/4)
* -XX:+PrintGCDetails:输出详细的GC处理日志
* 打印gc简要信息:① -XX:+PrintGC ② -verbose:gc
*/
12. 堆上分配是唯一吗?
在《深入理解Java虚拟机》中关于Java堆内存有这样一段描述:
- 随着JIT编译期的发展与逃逸分析技术逐渐成熟,栈上分配、标量替换优化技术将会导致一些微妙的变化,所有的对象都分配到堆上也渐渐变得不那么“绝对”了。
- 在Java虚拟机中,对象是在Java堆中分配内存的,这是一个普遍的常识。但是,有一种特殊情况,那就是如果经过逃逸分析(Escape Analysis)后发现,一个对象并没有逃逸出方法的话,那么就可能被优化成栈上分配。这样就无需在堆上分配内存,也无须进行垃圾回收了。这也是最常见的堆外存储技术。