jvm-运行时数据

jvm

运行时数据

jvm的运行时数据区分为:
程序计数器,java虚拟机栈,本地方法栈,java堆和方法区。

程序计数器

程序计数器是一块较小的内存空间,它是当前线程所执行的字节码行号指示器。字节码解释器工作时时通过改变这个计数器的值来选取来选取下一条需要执行的字节码指令,分支,循环,跳转,异常处理,线程恢复等基础功能都需要依赖这个计数器来完成。
每个线程都需要一个独立的程序计数器,各线程之间计数器互不影响,独立存储,因此该区域是线程私有的。
当一个线程执行的是java方法,这个计数器记录的是正在执行的虚拟机字节码指令地址;如果正在执行native方法,这个计数器值则为空。
该区域是唯一一个java虚拟机规范中没有规定任何OutOfMemoryError内存溢出的区域

OutOfMemoryError内存溢出

1、堆内存溢出(outOfMemoryError:java heap space)
在jvm规范中,堆中的内存是用来生成对象实例和数组的。
如果细分,堆内存还可以分为年轻代和年老代,年轻代包括一个eden区和两个survivor区。
当生成新对象时,内存的申请过程如下:
a、jvm先尝试在eden区分配新建对象所需的内存;
b、如果内存大小足够,申请结束,否则下一步;
c、jvm启动youngGC,试图将eden区中不活跃的对象释放掉,释放后若Eden空间仍然不足以放入新对象,则试图将部分Eden中活跃对象放入Survivor区;
d、Survivor区被用来作为Eden及old的中间交换区域,当OLD区空间足够时,Survivor区的对象会被移到Old区,否则会被保留在Survivor区;
e、 当OLD区空间不够时,JVM会在OLD区进行full GC;
f、full GC后,若Survivor及OLD区仍然无法存放从Eden复制过来的部分对象,导致JVM无法在Eden区为新对象创建内存区域,则出现”out of memory错误”:outOfMemoryError:java heap space
2、方法区内存溢出(outOfMemoryError:permgem space)
在jvm规范中,方法区主要存放的是类信息、常量、静态变量等。
所以如果程序加载的类过多,或者使用反射、gclib等这种动态代理生成类的技术,就可能导致该区发生内存溢出,一般该区发生内存溢出时的错误信息为:outOfMemoryError:permgem space
3、线程栈溢出(java.lang.StackOverflowError)
线程栈时线程独有的一块内存结构,所以线程栈发生问题必定是某个线程运行时产生的错误。
一般线程栈溢出是由于递归太深或方法调用层级过多导致的。
发生栈溢出的错误信息为:java.lang.StackOverflowError

常见原因:
内存中加载的数据量过于庞大
集合类中有对对象的引用,使用完为清空,使得jvm不能回收
代码中存在死循环或循环尝生过多重复的对象实体
启动参数内存值设置过小
解决java.lang.OutOfMemoryError的方法有如下几种:
检查JVM堆参数(-Xmx与-Xms),调大参数(栈容量只能由-Xss参数设定)

内存泄漏

内存泄漏是指那些本应该被jvm回收的内存对象无法被系统回收。
场景:
长生命周期的对象持有短生命周期对象的引用
修改hashset中对象的参数值,且参数是计算哈希值的字段

java虚拟机栈

虚拟机栈描述的是java方法运行时的内存模型。每个方法被执行的时候会同时创建一个栈帧,栈是用于支持虚拟机进行方法调用和方法执行的数据结构。对于执行引擎来讲,活动线程中只有栈顶的栈帧是有效的,称为当前栈帧,这个栈帧所关联的方法称为当前方法,执行引擎所运行的所有字节码指令都只针对当前栈帧操作,栈帧用于存储局部变量表,操作数栈,动态链接,方法出口等信息。局部变量表所需要的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。
可以通过设置-Xss参数来设定java虚拟机栈内存大小。该区域可能抛出StackOverflowError和OutOfMemoryError异常

本地方法栈

本地方法栈与虚拟机栈发挥的作用相似。本地方法栈是为虚拟机使用的native方法服务。JNI。

堆是虚拟机管理内存中最大的一块。是线程共享的。
此区域的唯一目的是存放对象实例。几乎所有的对象实例和数组都在这里分配。java堆是垃圾收集器管理的主要区域,又称为“GC堆”。从垃圾回收的角度看,现在收集器基本都采用分代收集算法,所以java堆细分为:
新生代 (Young Generation)
在方法中去 new 一个对象,那这方法调用完毕后,对象就会被回收,这就是一个典型的新生代对象。
老年代 (Old Generation)
在新生代中进里了N次的垃圾回收后仍然存活的对象就会被放到老年代中。而且大对象直接进入到
老年代
当 Survivor 空间不够用时,需要依赖于老年代进行分配担保,所以大对象直接进入老年代
永久代 (Permanent Generation)
即方法区。
当一个对象被创建时,它首先进入新生代,之后有可能被转移到老年代中。
新生代存放着大量的生命很短的对象,因此新生代在三个区域中垃圾回收的频率最高。为了更高效地进行垃圾回收,把新生代继续划分成以下三个空间:
Eden(伊甸园)
From Survivor(幸存者)
To Survivor
可以通过 -Xms 和 -Xmx 两个虚拟机参数来指定一个程序的 Java 堆内存大小,第一个参数设置初始值,第二个参数设置最大值。
eden:survior:survior = 8 : 1:1(经验) new : old = 1 : 3 或者 3:8

为什么新生代设置两个survivor

新生代使用的是复制算法,另一个Survivor区用于gc来回复制使用,因为新生代效率比较高,eden区大多数对象都会被回收掉,所以Survivor区的大小不是很大。
复制算法:
将原有的内存空间划分成两块,每次只使用其中一块,在垃圾回收的时候,将正在使用的内存中的存活对象复制到另一块内存区域中,然后清除正使用过的内存区域,交换两个区域的角色,完成垃圾回收。
Survivor的存在意义,就是减少被送到老年代的对象,进而减少Full GC的发生,Survivor的预筛选保证,只有经历16次Minor GC还能在新生代中存活的对象,才会被送到老年代。
设置两个survivor最大的好处是解决了内存碎片化。
刚刚新建的对象在Eden中,经历一次Minor GC,Eden中的存活对象就会被移动到第一块survivor space S0,Eden被清空;等Eden区再满了,就再触发一次Minor GC,Eden和S0中的存活对象又会被复制送入第二块survivor space S1(这个过程非常重要,因为这种复制算法保证了S1中来自S0和Eden两部分的存活对象占用连续的内存空间,避免了碎片化的发生)。S0和Eden被清空,然后下一轮S0与S1交换角色,如此循环往复。如果对象的复制次数达到16次,该对象就会被送到老年代中。
整个过程中,永远有一个survivor space是空的,另一个非空的survivor space无碎片。

方法区

方法区是线程共享区域。用于存放已被加载的类信息,常量,静态变量,即时编译器(JIT)编译后的代码。方法区域又被称为“永久代”。
和java堆一样不需要连续的内存,可以动态扩展,动态扩展是败会抛出OutOfMemoryError 异常。虚拟机规范允许该区域可以不实现来及回收。对该区域进行垃圾回收的主要目标是对常量此的回收和对类的卸载。

运行时常量池

该区域是方法区的一部分。Class文件中除了类的版本,字段,方法,接口等描述信息外,还有一项信息是常量池(Class文件常量池),用于存放编译器生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。运行时常量池另一个重要特征是具备动态性,比如String类的intern()。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值