您的位置:首页 > 科技 > 能源 > 六安网站建设招商_南宁网站推广策略_百度seo推广价格_影视站seo教程

六安网站建设招商_南宁网站推广策略_百度seo推广价格_影视站seo教程

2024/12/23 20:26:31 来源:https://blog.csdn.net/m0_51275144/article/details/144437751  浏览:    关键词:六安网站建设招商_南宁网站推广策略_百度seo推广价格_影视站seo教程
六安网站建设招商_南宁网站推广策略_百度seo推广价格_影视站seo教程

JVM内存结构

  • 一、JVM介绍
    • 1.1JDK、JRE、JVM
    • 1.2JVM:跨语言的平台
    • 1.3JVM的整体结构
  • 二、内存结构
    • 2.1程序计数器
      • 定义
      • 作用
    • 2.2虚拟机栈
      • 定义
      • 问题辨析
      • 栈内存溢出
        • 栈溢出案例
      • 线程运行诊断
    • 2.3本地方法栈
    • 2.4堆
      • 定义
      • 堆的组成
      • 堆内存溢出
      • 堆内存诊断
    • 2.5方法区
      • 方法区中方法执行过程
      • 组成
      • 方法区内存溢出
      • 运行时常量池
      • StringTable(字符串常量池)
      • StringTable特性
      • StringTable位置
      • StringTable垃圾回收机制
      • StringTable性能调优
    • 2.6直接内存
        • 定义
        • 直接内存溢出
        • 直接内存释放原理

一、JVM介绍

1.1JDK、JRE、JVM

Java development kit

Java runtime environment

Java virtual machine 跨平台

在这里插入图片描述

1.2JVM:跨语言的平台

在这里插入图片描述

  • Java虚拟机是一台执行Java字节码的虚拟计算机,它拥有独立的运行机制,其运行的Java字节码也未必由Java语言编译而成。

  • JVM平台的各种语言可以共享Java虛拟机带来的跨平台性、优秀的垃圾回器,以及可靠的即时编译器。

  • Java技术的核心就是Java虚拟机(JVM, Java Virtual Machine),因为所有的Java程序都运行在Java虛拟机内部。

  • 作用

    Java虚拟机就是二进制字节码的运行环境,负责装载字节码到其内部,解释/编译为对应平台上的机器指令执行。每一条Java指令,Java虚拟机规范中都有详细定义,如怎么取操作数,怎么处理操作数,处理结果放在哪里。

  • 特点

    • 一次编译,到处运行
  • 自动内存管理

    • 自动垃圾回收功能
  • 数组下标越界检查

jvm是运行在操作系统之上的,它与硬件没有直接的交互。

1.3JVM的整体结构

在这里插入图片描述

二、内存结构

在这里插入图片描述

2.1程序计数器

定义

Program Counter Register 程序计数器(寄存器)

  • 作用,是记住下一条jvm指令的执行地址
  • 特点
    • 是线程私有的
    • 不会存在内存溢出

作用

在这里插入图片描述

2.2虚拟机栈

定义

Java Virtual Machine Stacks (Java 虚拟机栈)

  • 每个线程运行时所需要的内存,称为虚拟机栈
  • 每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存
  • 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法

在这里插入图片描述

问题辨析

1.垃圾回收是否涉及栈内存?

答:不会。栈内存在方法调用结束后自动清除。

2.栈内存分配越大越好吗?

答:不是。因为栈内存变大会影响线程数量,栈越大线程越少。

3.方法内的局部变量是否线程安全?

  • 如果方法内局部变量没有逃离方法的作用访问,它是线程安全的
    在这里插入图片描述

  • 如果是局部变量引用了对象,并逃离方法的作用范围,需要考虑线程安全

在这里插入图片描述

在这里插入图片描述

栈内存溢出

  • 栈帧过多导致栈内存溢出
    在这里插入图片描述

  • 栈帧过大导致栈内存溢出

    在这里插入图片描述

通过设置-Xss来配置栈内存大小。

