1.Java虚拟机运行时的数据区

《深入理解Java虚拟机》第2章 Java内存区域与内存溢出异常 -关于照着操作电脑死机那件事。。。_java

2.对象创建的过程

《深入理解Java虚拟机》第2章 Java内存区域与内存溢出异常 -关于照着操作电脑死机那件事。。。_内存溢出_02

3. 对象的内存布局

32bit未锁=25bit哈希+4bit分代年龄+2bit锁状态+1bit 0 

《深入理解Java虚拟机》第2章 Java内存区域与内存溢出异常 -关于照着操作电脑死机那件事。。。_java_03

4.对象的访问定位

《深入理解Java虚拟机》第2章 Java内存区域与内存溢出异常 -关于照着操作电脑死机那件事。。。_java_04

《深入理解Java虚拟机》第2章 Java内存区域与内存溢出异常 -关于照着操作电脑死机那件事。。。_java_05

对象句柄优点:稳定,对象移动只需移动句柄指针,无需修改refrence指针;

直接指针优点:访问速度更快(Sun HotSpot在用的方式)

5.配置JVM参数

-verbose:gc

控制台显示GC的信息

-Xms20M

Java堆初始大小

-Xmx20M

Java堆最大大小

-Xmn10M

新生代大小

-XX:+PrintGCDetails

输出GC日志详情

-XX:SurvivorRatio=8

新生代分配比例,Eden:s0:s1=SurvivorRatio:1:1

-XX:+HeapDumpOnOutOfMemoryError

是否进行内存快照

-XX:HeapDumpPath=D:/program/test/testJ8/out/a.hprof

内存快照存放地址(可以不设置,会放在当前目录)

6.堆溢出

按照书中内容,试着执行了以下代码:

public class HeapOOM {
static class OOMObject{}
public static void main(String[] args){
List<OOMObject> items = new ArrayList<>();
while(true){
items.add(new OOMObject());
}
}
}

得到的结果:

《深入理解Java虚拟机》第2章 Java内存区域与内存溢出异常 -关于照着操作电脑死机那件事。。。_ide_06

7.栈溢出

后面这个部分,按照书中的代码实验,失败了,-Xss128k,然后我试着把它改到最小,108k,成功了,感觉这个例子不是特别稳定吧,先贴一下代码:

public class JavaVMStackSOF {
private int stackLength=1;
public void stackLeak(){
stackLength++;
stackLeak();
}

public static void main(String[] args) throws Throwable{
JavaVMStackSOF oom = new JavaVMStackSOF();
try{
oom.stackLeak();
}catch (Throwable e){
System.out.println("stack length:"+oom.stackLength);
throw e;
}
}
}

结果:

《深入理解Java虚拟机》第2章 Java内存区域与内存溢出异常 -关于照着操作电脑死机那件事。。。_jvm_07

事实上,我记得以前有个例子非常深刻,这个例子叫做斐波那契数列,用这个例子一定可以拿到StackOverFlowError:

public class JavaVMStackSOF {
private int stackLength=1;
public int stackLeak(int num, int depth){
if(num == 1){
stackLength = Math.max(depth,stackLength);
return 1;
}else if(num == 2){
stackLength = Math.max(depth,stackLength);
return 2;
}
++stackLength;
return stackLeak(num-1,depth)+stackLeak(num-2,depth);
}

public static void main(String[] args) throws Throwable{
JavaVMStackSOF oom = new JavaVMStackSOF();
try{
oom.stackLeak(10000,1);
}catch (Throwable e){
System.out.println("stack length:"+oom.stackLength);
throw e;
}
}
}

结果:

《深入理解Java虚拟机》第2章 Java内存区域与内存溢出异常 -关于照着操作电脑死机那件事。。。_jvm_08

Java内存分配参数图解:

《深入理解Java虚拟机》第2章 Java内存区域与内存溢出异常 -关于照着操作电脑死机那件事。。。_java_09

线程导致线程溢出(以下代码请不要测试,无法运行出结果,而且系统会崩,强制关机重启才能恢复,然后软件的打开恢复功能都不会生效,不要问我怎么知道的,o(╥﹏╥)o)

使用参数:-Xss2g

public class JavaVMStackOOM {
private void dontStop(){
while (true){

}
}

public void stackLeakByThread(){
while(true){
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
dontStop();
}
});
thread.start();
}
}

public static void main(String[] args){
JavaVMStackOOM oom = new JavaVMStackOOM();
oom.stackLeakByThread();
}
}

