java内存区域与内存溢出异常
java相对于其他需要手动分配和回收内存的语言(C类语言)来说,一个非常显著的特点就是内存自动回收机制,java开发人员不用过多的关心内存的分配和回收
内存分配情况介绍
总的来说jvm内存分为堆、栈,堆为java程序运行时线程所共享的区域,栈为线程的私有区域
程序计数器(PC寄存器)
只占用jvm一块很小的内存空间,记录着线程执行jvm指令的地址信息(如果执行的是native方法,则指向Undefiend),分支、循环、跳转、异常处理、线程恢复都需要依赖计数器来完成;每一个native线程都会存在一个程序计数器,保证线程之间互不干扰独立的运行,这就是为什么在多线程环境下单例的类不需要同步仍然能正确的运行的理由(当然类中不能包含非原子属性)
public class Test{
private Integer count = 0;
private Test obj = new Test();
private Test(){}
public static Test instance(){
return this.obj;
}
public void incr(){
count++;
}
public void hello(String name){
System.out.println("hello " + name);
}
public staic void main(String[] args){
//不管多少个线程调用hello方法都是幂等
//但是对于incr方法,多线程情况下,由于count是在堆中共享的,没有同步情况下都可能在过期数据上进行操作
}
}
java虚拟机栈
java虚拟机栈描述的是方法执行所使用内存信息,每一次方法执行都会有入栈操作(包含局部变量、操作数栈、动态链接、方法出口信息),栈的深度是有限制的,如果超过限制会出现StackOverflowError
/**
* -Xss:128k,设置栈大小,如果项目中有递归调用的程序最后通过其他方式来实现,极有可能出现OOM异常
* 如果执行这个方法则会出现栈溢出,当然如果虚拟机可以自动拓展深度,则在申请不到足够的内存时会出现OOM异常
*/
public void test(){
byte[] b = new byte[1024 * 1024 * 1];
test();
}
package com.oom;
import java.util.ArrayList;
import java.util.List;
/**
* unable to create new native thread
* 由于java虚拟机的改版,这个错误我没有复现
* @author q
*
*/
public class RuntimeConstantsPoolOOM {
/**
* -verbose:gc -XX:PermSize=1M -XX:MaxPermSize=1M -XX:+PrintGCDetails
* -XX:MaxMetaspaceSize=12m -XX:MetaspaceSize=12m
* @param args
*/
public static void main(String[] args) {
List<Thread> list = new ArrayList<>();
while(true){
Thread th = new Thread(()->{
});
list.add(th);
th.start();
}
}
}
方法区/运行时常量池
方法区包含jvm加载的类信息、静态常量、常量等数据,在oracle的hotspot虚拟机中称作永久代(Perm space),永久代并非正真的永久,也是可以被垃圾收集器回收的,比如类卸载、常量池的回收;但是相比较而言,GC很少光顾像死鱼一样的永久代(^^);
每一个class文件都包含运行时常量池,包含各种字面量和符号应用(比如用final static修饰的类),因此常量池在编译期间已经产生,但不是所有的常量都是编译期间产生,比如著名的String.intern方法;
这一部分区域也会抛出OOM异常,由于使用的java8,收集器默认为G1,perm space已经被metaspace取代了,而默认情况下,这个大小跟本地磁盘有关系,所以显示一直在进行full gc,这一点还是很吊的,perm space溢出的的烦恼没了
package com.oom;
import java.util.ArrayList;
import java.util.List;
public class RuntimeConstantsPoolOOM {
/**
* -verbose:gc -XX:PermSize=1M -XX:MaxPermSize=1M -XX:+PrintGCDetails
* -XX:MaxMetaspaceSize=12m -XX:MetaspaceSize=12m
* @param args
*/
public static void main(String[] args) {
List<String> list = new ArrayList<>();
int i = 0;
while(true){
list.add(("fdasfdsafdsa" + i++).intern());
}
}
}
[GC (Allocation Failure) [PSYoungGen: 16384K->2536K(18944K)] 16384K->7352K(62976K), 0.0082140 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[GC (Allocation Failure) [PSYoungGen: 18920K->2545K(35328K)] 23736K->18505K(79360K), 0.0143513 secs] [Times: user=0.06 sys=0.00, real=0.02 secs]
[GC (Allocation Failure) [PSYoungGen: 35313K->2552K(35328K)] 51273K->46925K(80384K), 0.0387483 secs] [Times: user=0.14 sys=0.00, real=0.04 secs]
[Full GC (Ergonomics) [PSYoungGen: 2552K->2037K(35328K)] [ParOldGen: 44373K->44662K(104448K)] 46925K->46699K(139776K), [Metaspace: 2580K->2580K(1056768K)], 0.3695243 secs] [Times: user=0.81 sys=0.00, real=0.37 secs]
[GC (Allocation Failure) [PSYoungGen: 34805K->2552K(44544K)] 79467K->79602K(148992K), 0.0859330 secs] [Times: user=0.19 sys=0.00, real=0.09 secs]
[Full GC (Ergonomics) [PSYoungGen: 2552K->0K(44544K)] [ParOldGen: 77050K->76300K(167424K)] 79602K->76300K(211968K), [Metaspace: 2580K->2580K(1056768K)], 0.3427573 secs] [Times: user=0.77 sys=0.00, real=0.34 secs]
[GC (Allocation Failure) [PSYoungGen: 41984K->2560K(54784K)] 118284K->118430K(222208K), 0.0722628 secs] [Times: user=0.11 sys=0.03, real=0.07 secs]
[GC (Allocation Failure) [PSYoungGen: 54784K->53755K(94208K)] 170654K->170737K(261632K), 0.1149814 secs] [Times: user=0.20 sys=0.05, real=0.12 secs]
[Full GC (Ergonomics) [PSYoungGen: 53755K->0K(94208K)] [ParOldGen: 116982K->158636K(282624K)] 170737K->158636K(376832K), [Metaspace: 2580K->2580K(1056768K)], 0.7545965 secs] [Times: user=1.59 sys=0.00, real=0.76 secs]
[GC (Allocation Failure) [PSYoungGen: 40448K->39616K(105984K)] 199084K->198260K(388608K), 0.1055923 secs] [Times: user=0.22 sys=0.00, real=0.11 secs]
[GC (Allocation Failure) [PSYoungGen: 72751K->65536K(103424K)] 231395K->226301K(386048K), 0.1645969 secs] [Times: user=0.30 sys=0.00, real=0.16 secs]
[GC (Allocation Failure) [PSYoungGen: 103424K->77304K(115200K)] 264189K->262806K(397824K), 0.3602709 secs] [Times: user=0.48 sys=0.02, real=0.36 secs]
[GC (Allocation Failure) [PSYoungGen: 115192K->99320K(129024K)] 300694K->299654K(411648K), 0.2391821 secs] [Times: user=0.36 sys=0.06, real=0.24 secs]
[GC (Allocation Failure) [PSYoungGen: 129016K->95128K(143872K)] 329350K->329254K(426496K), 0.2246560 secs] [Times: user=0.39 sys=0.00, real=0.23 secs]
[Full GC (Ergonomics) [PSYoungGen: 95128K->35617K(143872K)] [ParOldGen: 234125K->282317K(435712K)] 329254K->317935K(579584K), [Metaspace: 8341K->8341K(1056768K)], 1.4919280 secs] [Times: user=3.35 sys=0.05, real=1.49 secs]
java堆
java堆,又称为GC堆,细分为年轻代和老年代,年轻代又分为eden代、from survivor代、to survivor代;
java对象分为两个部分,第一部分对象头信息
存储内容 | 标志位 | 状态 |
---|---|---|
对象hash和age | 01 | 未锁定 |
指向锁记录的指针 | 00 | 轻量级锁定 |
指向重量级锁的指针 | 10 | 重量级锁定 |
空,不记录信息 | 11 | GC标志 |
偏向线程ID、偏向时间戳、对象age | 01 | 可偏向 |
第二部分保存了对象的指针,JVM用指针来确定对象的类型,程序访问对象就是通过虚拟机栈内的引用信息得到对象在堆中的位置而实现访问的
第三部分为填充信息,jvm规定对象必须为8的倍数,也就是占1个bit坑
堆内存也是会溢出的,如下面程序
package com.oom;
import java.util.ArrayList;
import java.util.List;
public class HeapSpaceOOM {
/**
* -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails
* -XX:SurvivorRatio=8
*
* Xms设置堆最小内存,xmx设置堆最大内存,xmn设置年轻代初始内存,XX:SurvivorRatio设置eden和survivor比值,上面为8:2
*
* [GC的类型 [发生GC的区域:GC前占用空间 -> GC后占用内存空间 (该区域总的内存空间)] GC前堆占用空间 -> GC后堆占用空间 (堆的总内存大小), GC消耗的时间]
* [Times时间统计:用户CPU耗时,系统CPU耗时,操作从开始到结束消耗的时间],其中user+sys>=real这是因为多核时user是多个CPU加起来的时间
*
* [GC (Allocation Failure) [PSYoungGen: 7644K->1000K(9216K)]
* 7644K->5305K(19456K), 0.0088295 secs] [Times: user=0.02 sys=0.00,
* real=0.01 secs] [GC (Allocation Failure) --[PSYoungGen:
* 9192K->9192K(9216K)] 13497K->19424K(19456K), 0.0139459 secs] [Times:
* user=0.01 sys=0.00, real=0.01 secs] [Full GC (Ergonomics) [PSYoungGen:
* 9192K->0K(9216K)] [ParOldGen: 10232K->10092K(10240K)]
* 19424K->10092K(19456K), [Metaspace: 2577K->2577K(1056768K)], 0.1537504
* secs] [Times: user=0.25 sys=0.00, real=0.15 secs] [Full GC (Ergonomics)
* [PSYoungGen: 7527K->7934K(9216K)] [ParOldGen: 10092K->7982K(10240K)]
* 17620K->15916K(19456K), [Metaspace: 2577K->2577K(1056768K)], 0.1334981
* secs] [Times: user=0.28 sys=0.00, real=0.13 secs] [Full GC (Allocation
* Failure) [PSYoungGen: 7934K->7931K(9216K)] [ParOldGen:
* 7982K->7982K(10240K)] 15916K->15913K(19456K), [Metaspace:
* 2577K->2577K(1056768K)], 0.0855968 secs] [Times: user=0.33 sys=0.00,
* real=0.09 secs] Exception in thread "main" java.lang.OutOfMemoryError:
* Java heap space at java.util.Arrays.copyOf(Unknown Source) at
* java.util.Arrays.copyOf(Unknown Source) at
* java.util.ArrayList.grow(Unknown Source) at
* java.util.ArrayList.ensureExplicitCapacity(Unknown Source) at
* java.util.ArrayList.ensureCapacityInternal(Unknown Source) at
* java.util.ArrayList.add(Unknown Source) at
* com.oom.HeapSpaceOOM.main(HeapSpaceOOM.java:14) Heap PSYoungGen total
* 9216K, used 8192K [0x00000000ff600000, 0x0000000100000000,
* 0x0000000100000000) eden space 8192K, 100% used
* [0x00000000ff600000,0x00000000ffe00000,0x00000000ffe00000) from space
* 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
* to space 1024K, 45% used
* [0x00000000fff00000,0x00000000fff75b40,0x0000000100000000) ParOldGen
* total 10240K, used 7982K [0x00000000fec00000, 0x00000000ff600000,
* 0x00000000ff600000) object space 10240K, 77% used
* [0x00000000fec00000,0x00000000ff3cbae8,0x00000000ff600000) Metaspace used
* 2608K, capacity 4486K, committed 4864K, reserved 1056768K class space
* used 289K, capacity 386K, committed 512K, reserved 1048576K
*
* @param args
*/
public static void main(String[] args) {
List<Object> list = new ArrayList<>();
while (true) {
list.add(new Object());
}
}
}
总结
以上为jvm内存分布,以及一些常见异常的案例分析,总结读书内容,温故而知新;java虚拟机我又来了^^