jvm-运行时数据区


以下的分区都是逻辑分区;
类加载器将class静态数据结构加载进lvm中,运行时数据区就是存储这些数据的;
jvm类似于冯诺依曼计算机模型:

1、输入设备:类加载器,将class文件加载进jvm
2、存储器:运行时数据区
3、输出设备:jvm输出给底层操作系统的二进制指令
4、中央处理器:(运算器、控制器):

jvm中又类似于cpu与内存的交互:

1、主内存:
	类比方法区和堆:线程共享的
2、cpu中的高速缓冲区:
	线程私有的java虚拟机栈、本地方法栈、程序计数器

方法区

方法区其实是一种规范,不同的java虚拟机需要自己去实现

jdk1.8之前的实现:永久代(PermGen)
jdk1.8及之后:元空间(PermGen)

方法区中到底存储的有哪些数据?

1、字符串常量池(jdk1.6之前存储在永久代,1.8及以后实际存储在堆当中)
2、静态变量(元空间中存储在堆中)
3、类信息:(元空间存储在直接内存)
	版本号、字段、方法、接口、父类等描述信息
4、即时编译过后的代码(如final修饰的)
5、运行时常量池

方法区是线程共享的

静态常量池

静态常量池是相对于运行时常量池来说的,属于描述class文件结构的一部分,由字面量符号引用组成,在类被加载后会将静态常量池加载到内存中也就是运行时常量池

字面量:文本,字符串以及Final修饰的内容
符号引用:类,接口,方法,字段等相关的描述信息

运行时常量池

类,接口,方法,字段等相关的描述信息,也就是真正的把文件的内容落地到JVM内存了

字符串量池

字符串作为最常用的数据类型,为减小内存的开销,专门为其开辟了一块内存区域(字符串常量池)用以存放;

JDK1.6及之前版本,字符串常量池是位于永久代(相当于现在的方法区)。
JDK1.8及之后,字符串常量池位于Heap堆中

常见问题:
String a ="aaaa";最多产生一个字符串对象

首先“aaaa”会被认为字面量,先在字符串常量池中查找(.equals()),如果没有找到,在堆中创建“aaaa”
字符串对象,并且将“aaaa”的引用维护到字符串常量池中(实际是一个hashTable结构,存放key-value
结构数据),再返回该引用;如果在字符串常量池中已经存在“aaaa”的引用,直接返回该引用。

String a =new String("aaaa");最多创建两个对象

首先“aaaa”会被认为字面量,先在字符串常量池中查找(.equals()),如果没有找到,在堆中创建“aaaa”
字符串对象,然后再在堆中创建一个“aaaa”对象,返回后面“aaaa”的引用

intern()

String s1 = new String("xxx");
String s2 = s1.intern();
System.out.println(s1 == s2); //false
String中的intern方法是一个 native 的方法,当调用 intern方法时,如果常量池已经包含一个等于此String
对象的字符串(用equals(object)方法确定),则返回池中的字符串。否则,将intern返回的引用指向当前
字符串 s1(jdk1.6版本需要将s1 复制到字符串常量池里)

堆是线程共享的
堆中存储什么?

1、对象实例和数组
2、类加载后创建的对应类的java.lang.Class对象

java虚拟机栈

线程私有的

1、虚拟机栈是一个线程执行的区域,保存着一个线程中方法的调用状态。换句话说,一个Java线程的运行状
	态,由一个虚拟机栈来保存,所以虚拟机栈肯定是线程私有的,独有的,随着线程的创建而创建
2、每一个被线程执行的方法,为该栈中的栈帧,即每个方法对应一个栈帧。调用一个方法,就会向栈中压入
	一个栈帧;一个方法调用完成,就会把该栈帧从栈中弹出

例如方法调用:

void a(){
b();
}
void b(){
c();
}
void c(){
}

在这里插入图片描述

栈帧

线程私有

