上节简单介绍了一下jvm的内存布局以及简单概念,那么对于虚拟机来说,它是怎么一步步的让我们能执行方法的呢:

1.首先,jvm启动时,跟个小领导一样会根据配置参数(没有配置的话jvm会有默认值)向大领导操作系统申请内存

2.jvm小领导这时候已经有资源了,要向下级分配资源,jvm是个清廉的领导,一点都没贪,根据配置将堆,栈,方法区内存分配好。

这时候jvm内存模型已经都有了,这些堆栈方法区等干活的拿到资金后,开始做下一步的准备工作,

3.jvm将类class信息加载入方法区,包含一些静态变量,代码块和常量信息(上节也说了,其实运行时常量池在逻辑上还是算方法区的,身在堆营心在方)。

4.万事具备,让我们走起来吧,这时候就可以开始执行方法了,方法压栈,堆中生成对象,开始java不归路

在jvm的角度这就算是系统跑起来的全部流程了,对于我们程序员来说,第四步才算是开始,我们会无限重复压栈出栈,对象生成,垃圾回收,而如何在有限的空间里,做更多的事情,才是一个程序员该考虑的问题,当然,我们要想优化,首先必须要知道他是怎么玩的。

上节我们已经知道堆是线程共享的,栈是线程私有的,栈在线程运行时会分配一块栈帧,这块是线程独占的,而堆里面的对象都是线程共享的,只要有引用就可以随意访问和修改(final对象也不例外,final只是规定了这个对象通过这个引用无法改变,但是如果final引用的对象被其他普通引用到,依旧是可以改的)。java基础还行的应该都知道,栈上的运行速度远高于堆的,原因很多,如:

1.堆内存是在运行时动态分配的,所以需要考虑并发安全问题(后面会讲到通过cas和分配缓冲方式),而堆在线程创建时都会分配一块tlab缓存,不会冲突,同时分配的tlab内存不够用时,就需要再次申请分配一块tlab。

2.栈不需要释放缓存,垃圾回收都不需要,因此会快很多

3.栈通过JIT即时编译优化,亲儿子有cpu指令加持就是牛

4.栈可以用到cpu的高速缓存

其实总结下来就两点:cpu加持,内存简单。既然他这么牛,那还用啥堆啊,全部放在栈里面他不香吗。其实我认为栈之所以快的一部分原因也是因为对象少,cpu爸爸还能照顾得过来,而且无论什么东西,只要多了自然而然效率会下降。如果像堆中,动不动搞个个把g的对象,咋的也玩不转。况且,很多对象是跨线程共享的对象,这就得涉及到垃圾回收,这样玩的话,还是咱们认识的青涩纯洁的小栈(这里绝对没有碰瓷肖战的意思)了吗,当然这些都是我的猜想,要理解为啥,咱还是得先看看堆是咋设计的,堆有啥是栈不可替代的呢:

被划分为新生代和老年代(G1也是如此,只不过物理内存没有划分那么开),新生代又被划分成Eden区和Survior区,Survior区又分为from区和to区(其实这俩没有区别,只是形象的表示是从一块区域复制到另一块区域)。

那么堆为什么要这么设计呢,这就要牵扯到垃圾回收了,由于堆内存共享的,很多变量被其他对象引用(栈是线程私有的,因此用完直接可以把这块内存清掉就行了),因此,既然我们不能像栈那样潇洒,那也不能任他自由生长,因此必须要对不用的对象做回收处理,在介绍垃圾回收之前我们还是先来了解一下我们常见的几个内存溢出的场景吧:

栈溢出:

          栈的大小可以通过 -Xss设置,默认为1m,很多同学觉得这个值太小了,其实这个1m是设置的每个线程分配1m,jvm不支持设置整个栈的大小。一般来说,栈的内存溢出主要有两种情况:

1. java.lang.StackOverflowError :这种很容易实现无限递归就可以了,我们先试一下:

   public static void main(String[] args) {
get();
} static void get(){
get();
}

执行之后很快就会出现:Exception in thread "main" java.lang.StackOverflowError

为什么我明明没有任何对象生成,却依然内存溢出了呢,原因还是要理解栈的内存结构,每次调用方法都要将信息入栈,退出方法时出栈,因此在无限调用时只进不出,默认1m内存自然很快就占满了。一般来说,出现这种情况的时候我们就要考虑代码中是否有死递归的情况发生了。

2.OutOfMemoryError: 这种就很复杂,由于栈无法限制整体内存大小,因此想要占满栈必须占满物理内存区域,可是这样电脑也卡的不行不行的了,尝试之前我得先保存一下了。

