您的位置:首页 > 其它

[置顶] 《JVM》第一章 JVM内存模型

2018-02-28 14:02 211 查看

一、内存模型图(JDK1.6)



二、概述

1.程序计数器    程序计数器是一块较小的内存空间,每条线程都有一个独立的程序计数器,它可以看作是当前线程所执行的字节码的行号指示器。存放的并不是代码对应Java文件中的行数,而是存放执行指令(每条JAVA代码可能会对应多个指令)的内存地址,多线程程序中会导致线程上下文不断被切换,当线程恢复执行时可根据此处存放的指令地址恢复执行,如果是native方法,计数器为空。此内存区域是唯一一个在java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。2.Java虚拟机栈  同样是线程私有,描述Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。一个方法对应一个栈帧。规定的异常情况有两种:1.线程请求的栈的深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;2.如果虚拟机可以动态扩展,如果扩展时无法申请到足够的内存,就抛出OutOfMemoryError异常。3.本地方法栈  和Java虚拟机栈很类似,不同的是本地方法栈为Native方法服务。4.Java堆  是Java虚拟机所管理的内存中最大的一块。由所有线程共享,在虚拟机启动时创建。堆区唯一目的就是存放对象实例。堆中可细分为新生代和老年代,再细分可分为Eden空间、From Survivor空间、To Survivor空间。堆无法扩展时,抛出OutOfMemoryError异常5.方法区  所有线程共享,存储已被虚拟机加载的类信息、常量、静态变量、即    时编译器编译后的代码等数据。当方法区无法满足内存分配需求时,抛出OutOfMemoryError6.运行时常量池   它是方法区的一部分,Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项是常量池(Const Pool Table),用于存放编译期生成的各种字面量和符号引用。并非预置入Class文件中常量    池的内容才进入方法运行时常量池,运行期间也可能将新的常量放入池中,这种特性被开发人员利用得比较多的便是String类的intern()方法。当方法区无法满足内存分配需求时,抛出OutOfMemoryError

三、举例

误区:对象的指针在栈中而对象的实例在堆中这个说法是不严谨的,因为指针有可能也在堆中,请看下面例子public class HelloWord {
public static final String name1 = "jacks1";
public static String name2 = "jacks2";
public String name3 = "jacks3";
public String name4 = new String("jacks4");

public static void main(String args[]) throws IOException {
String name5 = "jacks5";
String name6 = new String("jacks6");
}
}
对应内存中的结构图为:



(本内存图只表示了代码中字段的存储方式,其实实际的存储内容远不止这些)

四、堆内存(HotSpot)

由于每个厂家的虚拟机不同,对此空间的划分也会有一些差别,本节主要讲Hotspot虚拟机中堆内存的空间结构,因为对象的实例都会被分配在堆内存上,堆内存可以说是占有空间比较大的一块内存,该内存区域也是垃圾回收器(第二章会讲垃圾回收器相关的内容)回收的重点区域,由于对象实例存在的周期不同,对象又分为年轻代和老年代(至于为什么分成两个代和垃圾收集有关,第二章会讲到),其中年轻代又分为一个Eden区和两个Survivor区,默认为8:1:1,示意图如下



[b]1.Eden和Survivor[/b]我们会思考为什么年轻代分为Eden区和Survivor区而老年代不进行分区?其实这个对象的生命周期有关,在Java中多数的对象都是很快死亡的,可以想象一个tomcat的容器,一个用户请求会都创建一个线程,假设在这个线程里创建的对象不会被其他线程共享,那么当请求返回时这个线程也就结束了(实际情况是回归线程池,这里只是打个比方),那么理论上当请求线程结束那么这个线程所创建的对象都可以被回收,这个时间是非常短的(如果比较长,说明你的接口该做优化了),默认对象会优先分配在年轻代(大对象和担保分配除外),每次分配对象只会分配到Eden区+Survivor1区,空余Survivor2区不进行分配对象,当Eden区被填满后,垃圾回收器会自动进行回收,Eden区+Survivor1区中存活的对象放到Survivor2区中(虽然第一次回收Survivor1区中没有任何对象),注意这个时候Survivor1会和Survivor2区进行调换,也就是经过上次回收后如果再进行对象分配就会分配到Eden区+Survivor2区中,以此类推。每次回收后存活下来的对象年龄会+1(对象头中会有年龄信息),当对象达到一定年龄后(默认15岁)就会被迁移到老年代中
思考:如果Eden区加Survivor区回收后存活的对象大于另一个Survivor区内存大小怎么办?请自行查询“JVM空间担保分配”,由于防止篇幅过长,本章不进行过多讲解。
[b]2.为什么是8:1:1?[/b]8:1:1翻译过来就是对于年轻代来说每次认为只有10%的对象存活下来,如果Eden区过小会导致gc频率过大影响性能。此默认值估计也是经过开发者经过不断测试或抽取样本来确定的默认值。
[b]3.JVM内存相关参数[/b]-Xmx 设置JVM最大可用内存
-Xms 设置JVM初始内存
-Xmn 设置年轻代大小
-Xss  设置每个线程栈空间大小
-XX:NewRatio 设置年轻代和老年代的比值
-XX:SurvivorRatio 设置Eden区和Survivor区的比值
-XX:PermSize 设置方法区初始化大小
-XX:MaxPermSize 设置方法区大小
-XX:MaxTenuringThreshold 晋升老年代年龄
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: