java 程序计数器 堆_jvm:内存结构(堆、方法区、程序计数器、本地方法栈、虚拟机栈)...

1、jvm内存结构

8a3c8d2f535ead305744c0c808db0784.png

静态编译:把java源文件编译成字节码文件class,这个时候class文件以静态方式存在。

类加载器:把java字节码文件加载到内存中

方法区:将字节码放到方法区作为元数据(简单名字+描述符)。

堆:对象(类的实例)

方法区和堆:运行时数据区在所有线程间共享

虚拟机栈、本地方法栈、程序计数器:运行时数据区线程私有

2、堆

(1)对于大多数应用来说,java堆是java虚拟机所管理的内存中的最大的一块

(2)java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建,需要考虑线程安全的问题

(3)此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例对在这里分配内存

new Person();

(4)如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,OutOfMemoryError异常

String [] str =new String[1000000];

如果再分配数组的内存之前将jvm的数值调小后便会发生此异常。

(5)是垃圾收集器管理的主要区域

堆内存溢出(OutOfMemoryError)问题:

堆既然有垃圾回收机制,为什么还会有内存溢出的问题呢?

因为垃圾回收器回收的是不被使用的对象,如果不停的产生对象而对象又都在被使用就会出现内存溢出

(6)JDK1.7开始,将StringTable从常量池移动到了堆中,String table又称为StringPool(字符串常量池)

永久带的内存回收效率较低,full GC才会触发,也就是老年代的空间不足才会触发。但是移动到堆中之后,只需要Minor GC即可触发垃圾回收,大大减轻了字符串对内存的占用

JVM中的堆一般分为三大部分(jdk1.7):新生代、老年代、永久代(java8以后永久代被元空间代替,元空间使用本地内存,是不与堆内存相连的。因此,默认情况下,元空间的大小仅受本地内存的限制)

3、方法区

是各个线程共享的内存区域,虚拟机启动的时候创建,jdk1.8之前属于堆的永久区的一部分,会被垃圾回收机制所回收只不过回收的条件较为苛刻,也就造成了永久区较难被回收。1.8开始,将方法区从永久区剥离了出来,取而代之的是元空间,相较于永久区它使用的是物理内存,默认大小是物理内存的大小,但是也可以进行配置

(1)存放的信息

已经被jvm加载的类的信息

常量

静态变量

及时编译器编译后的代码(JIT)

(2)JIT:热点代码编译后存储到方法区

for(int i=0;i<100;i++){

add();

}

上面的代码编译后存放起来,避免反复编译

(3)编译的过程

61006a42134f74b041e3c122e6ab5e66.png

(4)运行时常量池

常量池:是一个常量表

2680d5a6929f5593499761943505a624.png

运行时常量池:

常量池是*.class文件中的,当该类被加载,它的常量池信息就会放入到运行时常量池,并将里面的符号地址变为真实地址

4、程序计数器(PC Register)

(1)一块较小的内存空间,它的作用是当前线程所执行的字节码行号指示器(记录下一条jvm指令的执行地址)

(2)一个处理器只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器(线程私有)

(3)唯一一个在jvm中没有规定任何OutOfMemoryError的区域(java的规范所规定)

5、本地方法栈

为本地方法(不是由java代码编写的方法)的运行提供的内存空间

protected native Object clone() throws CloneNotSupportedException;

该方法没有方法实现,底层是c或c++实现的,是C或c++程序提供给java程序的接口,也存在两种异常

(1)栈是有深度的:

private Long aLong=1l;public void test(int a,intb){

aLong++;

System.out.println(aLong);

test(a,b);

}public static voidmain(String[] args) {

Test1 test1=newTest1();

test1.test(0,0);

}

cdbbbd2ba3e66b19af5fb3d2701148d0.png

每调用一次占用一个栈帧:

158badb69c763f2963a42e1c2e99f970.png

栈的默认大小为5248,默认1M。

函数的调用过程:

25b327d08d6f46935be590b9aeaec8c3.png

6、虚拟机栈(每一个线程运行的时候所需要的内存)

