继续深入JVM内存区域

JVM系列文章目录

初识JVM

深入理解JVM内存区域

玩转JVM对象和引用

JVM分代回收机制和垃圾回收算法

细谈JVM垃圾回收与部分底层实现

Class文件结构及深入字节码指令

玩转类加载和类加载器

方法调用的底层实现

Java语法糖及底层实现

GC调优基础知识工具篇之JDK自带工具

GC调优基础知识工具篇之Arthas与动态追踪技术

JVM调优之内存优化与GC优化

JVM调优之预估调优与问题排查

JVM调优之玩转MAT分析内存泄漏

直接内存与JVM源码分析

JVM及时编译器



前言

注:本文基于JDK1.8,是博主个人的JVM学习记录,欢迎各位指正错误的地方


一、深入JVM内存区域

1.JHSDB工具

JHSDB 是一款基于服务性代理实现的进程外调试工具。服务性代理是 HotSpot 虚拟机中一组用于映射 Java 虚拟机运行信息的,主要基于 Java 语言实现的 API集合。它可以用来查看包括诸如:GC回收,分代信息等。具体如何使用各位可以自行百度,这里先不做介绍了。


2.JVM内存处理全流程

  1. JVM向操作系统申请内存区域:通过你设置好的参数先操作系统申请内存;

  2. 初始化运行时数据区:JVM自己获得内存空间后,根据你的指示(根据配置参数),给下面的小弟分配资源(分配堆、栈以及方法区的内存大小);
    在这里插入图片描述

  3. 类加载:这里主要是把 class 放入方法区、还有 class 中的静态变量和常量也要放入方法区(后面的博客会细聊);

  4. 执行方法及 创建对象:启用一个线程,执行main方法,创建对象A1,A2,并将A1,A2的引用放在栈中
    在这里插入图片描述


JVM优化

在搞清楚上面的JVM的内存处理流程之后,我们结合JHSDB就可以了解以下栈的优化——栈帧之间数据共享(一般情况下,两个不同的栈帧的内存区域是独立的,但是大部分JVM都会做一些优化,是的两个栈有一小部分重叠,这主要体现在方法之间的调用传惨上)。
从字面上可以看出来就是各个栈共享某些数据下面我以一段代码为例来解释以下这个

public class Test {
    public void a(int n){
        int num = n + 9;
        try {
            Thread.currentThread().sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(num);
    }

    public static void main(String[] args) {
        Test test = new Test();
        test.a(1);
    }
}

在上面这个方法中我们可以看到mian方法调用了a方法并传递了一个int数值1;
那么在JVM中的现象是这样的,mian方法对应的栈帧中的操作数栈中存入了一个1,然后进入到b方法,a方法的栈帧进入虚拟机栈;a方法变量x(传入的变量1)需要存入到局部变量中,;此时我们可以思考一下,mian的操作数栈中有一个1,a的局部变量中也有一个1,如果在计算机实际内存中使用两块内存来分别存储这个1是不是就有点浪费了,所以JVM中就让它们指向同一个实际内存地址。
大概是这么个样子的图:
在这里插入图片描述
用JHSDB来内存信息如下:红色框是它们的共享内存000000
在这里插入图片描述



二、内存溢出

做Java开发的话可能很多人都听说这个词,但是大部分人可能都没有详细了解过解决过这种情况
内存溢出有以下几种情况:


栈溢出

栈指的就是虚拟机栈,虚拟机栈大小是设定好的,当虚拟机栈不停的入栈但是不出栈,当它就撑爆了,比如说递归调用方法死循环。可以通过-XssSize 如-Xss 2M来扩大栈内存,但是在此之前请检查代码查明撑爆的原因。


堆溢出

JVM中堆主要存储堆是对象,这一块是最容易出问题,调优大多数也是调堆,如果这块溢出了,快检查你的对象是不是长生不老了。
可以通过-Xms,-Xmx 参数来调大小。


方法区溢出

方法区溢出一般是这两种情况:
(1) 运行时常量池溢出
(2)方法区中保存的 Class 对象没有被及时回收掉或者 Class 信息占用的内存超过了我们配置。


直接内存溢出

直接内存溢出这个我没遇到过,就抄一下理论知识了:由直接内存导致的内存溢出,一个比较明显的特征是在 HeapDump 文件中不会看见有什么明显的异常情况,如果发生了 OOM, 同时 Dump 文件很小,可 以考虑重点排查下直接内存方面的原因。




三、常量池


静态常量池

静态常量池指的是class文件里的常量池
在这里插入图片描述

运行时常量池

包括字面量(八大基本类型),运行时的方法和字段的引用(官方描述是这样的:运行时常量池(Runtime Constant Pool)是每一个类或接口的常量池(Constant_Pool)的运行时表示形式,它包括了若干种不同的常量: 从编译期可知的数值字面量到必须运行期解析后才能获得的方法或字段引用。)


字符串常量池

这个东西比较有争议,在官方文档中没有明确的定义。但它的确实际存在,在JVM运行的时候的确会有给String类型常量单独分一块区域去存储,并专门优化。下面来针对String来分析。


String


String源码:

在这里插入图片描述
从这里其实可以看出String对象的不可变,那么String对象就相对稳定,hash指也就能确定唯一,这样就方便字符串常量池的实现。


String的创建方式

下面描述几种String的创建不同内存情况

  1. String a = "abc"
    这种情况会在常量池中创建abc
    在这里插入图片描述

  2. String a = new String("abc")
    这种情况会在字符串常量池中创建abc,在堆中间创建a对象,并且将a指向常量池中的abc。
    在这里插入图片描述

  3. String a= "ab"+ "cd"+ "ef";
    这个在大部分JVM中,在编译阶段会进行优化,变成 abcdef,我们可以反编译class文件来查看,基本上可以看到这样的String a= "abcdef";
    在这里插入图片描述

  4.     String a = "abc";
        for (int i = 0; i < 10000;i++){
            a = a + i;
        }
    

    经过编译后会优化成这种的

     String a = "abc";
      for (int i = 0; i < 10000;i++){
          a = new StringBuilder(String.valueOf(a)).append(i).toString();
      }
    
  5.  	String a = new String("abc").intern();
         String b = new String("abc").intern();
         if (a == b){
             System.out.println("a=b");
         }else {
             System.out.println("a!=b");
         }
    

    这种情况intren()方法会判断字符串常量池中是不是有abc这个变量,有就引用,无就创建。
    所以输出结果是a=b。


上一篇:初识JVM

下一篇:玩转JVM对象和引用

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值