栈溢出案例
public class stack {public static void main(String[] args) throws JsonProcessingException {Dept dept = new Dept("开发部");Emp emp1 = new Emp("张三", dept);Emp emp2 = new Emp("李四", dept);dept.addEmp(emp1);dept.addEmp(emp2);dept.setEmps(Arrays.asList(emp1, emp2));ObjectMapper objectMapper = new ObjectMapper();System.out.println(objectMapper.writeValueAsString(dept));}
}
class Emp {private String name;private Dept dept;public Emp(String name, Dept dept) {this.name = name;this.dept = dept;}
}
class Dept {private String name;private List<Emp> emps;public Dept(String name) {this.name = name;this.emps = new ArrayList<>();}public void addEmp(Emp emp) {emps.add(emp);}public void set(String name) {this.name = name;}public void setEmps(List<Emp> emps) {this.emps = emps;}
}

上述代码会出现栈溢出错误:
在这里插入图片描述

原因是:

 dept.setEmps(Arrays.asList(emp1, emp2));

emp中含有dept会出现:一直无限往里套娃!

{name:'研发部',emps;[{name:'张三',dept:{name:'',emps:[{}]}}]}

为了避免出现这样的问题,需要在emp的dept字段加上@JsonIgnore

在这里插入图片描述

在这里插入图片描述

线程运行诊断

案例1:cpu占用过多(Linux环境下)

定位:

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

2.3本地方法栈

为本地方法提供内存空间。

2.4堆

定义

Heap 堆

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

堆的组成

img

  • 新生代(Young Generation):新生代分为Eden Space和Survivor Space。在Eden Space中, 大多数新创建的对象首先存放在这里。Eden区相对较小,当Eden区满时,会触发一次Minor GC(新生代垃圾回收)。在Survivor Spaces中,通常分为两个相等大小的区域,称为S0(Survivor 0)和S1(Survivor 1)。在每次Minor GC后,存活下来的对象会被移动到其中一个Survivor空间,以继续它们的生命周期。这两个区域轮流充当对象的中转站,帮助区分短暂存活的对象和长期存活的对象。
  • 老年代(Old Generation/Tenured Generation):存放过一次或多次Minor GC仍存活的对象会被移动到老年代。老年代中的对象生命周期较长,因此Major GC(也称为Full GC,涉及老年代的垃圾回收)发生的频率相对较低,但其执行时间通常比Minor GC长。老年代的空间通常比新生代大,以存储更多的长期存活对象。
  • 元空间(Metaspace):从Java 8开始,永久代(Permanent Generation)被元空间取代,用于存储类的元数据信息,如类的结构信息(如字段、方法信息等)。元空间并不在Java堆中,而是使用本地内存,这解决了永久代容易出现的内存溢出问题。
  • 大对象区(Large Object Space / Humongous Objects):在某些JVM实现中(如G1垃圾收集器),为大对象分配了专门的区域,称为大对象区或Humongous Objects区域。大对象是指需要大量连续内存空间的对象,如大数组。这类对象直接分配在老年代,以避免因频繁的年轻代晋升而导致的内存碎片化问题。

堆内存溢出

在这里插入图片描述

使用-Xmx8m进行堆空间大小修改

堆内存诊断

  1. 案例
    垃圾回收后,内存占用仍然很高,使用jvisualvm进行可视化的查看。

2.5方法区

方法区中方法执行过程

当程序中通过对象或类直接调用某个方法时,主要包括以下几个步骤:

  • 解析方法调用:JVM会根据方法的符号引用找到实际的方法地址(如果之前没有解析过的话)。
  • 栈帧创建:在调用一个方法前,JVM会在当前线程的Java虚拟机栈中为该方法分配一个新的栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
  • 执行方法:执行方法内的字节码指令,涉及的操作可能包括局部变量的读写、操作数栈的操作、跳转控制、对象创建、方法调用等。
  • 返回处理:方法执行完毕后,可能会返回一个结果给调用者,并清理当前栈帧,恢复调用者的执行环境。

组成

在这里插入图片描述

在这里插入图片描述

《深入理解Java虚拟机》书中对方法区(Method Area)存储内容描述如下:它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等。

  • 类信息:包括类的结构信息、类的访问修饰符、父类与接口等信息。
  • 常量池:存储类和接口中的常量,包括字面值常量、符号引用,以及运行时常量池。
  • 静态变量:存储类的静态变量,这些变量在类初始化的时候被赋值。
  • 方法字节码:存储类的方法字节码,即编译后的代码。
  • 符号引用:存储类和方法的符号引用,是一种直接引用不同于直接引用的引用类型。
  • 运行时常量池:存储着在类文件中的常量池数据,在类加载后在方法区生成该运行时常量池。
  • 常量池缓存:用于提升类加载的效率,将常用的常量缓存起来方便使用。

