Java运行时数据区域?
1. 方法区,堆,本地方法栈,虚拟机栈,程序计数器
1. 线程私有的:程序计数器、虚拟机栈、本地方法栈
2. 线程共享的:堆、方法区、直接内存
程序计数器
1. 程序计数器时一块较小的内存空间,可以看作是当前线程锁执行的字节码的行号指示器。字节码解释器工作时通过改变这个计数器的值来选区下一条需要执行的字节码指令,分支,循环,跳转,异常处理,线程恢复等功能都需要依赖这个计数器来完成。
2. 另外,线程切换后能恢复到正确的执行位置,每个线程都需要有一个独立的程序计数器,各个线程之前的计数器互不影响,独立存储,我们称这类内存区域为线程私有内存
从上面的介绍中我们指定程序计数器的作用主要有两个:
1. 字节码接收器通过改变程序计数器来一次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。
2. 在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够指定线程赏赐运行到哪了
3. 程序计数器是唯一一个不会出现内存溢出的区域,他的生命周期随线程的创建而创建,随线程的结束而死亡。
Java虚拟机栈
1. 与程序计数器一样,java虚拟机栈也是线程私有的,它的生命周期和线程相同,描述的是java方法执行的内存模型
2. Java内存可以粗糙的区分为堆内存和栈内存,其中栈就是谁的虚拟机栈,或者谁是虚拟机栈中局部变量表部分,(实际上,java虚拟机栈是由一个个栈帧组成,而每个栈帧中都有:局部变量、操作数栈、动态连接、方法出口信息。)
3. 局部变量表主要存放了编译器可知的葛总数据类型、对象引用
4. Java虚拟机栈会出现凉后再那个异常
1. 内存泄漏:分配出去的内存无法回收称内存泄漏
2. 内存溢出:内存不够用称为内存溢出
本地方法栈
1. 和虚拟机栈说发挥的作用非常相似,区别是:虚拟机栈为虚拟机执行java方法服务,而本地方法栈为虚拟机使用到native方法服务。在hotSpot虚拟机中和jvava虚拟机栈合二为一。
2. 本地方法被执行的时候在本地方法栈会创建一个栈帧,用于存放该本第方法的局部变量表操作舒展、动态连接、出口信息等
3. 方法执行完毕后型用的盏中也会出栈并且释放内存空间,也会出现异常
堆
1. Java虚拟机所管理的内存中最大的一块,java堆是说有线程共享的一块内城区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及宿主 都在这里分配内存。
2. Java堆时垃圾回收器管理的主要区域,因此也被称为GC堆,从垃圾回收的角度,有域现在受七七基本都采用分代4垃圾收集算法,所有java堆还可以细分为:新生代和老年代。在细致一点有:eden空间、from、survivor、to survivor空间等。进一步划分的目的是更好的回收内存,或者更快地分配内存。
方法区:
1. 方法区域Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即是编译器编译后的代码等事件。虽然java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却又一个别名叫NonHeap(非堆),目的应是与java的堆内存区分。
2. 垃圾回收收集行为在这个区域是比较少见的,但是并非数据进入方法去后就永久存在了
运行时常量池
1. 运行时常量池时方法区的一部分,class文件中除了有类的版本、字段、方法,接口等描述信息外,还有常量池信息(用于存储编译时期生成的各种字面量和符合引用)。
2. 既然运行时常量池是方法区的一部分,自然受到方法区内存的限制,当常量池无法在申请到内存时会抛出内存溢出溢出
对象的创建:
1. 类加载检查:
虚拟机遇到一条new指令的时候,首先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否以被加载过、解析过和舒适和过,如果没有,那必须先执行响应的类加载过程
1. 分配内存
1. 在类加载检查通过后,家下来虚拟机将为新生对象分配内存,对象所需的内存导向在类加载完成后便可确定,为对象分配空间的任务等同于把一块确定大小的内存从java堆中划分除了。分配方式网友“指针碰撞和空闲列表两个,选择哪种分配方式由java堆是否规整决定,而java是否规整又由所采用的垃圾收集器是否带有压缩功能决定。
2. 内存分配的两种方式:
1. 选择以上两种方式中的哪一种,取决于java堆内存是否规整,而java堆内存是否规整,取决于GC收集器的算法是 标记-清除 还是 标记-整理(也称作为标记-压缩),值得注意的是,复制算法也是规整的。
1. 内存分配并发为题
在创建对象的时候有一个很重要的问题,就是线程安全,因为在事件开发过程张,创建对象是很频繁的事情,作为虚拟机来说,必须保证线程是安全的,通常来讲,虚拟机采用两种方式来保证线程安全
1. CAS+失败重试:CAS是乐观锁的一种实现方式,所谓乐观锁就是,每次不加锁而加锁没有冲突而去完成某项操作,如果因为冲突失败就重试,知道成功为止。虚拟机采用CAS配上失败重试的方式保证更新操作的原子性
2. TLAB:为每一个线程预先在eden区分配一块内存,java在给线程中的对象分配u内存时,首先在TLBA分配,当对象大于TLBA中的剩余内存或TLBA中的内存已经用尽时,再采用上述的CAS进行内存分配
1. 初始化零值
内存分配完成后,虚拟机需要将分配到内存空间都初始化为零值(不包括对象头)。这一步操作保证了对象的实例字段在java代码中可以不赋值就直接使用,程序能访问到这些字段的事件类型所对应得零值
1. 设置对象头
初始化零值完成之后,虚拟机要对对象进行必要得设置,例如这个对象是那个对象得实例、如何才能找到类的原数据信息、对象的哈希码、对象的GC分代年龄等信息。这些信息存放在对象头中。另外,根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象都会有不同的设置方式
1. 执行init方法
在上面的工作完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但是从java程序的视角来看,对象的创建才刚刚开始,init方法还没有执行,所有的字段都还为零,把对象程序安装程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来。对象的访问定位
1. 建立对象就是为了使用对象,我们的java程序通过栈上的refrence数据来操作堆上的具体对象,对象的访问方式由虚拟机实现而定,目前主流的访问方式有使用句柄和直接指针两种
1. 句柄:如果使用句柄的话,哪呢java堆中捡回划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象的事件数据于类型数据各自的具体地址
2. 直接指针:如果使用直接指针访问,那么java对象的布局就必须考虑如何放置访问数据类型的相关信息,而reference中存储的直接就是对象的地址
这两种对象的访问各有优势,使用句柄访问最大的好处就是referece中存储的是稳定的句柄地址,在对象一定时指挥改变句柄中实例数据指针,而reference本身不需要修改,使用直接指针访问方式最大的好处就是书店块,它节省了一次指针定位的时间开销,重点补充内容
1. String对象和常量池
1. String对象创建的两种方式:
String str1 = “abcd“String str2 = new String(“abcd”)Str1 == str2 //false这两种不同的创建方式是有差别的,第一种方式是在常量池中拿对象,第二种方式是直接在对内存空间中创建一个新的对象。 B) String类型的常量池比较特殊,它的主要使用方法有两种:I) 直接使用双引号声明出来的String对象会直接存储在常量池中.II) 如果不是用双引号声明的String对象,可以使用String提供的intern方法,String.intern()是一个Native方法,它的作用是:如果运行时常量池中已经包含一个等于此String对象内容的字符串,则返回常量池中该字符串的引用;如果没有,则在常量池中篡改就与此String内容相同的字符串,并返回常量池中创建的字符串引用。
1. String字符串的拼接
1. 尽量避免多个字符串拼接,因为这样会重写创建对象,如果需要改变字符串的话,尽量使用Stringbulider和Stringbuffer
2. String s = new String(“abc”)中创建了几个对象
1. 创建两个对象
2. 先有字符串“abc“放入常量池,然后new了一份字符串”abc“放入java堆中(字符串常量”abc“在编译器就已经确定放入常量池中,而javadui上的”abc“是运行期初始化阶段才确定的),然后java栈str1指向java堆中的”abc“