1.Java虚拟机运行时的数据区
2.对象创建的过程
3. 对象的内存布局
32bit未锁=25bit哈希+4bit分代年龄+2bit锁状态+1bit 0
4.对象的访问定位
对象句柄优点:稳定,对象移动只需移动句柄指针,无需修改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());
}
}
}
得到的结果:
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;
}
}
}
结果:
事实上,我记得以前有个例子非常深刻,这个例子叫做斐波那契数列,用这个例子一定可以拿到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内存分配参数图解:
线程导致线程溢出(以下代码请不要测试,无法运行出结果,而且系统会崩,强制关机重启才能恢复,然后软件的打开恢复功能都不会生效,不要问我怎么知道的,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的提示
所以看到元空间溢出的时候,就应该想到可能是消息增强,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);
}
}
}
特点就是OOM没有后缀
看了这么多溢出方式,会比较乱,画个图整理一下:
复习用的问题:(下一节放答案)
Java虚拟机的内存分区分为哪些部分?每一部分保存哪些数据?
哪些虚拟机内存是线程共享的?哪些是线程独有的?
哪些内存区域可能产生OOM?哪些内存区域可能产生SOF?
对象创建的过程是怎样的?
内存分配的方式有哪些?
内存分配的冲突如何解决?
对象内存布局分为哪几部分?每部分存放哪些数据?各自有什么特点?
哪些区域可能产生内存溢出?出现此类溢出后有什么特征?如何解决?
以下JVM调参常用参数:堆、栈、方法区、直接内存、控制台输出、快照