方法区内存溢出

jdk 1.8前导致永久代内存溢出:这里不在演示

jdk 1.8之后导致原空间内存溢出

在这里插入图片描述

场景:

  • spring
  • mybatis

运行时常量池

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

在这里插入图片描述
在这里插入图片描述

  • 运行时常量池,常量池是 *.class 文件中的,当该类被加载,它的常量池信息就会放入运行时常量池,并把里面的符号地址变为真实地址

StringTable(字符串常量池)

// StringTable [ "a", "b" ,"ab" ]  hashtable 结构,不能扩容
// 常量池中的信息,都会被加载到运行时常量池中, 这时 a b ab 都是常量池中的符号,还没有变为 java 字符串对象
// ldc #2 会把 a 符号变为 "a" 字符串对象
// ldc #3 会把 b 符号变为 "b" 字符串对象
// ldc #4 会把 ab 符号变为 "ab" 字符串对象
public static void main(String[] args) {String s1 = "a"; // 一开始存在常量池,用到才会创建对象,懒惰的String s2 = "b";String s3 = "ab";String s4 = s1 + s2; // new StringBuilder().append("a").append("b").toString()  new String("ab")//s3 != s4String s5 = "a" + "b";  // javac 在编译期间的优化,结果已经在编译期确定为ab   上边s4是两个变量拼接,可以变,所以不确定System.out.println(s3 == s5);
}

分析:使用 javap -v 类名.class查看常量池文件

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

StringBuilder中的toString的方法:相当于调用了new String()方法。

在这里插入图片描述

StringTable特性

  • 常量池中的字符串仅是符号,第一次用到时才变为对象
  • 利用串池的机制,来避免重复创建字符串对象
  • 字符串变量拼接的原理是 StringBuilder (1.8)
  • 字符串常量拼接的原理是编译期优化
  • 可以使用 intern() 方法,主动将串池中还没有的字符串对象放入串池
    • 1.8 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池, 会把串池中的对象返回
    • 1.6 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有会把此对象复制一份,放入串池, 会把串池中的对象返回
//  ["ab", "a", "b"]
public static void main(String[] args) {String x = "ab";String s = new String("a") + new String("b");// 堆  new String("a")   new String("b") new String("ab")String s2 = s.intern(); // 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池, 会把串池中的对象返回System.out.println( s2 == x); //true 串池中的"ab"System.out.println( s == x ); //false 堆中的对象 跟 串池中的并不相等
}
//StringTable面试题public static void main(String[] args) {String s1 = "a";String s2 = "b";String s3 = "a" + "b"; // ab  StringTable{"a","b","ab"};String s4 = s1 + s2;   // new String("ab") 放在了堆中String s5 = "ab";      // 串中有,不会新建了String s6 = s4.intern(); // 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池,// 会把串池中的对象返回// 问System.out.println(s3 == s4); // false 因为堆中的对象跟串中的对象不相等System.out.println(s3 == s5); // true 都是串池中的对象System.out.println(s3 == s6); // true 都是串池中的对象String x2 = new String("c") + new String("d"); // new String("cd") 放到堆中x2.intern(); // 尝试放入串池,如果串池中有,则不会放入,如果没有,则放入串池,返回串池中的对象String x1 = "cd"; // 串中有,不会新建了// 问,如果调换了【最后两行代码】的位置呢,如果是jdk1.6呢System.out.println(x1 == x2); // true}

StringTable位置

在jdk1.6时候,位于常量池中,常量池位于永久代中,内存空间不足导致永久代空间不足。

在jdk1.8以后,位于堆中。

