一、JVM结构

1.Class Loader:依据特定格式,加载class文件到内存
2.Execution Engine:对加载的二进制字节码命令进行解析
3.Native Interface:融合不同开发语言的原生库为Java所用
4.Runtime Data Area:JVM内存空间结构模型
二、JVM内存空间结构模型
1.内存模型结构
(1) 线程私有
程序计数器(字节码指令)、虚拟机栈(java方法)、本地方法栈(native方法)
(2) 线程共享
MetaSpace、Java堆
2. 线程私有:程序计数器(Program Counter Register)
作用:
(1)线程独立拥有计数器,是当前线程所执行的字节码行号指示器(逻辑)
(2)改变计数器的值来选取当前线程需要执行的下一条字节码指令
(3)线程独有,即一个线程拥有一个计数器
(4)对Java方法计数,如果是Native方法则计数器值为Undefined
(5)不会发生内存泄漏
3. 线程私有:Java虚拟机栈
(1)作用:java方法执行的内存模型,包含多个栈帧
(2)程序执行过程:对于当前线程,每个方法会产生1个栈帧,当方法执行结束后,该栈帧取消。而每个栈帧中包含:局部变量表、操作栈、动态链接、返回地址等等。
(3) 当前栈中包含内容
局部变量表:包含了执行过程中的所有变量(boolen,char,string等等)
操作数栈:操作数栈:入栈、出栈、复制、交换、产生消费变量(4) 实例实现当前线程调用过程
测试类:MemoryCodeSample.java
package com.spring.ioc.c5;
public class MemoryCodeSample {
public static int add(int a,int b){
int c=0;
c=a+b;
return c;
}
}
- 编译:
src\main\java\com\spring\ioc\c5>javap -verbose MemoryCodeSample.class
- 反编译:
src\main\java\com\spring\ioc\c5>javap -verbose MemoryCodeSample.class
- 结果:
Classfile …/src/main/java/com/spring/ioc/c5/MemoryCodeSample.class
Last modified 2019-12-11; size 292 bytes
MD5 checksum 6d23ead3e45557a26587f2d70fbb9ee5
Compiled from "MemoryCodeSample.java"
public class com.spring.ioc.c5.MemoryCodeSample //描述类信息
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #3.#12 // java/lang/Object."<init>":()V
#2 = Class #13 // com/spring/ioc/c5/MemoryCodeSample
#3 = Class #14 // java/lang/Object
#4 = Utf8 <init>
#5 = Utf8 ()V
#6 = Utf8 Code
#7 = Utf8 LineNumberTable
#8 = Utf8 add
#9 = Utf8 (II)I
#10 = Utf8 SourceFile
#11 = Utf8 MemoryCodeSample.java
#12 = NameAndType #4:#5 // "<init>":()V
#13 = Utf8 com/spring/ioc/c5/MemoryCodeSample
#14 = Utf8 java/lang/Object
{
public com.spring.ioc.c5.MemoryCodeSample();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
public static int add(int, int);
descriptor: (II)I // 接受两个int输入,返回也是一个int
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=3, args_size=2 // 栈容量2,本地变量3,参数2个
0: iconst_0 // 把0压入操作数栈顶;同时获取输入的局部变量(1,2)
1: istore_2 // 把操作数栈顶元素取出,放入局部变量temp[2]位置
2: iload_0 // 把局部变量temp[0]压入操作数栈顶
3: iload_1 // 把局部变量temp[1]压入操作数栈顶
4: iadd // 把操作数栈中元素加和,压入操作数栈顶
5: istore_2 // 把操作数栈顶元素取出,放入局部变量temp[2]
6: iload_2 // 把局部变量temp[2]压入操作数栈中
7: ireturn // 把操作数栈顶元素返回
LineNumberTable:
line 6: 0 //代码第6行,对应字节码第0行
line 7: 2 //代码第7行,对应字节码第2行
line 8: 6 //代码第8行,对应字节码第6行
}
SourceFile: "MemoryCodeSample.java"
具体过程add(1+2)实例
4. 线程私有:本地方法栈
与虚拟机栈类似,主要用于标注native方法
5.线程共享:元空间(MetaSpace)
(1) 作用:存储class基本信息,包括java对象的method和field等
(2) 元空间使用的是本地内存,而永久代是使用jvm内存
(3) 元空间(MetaSpace)与永久代(PermGen)区别
在Java8中,元空间(Metaspace)登上舞台,方法区存在于元空间(Metaspace)。同时,元空间不再与堆连续,而且是存在于本地内存(Native memory)。
参考:https://www.cnblogs.com/secbro/p/11718987.html
6.线程共享:java堆
(1)堆内存分类:根据对象存活的周期不同,把堆内存划分为:新生代、老年代和永久代(对HotSpot虚拟机而言),这就是JVM的内存分代策略。
(2)作用:堆内存是虚拟机管理的内存中最大的一块,也是垃圾回收最频繁的一块区域,我们程序所有的对象实例都存放在堆内存中。
给堆内存分代是为了提高对象内存分配和垃圾回收的效率。试想一下,如果堆内存没有区域划分,所有的新创建的对象和生命周期很长的对象放在一起,随着程序的执行,堆内存需要频繁进行垃圾收集,而每次回收都要遍历所有的对象,遍历这些对象所花费的时间代价是巨大的,会严重影响我们的GC效率。
有了内存分代,新创建的对象会在新生代中分配内存;经过多次回收仍然存活下来的对象存放在老年代中;静态属性、类信息等存放在永久代中。新生代中的对象存活时间短,只需要在新生代区域中频繁进行GC;老年代中对象生命周期长,内存回收的频率相对较低,不需要频繁进行回收;永久代中回收效果太差,一般不进行垃圾回收。还可以根据不同年代的特点采用合适的垃圾收集算法。分代收集大大提升了收集效率,这些都是内存分代带来的好处。
(2)Java堆的分配区域
- JAVA1.8之前
参考:https://www.cnblogs.com/guanghe/p/10524314.html
- JAVA1.8之后
参考:https://blog.csdn.net/ztx114/article/details/79400789
-
元空间和永久代之前区别
元空间并不在虚拟机中而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制。类的元数据放入native memory,字符串池和类的静态变量放入java堆中,这样可以加载多少类的元数据就不再由MaxPermSize控制,而由系统的实际可用空间来控制。
四、问题
1.递归由于调度栈过深,导致超过虚拟机栈帧深度会引发java.lang.StackOverflowError异常
(1)代码:斐波那契数列
package com.spring.ioc.c5;
public class Fibonacci {
public static int fibonacci(int n){
if(n==0) return 0;
if(n==1) return 1;
return fibonacci(n-1)+fibonacci(n-2);
}
public static void main(String[] args) {
System.out.println(fibonacci(1));
System.out.println(fibonacci(2));
System.out.println(fibonacci(3));
System.out.println(fibonacci(4));
System.out.println(fibonacci(5));
System.out.println(fibonacci(1000000));
}
}
报错:
1
1
2
3
5
Exception in thread "main" java.lang.StackOverflowError
(2)解决:限制递归次数,或者使用循环替换递归
2.内存不够导致java.lang.OutOfMemoryError问题
(1)代码
package com.spring.ioc.c5;
public class stackLeak {
public void stackLeakByThread(){
while (true){
new Thread(){
public void run(){
while (true){
}
}
}.start();
}
}
}
(2)报错:(警告:绝对不建议尝试,可能引起死机)
Exception in thread "main"java.lang OutOfMemoryError: unable to create new native thread
五、堆内存调优
1.堆内存调优参数
(1)参数
Xms:设置初始分配大小,默认为物理内存的“1/64”
Xmx:最大分配内存,默认为物理内存的“1/4”
-XX:+PrintGCDetails 输出详细的GC处理日志
(2)实例
-》代码
package com.algorithm.learn.test;
import java.util.Random;
public class test1 {
public static void main(String[] args) {
String str="hello world";
while (true){
str+=str+new Random().nextInt()+new Random().nextInt(88888);
}
}
}
-》参数:-Xms8m -Xmx8m -XX:+PrintGCDetails
-》结果
[GC (Allocation Failure) [PSYoungGen: 1443K->481K(2048K)] 1443K->713K(7680K), 0.0052305 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[GC (Allocation Failure) [PSYoungGen: 1605K->511K(2048K)] 1837K->1015K(7680K), 0.0009143 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 1990K->288K(2048K)] 3452K->1990K(7680K), 0.0008724 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Ergonomics) [PSYoungGen: 1283K->0K(2048K)] [ParOldGen: 5538K->1561K(5632K)] 6822K->1561K(7680K), [Metaspace: 3112K->3112K(1056768K)], 0.0068044 secs] [Times: user=0.05 sys=0.00, real=0.01 secs]
[GC (Allocation Failure) [PSYoungGen: 1024K->96K(2048K)] 4503K->3574K(7680K), 0.0004818 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 96K->64K(2048K)] 3574K->3542K(7680K), 0.0003641 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 64K->0K(2048K)] [ParOldGen: 3478K->3480K(5632K)] 3542K->3480K(7680K), [Metaspace: 3125K->3125K(1056768K)], 0.0090112 secs] [Times: user=0.03 sys=0.00, real=0.01 secs]
[GC (Allocation Failure) [PSYoungGen: 0K->0K(2048K)] 3480K->3480K(7680K), 0.0002866 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(2048K)] [ParOldGen: 3480K->3464K(5632K)] 3480K->3464K(7680K), [Metaspace: 3125K->3125K(1056768K)], 0.0078615 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3332)
at java.lang.AbstractStringBuilder.expandCapacity(AbstractStringBuilder.java:137)
at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:121)
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:647)
at java.lang.StringBuilder.append(StringBuilder.java:208)
at com.algorithm.learn.test.test1.main(test1.java:30)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)
Heap
PSYoungGen total 2048K, used 57K [0x00000000ffd80000, 0x0000000100000000, 0x0000000100000000)
eden space 1536K, 3% used [0x00000000ffd80000,0x00000000ffd8e6f8,0x00000000fff00000)
from space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
to space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
ParOldGen total 5632K, used 3464K [0x00000000ff800000, 0x00000000ffd80000, 0x00000000ffd80000)
object space 5632K, 61% used [0x00000000ff800000,0x00000000ffb621a8,0x00000000ffd80000)
Metaspace used 3194K, capacity 4494K, committed 4864K, reserved 1056768K
class space used 348K, capacity 386K, committed 512K, reserved 1048576K
六、参考
1.面试问题:你了解Java内存结构么(Java7、8、9内存结构的区别)
https://www.cnblogs.com/jobbible/p/9762299.html