JVM就是Java虚拟机。虚拟机是指通过软件虚拟出来的具有完整硬件功能的计算机系统,它的运行环境是完全隔离的。
我们知道Java是一个跨平台的语言,可以不加修改的在任何操作系统中运行,这就是依托于其运行在JVM中实现的。
1. JVM运行流程
- .java 文件 被编译成 .class 文件,。
- 通过 类加载子系统 将 .class 二进制字节码文件加载到内存中。
- 方法区保存类对象,类对象是new对象的模板。
- new出来的对象全都放在堆内存中。
- 每个线程都会在Java虚拟机栈中分配一个与之对应的内存空间,栈中存放是是线程对方法的调用层级。
- 本地方法栈中存放的是本地方法的调用层级。
- 程序计数器,保存的是当前线程执行的行号。

2. Java运行时数据区

2.1 方法区(内存共享)
方法区保存的是类的类对象,这个类对象就是我们在new对象时的模板。由于存放的是类对象,是公共的数据,所以方法区是线程共享的,所以线程都可以访问这个区域。
在JDK7的实现中被称为永久代。
在JDK8的实现中被称为元空间。
2.2 堆(内存共享)
所有new出来的对象都存放在堆中。堆是JVM内存中使用最大的内存区域,默认占内存的八分之一,不过这个比例是可以JVM参数设置进行设置的。
2.3 Java虚拟机栈(线程私有)
每创建一个线程就会在内存中创建一个与之对应的Java虚拟机栈。Java虚拟机栈的生命周期和线程是相同的,线程结束,对应的Java虚拟机栈就会被垃圾回收掉。
在这个Java虚拟机栈中,调用一个方法就会将该方法压入栈,此时我们将其称为栈帧,当方法执行完之后就会出栈,直到这个栈中的栈帧全部出栈,此时就代表着栈空了,也就意味线程结束了。

2.4 本地方法栈(线程私有)
本地方法栈和虚拟机栈类似,只不过Java虚拟机栈是给JVM使用的,而本地方法栈是给本地方法使用的,记录本地方法的调用层级。
2.5 程序计数器(线程私有)
我们知道多个线程在CPU上是抢占式执行的。那么一个线程重新调度到CPU上怎么知道上一次执行到了什么地方呢,就是通过程序计数器来记录的。
这个计数器记录的是正在执行的虚拟机字节码指令的地址,如果正在执行的是一个native方法,这个计数器值为空。
程序计数器内存区域是唯一一个JVM规范中没有规定任何OOM情况的区域。
2.6 总结

2.7 内存布局中的异常问题
2.7.1 Java堆溢出
Java堆⽤于存储对象实例,只要不断的创建对象,并且保证GC Roots到对象之间有可达路径来避免来GC清除这些对象,那么在对象数量达到最⼤堆容量后就会产⽣内存溢出异常。
Java堆内存的OOM异常是实际应用中最常见的内存溢出情况。当出现Java堆内存溢出时,异常堆栈息"java.lang.OutOfMemoryError"会进一步提示"Javaheap space"。当出现"Javaheap space"则很明确的告知我们,OOM发生在堆上。
我们需要进一步分析究竟是内存泄漏 还是内存溢出。
内存泄漏: 泄漏对象无法被GC。
内存溢出: 内存对象确实还应该存活。此时要根据JVM堆参数与物理内存相比较检查是否还应该把JVM
堆内存调大;或者检查对象的生命周期是否过长。
2.7.2 虚拟机栈和本地方法栈溢出
由于我们HotSpot虚拟机将虚拟机栈与本地⽅法栈合⼆为⼀,因此对于HotSpot来说,栈容量只需要由-Xss参数来设置。
关于虚拟机栈会产⽣的两种异常:
1. 如果线程请求的栈深度⼤于虚拟机所允许的最⼤深度,会抛出StackOverFlow异常
2. q 如果虚拟机在拓展栈时⽆法申请到⾜够的内存空间,则会抛出OOM异常
2.7.3 栈溢出和堆溢出的区别?
1. 栈溢出(深度)
原因:当方法调用(尤其是递归调用)的深度过大,导致不断压入栈帧,最终栈的空间被耗尽,无法再为新的方法调用分配栈帧时,就会抛出 StackOverflowError。
无限递归(缺少基准条件或条件错误)。
深度递归(即使有限,但深度超过了栈的容量)。
方法间循环调用(A调用B,B又调用A)。
2. 堆溢出(总量)
原因:当JVM的堆内存不足以容纳新创建的对象,并且垃圾回收器(GC)经过努力回收后,仍然无法获得足够的内存来创建新对象时,就会抛出 OutOfMemoryError: Java heap space。
内存泄漏:这是最常见的原因。对象已经不再使用,但由于被意外的静态集合或长生命周期对象引用,导致GC无法回收它们。
3. JVM 类加载
3.1 类加载的过程

3.1.1 加载
- 通过类

最低0.47元/天 解锁文章
811

被折叠的 条评论
为什么被折叠?