说实话这个场景太难复现了,尝试了很多次,要么电脑卡死重启,反正就是出不来,贴上代码大家试一下,或者有大佬看到可以指点一番:

public static void main(String[] args) {
for (int i=0;i<1000000000;i++){
new get().start();
}
} static class get extends Thread{
@Override
@SneakyThrows
public void run() {
Thread.sleep(20000L);
}
}

其实原理就是每个线程都会分配一块固定的内存区域,因此,当同时运行线程数量很多时,就会占用很多内存,导致出现oom。

堆溢出:

           堆内存溢出很简单,由于堆内存是可以限制的只需要设置 -Xms,-Xmx的参数大小再添加一个大对象就可以复现(对象都是在堆中分配),我们先上代码:

public static void main(String[] args)
{
String[] strings = new String[100*1000*1000];
}

只要设置-Xmx100m以下,就会出现java.lang.OutOfMemoryError: Java heap space,堆内存溢出嘛,这个很好理解,其实在真正生产环境我们不可能会有这样的大对象能直接塞满堆得,真出现了这种情况,赶快检查一下数据库操作的代码,因为没准过不了多久就会出现删库跑路的新闻,哈哈。但是原理都是一样的->没有垃圾回收的对象太多,堆放不下了。

方法区溢出:

               方法区内存主要包含运行时常量池,class信息,那我们在什么情况下会出现内存溢出的场景呢,首先运行时常量池在jdk1.8以后物理内存被挪到堆内存中去了(现在互联网公司基本上都是1.8以上的版本,太低的版本就不演示了,知道就行了),那么在1.8以后我们主要考虑的还是class信息过多,有的同学会问了,class信息大小不是在系统启动之初就确定了吗,你还能在项目跑的时候加一个类进去,答案是可以,不过当然不是运行的好好地,我写个类编译完然后塞到jar包中,相信动态代理很多人都知道,但是原理可能不太清楚,其实动态代理做的就是在运行的时候,根据不同场景,动态的生成class,那么我们就来试试吧:

public static void main(String[] args) {
while (true) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(MethodOOM.class);
enhancer.setUseCache(false);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable {
return arg3.invokeSuper(arg0, arg2);
}
});
enhancer.create();
}
} public static class MethodOOM { }

网上copy了一下动态代理的代码,加了个死循环,无限生成class,再把方法区内存区域设小一点,很快就会出现java.lang.OutOfMemoryError: Metaspace。

      直接内存溢出:  

    直接内存上节也说了,其实并不属于jvm管理,但是当我们在使用nio如常用的netty,java自带的unsafe都是可以操作到直接内存的,我们可以通过设置 MaxDirectMemorySize参数限制直接内存大小,一般来说直接内存很难排查(毕竟不归jvm管),但是正因为如此,当我们在出现大量oom,但是dump文件很小,很难看出问题时,就可以考虑排查直接内存的影响了。

总结:   

想当年被这些玩意坑过很多次,当时也不知道这些参数到底代表什么,只是出现这个问题就百度,网上很多只给了答案,但是每个系统占用内存是不一样的,所以其实是没有标准答案的,因此后来就研究了很久关于这些jvm如何优化,本来很高深的东西学着学着好像很简单,但是再深入,又会觉得未知的越来越多,或许正是这些矛盾让我们更加享受去钻研这些看似平时用不到的东西吧。

