第二部分 自动内存管理机制
一、java内存区域与内存溢出异常
2.1运行时数据区
(五个组成!!!!!)
2.1.1 程序计数器:当前线程所执行的字节码的行号指示器,java方法:记录的是正在执行的虚拟机字节码指令的地址,如果正在执行得是Native方法这个计数器值为空。
2.1.2 虚拟机栈:生命周期与线程一样:创建,就绪,运行,死亡(中间可能出现赌塞)
描述java方法执行的内存模型,每个方法在执行的同时,会创建一个栈贞,用于存储局部变量表,操作数栈,方法出口等。一个方法执行的过程就是栈针出栈入栈的过程。
虚拟机栈内容
局部变量表 1.基本数据类型 2.对象引用类型 3.returnAdress类型 |
操作数栈 |
动态链接 |
方法出口 |
等等
本地方法栈:为本地方法服务 |
2.1.3 Java堆
Java 堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。这个区域是用来存放对象实例的,几乎所有对象实例都会在这里分配内存。堆是Java垃圾收集器管理的主要区域(GC堆),垃圾收集器实现了对象的自动销毁。Java堆可以细分为:新生代和老年代;再细致一点的有Eden空间,From Survivor空间,To Survivor空间等。Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可,就像我们的磁盘空间一样。可以通过-Xmx和-Xms控制。
2.1.4方法区
是线程共享的内存区域,存储被虚拟机已经加载的类信息,常亮,静态变量,等等。
2.1.5 运行时常量池
是方法区的一部分,用于存放编译期生成的各种字面量和符号引用,类加载之后将它们放进运行时常量池。
2.2.1 直接内存
不是运行时数据区的一部分,在JDK1.4中,加入了NIO类,引入了一种基于通道与缓冲区的I/O方式,他可以使用Native函数库直接分配堆外内存,通过存储在java堆中的DirectByteBuffer对象作为作为这块内存的引用进行操作,避免了Java堆和Native堆来回复制数据。
2.2.2 对象内存的创建
1、遇到new的指令,检查这个指令参数能否在常量池中定位到一个类的符号引用,检查这个引用代表的类是否已经被加载,解析,初始化过。如果没有,必须进行以上步骤。
2、虚拟机为新生对象分配内存:
堆内存是规整的:指针碰撞(并发情况会引起线程安全问题)
解决方案:1、同步处理
2、每个线程在java堆中预先分配一块内存称为本地线程分配缓冲。
堆内存不是规整的:空闲列表
3.虚拟机将分配到的内存空间都初始化为0
4.接线来虚拟机对对象进行必要的设置
5.执行init()操作
2.2.3 对象的内存布局
对象内存中存储布局可以分为3块区域:对象头,实例数据,对齐填充。
2.2.4 对象的访问定位
句柄访问
2.2.5 Java堆溢出直接指针访问
Java堆用于不断的存储对象实例,到达一定的容量限制之后就会产生内存溢出异常。
2.2.6 虚拟机栈和本地方法栈溢出
Java内存模型(重点!!!)
1、Java内存模型的三大特征
(1) 原子性:读取基本数据类型,不包括long和double,和read,load,assign,use,store,write操作。
Synchronized块之间的操作也具有原子性。
(2)可见性:volatile保证这点,与普通变量不同的是,volatile保证新值能够立即同步到主内存,以及每次使用之前都先从主内存刷新。
Synchronized和final,同步块是对一个变量执行unlock之前先把它同步回主内存,final是在构造器中一旦初始化完成,并且构造器没有把this的引用传递出去,那在其他线程中就能看到final字段的值
(3)有序性:volatile和synchornized volatile本身就包含禁止指令重排序,synchornized允许在同一个时刻只允许一条线程对其进行lock操作。也就是持有同一个锁的两个同步块只能串行进入。
2、线程实现的三种方式
(1) 使用内核实现
(2) 使用用户线程实现
(3) 使用用户线程加轻量级进程混合实现。
(4) Java线程的实现:
Jdk1.2之前是基于“绿色线程”的用户线程来实现的。
Jdk1.2中是基于操作系统的原生模型来实现的
现在要不就是一对一模型要不就是同时支持一对一和多对多。
3、java线程调度
系统为线程分配处理器使用权的过程,主要调度方式有:
协同式线程调度:
抢占式线程调度:
4、状态转换
线程阻塞和线程等待的区别:
阻塞状态:等待获取一个排他锁,这个事件将在另一个线程放弃这个锁的时候发生。
等待状态:在等一段时间或者唤醒动作的发生,在程序等待进入同步区域的时候,线程处于此状态。