JVM(一)java内存区域
-
Sun/Oracle系列的虚拟机
- Sun Classic/Exact VM 第一款商用JAVA虚拟机
- HotSpot VM 目前使用范围最广的JAVA虚拟机
- Mobile系列 面对移动和嵌入式市场
-
其他公司的虚拟机
- BEA JRockit
- IBM J9 VM
- BEA Liquid VM
- Google Android Dalvik VM
- Apache Harmony
- Microsoft JVM及其他
-
未来的的JAVA技术
- 模块化
- 混合语言
- 多核并行
- 丰富语法
- 64位
- 更强的垃圾回收
-
运行时数据区域
-
定义:java虚拟机在执行java程序的过程中会把它所管理的内存划分为若干个不同的数据区域
-
类型:
1> 程序计数器:较小的内存空间,当前线程执行的字节码的行号指示器;各个线程之间独立存储,互不影响
2> 虚拟机栈:线程私有,生命周期和线程,每个方法在执行的同事都会创建一个栈帧用于存储局部变量表,操作数栈,动态链接,方法出口等信息。方法的执行就对应着栈帧在虚拟机栈中入栈和出栈的过程;栈里面存放着各种基本数据类型和对象的应用(-XSS),默认大小1M
3> 本地方法栈:本地方法栈保存的是navtive方法的信息,当一个JVM创建的线程调用native方法后,JVM不再为其在虚拟机栈中创建栈帧,JVM只是简单地动态链接并直接调用native方法
4> java堆:java堆是javaver需要重点关注的一块区域,因为涉及到内存的分配(new关键字,反射等)与回收(回收算法,收集器等)a, -Xms 堆的最小值 b, -Xmx 堆的最大值 c, -Xmn 新生代的大小 d, -XX:NewSize 新生代最小值 e, -XX:MaxNewSize 新生代最大值
5> 方法区(jdk7以后不存在了):也叫永久区,用于存储已经被虚拟机加载的类信息,常量("zsd","123"等),静态变量(static变量)等数据
a, -XX:PermSize(jdk7及以前) b, -XX:MaxPermSize(jdk7及以前) c, -XX:MetaspaceSize(jdk7以后) d, -XX:MaxMetaspaceSize(jdk7以后)
6> 运行时常量池:运行时常量池是方法区的一部分,用于存放编译期生成的各种字面量("zsd","123"等)和符号引用。jdk7后放在堆里,jdk7即以前放在方法区里
a, 永久代来存储类信息、常量、静态变量等数据不是个好主意,很容易遇到内存溢出的问题。 b, 对永久代进行调优是很难的,同时将元数据空间(本地内存)与堆的垃圾回收进行了隔离,避免永久代引发的Full GC和OOM等问题
7> 直接内存:不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域;网络推送使用最多
a, 如果使用NIO,这块区域会被频繁使用,在java堆内可以用directByteBuffer对象直接引用并操作 b, 这块内存不受java堆大小限制,但受本机总内存的限制,可以通过MaxDirectMemorySize来设置(默认与堆内存最大值一样),所以也会出现OOM异常
-
JDK1.6内存区域
JDK1.6内存区域.png -
JDK1.7内存区域
JDK1.7内存区域.png
-
-
站在线程角度来看java内存区域
- 线程共享内存区:java堆,方法区
- 线程私有内存区:虚拟机栈,本地方法栈,程序计数器。随着线程产生和消亡,因此不需要过多考虑内存回收的问题。编译时确定所需内存大小
-
深入辨析堆和栈
-
功能
1> 以栈帧的方式存储方法调用的过程,并存储方法调用过程中基本数据类型的变量(Int,short,long,byte,float,double,boolean,char等)以及对象的引用变量,其内存分配在栈上,变量出了作用域就会自动释放;
2> 而堆内存用来存储Java中的对象。无论是成员变量,还是类变量,他们指向的对象都存储在堆内存中 -
线程独享还是共享
1> 栈内存归属于单个线程,每个线程都会有一个栈内存,其存储的变量只能在其所属线程中可见,即栈内存可以理解成线程的私有内存
2> 堆内存中的对象对所有线程可见。堆内存中的对象可以被所有线程访问 -
空间大小
1> 栈的内存要远远小于堆内存,栈的深度是有限制的,可能发生StackOverFlowError问题
-
-
方法的出入栈
- 方法会打包成栈帧,一个栈帧至少要包含局部变量表,操作数栈和帧数据区
- 栈上分配
1> 跟着函数调用自行销毁,提高性能
2> 逃逸分析 : 判断方法内的变量是否会逃出函数体
3>栈上分配演示
a, -server: JVM运行的模式,才能进行逃逸分析 。其他选项:mix/client
b, -Xmx 10m: 堆的大小
c, -XX:+DoEscapeAnalysisi:启用逃逸分析。"+"开启功能,"-"关闭功能
d, -XX:+PrintGC:打印GC日志
e, -XX:+EliminateAllocations:标量替换。变成"-"就不是栈上分配
f, -XX:-UseTLAB:TLAB(ThreadLocalAllocBuffer)事先为每个线程分配一块私有内存
-
虚拟机中的对象的初始化过程
- 检查加载
- 分配内存
1> 划分内存:指针碰撞和空闲列表
2> 并发安全问题:用CAS配上失败重试本地线程分配缓冲 - 内存空间初始化
- 设置
- 对象初始化
-
对象的内存布局
- 对象头:对象自身的运行数据(hashCode等)和类型指针
- 实例数据:程序代码中所定义的各种类型的字段内容
- 对齐填充:虚拟机处理,占位,对象的大小必须是8个字节整数倍
-
对象的访问定位
-
使用句柄:指向句柄(类似指示牌)池
-
直接指针:hotspot虚拟机使用的访问方式
-
示例图
对象访问定位.png
-
-
堆参数设置,对性能的影响和内存溢出实战
- java堆溢出
1> java.lang.OutOfMemoryError: Java heap space 在分配的时候,有巨型对象在分配
2> java.lang.OutOfMemoryError: GC overhead limit exceeded 某个循环里不停的分配对象,但是分配太多,把堆撑爆了 - 新生代配置
1> -XX:NewSize/MaxNewSize 优先级最高
2> -Xmn (NewSize=MaxNewSize) 优先级第二
3> -XX:NewRatio 新生代和老年代的比例 优先级最小
4> -XX:SurvivorRatio 表示eden和Surviror的比值,缺省为8 Eden:FromS:ToS = 8:1:1
5> 示例 生成10M
-Xms20M -Xmx20M -XX:+PrintGCDetails -Xmn2m -XX:SurvivorRatio=2
-Xms20M -Xmx20M -XX:+PrintGCDetails -Xmn7m -XX:SurvivorRatio=2
-Xms20M -Xmx20M -XX:+PrintGCDetails -Xmn15m -XX:SurvivorRatio=8
-Xms20M -Xmx20M -XX:+PrintGCDetails -XX:NewRatio=2 新生代和老年代比例 1:2 - 方法区和运行时常量池溢出
- 虚拟机栈和本地方法栈溢出
1> java.lang.StackOverFlowError 要考虑是否有无限递归 - 本机直接内存溢出
1> ByteBuffer b = ByteBuffer.allocateDirect(size)
- java堆溢出