JVM是java virtual mechine 的缩写,是在真实的计算机基础上模拟计算机的各种功能的一种实现,JVM的本身对于计算机而言也是一个程序。 Java的跨平台性其实是依赖JVM,也就是在任何计算机平台安装JVM,就可以执行java的字节码文件,达到了"一处编译,处处运行"。
JVM由五部分组成:堆、栈:(虚拟机栈、本地方法栈)、方法区、程序计数器。
image.png
堆:根据对象的声明周期不同,划分不一样的区域:新生代、老年代、永久代(1.8之后被元空间取代)。新生代存放的一些朝生夕死的对象,老年代存放一些大对象或者多次GC后还存在的对象,永久代存放一些类信息、常量、静态变量等数据。这样划分的目的,是为了更好的回收内存,对于一些需要频繁回收,声明周期短的对象,放在新生代中。
image.png
其中Eden区和From Survivor区、To Survivor区比例8:1:1,GC开始时对象只会存在Eden和From区,To区是空的。GC进行时,Eden区存活的对象会被复制到To区,From区会根据对象的年龄来决定去向,默认15,超过会将From区移到老年代,否则移到To 区。 接着会将Eden和From 区清空,然后将To 区域的对象重新复制到新的From区域。其中如果To区没有足够的空间来保存上次新生代存活下的对象时,有老年代担保将这些对象放在老年代中。
虚拟机栈:是用于描述java方法执行的内存模型。每个方法在执行的时候都会创建一个栈帧,栈帧的组成:局部变量表、操作数栈、动态连接、方法出口。每个方法执行的过程就是一个压栈和出栈的过程。
image.png
每个线程有独自的虚拟机栈,线程之间隔离。若单个线程请求栈的深度大于虚拟机允许的深度,会抛出StackOverFlowError。
本地方法栈 :本地方法栈于虚拟机栈类似,不同的是调用的native方法。
方法区:类的信息、常量、静态变量等信息,1.7开始,字符串常量池移到java heap中,1.8 开始元空间取代永久代。方法区只是一种规范,一开始HotSpot只是用永久代来实现方法区,能够省去这块内存回收的逻辑。但是永久代受-XX:MaxPermize大小限制,会出现OOM。
程序计数器:较小的一块空间,主要是记录程序执行字节码的行号指示器。对于Java的多线程而言,是通过切换分配处理器执行时间来实现的,所以为了线程切换后能够恢复到正确的位置,每条线程都有一个程序计数器。
对于Java来说,程序计数器、虚拟机栈、本地方法栈这些是伴随着线程一块销毁,而堆和方法区是需要回收的,针对这块内存JVM如何分配,如何收入,下面就来一块看下。
首先,先搞明白这三个问题:
image.png
GC Root的对象:虚拟机栈的局部变量表的所有对象;方法区静态属性的所有对象;方法区常量的所有对象;Native方法的所有对象。
java自带的命令
参数配置
打印trace信息
参数 | 描述 |
---|---|
-XX:+PrintGC或者-verbose:gc | 打印GC日志 |
-XX:+PrintGCDetails | GC详细日志 |
-XX:+PrintGCTimeStamps | gc的时间戳 |
-Xloggc:/log/gc.log | gc文件的目录 |
-XX:+PrintHeapAtGC | 打印GC前后堆的日志 |
-XX:TraceClassLoading | 监控类的加载 |
堆的分配参数 堆的内存= 年轻代+老年代+永久代 永久代固定值64m,年轻代建议占3/8。
参数 | 描述 |
---|---|
-Xmx,-Xms | 堆的最大值和最小值,开发过程中经常配置为一样的,是为了GC后不需要重新分配造成资源浪费 |
-XX:+HeapDumpOnOutOfMemoryError、-XX:+HeapDumpPath 查看系统dump的时候发生什么,导出OOM的路径。