JVM学习1 - JVM基础\堆\栈\方法区

一. 什么是JVM

Java Virtual Machine - java程序的运行环境(Java二进制字节码的运行环境)

优点:

  • 支持跨平台运行
  • 自动内存管理,垃圾回收功能
    在这里插入图片描述
    在这里插入图片描述

二. 内存结构

1. 程序计数器

(1)定义

Java源代码-》jvm指令-》解释器-》机器码-》CPU运行
用于存储下一条jvm指令的执行地址,物理上通过寄存器实现

(2)特点
  • 线程私有,每个线程都有自己的程序计数器。当线程时间片用完时,将下一条指令代码地址存储至程序计数器中,等到下次再取出执行。
  • 不会存在内存溢出。

2. 虚拟机栈

(1)定义

线程运行时所需的内存空间,每个线程都有自己的虚拟机栈虚拟机栈由一个个栈帧组成
栈帧:每个方法运行所需要的内存,包含参数、局部变量、返回地址等所需的内存。当方法执行完后,释放栈帧内存。
每个线程只有一个活动栈帧(栈顶),对应着当前正在执行的方法。

(2)问题解析
  • 垃圾回收不涉及栈内存,每次方法调用结束后会自动释放栈帧内存。
  • 内存总量一定,栈内存设置过大,会导致线程数变少。(-Xss,默认1M)
  • 方法内的没有逃离方法作用范围的局部变量是线程安全(线程私有)的。(因为一个线程对应一个栈,每次方法调用时会产生自己的栈帧,每个栈帧里都会初始化自己的局部变量)而static变量是共享的,每个线程调用结束后将新值写回,因此不是线程安全的。
    在这里插入图片描述
    在这里插入图片描述
(3)栈内存溢出

java.lang.StackOverFlowError

  • 栈帧过多导致栈内存溢出。比如递归调用、类间的循环使用。
    在这里插入图片描述
  • 栈帧过大导致栈内存溢出。(不常见)

三. 线程诊断

1. CPU占用过高

  • top 查看进程信息
    用 top 命令查看是哪个进程对 CPU 的占用过高
  • ps 查看线程信息
    再查看某个进程下的线程信息:ps H -eo pid,tid,%cpu | grep 进程号
  • “jstack 进程号” 查看该进程中的线程(Java类命令)
    将 ps 查看到的占用CPU高的线程(十进制)在 jstack 展示出来的线程(十六进制)中找出来,即可根据 jstack 中展示的代码位置找到问题所在。

2. 代码运行长时间无结果

可能出现了死锁问题。同样用 “jstack 进程号” 命令,在信息最后可以找到死锁信息。

四. 本地方法栈

本地方法栈:给本地方法的运行提供内存空间,线程私有。
本地方法(native):用其他语言编写的代码,Java 间接调用。

五. 堆(Heap)

1. 特点

  • 通过 new 关键字创建的对象都是使用堆内存
  • 线程共享,堆中的对象需要考虑线程安全问题
  • 有垃圾回收机制

2. 堆内存溢出

java.lang.OutOfMemoryError: Java heap space
-Xmx 更改堆内存默认大小(默认4G)

3. 堆内存诊断

  • jps 查看当前系统中有哪些 java 进程(Java类命令)
  • “jmap -heap 进程号” 查看 Heap Usage(Java类命令)
  • jconsole 图形化展示堆内存占用情况(Java类命令)

案例: 垃圾回收后内存占用仍然很高

jvisualvm 图形化展示堆内存情况(堆转储 dump 抓取内存快照)

六. 方法区

1. 定义

用于存储已经被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。方法区在虚拟机启动时被创建,逻辑上是堆的一部分。
元空间默认使用系统内存,无上限值,可用 -XX 修改默认大小。
在这里插入图片描述
方法区溢出:java.lang.OutOfMemoryError: Metaspace

案例:在运行期间动态生成字节码加载类(如代理机制、spring、mybatis)

2. 常量池

常量池:一张表,虚拟机指令根据这张表查找要执行的类名、方法名、参数类型、字面量等信息。

javap -v xxx.class // 反编译字节码,展示类基本信息、常量池、类方法定义

运行时常量池:常量池是 xxx.class 文件中的,当该类被加载时,它的常量信息就会被放入运行时常量池里。

3. StringTable 串池

(1)基本概念
//示例
String s1 = "a";
String s2 = "b";
String s3 = “ab”; //存在串池里
String s4 = s1 + s2; //new StringBuilder().append("a").append("b").toString() => new String("ab") 存在堆里
String s5 = "a" + "b"; //"a" "b"为常量,javac 在编译期间会优化。串池中已经存在“ab”,不会创建新的字符串
String s6 = new String("a") + new String("b"); // new String("ab") 存在堆里
String s7 = s6.intern(); // 将这个s6字符串尝试放入串池中(没有才放),并返回串池中的对象

System.out.println(s3 == s4); // false
System.out.println(s3 == s5); // true
System.out.println(s6 == "ab"); //true,如果"ab"在常量池中先有了,则不相等
System.out.println(s7 == "ab"); //true

StringTable[“a”, “b”, “ab”, …]:存储字符串常量,hashtable结构,1.8 位置在堆里,会被垃圾回收,不能扩容。

常量池中的信息,都会被加载到运行时常量池中。在使用之前,a、b、ab都是常量池中的符号,还没有变成Java字符串对象。只有当运行到该行时,才会将 a 等符号变成 “a” 字符串对象,放入 StringTable 串池中(串池中没有才放)。

总结:

  • 常量池中的字符串仅是符号,第一次用到时才变为对象;
  • 利用串池的机制,避免重复创建字符串对象;
  • 字符串变量拼接的原理是 StringBuilder(1.8);
  • 字符串常量拼接的原理是编译期优化;
  • 可以使用intern 方法,主动将串池中还没有的对象放入串池:1.7以上直接放入,1.6拷贝放入
(2)性能调优
  • 调整桶的个数
  • 考虑字符串入池(去除重复字符串,节约堆内存占用)

性能与 StringTable 中桶的个数有关,桶越多,hash分布越好,冲突越小,存储和查找常量的速度越快。

-XX:StringTableSize=桶个数

七. 直接内存

ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024* 1024 * 100);
  • 常用于IO操作,用于数据缓冲区
  • 属于系统内存读写性能高,分配回收成本高
  • 不受 JVM 内存回收管理,但当ByteBuffer对象被垃圾回收,那么分配的直接内存也会被自动释放(unsafe.freeMemory())

普通读写:需要先存到系统缓存区,再到Java缓存区
在这里插入图片描述
直接内存读写:Java代码可直接读写该区域
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值