对JVM的一些理解

版权声明:@Author 犯罪嫌疑人卢某 洒家辛苦总结 希望尊重洒家的劳动哟 https://blog.csdn.net/unscdf117/article/details/78692476

又是一年忙碌,终于可以静下来了.本来想着能够再休息一段时间,但是双手离开键盘就是让人不快,尤其是我这种编码疯狗而言,不写代码就如同少了狗粮一般.

我觉得休息一段时间有利于我的身心健康,好让我在下一次疯狂之前能够补充好体力和营养,我错了.休息到12月出来面试是最错误的选择.拉弓没有回头箭,Hello world again..

JMM(Java Memory Model)内存模型:
JVM中的JMM
1.程序计数器: 当前线程执行字节码行号的指示器.分支,循环,跳转,异常处理,线程恢复等基础功能都需要它完成.JVM的多线程是通过线程轮流切换并分配CPU执行时间来实现的,一个CPU在一个时间片上只能执行一条线程中的指令,为了线程切换之后能恢复到正确的执行位置,每个线程都有一个独立的程序计数器,线程之间的计数器互不影响,独立存储.(这是唯一没有规定任何OOM的区域)

2.虚拟机栈:线程私有,生命周期和线程相同.虚拟机栈描述的是Java方法执行的内存模型,每个方法被执行的时候都会同时创建一个栈帧,用来存储局部变量表,操作栈,动态链接,方法返回地址等信息.对于执行引擎而言,活动线程中只有栈顶的栈帧(当前栈帧),这个栈帧关联的方法为当前方法.执行引擎运行的所有字节码指令只对当前栈帧进行操作.

3.局部变量表:用于存放方法参数和方法内的局部变量.当Java文件被编译成Class文件时,在方法的Code属性的Max_Locals中确定该方法需要配置的最大局部变量表的容量.基本单位是Slot(变量槽).虚拟机是通过局部变量表来完成参数值到变量列表的传递过程.如果是实例方法(非static),局部变量表的0位索引的Slot默认用于传递方法所属的实例引用,在方法中通过this访问.Slot可以复用,如果Slot中的变量超出了作用域,下一次分配Slot时会覆盖原先的数据.Slot对对象的引用会影响GC(如果被引用则不会被回收).系统不会为局部变量初始化,而实例变量和类变量(static修饰)则会被初始化赋值.注意局部变量表是通过索引来访问的.

4.操作数栈:通过栈操作–压栈和弹栈进行操作(不同于局部变量表).JVM在操作数栈中存储数据方式和局部变量表是相同的比如int,long,float,double,reference,returnType的存储
byte,short,char在压入操作数栈之前,都会被转换成int.

5.动态连接:JVM运行时,常量池会保存大量的符号引用,这些符号可以理解成各个方法的间接引用.比如代表栈帧A的方法想调用代表栈帧B的方法,JVM的方法调用指令就以B的符号引用作为参数,但是因为符号引用并不是直接指向方法B的内存位置,所以调用之前必须把符号引用转换成直接引用.如果符号引用在类加载阶段或第一次使用时转化为直接引用,就是静态解析.如果是运行时转换为直接引用就是动态连接.

6.内存溢出和内存泄漏:
OOM是程序在申请内存时,没有足够的空间供其使用,引发OOM.例如一个BigIntger才能容下的数值,非要存入一个Integer中,必然导致OOM.
内存泄漏(Memory leak)是指程序申请内存后,无法释放申请的内存空间,内存泄漏堆积导致内存消耗殆尽最后必然导致OOM.

