java虚拟机入门(二)-探索内存世界
上节简单介绍了一下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虚拟机入门(二)-探索内存世界的更多相关文章
- 重读《深入理解Java虚拟机》二、Java如何分配和回收内存?Java垃圾收集器如何工作?
线程私有的内存区域随用户线程的结束而回收,内存分配编译期已确定,内存分配和回收具有确定性.共享线程随虚拟机的启动.结束而建立和销毁,在运行期进行动态分配.垃圾收集器主要对共享内存区域(堆和方法区)进行 ...
- Java虚拟机(二):JVM内存模型
所有的Java开发人员可能会遇到这样的困惑?我该为堆内存设置多大空间呢?OutOfMemoryError的异常到底涉及到运行时数据的哪块区域?该怎么解决呢?其实如果你经常解决服务器性能问题,那么这些问 ...
- Java虚拟机垃圾收集器与内存分配策略
Java虚拟机垃圾收集器与内存分配策略 概述 那些内存须要回收,什么时候回收.怎样回收是GC须要完毕的3件事情. 程序计数器.虚拟机栈与本地方法栈这三个区域都是线程私有的,内存的分配与回收都具有确定性 ...
- 转载: Java虚拟机:运行时内存数据区域、对象内存分配与访问
转载: https://blog.csdn.net/a745233700/article/details/80291694 (虽然大部分内容都其实是深入理解jvm虚拟机这本书里的,不过整理的很牛逼 ...
- 实战Java虚拟机之二“虚拟机的工作模式”
今天开始实战Java虚拟机之二:“虚拟机的工作模式”. 总计有5个系列 实战Java虚拟机之一“堆溢出处理” 实战Java虚拟机之二“虚拟机的工作模式” 实战Java虚拟机之三“G1的新生代GC” 实 ...
- 【深入理解JAVA虚拟机】第二部分.内存自动管理机制.1.内存区域
1.内存区域 根据<Java虚拟机规范(Java SE 7版)> 的规定,Java虚拟机所管理的内存将会包括以下几个运行时数据区域,如图所示. 程序计数器 当前线程所执行的字节码的行号指 ...
- 深入理解java虚拟机读书笔记1--java内存区域
Java在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域.这些区域都有各自的用途.创建和销毁的时间,有一些是随虚拟机的启动而创建,随虚拟机的退出而销毁,有些则是与线程一一对应,随 ...
- Java虚拟机解析篇之---内存模型
今天闲来无事来,看一下Java中的内存模型和垃圾回收机制的原理.关于这个方面的知识,网上已经有非常多现成的资料能够供我们參考,可是知识还是比較杂的,在这部分知识点中有一本书不得不推荐:<深入理解 ...
- 《深入理解 Java 虚拟机》学习笔记 -- 内存区域
<深入理解 Java 虚拟机>学习笔记 -- 内存区域 运行时数据区域 主要分为 6 部分: 程序计数器 虚拟机栈 本地方法栈 Java 堆 方法区 如图所示: 1. 程序计数器(线程私有 ...
随机推荐
- 关于c语言单项链表尾添加
犹豫了几天,看了很多大牛写的关于c语言链表,感触很多,终于下定决心,把自己对于链表的理解随之附上,可用与否,自行裁夺.由于作者水平有限也是第一次写,不足之处,竭诚希望得到各位大神的批评指正.制作不易, ...
- pycharm的快捷键的使用
作为未来的程序猿,快捷键对我们来说很重要,因为它方便且快捷,今天就给大家介绍pycharm中常用的快捷键 1.编辑: Ctrl + Space------------------基本的代码完成(类.方 ...
- Django中关于“CSRF verification failed. Request aborted”的问题
遇到该问题的情境 在Django中采用Ajax提交表单,涉及到跨域问题. 解决措施 在html页面中的表单内添加如下代码: {% csrf_token %} 在视图函数所在的py文件中添加如下代码: ...
- MD5,BASE64Encoder加密
package com.cn.peitest; import java.io.UnsupportedEncodingException; import java.security.MessageDig ...
- 事务的概念,以及事务在JDBC编程中处理事务的步骤
事务是作为单个逻辑工作单元执行的一系列操作,一个逻辑工作单元必须有四个属性,称为原子性.一致性.隔离性和持久性 (ACID) 属性,只有这样才能成为一个事务 .JDBC处理事务有如下操作: 1,con ...
- Java学习日报8.3
package car;class Person{ private String name; private int age; private Car car; public Person(Strin ...
- 使用jmeter进行压力测试与nginx连接数优化
案例训练目标 学会使用jmeter工具 学会配置nginx连接数优化 包含技能点 使用jmeter做压力测试 配置nginx的并发连接数 环境要求 PC支持VT,4G内存以上:vmware虚拟机安装有 ...
- Unraid修改docker镜像地址&默认启动
起源 由于Unraid系统每次启动都会清空Docker的镜像地址配置,故需要默认配置镜像地址 方法 添加修改镜像脚本到开机文件中实现 先找一个镜像加速地址,我使用的是阿里云的容器镜像服务 形如 :ht ...
- 数据库表空间收缩之pg_squeeze,pg_repack
数据库表空间收缩之pg_squeeze,pg_repack 目录 数据库表空间收缩之pg_squeeze,pg_repack pg_squeeze1.2 原理 优点 安装 使用 pgstattuple ...
- 基于Python的接口自动化-读写excel文件
引言 使用python进行接口测试时常常需要接口用例测试数据.断言接口功能.验证接口响应状态等,如果大量的接口测试用例脚本都将接口测试用例数据写在脚本文件中,这样写出来整个接口测试用例脚本代码将看起来 ...