数据区域图:
1 程序计数器
程序计数器就是当前线程所执行的字节码的行号指示器。程序计数器通过其计数值来明确到底下一条需要执行哪些指令。譬如循环等。java中的线程特点就是一种轮流切换然后分配执行时间。但是如何才能标记线程切换的位置(就是让下一条线程从正确的位置开始,而不是盲目的从当前线程结束的地方开始,如果让我们去设计肯定会为每个线程添加一个计数器,当前线程从哪开始,执行到哪里了,万一中途就被其他线程抢占资源了,执行到中途就结束了,是不得标记当前位置,好方便下次执行,总不会每次都从头开始吧)。所以为了线程切换后能恢复到正确位置,每个线程都需要独立的程序计数器来方便的记录线程的执行。
为什么每个线程的程序计数器要独立?
程序计数器是线程的记录者。线程通过记录来查询执行的开始位置。,如果多个线程共享同一个程序计数器,那么问题又回到了轮流切换的问题上了。所以说程序计数器是每个线程独有的,互不影响,这块内存区域成为线程私有的内存。
2 java虚拟机栈(如图上的Stack)
虚拟机栈同样也是独立私有的,其生命周期与线程相同。虚拟机栈顾名思义肯定使用的是栈结构,那么肯定会有栈帧,通过栈帧来判断栈空还是沾满。实际上还是玩的标记方式,只是容器进出的方式是后进先出。这个栈用来存储局部变量表、操作数栈等信息。方法如果调用了,那么就让它进栈,如果调用完毕了,那么就出栈。局部变量表在编译期就知道了基本数据类型,完成了所需空间的分配。等到运行期间,如果发现局部变量表不够大,那么虚拟机就会扩展局部变量表,申请不到空间就会报异常OutOfMemoryError。
我们写方法的时候为什么能定义成局部变量的就不定义成全局变量?
因为局部变量定义在虚拟机栈中,而每个虚拟机栈中都是每个线程独立私有的,这样不会出现多个线程修改的问题,保证了原子性。
3 本地方法栈(如图上的Native Method Area)
本地方法栈主要是为了虚拟机使用native方法服务的,native关键字修饰的方法是原生态方法,就是其实现不在当前的文件中,对应的在c/c++中来实现的,其实就是使用别的语言来实现底层的访问,java语言本身不能对操作系统底层访问的。所以现在出现了java实现的方法和非java实现的方法。非java实现的方法则在本地方法栈上。java实现的方法在虚拟机栈上。
4 java堆(如图上的Heap)
堆的特点就是被所有线程所共享的一块内存区域。作用就是用来存放对象的实例(不绝对)。对象的创建与销毁同样肯定也是垃圾收集器关注的区域。所以堆也是垃圾收集器的主要关注点。由于垃圾收集器对于不同对象的重视程度不同,所以会采用不同的垃圾收集算法。所以堆又被细分为新生代和老年代。堆可以处于物理不连续的内存空间中,逻辑上连续就行。如果堆没地方让你创建实例,那么就会报OutOfMemoryError异常。
5 方法区(如图上Method Area)
方法区的特点就是被所有线程所共享的内存区域。作用就是用来存储虚拟机加载的类信息、常量信息、静态变量等数据。称为永久代在jdk1.7的HotSpot中,已经将永久代的字符串常量池移除了。方法区可以像堆物理内存不连续,还可以不实现垃圾收集。
运行时常量池作为方法区的一部分。Class文件是存放类的版本、字段、方法、接口描述信息外。还存放编译器生成的各种字面量和符号的引用,这部分内容在类加载后进入方法区的运行时常量池。