java虚拟机入门(二)-探索内存世界的更多相关文章

  1. 重读《深入理解Java虚拟机》二、Java如何分配和回收内存?Java垃圾收集器如何工作?

    线程私有的内存区域随用户线程的结束而回收,内存分配编译期已确定,内存分配和回收具有确定性.共享线程随虚拟机的启动.结束而建立和销毁,在运行期进行动态分配.垃圾收集器主要对共享内存区域(堆和方法区)进行 ...

  2. Java虚拟机(二):JVM内存模型

    所有的Java开发人员可能会遇到这样的困惑?我该为堆内存设置多大空间呢?OutOfMemoryError的异常到底涉及到运行时数据的哪块区域?该怎么解决呢?其实如果你经常解决服务器性能问题,那么这些问 ...

  3. Java虚拟机垃圾收集器与内存分配策略

    Java虚拟机垃圾收集器与内存分配策略 概述 那些内存须要回收,什么时候回收.怎样回收是GC须要完毕的3件事情. 程序计数器.虚拟机栈与本地方法栈这三个区域都是线程私有的,内存的分配与回收都具有确定性 ...

  4. 转载: Java虚拟机:运行时内存数据区域、对象内存分配与访问

    转载:  https://blog.csdn.net/a745233700/article/details/80291694  (虽然大部分内容都其实是深入理解jvm虚拟机这本书里的,不过整理的很牛逼 ...

  5. 实战Java虚拟机之二“虚拟机的工作模式”

    今天开始实战Java虚拟机之二:“虚拟机的工作模式”. 总计有5个系列 实战Java虚拟机之一“堆溢出处理” 实战Java虚拟机之二“虚拟机的工作模式” 实战Java虚拟机之三“G1的新生代GC” 实 ...

  6. 【深入理解JAVA虚拟机】第二部分.内存自动管理机制.1.内存区域

    1.内存区域 根据<Java虚拟机规范(Java SE 7版)> 的规定,Java虚拟机所管理的内存将会包括以下几个运行时数据区域,如图所示.  程序计数器 当前线程所执行的字节码的行号指 ...

  7. 深入理解java虚拟机读书笔记1--java内存区域

    Java在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域.这些区域都有各自的用途.创建和销毁的时间,有一些是随虚拟机的启动而创建,随虚拟机的退出而销毁,有些则是与线程一一对应,随 ...

  8. Java虚拟机解析篇之---内存模型

    今天闲来无事来,看一下Java中的内存模型和垃圾回收机制的原理.关于这个方面的知识,网上已经有非常多现成的资料能够供我们參考,可是知识还是比較杂的,在这部分知识点中有一本书不得不推荐:<深入理解 ...

  9. 《深入理解 Java 虚拟机》学习笔记 -- 内存区域

    <深入理解 Java 虚拟机>学习笔记 -- 内存区域 运行时数据区域 主要分为 6 部分: 程序计数器 虚拟机栈 本地方法栈 Java 堆 方法区 如图所示: 1. 程序计数器(线程私有 ...

随机推荐

  1. [MVC] - Asynchronous操作

    最近在项目里遇到个问题,是在MVC的项目里处理异步操作的,操作流程是这样的: 1.从MVC的一个controller调用一个异步方法: public ActionResult CreateOrUpda ...

  2. 线程 - Java中的Copy-On-Write容器

    http://ifeve.com/java-copy-on-write/ 什么是CopyOnWrite容器 CopyOnWrite容器即写时复制的容器.通俗的理解是当我们往一个容器添加元素的时候,不直 ...

  3. ipython快捷键

    IPython Notebook有两种不同的键盘输入模式(编辑模式和命令模式). 编辑模式:允许你输入代码或者文本到一个单元格(cell这里我译作单元格)内,并且单元格外面有灰色的选中框(注:Jupy ...

  4. Java学习日报7.18

    /** * *//** * @author 86152 * */ package trangle;import java.util.Scanner; public class Trangle{ pub ...

  5. Linux服务器以及系统性能排查常用命令

    一.在Linux系统中排查CPU故障的方法和技巧 1.top命令 Linux内部命令,可以查看实时的CPU的使用情况,也可以查看CPU最近一段时间CPU的使用情况 Linux下常用的性能分析工具,能够 ...

  6. 使用BigDecimal舍小数取整数

    项目需求说明: 解决WMS系统收货容差问题,例如:SKU的采购数量95件,容差是5,95+95*5/100=99.75,传WMS的数量是99,且容差传零. 参数说明: 其中ROUND_UP:向上取整, ...

  7. WPF学习笔记02_布局

    布局原则 WPF窗口只能包含单个元素.如果要放置多个元素,需要放置一个容器,然后在容器中添加元素. 不应显示的设定元素的尺寸 不应该使用屏幕坐标指定元素的位置 布局容器的子元素"共享&quo ...

  8. JMeter如何设置语言为中文

    一.现象 JMeter安装后,默认语言为英文,如下图所示: 对于英文水平一般的人来说,刚开始使用起来比较费劲(比如我),影响我工作效率.那么,怎么将英文改为中文呢? 二.解决方法 1.修改设置 点击菜 ...

  9. 2021超详细的HashMap原理分析,面试官就喜欢问这个!

    一.散列表结构 散列表结构就是数组+链表的结构 二.什么是哈希? Hash也称散列.哈希,对应的英文单词Hash,基本原理就是把任意长度的输入,通过Hash算法变成固定长度的输出 这个映射的规则就是对 ...

  10. 记一次flask上传文件返回200前端却504的问题

    前言 好久没写了, 主要是太忙了, 本篇记一下今天解决的一个问题吧, 耗了我大半天的时间才解决 问题 今天在调试代码时, 发现了一个诡异的问题, 我之前写了一个接口, 作用是接收上传的文件, 因为这个 ...