Java的内存结构

1.什么是JVM

在这里插入图片描述

2.Java的内存结构

Java依靠JVM屏蔽的底层的操作系统,避免了和操作系统直接打交道,通过JVM进行内存分别和回收等一些列操作;它的结构布局如下:
在这里插入图片描述

3.程序计数器(JVM)

程序计数器是Java依托操作系统CPU的寄存器实现的,是用于记录JVM下一条指令的执行地址。

它的作用如下图所示:
在这里插入图片描述
特点:

  • 是线程私有的;
  • JVM内存结构中唯一不会存在内存溢出的区域;

4.虚拟机栈(JVM)

每个线程运行时所需要的内存称为Java虚拟机栈。

特点:

  • 每个线程运行时都会开辟Java虚拟机栈,而栈又是由栈帧组成,对应着方法调用时所需要的内存;
  • 栈帧里面存者局部变量,方法参数,返回值等信息;

Java虚拟机栈的设置:
在这里插入图片描述
注意事项:
由于物理内存一定,调整虚拟机栈的大小会影响线程的并发数,因为虚拟机栈的大小越大,并发线程数就越少,所以会影响线程的并发数。

5.堆(JVM)

通过New关键字创建的对象使用的内存称为堆。

特点:

  • 堆内存是线程共享的,需要考虑线程安全问题;
  • 有垃圾回收机制;

修改堆内存大小:
在这里插入图片描述

6.本地方法栈(JVM)

由于Java不直接跟操作系统底层打交道,在实现有关功能时需要调用本地方法,调用本地方法所需要的空间叫本地方法栈。

7.方法区(JVM)

方法区是用来存放跟类相关的信息,如:类加载器、常量,静态变量,类方法、运行时常量池、接口。

注意点:
1.JDK1.6的方法区实现叫做“永久代”,使用的是堆内存(和老年代垃圾回收是捆绑在一起的)。
在这里插入图片描述
2.JDK1.8的方法区实现叫“元空间”,使用的是操作系统的内存。
在这里插入图片描述
3.调整方法区大小
在这里插入图片描述
特点:

  • 方法区是线程共享的需要考虑线程安全问题;
  • JDK1.6以前叫“永久代”,有垃圾回收机制,JDK1.8叫“元空间”,使用的是本地内存,并将运行时常量池移动到堆内存,使用的YongGC就可以回收内存;

8.直接内存

直接内存并不是Java虚拟机运行的数据区的一部分,已不是Java虚拟机规范中定义的内存区域;常见于NIO操作时,用于数据的缓冲区。

1:传统的阻塞IO操作原理图:
在这里插入图片描述
Java程序首先会调用系统的本地方法,将数据读取到系统缓冲区,然后再从系统缓冲区读取到Java堆的缓冲区。
2:使用直接内存进行IO操作时:

在这里插入图片描述
Java程序首先会调用系统的本地方法,将数据读取到直接内存(缓冲区),再从直接内存读取到Java堆的缓冲区。
3:代码示例:


import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class Test0927_ZhiJieNeiCun {
    private static File file=new File("D:\\Daliy_DownLoad\\Java_Kuang_Jar\\JVM\\a.txt");
    private static File newFile=new File("D:\\TestIO.txt");
    public static void main(String[] args) throws IOException {
       io();//传统IO拷贝
        direcBuffer();

    }
    private static void io() throws IOException {
        FileInputStream in=new FileInputStream(file);
        FileOutputStream os=new FileOutputStream(newFile);
        int len=0;
        byte[] data=new byte[1024];
        while ((len=in.read(data))!=-1){
            os.write(data,0,len);
        }
    }
    private static void direcBuffer() throws IOException {
        //FileChannel是直接把输入和输出流建立联系,效率更高
        FileChannel in=new FileInputStream(file).getChannel();
        FileChannel os=new FileOutputStream(newFile).getChannel();
        ByteBuffer bf=ByteBuffer.allocate(1024); //分配直接内存
        int len=0;
        while ((len=in.read(bf))!=-1){
            os.write(bf);
        }
    }
}

4:直接内存的应用和释放


import sun.misc.Unsafe;

import java.io.IOException;
import java.lang.reflect.Field;

/**
 * 直接内存分配的底层原理:Unsafe
 */
public class Demo1_27 {
    static int _1Gb = 1024 * 1024 * 1024;

    public static void main(String[] args) throws IOException {
        Unsafe unsafe = getUnsafe();
        // 分配内存
        long base = unsafe.allocateMemory(_1Gb); //相当于地址
        unsafe.setMemory(base, _1Gb, (byte) 0);
        System.in.read();

        // 释放内存
        unsafe.freeMemory(base);
        System.in.read();
    }

    public static Unsafe getUnsafe() {
        try {
            Field f = Unsafe.class.getDeclaredField("theUnsafe");
            f.setAccessible(true);
            Unsafe unsafe = (Unsafe) f.get(null);
            return unsafe;
        } catch (NoSuchFieldException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }
}

5:2 分配和回收原理

  • 使用了 Unsafe 对象完成直接内存的分配回收,并且回收需要主动调用 freeMemory 方法。
  • ByteBuffer的实现类内部,使用了 Cleaner (虚引用)来监测 ByteBuffer 对象,一旦ByteBuffer 对象被垃圾回收,那么就会由 ReferenceHandler 线程通过 Cleaner 的 clean 方法调用 freeMemory 来释放直接内存。

9.运行时常量池

1:看一段程序代码:


public class TestStringTable0927 {
    public static void main(String[] args) {
        String s1="a";
        String s2="b";
        String s3="a"+"b";
        String s4=s1+s2;
        String s5="ab";
        String s6=s4.intern();

        System.out.println(s3==s4); //false  ==>s3:在编译时会产生优化等价于 s3="ab",而s4=new String("ab");
        System.out.println(s3==s5); //true  
        System.out.println(s3==s6); //true  ==> s3="ab",s6等于S4入池方法后所以是相等的
    }
}

2:图解其内存布局:
使用Java反编译查看字节码:
1:利用工具反编译
在这里插入图片描述
2:使用Java的指令查看字节码:
在这里插入图片描述
3:查看相关的字节码信息:
在这里插入图片描述
在这里插入图片描述
4:根据字节码信息分析内存布局:
在这里插入图片描述

10.线程运行诊断

  • 用top定位哪个进程对cpu的占用过高;
  • ps H -eo pid,tid,%cpu | grep 进程id;(用ps命令进一步定位是哪个线程引起的cpu占用过高);
  • jstack 进程id 可以根据线程id找到有问题的线程,进一步定位到问题代码的源码行号;

示例:
1:top命令查看进程
在这里插入图片描述
2:ps H -eo pid,tid,%cpu|grep 进程Id
在这里插入图片描述
3:jstack进程进去查看线程(十进制–》十六进制)
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值