转载自https://blog.csdn.net/Scythe666/article/details/51841161
http://blog.csdn.net/scythe666/article/details/51700142
jvm内存
一.运行时内存
二.程序计数器
三.Java虚拟机栈
此外,java中没有寄存器,因此所有的参数传递依靠操作数栈。
栈上分配,小对象(一般几十个bytes),在没有逃逸的情况下,可以直接分配在栈上。(没有逃逸是指,对象只能给当前线程使用,如果多个线程都要用,则不可以,因为栈是线程私有的。)直接分配在栈上,可以自动回收,减轻GC压力。因为栈本身比较小,大对象也不可以分配,会影响性能。
-XX:+DoEscapeAnalysis 启用逃逸分析,若非逃逸则可栈上分配。
四.本地方法栈
五. Java堆
关于TLAB
Sun Hotspot JVM为了提升对象内存分配的效率,对于所创建的线程都会分配一块独立的空间TLAB(Thread Local Allocation Buffer),其大小由JVM根据运行的情况计算而得,在TLAB上分配对象时不需要加锁,因此JVM在给线程的对象分配内存时会尽量的在TLAB上分配,在这种情况下JVM中分配对象内存的性能和C基本是一样高效的,但如果对象过大的话则仍然是直接使用堆空间分配
TLAB仅作用于新生代的Eden Space,因此在编写Java程序时,通常多个小的对象比大的对象分配起来更加高效。
六.方法区
直接内存
并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域,也可能导致 OOM 异常(内存区域综合>物理内存时)。NIO类,可以使用Native 函数库直接分配堆外内存,然后通过一个存储在Java 堆里面的 DirectByteBuffer 对象作为这块内存的引用进行操作。
类加载时 方法信息保存在一块称为方法区的内存中, 并不随你创建对象而随对象保存于堆中。
逻辑内存模型我们已经看到了,那当我们建立一个对象的时候是怎么进行访问的呢?
在Java 语言中,对象访问是如何进行的?对象访问在Java 语言中无处不在,是最普通的程序行为,但即使是最简单的访问,也会却涉及Java 栈、Java 堆、方法区这三个最重要内存区域之间的关联关系,如下面的这句代码:
Object obj = new Object();
假设这句代码出现在方法体中,那“Object obj”这部分的语义将会反映到Java 栈的本地变量表中,作为一个reference 类型数据出现。而“new Object()”这部分的语义将会反映到Java 堆中,形成一块存储了Object 类型所有实例数据值(Instance Data,对象中各个实例字段的数据)的结构化内存,根据具体类型以及虚拟机实现的对象内存布局(Object Memory Layout)的不同,这块内存的长度是不固定的。另外,在Java 堆中还必须包含能查找到此对象类型数据(如对象类型、父类、实现的接口、方法等)的地
址信息,这些类型数据则存储在方法区中。由于reference 类型在Java 虚拟机规范里面只规定了一个指向对象的引用,并没有定义这个引用应该通过哪种方式去定位,以及访问到Java 堆中的对象的具体位置,因此不同虚拟机实现的对象访问方式会有所不同,主流的访问方式有两种:使用句柄和直接指针。
(1)如果使用句柄访问方式,Java 堆中将会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据和类型数据各自的具体地址信息,如下图所示。
(2)如果使用直接指针访问方式,
Java 堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,reference 中直接存储的就是对象地址,如下图所示
这两种对象的访问方式各有优势,使用句柄访问方式的最大好处就是reference 中存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而reference 本身不需要被修改。使用直接指针访问方式的最大好处就是速度更快,它节省了一次指针定位的时间开销,由于对象的访问在Java 中非常频繁,因此这类开销积少成多后也是一项非常可观的执行成本。就本书讨论的主要虚拟机Sun HotSpot 而言,它是使用第二种方式进行对象访问的,但从整个软件开发的范围来看,各种语言和框架使用句柄来访问的情况也十分常见。
七. 堆溢出、栈溢出原因及实例,线上如何排查
(1)栈溢出
递归,容易引起栈溢出stackoverflow;因为方法循环调用,方法调用会不断创建栈帧。
造成栈溢出的几种情况:
1)递归过深
2)数组、List、map数据过大
3 ) 创建过多线程
对于Java虚拟机栈和本地方法栈,Java虚拟机规范规定了两种异常状况:
① 线程请求深度>虚拟机所允许的深度,将抛出StackOverFlowError(SOF)异常;
② 如果虚拟机可动态扩展,且扩展时无法申请到足够的内存,就会抛出OutOfMemoryError(OOM)异常。
解决栈溢出的方法
package com.yhj.jvm.memory.stack;
/**
* @Described:栈层级不足探究
* @VM args:-Xss128k
* @FileNmae com.yhj.jvm.memory.stack.StackOverFlow.java
*/
public class StackOverFlow {
private int i ;
public void plus() {
i++;
plus();
}
/**
* @param args
*
*/
public static void main(String[] args) {
StackOverFlow stackOverFlow = new StackOverFlow();
try {
stackOverFlow.plus();
} catch (Exception e) {
System.out.println("Exception:stack length:"+stackOverFlow.i);
e.printStackTrace();
} catch (Error e) {
System.out.println("Error:stack length:"+stackOverFlow.i);
e.printStackTrace();
}
}
}
(2)堆溢出
如果在堆中没有内存完成实例分配,且堆无法扩展时,将抛出OOM异常。
在方法区也会抛出 OOM 异常。
package OOM;
import java.util.ArrayList;
import java.util.List;
public class App1 {
static class OOMClass {
long[] num = new long[10240];
}
public static void main(String[] args) {
List<OOMClass> list = new ArrayList<>();
while (true) {
list.add(new OOMClass());
}
}
}
怎么解决?
-vmargs //虚拟机设置
-Xms40m //初始内存
-Xmx256m //最大内存
-Xmn16m //最小内存
-XX:PermSize=128M //非堆内存
-XX:MaxPermSize=256M