java虚拟机内存管理

java内存区域和内存溢出

HotSpot VM是SunJDK和OpenJDK中所带的虚拟机,也是目前使用范围最广的java虚拟机。
如果一段java方法被调用次数达到一定程度,就会被判定为热代码交给JIT编译器即时编译为本地代码,提供运行速度,这就是HotSpot虚拟机名称的由来。

一、内存区域构成

1、程序计数器
线程私有的内存。
在虚拟机的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、异常处理、线程恢复都需要依赖这个计数器来完成。
为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储。
消耗内存很少,可以忽略。
2、java虚拟机栈
线程私有的内存,生命周期和线程相同。常有人把内存分为堆和栈,这个栈就是虚拟机栈,或者虚拟机栈中的局部变量表部分。
局部变量表存放了各种基本数据类型、对象引用和returnAddress(字节码指令的地址)
3、本地方法栈
与虚拟机栈作用相似。区别是本地方法栈为虚拟机执行java方法服务,而本地方法栈则为虚拟机使用到的Native方法服务,即由非java语言实现的方法。
4、java堆
所有线程共享,虚拟机启动时创建。
堆是java虚拟机所管理的内存中最大的一块。唯一目的是存放对象实例,几乎所有对象实例都在这里分配。
如果堆中没有内存来完成实例分配,且不可扩展将会抛出OutOfMemoryError。
通过-Xmx和-Xms控制堆内存最大值最小值
5、方法区
线程共享的内存区域,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。很多人习惯把Hotspot虚拟机中的方法区成为永久代,因为
GC很少出现在这个区域。方法区无法满足内存分配需求时会跑出OutOfMemoryError。
使用动态代理或CGLib这类字节码技术时,需要消耗很大的方法区来保证动态生成的Class能加载入内存。
通过MaxPermSize控制方法区容量
6、运行时常量池
运行时常量池是方法区的一部分

二、内存溢出

Hotspot虚拟机中,对象在内存中存储的布局可以分为3块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)
1、堆溢出

java.lang.OutOfMemoryError:java heap space

2、虚拟机栈和本地方法栈溢出

java.lang.StackOverflowError

3、方法区和运行时常量池溢出

java.lang.OutOfMemoryError:PermGen space

垃圾收集算法和内存分配策略

主要是为了解决两个问题:内存分配与内存回收

一、垃圾收集算法

1、标记-清除算法
首先标记出需要回收的对象,标记完成后统一回收。标记清除效率较低,而且会产生大量不连续的内存碎片。
2、复制算法
现在的商业虚拟机都采用这种收集算法来回收新生代。将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中一块Survivor。
当回收时将Eden和Survior中还存活的对象一次性的复制到另一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor空间。
Hotspot虚拟机默认Eden和Survivor大小比例是8:1,也就是说每次新生代中可用的内存空间为整个新生代容量的90%,只有10%的内存会被浪费。
如果Survivor空间不够用时,需要依赖老年代进行分配担保。
内存的分配担保就好像我们去银行贷款,如果信誉良好,银行会默认我们会如期还款,只需要有一个担保人保证如果不能还,可以从他账户里扣钱,这样对银行就没有风险了。
如果另外一块Survivor空间没有足够空间存放上一次新生代收集下来的存活对象,这些对象直接通过分配担保机制进入老年代。
3、标记-整理算法
复制算法在对象存活率较高时要进行较多的复制操作,效率会降低。老年代采用这种算法,与标记清除不同的时,不是直接堆可回收的对象进行清理,而是让所有存活的对象
向一端移动,然后直接清理掉边界以外的内存。
4、分代收集算法
一般把java堆分为新生代和老年代,新生代中每次垃圾收集都发现有大批对象死去,只有少量存活,那就选用复制算法,老年代因为对象存活率高,没有额外空间对他进行
内存担保,必须使用标记-清理或标记-整理算法

二、内存分配与回收策略

-Xmx20M
-Xms20M
-Xmn10M
-XX:+PrintGCDetails
-XX:SurvivorRatio=8
-XX:PretenureThreshold=3145728

1、对象优先在Eden分配
-XX:+PrintGCDetails告诉虚拟机在发生垃圾收集行为时打印内存回收日志,并且在进程退出时输出当前的内存各区域分配情况。
上面的设置可以限制java堆大小为20M不可扩展,其中新生代老年代大小分别为10M

2、大对象直接进入老年代
经常出现大对象容易导致内存还有不少空间时就提前触发GC以获取足够多的连续空间来安置它们,写程序时要尽量避免,更要避免短命大对象。
虚拟机提供一个-XX:PretenureThreshold参数,上面的配置大于3M的对象直接在老年代分配,这样做的目的是避免在Eden区和两个Survivor区之间发生大量的内存复制。
根据内存担保,Survivor区无法容纳的对象会直接进入到老年代。

3、长期存活的对象将进入老年代
对象在Survivor区每熬过一次GC年龄就增加一岁,当增加到一定程度(默认是15岁),就会晋升到老年代中。老年代做担保时需要老年代有足够多的空间,通过触发FullGC来收集。

虚拟机性能监控命令与可视化工具

命令行

  • jps 虚拟机进程状况工具
  • jstat 用于监视虚拟机各种运行状态信息的命令行工具
  • jstack 用于生成虚拟机当前时刻的线程快照,目的是定位线程长时间卡顿的原因

可视化工具

  • JConsole
  • VisualVM

你可能感兴趣的:(高性能)