(一)JVM内存模型和分区,每个分区放什么?
- Java内存模型(即Java Memory Model,简称JMM)本身是一种抽象的概念。java内存模型中分为
主内存
和工作内存
,主内存
可粗略认为是堆
,工作内存
认为是栈
。 - 主内存里面存储着所有变量,属于共享内存区域,所有线程都可以访问。
- 每一个线程都私有一个
工作内存
,保存着主内存里面变量值的副本,线程对变量的操作都是在工作内存中完成,操作结束后再放回主内存。 - 操作系统中,一般CPU都会从内存取数据到寄存器,然后进行处理,但由于内存的处理速度远远低于CPU,导致CPU在处理指令时往往花费很多时间在等待内存做准备工作,于是在寄存器和主内存间添加了CPU缓存,CPU缓存比较小,但访问速度比主内存快得多。所以Java虚拟机在程序执行过程会把jvm的内存分为若干个不同的数据区域。有:堆,栈,方法区,程序计数器,本地方法栈
- 堆: 类,方法,常量,变量,保存我们所有引用类型的真实对象。
- 栈: 八种基本类型变量+对象的引用变量,栈操作,栈帧数据。
- 方法区: 静态变量,常量,类信息(构造方法,接口定义),运行时的常量池存在方法区中,但是实例变量存在堆内存中,和方法区无关。方法区存储了每一个类的结构信息,就是模板的意思。
- 程序计数器: 可以简单理解为一个指针,里面存的就是下一个将要执行的的方法的指针。
- 本地方法栈: 装native方法专用的栈,这是一个特殊的栈
JNI
- 补充:常量池在1.7中处于永久代,但在1.8中,处于堆中,因为1.8中,永久代变成了元空间,但是元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制,所以可以通过参数调优来指定元空间的大小。
(二)堆里面的分区以及特点。
-
堆里面分为新生代和老生代(java8取消了永久代,采用了元空间
Metaspace
),新生代包含伊甸园区和幸存区Eden+Survivor
区,survivor区里面分为from和to区,内存回收时,在新生代使用的是复制算法,从from复制到to,当经过一次或者多次GC之后,存活下来的对象会被移动到老年区,当JVM内存不够用的时候,会触发重GC(Full GC),清理JVM老年区。 -
当新生区满了之后会触发YGC,先把存活的对象放到其中一个Survice区,然后进行垃圾清理。因为如果仅仅清理需要删除的对象,这样会导致内存碎片,因此一般会把Eden 进行完全的清理,然后整理内存。那么下次GC 的时候,就会使用下一个Survive,这样循环使用。如果有特别大的对象,新生代放不下,就会使用老年代的担保,直接放到老年代里面。因为JVM 认为,一般大对象的存活时间一般比较久远。
-
补充: 1.7之前新生代和老年代都在java堆,永久代在方法区,1.8之后变换为元空间,元空间使用本地内存。
(三)如何解决OOM问题?
-
内存泄露: 申请使用完的内存没有释放,导致虚拟机不能再次使用该内存,此时这段内存就泄露了,因为申请者不用了,而又不能被虚拟机分配给别人使用。
-
内存溢出: 申请的内存超出了JVM能提供的内存大小,此时称之为溢出。
-
用来存放JDK自身携带的Class对象,Interface元数据,存储的是java运行时的一些环境或者类信息,这个区域不存在垃圾回收,关闭JVM虚拟机会释放这个区域的内存。
例如:一个启动类,加载了大量的第三方jar包,Tomcat部署了太多的应用,大量动态生成的反射类,不断地被加载,直到内存满,就会出现OOM -
OutOfMemory
出现的原因:
- 分配的少了:比如虚拟机本身可使用的内存(一般通过启动时的VM参数指定)太少。
- 应用用的太多,并且用完没释放,浪费了。此时就会造成内存泄露或者内存溢出。
- 如何解决?
- 能够看到代码第几行出错:内存快照分析工具,MAT,Jprofiler工具
- Dubug,一行行分析代码。(效率低下)
- MAT,JProfiler 的作用:
- 分析Dump文件,快速定位内存泄漏
-Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError
-Xms 设置初始化内存分配大小
-Xmx 设置最大分配内存,默认1/4
-XX:+PrintGCDetails 打印GC垃圾回收信息
-XX:+HeapDumpOnOutOfMemoryError
- 获得堆中的数据
- 获得大的对象
(四)堆内存调优
- 默认情况下,分配的总内存,是电脑的1/4,而初始化的内存是1/16
- 在配置中修改参数语句:
-Xms1024m -Xmx1024m -XX:+PrintGCDetails
(五)元空间调优
- 元空间虚拟机控制元空间的增长。但是有些时候我们想限制其增长,比如通过显式在命令行中设置-XX:MaxMetaspaceSize。默认情况下,-XX:MaxMetaspaceSize的值没有限制,因此元空间甚至可以延伸到交换区,但是这时候当我们进行本地内存分配时将会失败。
- 对于一个64位的服务器端JVM来说,其默认的
–XX:MetaspaceSize
值为21MB。这就是初始的高水位线。一旦触及到这个水位线,Full GC将会被触发并卸载没有用的类(即这些类对应的类加载器不再存活),然后这个高水位线将会重置。新的高水位线的值取决于GC后释放了多少元空间。如果释放的空间少,这个高水位线则上升。如果释放空间过多,则高水位线下降。如果初始化的高水位线设置过低, 上述高水位线调整情况会发生很多次。通过垃圾回收器的日志我们可以观察到Full GC多次调用。为了避免频繁的GC,可以将–XX:MetaspaceSize
设置为一个相对较高的值。 - 经过多次GC之后,元空间虚拟机自动调节高水位线,以此来推迟下一次垃圾回收到来。有这样两个选项
XX:MinMetaspaceFreeRatio
和XX:MaxMetaspaceFreeRatio
,他们类似于GC的FreeRatio选项,用来设置元空间空闲比例的最大值和最小值。我们可以通过命令行对这两个选项设置对应的值。