您的位置:首页 > 其它

JVM内存模型分析

2017-09-24 11:00 253 查看

关注点:

1·内存结构的来龙去脉
2·内存结构都有些什么?
3·对象分配规则
4·对象晋升规则
5·内存结构和我们写代码有什么关系?

 疑问:

1·java6,java7,java8内存模型究竟发生了那些变化?
2·方法区的常量池如果是放到了堆里,那static常量岂不是
要放到堆里,那堆里岂不是不仅仅是存放对象了?这不就矛盾了吗?

一:内存结构的来龙去脉

先来看一张“JVM结构图”



        JVM = 类加载器classloader+ 执行引擎 executionengine + 运行时数据区域 runtime data area
首先Java源代码文件被Java编译器编译为字节码文件,然后JVM中的类加载器加载完毕之后,交由JVM执行引擎执行。在整个程序执行过程中,JVM中的运行时数据区(内存)会用来存储程序执行期间需要用到的数据和相关信息。
因此,在Java中我们常常说到的内存结构管理就是针对这段空间进行管理(如何分配和回收内存空间),同样,jvm内存结构的来龙去脉便一目了然。接下来我们将具体分析运行时数据区这一部分。

二:内存结构都有些什么?

 



 
其内存结构共包含5部分,按照是否线程共享分为两大部分,如图,左(方法区、堆)为线程共享,右(Java栈、本地方法栈、程序计数器)为线程私有。

Java堆(Heap),是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。
方法区(Method Area),方法区(Method
Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
程序计数器(Program Counter Register),程序计数器(ProgramCounter
Register)是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器。
JVM栈(JVM Stacks),与程序计数器一样,Java虚拟机栈(JavaVirtual
Machine Stacks)也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack
Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
本地方法栈(Native Method Stacks),本地方法栈(Native
MethodStacks)与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native方法服务。



 


三:对象分配规则

对象优先分配在Eden区, 如果启用了本地线程分配缓冲, 则优先在TLAB(线程私有)上分配,
如果Eden区没有足够的空间时,虚拟机执行一次Minor GC。
大对象直接进入老年代(大对象是指需要大量连续内存空间的对象,最典型的大对象就是那种很长的字符串和数组)。这样做的目的是避免在Eden区和两个Survivor区之间发生大量的内存拷贝(新生代采用复制算法收集内存)。
四:对象晋升规则
年龄阈值
VM为每个对象定义了一个对象年龄(Age)计数器, 对象在Eden出生如果经第一次Minor GC后仍然存活, 且能被Survivor容纳的话,将被移动到Survivor空间中, 并将年龄设为1. 以后对象在Survivor区中每熬过一次Minor GC年龄就+1.当增加到一定程度(-XX:MaxTenuringThreshold, 默认15), 将会晋升到老年代.

提前晋升:动态年龄判定
然而VM并不总是要求对象的年龄必须达到MaxTenuringThreshold才能晋升老年代: 如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,
年龄大于或等于该年龄的对象就可以直接进入老年代, 而无须等到晋升年龄.

五:内存结构和我们写代码有什么关系?
jvm内存结构的认知,方能更好的认识我们自己写的代码,以及如何更好的根据jvm特性去写好我们的对象。下面以一段代码为例,看看真实的代码对应在jvm内存结构中的位置。
package com.step2.DifnationClassLoader;

/**
*
* @author 贾丽敏
*
*/
public class JVMTest {//类加载时将类信息放在方法区内,堆内生成java.lang.class对象,持有指向方法区该类的引用

/*
* i1,i2都为Integer对象的引用,线程运行时放在java栈中新建的帧栈中
* 该两个引用执行堆中的Integer实例的引用,而两实例在堆中的地址是不同的,所以,i1==i2返回false
*/
static Integer i1=new Integer(1);
static Integer i2=new Integer(1);

/*
* a,b引用同样放在java栈中帧栈中,指向常量池中的同一内存空间,所以,a==b返回true
*/
static Integer a=1;
static Integer b=1;

public static void main(String[] args){//main方法放入方法区
/**
* stu是对student对象的引用,放入栈中,指向堆中对象的内存地址。
* new出来的student对象放在堆中,并持有方法区中student类型信息的引用
*/
Student stu=new Student("jialimin");
/**
* 执行add方法时,根据java栈中stu定位到堆中的对象实例,再根据堆中持有的位于方法区的student类型信息,获得add()
* 字节码,执行此方法执行,打印出结果
*/
stu.add();
}
}

class Student{
public String name;
public Student(String name){
this.name=name;
}
public void add(){
System.out.println(name);
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  jvm 内存 结构 对象