1、每个栈帧对应一个被调用的方法,可以理解为一个方法的运行空间
2、每个栈帧中包括局部变量表(Local Variables)、操作数栈(Operand Stack)、指向运行时常量池的引用
	(A reference to the run-time constant pool)、方法返回地址(Return Address)和附加信息
1、局部变量表:方法中定义的局部变量以及方法的参数存放在这张表中
	局部变量表中的变量不可直接使用,如需要使用的话,必须通过相关指令将其加载至操作数栈中作为操作数
	使用。
2、操作数栈:以压栈和出栈的方式存储操作数的
3、动态链接:每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法
	调用过程中的动态连接(Dynamic Linking)。
4、方法返回地址:当一个方法开始执行后,只有两种方式可以退出,一种是遇到方法返回的字节码指令;一种是
	遇见异常,并且这个异常没有在方法体内得到处理。

程序计数器

线程私有的

我们都知道一个JVM进程中有多个线程在执行,而线程中的内容是否能够拥有执行权,是根据CPU调度来的
假如线程A正在执行到某个地方,突然失去了CPU的执行权,切换到线程B了,然后当线程A再获得CPU执行权
的时候,怎么能继续执行呢?这就是需要在线程中维护一个变量,记录线程执行到的位置。
如果线程正在执行Java方法,则计数器记录的是正在执行的虚拟机字节码指令的地址;
如果正在执行的是Native方法,则这个计数器为空。

本地方法栈

线程私有的

1、如果当前线程执行的方法是Native类型的,这些方法就会在本地方法栈中执行。
2、那如果在Java方法执行的时候调用native的方法呢?
	在栈帧中通过动态链接关联
1、栈指向堆
	在方法中 A a=new A(),局部变量引用指向堆对象(其实可能不会存在逃逸:如果这个对象的使用
	范围只是在方法中,那么对象就会存在堆中为线程分配一块内存,在队中所以还是共享的)。
2、方法区指向堆
	静态变量引用指向堆中对象
3、堆指向方法区
	一个对象怎么知道它是由哪个类创建出来的?堆中的对象存储时会存一些额外信息:比如class point 指向
	方法区中存储的类信息;还会存储mark word(存储锁标识、gc时的年龄)、示例数据(就是实例变量)、(如果是数组还会额外存储数组长度)、对齐填充

逃逸

在计算机语言编译器优化原理中,逃逸分析是指分析指针动态范围的方法,它同编译器优化原理的指针分析和外形分析相关联。当变量(或者对象)在方法中分配后,其指针有可能被返回或者被全局引用,这样就会被其他方法或者线程所引用,这种现象称作指针(或者引用)的逃逸(Escape)。通俗点讲,如果一个对象的指针被多个方法或者线程引用时,那么我们就称这个对象的指针(或对象)的逃逸(Escape)。
逃逸分析
逃逸分析,是一种可以有效减少Java 程序中同步负载和内存堆分配压力的跨函数全局数据流分析算法。通过逃逸分析,Java Hotspot编译器能够分析出一个新的对象的引用的使用范围从而决定是否要将这个对象分配到堆上。 逃逸分析(Escape Analysis)算是目前Java虚拟机中比较前沿的优化技术了。
对象逃逸:对象的使用范围超过了这个方法

public User doSomething1() {
   User user1 = new User ();
   user1 .setId(1);
   user1 .setDesc("xxxxxxxx");
   // ......
   return user1 ;
}

对象未逃逸:

public void doSomething2() {
   User user2 = new User ();
   user2 .setId(2);
   user2 .setDesc("xxxxxxxx");
   // ...... 
}

什么条件下会触发逃逸分析?

对象会先尝试栈上分配,如果不能成功分配,那么就去TLAB,如果还不行,就判定当前的垃圾收集器悲观策略,可不可以直接进入老
年代,最后才会进入Eden。

在这里插入图片描述

TLAB,全称:Thread Local Allocation Buffer。对象优先会在TLAB上分配;JVM默认为每个线程在Eden上开辟一个buffer区域(还是在堆中)
	在队中所以还是共享的
垃圾收集器悲观策略:大对象
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值