在并发编程中,Java采用的是共享内存模型来实现多线程间的信息交换和数据同步.线程之间通过共享程序公共的状态,通过读–写内存中的公共状态的方式进行隐式通信.同步指的是程序在控制多个线程指尖执行程序的相对顺序的机制,在共享内存模型之中同步是显式的,码农必须显式指定某个方法/代码块需要在多个线程间互斥执行.
JVM管理一块堆内存同时也管理部分非堆内存,堆是运行时数据区域,所有类的实例和数组的内存都在这里分配,是给开发人员使用的.非堆内存是JVM自留的,包括方法区,JVM内部处理或优化所需的内存(比如JIT–即时编译),每个类结构(比如运行时常量池、字段、方法数据)以及方法和构造方法的代码.
JMM
JVM启动时已经保留固定的内存控件给堆内存,这部分内存不一定立刻被JVM使用,但是能确保不被其他进程使用,内存大小由-Xmx参数来指定.另一部分内存在JVM启动时就分配给了JVM,用来初始化堆内存,大小由-Xms来决定.
JMM的配置
默认空余堆内存小于40%时,JVM会增大堆直到配置的最大限制,可以由-XX:MinHeapFreeRatio指定.
默认空余堆内存大于70%时,JVM会减少堆直到-Xms的最小限制,可以由-XX:MaxHeapFreeRatio指定.
可以通过-XX:MaxPermSize设置非堆内存的大小.
(注: 1.8以后失效 1.8以后因为Metaspace的缘故 需要设置 -XX:MaxMetaspaceSize)

Java内存分配和管理
Java内存管理实际上是变脸共和对象的管理,包括对象的分配和释放.如图:
Java内存管理
JVM内存申请过程:
1.JVM会试图给相关的对象在Eden区中申请内存区域
2.Eden区空间足够,内存申请结束.
3.如果Eden区空间不足,JVM会试图释放Eden区中所有不活跃的对象,释放之后Eden区空间如果仍然不足以放入新对象,则试图把部分Eden中的活跃对象存入Survivor区.
4.Survivor区是用来作为Eden和Old区的交换区域,Old区空间足够时,Survivor区的对象会转移到Old区,否则会保留在Survivor区
5.Old区空间不足时,JVM会在Old区进行完全的垃圾回收
6.完全回收之后,如果Survivor区和Old区如果依然不能存入从Eden区复制过来的部分对象,JVM就无法在Eden区再为新对象申请内存区域,会引发OOM.

Java运行时数据
1.栈内存:栈的内存地址是非连续的,每个线程都有自己的栈.栈里面储存的是StackFrame(栈帧:包含局部变量,执行环境,操作数栈).
局部变量用来存储一个类的方法中使用到的局部变量.
执行环境用来保存解释器对java字节码解释时需要的信息:上次调用的方法,局部变量指针,操作数栈的栈顶和栈底指针
操作数栈用来保存运算需要的操作数和结果

2.堆内存:用来存放对象信息,内存也是不连续的,堆随着JVM的启动而创建,堆是一个存储的单位,用来解决数据存储问题.存储所有对象的实例和数组.

3.程序计数寄存器:当前线程执行的字节码的行号的指示器.字节码解释器通过改变这个计数器的值来选取需要执行的字节码指令,分支,循环,跳转,异常处理,线程恢复等基础功能.

4.方法区(已过时):JDK1.8之前通过永久代来实现方法区,1.8之后被替换为MetaSpace(元空间).因为需要融合HotSpot和JRockit(这个虚拟机没有永久代),所以永久代被废弃.元空间和永久代不同,并不在虚拟机中而是使用本地内存.JDK8以后字符串常量从永久代(已过时)转移到了堆中.元空间的大小是动态变化的,但是不是无限的(需要注意).

5.常量池:JDK1.8以后,常量池被转移到了元空间里.

GC的原理
Java不能和C/C++一样去手动释放内存的显式操作,所有的内存释放通过GC机制(Garbage Collection)完成.
GC可以自动监测对象是否超出作用域从而达到自动回收内存的目的.
当使用new关键字时,GC开始监控new出的对象的地址、大小、使用情况.通常GC采用有向图的方式记录管理堆中的所有对象.通过这种方式确定哪些对象是可达的和不可达的(可达性算法).
GC算法
有引用计数算法,可达性分析算法,

猜你喜欢

转载自blog.csdn.net/unscdf117/article/details/78692476