这个教训记住了,如果Windows系统作为服务器崩了,可以检查一下Xss这个参数,太大又线程数过多的情况,是有可能导致系统宕机的,通常128k够了

根据作者描述,会产生一下错误:

Exception in thread"main"java.lang.OutOfMemoryError:unable to create new native thread

其实,真的出现这个错误,还是很好识别的,直接看到OOM后面有一个thread,数目是线程原因导致

8.字符串常量溢出(废弃,Java1.7搬到堆区了)

PermSize这个设置,有一种过时的味道。Java 1.8这个参数无效了,而且字符串常量池1.7就搬到堆了,所以这部分,直接看一下结论:

Exception in thread"main"java.lang.OutOfMemoryError:PermGen space at java.lang.String.intern(Native Method) at org.fenixsoft.oom.RuntimeConstantPoolOOM.main(RuntimeConstantPoolOOM.java:18)

关键词:PermGen,这个词可以看出是永久代溢出了

9.CGLib方法区内存溢出

CGLib其实就是AOP的非接口方式原理,原版是下面这样:

public class CglibProxy implements MethodInterceptor {

private Enhancer enhancer = new Enhancer();
public Object getProxy(Object obj){
enhancer.setSuperclass(obj.getClass());
enhancer.setCallbacks(new Callback[]{this, NoOp.INSTANCE});
return enhancer.create();
}
@Override
public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("before do something");//前置代码
Object res = methodProxy.invokeSuper(object,args);
System.out.println("after do something");//后置代码
return res;
}
}
public class TestProxy {
public static void main(String[] args) {
CglibProxy cglibProxy = ProxyFactory.getCglibProxy();
CglibService cglibService = new CglibService();
cglibProxy.getProxy(cglibService);
CglibService cglibServiceProxy = (CglibService) cglibProxy.getProxy(cglibService);
System.out.println(cglibServiceProxy.query());
cglibServiceProxy.insert();
}
}
public class CglibService {
public String query() {
return "query";
}

public void insert() {
System.out.println("insert");
}
}

大概步骤就是:

1. 实现MethodInterceptor接口,重写interceptor方法

2.借助Enhancer获取装载对象到代理中

3. CGLib前后增加处理代码(这就是非接口格式AOP)

4.执行原类的方法执行,查看效果

CGLib需要导入包,参考包名:cglib-nodep-2.2.2.jar

public class JavaMethodAreaOOM {
public static void main(String[] args){
while(true){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(OOMObject.class);
enhancer.setUseCache(false);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy proxy) throws Throwable {
return proxy.invokeSuper(o,args);
}
});
enhancer.create();
}
}

static class OOMObject{}
}

参考配置:

-XX:MetaspaceSize=10M
-XX:MaxMetaspaceSize=10M
-Xms20M
-Xmx20M
-Xmn10M
-XX:SurvivorRatio=8

原书中给出的是配置PermSize和MaxPermSize,由于运行环境是1.8,修改为MetaspaceSize和MaxMetaspaceSize,可以看出有Metaspace的提示

《深入理解Java虚拟机》第2章 Java内存区域与内存溢出异常 -关于照着操作电脑死机那件事。。。_java_10

所以看到元空间溢出的时候,就应该想到可能是消息增强,AOP方面反复创建对象造成的

10. 本机直接内存溢出

public class DirectMemoryOOM {
private static final int _1MB = 1024*1024;
public static void main(String[] args) throws Exception{
Field unsafeField = Unsafe.class.getDeclaredFields()[0];
unsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe) unsafeField.get(null);
while(true){
unsafe.allocateMemory(_1MB);
}
}
}

《深入理解Java虚拟机》第2章 Java内存区域与内存溢出异常 -关于照着操作电脑死机那件事。。。_java_11

特点就是OOM没有后缀

看了这么多溢出方式,会比较乱,画个图整理一下:

《深入理解Java虚拟机》第2章 Java内存区域与内存溢出异常 -关于照着操作电脑死机那件事。。。_jvm_12


复习用的问题:(下一节放答案)

Java虚拟机的内存分区分为哪些部分?每一部分保存哪些数据?

哪些虚拟机内存是线程共享的?哪些是线程独有的?

哪些内存区域可能产生OOM?哪些内存区域可能产生SOF?

对象创建的过程是怎样的?

内存分配的方式有哪些?

内存分配的冲突如何解决?

对象内存布局分为哪几部分?每部分存放哪些数据?各自有什么特点?

哪些区域可能产生内存溢出?出现此类溢出后有什么特征?如何解决?

以下JVM调参常用参数:堆、栈、方法区、直接内存、控制台输出、快照