每一个方法在执行的时候都会创建一个栈帧(一个栈帧对应一个方法的调用,栈帧即每一个方法需要的内存)用于存储分配基本类型和自定义对象的引用,用于存放,局部变量表、操作数栈、动态链接\方法的返回地址

每一个线程只有一个活动栈帧,对应着当前线程正在执行的那个方法

垃圾回收不涉及栈内存

可以通过指令来指定栈的大小,但是并不是栈的内存越大越好,栈的内存变大可能引起线程数量的减少程序反而会变慢

局部变量没有逃离方法的作用域是线程安全的(线程私有),当作为参数传递的变量或是局部变量作为返回值都不是线程安全的,因为可能被别的线程访问到

(1)一个栈中的多个栈帧

facff31a0deb779ca1713dfde336a58d.png

栈帧1先入栈,然后是栈帧2、栈帧3,相当于方法1调用方法2,方法2又调用了方法3,出栈的时候栈帧3先出栈,然后是栈帧2和栈帧3

(2)异常

StackOverflowError:

线程请求的深度大于虚拟机栈的深度(栈内存溢出),方法的递归调用容易出现

栈帧过大,直接将栈内存存满,发生的情况极其少

例如:定义一个学生类和一个班级类,一个学生只属于一个班级,在学生类里面有一个班级编号属性,一个班级有多个学生,班级类里面可以定义一个集合代表多个学生,在进行JSON转换的时候就会出现循环引用的现象,即一个学生对应一个班级,一个班级又有多个学生......,要把学生和班级的引用改为单向的,就不会出现循环引用的现象了。

OutOfMemoryError:扩展时无法申请到足够的内存

7、堆、栈、方法区

(1)字符串相关:

48be397ead370db8c29f40018f62d0ab.png

JDK1.7开始,将StringTable从常量池移动到了堆中

String string="q"+"w"+"3";

后面的三个字符只创建了一个对象,因为存在字符串的折叠

String string=new String("hello");

当常量池中已经有了“hello”字符串后在常量池中就不必再创建了,只需在栈内存中创建一个对象即可;但是,如果在常量池中没有“hello”字符串的话,就需要创建两个字符串对象了。

(2)JVM执行流程

public classPerson {privateString name;public voidsayhello(String name){

System.out.println("hello"+name);

}public static voidmain(String[] args) {

Person person=newPerson();

person.sayhello("Tom");

}

}

JVM去方法区寻找Person类信息如果我不到,Classloader加载Person类信息进入内存方法区

在堆内存中创建Person对象,并持有方法区中Person类的类型信息的引用

把person添加到执行main0方法的主线程java调用栈中,指向堆空间中的内存对象

执行person.sayHello0时,JVM根据person定位到堆空间的Person实例

根据Person实例在方法区持有的引用,定位到方法区Person类型信息,获得sayHello0字节码,执行此方法执行,打印出结果。

8、局部变量表

(1)存放了各种基本数据类型、对象引用和returnAddress类型(指向了一条字节码指令的地址)

(2)long和double类型的数据会占用2个局部变量空间(Slot),其余的数据类型只占用1个

9、常量池与运行时常量池、字符串常量池

(1)常量池存储字面量和符号引用(是class文件中的常量池,编译的时候产生)

字面量:字符串、被声明为final的常量值、基本数据类型等

符号引用:类的完全限定名、字段名称和描述符、方法名称和描述符

public static voidmain(String[] args) {int a=123;

Stringstring="abc";

finalint num=123;

}

9b885d6a60a635bf1f6063b064596d20.png

程序中的数字并未放入到常量池中,这是因为只有数字超过一定的值以后才会放入到常量值中。常量池在堆中

(2)运行时常量池(类加载到内存中后产生)

将符号引用转换为实际的地址

df3c6883e866ad25ee115902550fe862.png

将class加载到内存之后经过验证、链接等之后,将符号替换为真正的地址。jdk1.8放在元空间里面,和堆相独立

(3)字符串常量池(编译时)

存储字符串,在堆中

常量池是为了避免频繁的创建和销毁对象而影响系统性能,其实现了对象的共享。常量池中所有相同的字符串常量被合并,只占用一个空间,节省了空间。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值