万字精美图文 | JVM学习面试大总结
2、堆区的空间分配是怎么样?堆溢出的演示
3、创建一个新对象内存是怎么分配的?
4、方法区 到 Metaspace 元空间
5、栈帧是什么?栈帧里有什么?怎么理解?
6、本地方法栈
8、程序计数器
9、Code Cache 是什么?
注:请 区分 JVM内存结构(内存布局) 和 JMM(Java内存模型)这两个不同的概念!
概览
PS:线程是否共享这点,实际上理解了每块区域的实际用处之后,就很自然而然的就记住了。不需要死记硬背。
一、Heap (堆区)
1.1 堆区的介绍
延伸知识点:JIT编译优化中的一部分内容 - 逃逸分析。
1.2 堆区的调整
-Xms256M-Xmx1024M
,其中
-X
这个字母代表它是JVM运行时参数,
ms
是
memory start
的简称,中文意思就是内存初始值,
mx
是
memory max
的简称,意思就是最大内存。
Xms
和 Xmx
会设置成同样大小,避免在GC 后调整堆大小时带来的额外压力。
1.3 堆的默认空间分配
java -XX:+PrintFlagsFinal-version
输出
>java -XX:+PrintFlagsFinal -version
[Global flags]
...
uintx InitialSurvivorRatio = 8
uintx NewRatio = 2
...
java version "1.8.0_131"
Java(TM) SE Runtime Environment (build 1.8.0_131-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.131-b11, mixed mode)
因为新生代是由Eden + S0 + S1组成的,所以按照上述默认比例,如果eden区内存大小是40M,那么两个survivor区就是5M,整个young区就是50M,然后可以算出Old区内存大小是100M,堆区总大小就是150M。
1.4 堆溢出 演示
/**
* VM Args:-Xms10m -Xmx10m -XX:+HeapDumpOnOutOfMemoryError
* @author Richard_Yi
*/
public class HeapOOMTest {
public static final int _1MB = 1024 * 1024;
public static void main(String[] args) {
List<byte[]> byteList = new ArrayList<>(10);
for (int i = 0; i < 10; i++) {
byte[] bytes = new byte[2 * _1MB];
byteList.add(bytes);
}
}
}
输出
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid32372.hprof ...
Heapdump file created [7774077 bytes in0.009 secs]
Exceptionin thread "main" java.lang.OutOfMemoryError: Java heap space
at jvm.HeapOOMTest.main(HeapOOMTest.java:18)
-XX:+HeapDumpOnOutOfMemoryError
可以让JVM在遇到OOM异常时,输出堆内信息,特别是对相隔数月才出现的OOM异常尤为重要。
创建一个新对象 内存分配流程
YoungGarbageCollection
,即
YGC
。垃圾回收的时候,在Eden区实现清除策略,没有被引用的对象则直接回收。依然存活的对象会被移送到Survivor区。Survivor区分为so和s1两块内存空间。每次
YGC
的时候,它们将存活的对象复制到未使用的那块空间,然后将当前正在使用的空间完全清除,交换两块空间的使用状态。如果
YGC
要移送的对象大于Survivor区容量的上限,则直接移交给老年代。一个对象也不可能永远呆在新生代,就像人到了18岁就会成年一样,在JVM中
-XX:MaxTenuringThreshold
参数就是来配置一个对象从新生代晋升到老年代的阈值。默认值是
15, 可以在Survivor区交换14次之后,晋升至老年代。
上述涉及到一部分垃圾回收的名词,不熟悉的读者可以查阅资料或者看下本系列的垃圾回收章节。
二、Metaspace 元空间
Class
和
Method
。每当一个类初次被加载的时候,它的元数据都会放到永久代中。
java.lang.OutOfMemoryError:PermGen
,为此我们不得不对虚拟机做调优。
java.lang.OutOfMemoryError:PermGen
,因此 JVM 的开发者希望这一块内存可以更灵活地被管理,不要再经常出现这样的 OOM
java/lang/Object
类元信息、静态属性
System.out
、整形常量
100000
等。
In JDK 8, classes metadata is now stored in the native heap and this space is called Metaspace.
三、 Java 虚拟机栈
栈对应线程,栈帧对应方法
StackOverflowError
表示请求的
栈溢出, 导致内存耗尽, 通常出现在递归方法中。
1. 局部变量表
局部变量表就是 存放方法参数和方法内部定义的局部变量的区域。
publicint test(int a, int b) {
Object obj = newObject();
return a + b;
}
2. 操作栈
/**
* @author Richard_yyf
*/
public class OperandStackTest {
public int sum(int a, int b) {
return a + b;
}
}
.class
文件之后,再反汇编查看汇编指令
> javac OperandStackTest.java
> javap -v OperandStackTest.class> 1.txt
public int sum(int, int);
descriptor: (II)I
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=3 // 最大栈深度为2 局部变量个数为3
0: iload_1 // 局部变量1 压栈
1: iload_2 // 局部变量2 压栈
2: iadd // 栈顶两个元素相加,计算结果压栈
3: ireturn
LineNumberTable:
line 10: 0
3. 动态连接
4. 方法返回地址
RETURN
、
IRETURN
、
ARETURN
等
2、异常退出
2、异常信息抛给 能够处理的栈帧
3、PC 计数器指向方法调用后的下一条指令
延伸阅读:JVM机器指令集图解
四、本地方法栈
五、程序计数器
白话版本:因为代码是在线程中运行的,线程有可能被挂起。即CPU一会执行线程A,线程A还没有执行完被挂起了,接着执行线程B,最后又来执行线程A了,CPU得知道执行线程A的哪一部分指令,线程计数器会告诉CPU。
六、直接内存
Code Cache
nmethod
。该
nmethod
可能是一个完整的或内联Java方法。
java.lang.OutOfMemoryErrorcode cache
。
诊断选项
延伸阅读 Introduction to JVM Code Cache
参考
2、《码出高效》
3、 Metaspace in Java 8
4、 JVM机器指令集图解
5、 Introduction to JVM Code Cache
如果本文有帮助到你,希望能点个赞,这是对我的最大动力。