图上所涉及的是Java的技术组成,确实,没有必要全部深入了解。但是大致的框架是有必要明确的。业务层的程序员可能并不了解这些,所以我还是弄一篇笔记用来学习和分享
传统意义上,Java包括了:
- Java程序设计语言
- 各种平台上的Java虚拟机(JVM)
- class文件格式
- Java API
- 第三方Java库
jvm是Java最小运行环境,JDK是Java最小开发环境
想知道对象怎么创建的,就要先知道,JVM的内存管理,先介绍一下
内存管理
Java比C++多了一个内存管理,减少了程序员对内存回收的操作,方便的同时也带来了新的麻烦,所以我们需要了解Java的内存管理技术,避免过于浪费性能。
-
程序计数器
标记当前线程程序执行到的位置,这样可以方便线程切换时恢复到正确的执行位置。所以计数器是线程私有数据。 -
虚拟机栈
初学Java的时候,老师喜欢把Java内存模型分为堆内存和栈内存,栈内存就是这玩意儿,是Java方法执行的内存模型,每个方法执行的时候都会创建一个栈帧(Stack Frame) 存储局部变量,操作数栈,动态链接,方法出口等。
所以堆内存和栈内存的分类是比较粗糙的,实际上要复杂的多。
在虚拟机中,如果线程请求的深度大于栈的深度就会出现StackOverflowError
错误,如果是动态扩展的栈,当扩展无法获得足够的内存会抛出OutOfMemoryError
错误 -
本地方法栈
执行Native方法的服务,具体看JVM的实现,方便程序调用本地的方法与服务。 -
Java堆
Java堆是被一群线程共享是一块内存,应该是最大的一块存储空间了。在虚拟机启动时创建,GC管理的最大的一块地方。这里可以划分出多个线程私有的缓冲区,但是这些划分与存放内容无关,这里主要存的,是对象。当然,这里是可拓展的。 -
方法区
存放虚拟机加载的类信息,常量,静态变量,即时编译的代码数据。 -
运行时常量池
是方法区的一部分,Class文件除了类版本,字段,方法,接口等描述信息外,还有一项是常量池,存放编译时,生成的各种字面量,符号引用,这部分内容将在类加载后进入方法区的运行时常量池内
上面说了这么多,那Java是怎么创建对象的呢?
对象的创建
当虚拟机获得new指令的时候,首先会在常量池中找到类符号引用,检查符号是否被加载、解析、初始化过,检查通过以后执行未执行的操作,然后虚拟机会为新对象分配内存(把一块确定大小的内存从堆中分出来)。
在规整的Java堆内存中(用过的内存放在一边,没用过的内存放在另一边,中间靠一个指针来分开)
,分配的内存就是将指针移动一下,让出对象对应大小的位置。而在非规整的堆内存中,空闲与非空闲的相互交错,这时候,Java会维护一个列表,记录内存可用的区块,分内存给对象的时候,就是找到一块足够大的地方,存下这个对象,并更新记录表。不同的JVM有不同的记录方式。
这样分配如何保证线程安全呢,即使在仅仅改变内存指针的行为,在并发情况下,也不是安全的,可能线程A正在给对象分配内存,还没来得及修改,而线程B却使用了老的指针地址。这样,要么对修改指针做同步处理,要么把内存分配动作按线程划分在不通空间(本地线程分配缓冲)。分配完,对内存进行清零,让后对对象进行初始化。将对象的哈希码,GC分代年龄存放在对象头中。
对象的访问定位
访问对象要靠栈上的reference数据寻址。Java没有定义这个引用如何定位,所以有两种方式访问。(句柄访问,指针访问)
句柄访问:栈reference中存储的是句柄的地址,对应的堆的句柄池内的数据。如图:
指针访问:指针直接访问对象
句柄访问存储比较稳定,对象移动的时候只需要改变指针内的地址,reference内的数据不用改变,指针直接访问速度更快,节省开销。
虽然Java有GC,但是GC不是万能的,内存溢出还是会存在。所以应该说了解JVM,应该也是是Java程序员的必修课之一吧。