JVM-运行时的数据区
问:
1、java的内存分为哪几个部分?
2、那部分内存会溢出?
3、JDK7与8之间有什么区别?
为什么了解学习这部分内容?
1、了解JVM内存结构
2、分析内存问题,产生原因
3、解决内存问题,堆JVM内存调优
总览
从线程共享与否分为
线程不共享:程序计数器、java虚拟机栈、本地方法栈
线程共享:方法区、堆
线程不共享
一、程序计数器
程序计数器可以控制程序指令的进行,实现分支、跳转、异常等逻辑。
程序计数器是不会出现内存溢出的
关于程序计数器不会溢出的解释:
程序计数器仅仅只是一个运行指示器,它所需要存储的内容仅仅就是下一个需要待执行的命令的地址,无论代码有多少,最坏情况下死循环也不会让这块内存区域超限,因为程序计数器所维护的就是下一条待执行的命令的地址,所以不存在OutOfMemoryError
这里有张图:
二、Java虚拟机栈
java虚拟机栈维护着:
局部变量表、操作数栈、帧数据
1)、java虚拟机栈-局部变量表
参数
1、起始pc
字节码指令生效的起始位置
2、长度
字节码指令生命周期长度
3、序号
字节码指令在变量表中的位置
局部变量表存放参数顺序
1、实例对象this
2、方法参数
3、方法体中的变量
看了前面的内容,可能已经对局部变量表有了一定的了解,那么下面这段代码,在局部变量表中是如何存储的呢?
你对了吗?
下面这张图,解释的很详细
2)、JVM虚拟机栈-操作数栈
jvm执行指令过程中用栈式结构存放中间数据,在编译器就可以确定该栈的最大深度,为其分配内存大小
3)、帧数据
1、动态连接
2、方法出口
3、异常表
栈内存溢出
运行程序时可能会出现栈溢出异常
那就可以通过手动指定栈的大小来解决:
为虚拟机栈设置栈大小
指令:
-Xss1024k
K-KB M-MB G-GB 没有就是一字节
注意事项
不同的操作系统下栈最大最小范围有所不同
三、本地方法栈
线程共享
一、堆
- 用来存放创建出的对象
- 最容易出现内存溢出的内存区域
- 模拟堆内存溢出
堆内存需要关注的三个值
used:已使用的堆内存
total:已经分配可用的堆内存
max:可以分配的最大堆内存
java虚拟机为堆内存扩容
扩容上限
那是否扩容上限上限时,userd==max?
其实不然 这里used并不会达到max的最大值 具体溢出的判断条件比较复杂
设置堆大小
Xmx:max
Xms:total
实际上堆内存与实际分配的大小不是完全一致的,这跟GC有关系,后面的文章会讲解
给在最后的建议,在设置堆内存时,尽量将Xmx与Xms设置为相同值。
二、方法区
用来存放基础信息的位置,线程共享
方法区也可能出现内存溢出的情况
包括三部分内容:
类的元信息、运行时的常量池、字符串常量池
方法区是一个虚拟的概念,JDK7及以前,JDK8及以后方法区的存储存在着变化
JDK7以前方法区直接存放在堆空间中,JDK8及以后存放在堆以外的元空间中,这块空间独立于虚拟机直接与操作系统相关。
下面是各JDK版本下的运行时的数据区域 堆和方法区
永久代:JDK6 ---> Java 的内存中有一块称之为方法区的部分
JDK6
JDK7
JDK8
1、元信息
一般称为instanceClass对象,在类的加载阶段完成
元信息中包括信息如下
但是常量池 和 方法 java虚拟机会额外用一段内存区域来存放,这里只是存放了它的引用地址
2、运行时常量池
建议设成256MB
3、字符串常量池
stringTable--字符串常量池
字符串常量池存放着字符串内容例如:"123","abc"
通过对象new出来的String内容,直接放在堆内存中 变量的地址指向堆。
而直接赋值的字符串存放在常量池,变量指向常量池。
常量池与方法区有什么联系?
下面分别介绍了JDK7及以前JDK8开始的字符串常量池存储位置发生的变化
下面通过一道练习题来了解字符串常量池的存储
字节码指令示例:
通过字节码指令发现,a和b通过stringBuilder拼接后存储到堆内存中,d指向堆中
intern()方法
JDK6
JDK7及以后
这里有一点需要注意的是,”java“这个字符串会被java虚拟机会被直接加载到字符串常量池中。
所以JDK6的结果是:false、false
JDK7以后:false、true
直接内存
直接内存不属于java虚拟机运行的内存区域
自己如果要实现直接内存上的数据,可以通过ByteBuffer
指定直接内存大小