/*** 演示 StringTable 位置* 在jdk8下设置 -Xmx10m -XX:-UseGCOverheadLimit* 在jdk6下设置 -XX:MaxPermSize=10m*/
public class Demo1_6 {public static void main(String[] args) throws InterruptedException {List<String> list = new ArrayList<String>();int i = 0;try {for (int j = 0; j < 260000; j++) {list.add(String.valueOf(j).intern());//放到StringTable中i++;}} catch (Throwable e) {e.printStackTrace();} finally {System.out.println(i);}}
}

堆空间不足。
在这里插入图片描述

StringTable垃圾回收机制

/*** 演示 StringTable 垃圾回收* -Xmx10m -XX:+PrintStringTableStatistics -XX:+PrintGCDetails -verbose:gc*/
public class Demo1_7 {public static void main(String[] args) throws InterruptedException {int i = 0;try {for (int j = 0; j < 100000; j++) { // j=100, j=10000String.valueOf(j).intern();i++;}} catch (Throwable e) {e.printStackTrace();} finally {System.out.println(i);}}
}

在这里插入图片描述

StringTable性能调优

StringTable的底层相当于HashTable。可以调整桶的大小来改变StringTable的存储速度。

/*** 演示串池大小对性能的影响* -Xms500m -Xmx500m -XX:+PrintStringTableStatistics -XX:StringTableSize=1009*/
public class Demo1_24 {public static void main(String[] args) throws IOException {try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream("linux.words"), "utf-8"))) {String line = null;long start = System.nanoTime();while (true) {line = reader.readLine();if (line == null) {break;}line.intern();}System.out.println("cost:" + (System.nanoTime() - start) / 1000000);}}
}

默认桶大小时:
在这里插入图片描述

在这里插入图片描述

考虑字符串是否存在,使用intern()

/*** 演示 intern 减少内存占用* -XX:StringTableSize=200000 -XX:+PrintStringTableStatistics* -Xsx500m -Xmx500m -XX:+PrintStringTableStatistics -XX:StringTableSize=200000*/
public class Demo1_25 {public static void main(String[] args) throws IOException {List<String> address = new ArrayList<>();System.in.read();for (int i = 0; i < 10; i++) {try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream("linux.words"), "utf-8"))) {String line = null;long start = System.nanoTime();while (true) {line = reader.readLine();if(line == null) {break;}address.add(line.intern());}System.out.println("cost:" +(System.nanoTime()-start)/1000000);}}System.in.read();}
}

2.6直接内存

定义

Direct Memory(直接内存)

  • 常见于 NIO 操作时,用于数据缓冲区
  • 分配回收成本较高,但读写性能高
  • 不受 JVM 内存回收管理

在这里插入图片描述

直接内存溢出
/*** 演示直接内存溢出*/
public class Demo1_10 {static int _100Mb = 1024 * 1024 * 100;public static void main(String[] args) {List<ByteBuffer> list = new ArrayList<>();int i = 0;try {while (true) {ByteBuffer byteBuffer = ByteBuffer.allocateDirect(_100Mb);list.add(byteBuffer);i++;}} finally {System.out.println(i);}// 方法区是jvm规范, jdk6 中对方法区的实现称为永久代//                  jdk8 对方法区的实现称为元空间}
}

在这里插入图片描述

直接内存释放原理
  • 使用了 Unsafe 对象完成直接内存的分配回收,并且回收需要主动调用 freeMemory 方法
  • ByteBuffer 的实现类内部,使用了 Cleaner (虚引用)来监测 ByteBuffer 对象,一旦ByteBuffer 对象被垃圾回收,那么就会由 ReferenceHandler 线程通过 Cleaner 的 clean 方法调用 freeMemory 来释放直接内存
/*** 直接内存分配的底层原理: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);}}
}

ByteBuffer底层源码:

DirectByteBuffer(int cap) {                   // package-privatesuper(-1, 0, cap, cap);boolean pa = VM.isDirectMemoryPageAligned();int ps = Bits.pageSize();long size = Math.max(1L, (long)cap + (pa ? ps : 0));Bits.reserveMemory(size, cap);long base = 0;try {base = unsafe.allocateMemory(size);} catch (OutOfMemoryError x) {Bits.unreserveMemory(size, cap);throw x;}//调用了 setMemory方法存储unsafe.setMemory(base, size, (byte) 0);if (pa && (base % ps != 0)) {// Round up to page boundaryaddress = base + ps - (base & (ps - 1));} else {address = base;}//cleaner 为虚引用,当他指向的对象被垃圾回收后,它也回收cleaner = Cleaner.create(this, new Deallocator(base, size, cap));att = null;
}
//Deallocator中的run方法
public void run() {if (address == 0) {// Paranoiareturn;}//回收数据unsafe.freeMemory(address);address = 0;Bits.unreserveMemory(size, capacity);
}
/*关闭显示的垃圾回收* -XX:+DisableExplicitGC 显式的*/

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com