JVMstudy(01内存结构)——B站黑马0702

JVMstudy(01内存结构)——B站黑马0702

JVM一共分为五个部分:堆,虚拟机栈,本地方法栈,程序计数器,方法区

定义:

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

好处:

  1. 一次编写,到处运行
  2. 自动内存管理,垃圾回收功能
  3. 数组下标越界检查
  4. 多态

1 内存结构

1.1 程序计数器

1.1.1 定义

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

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

简介:

JVM中的程序计数寄存器(Program Counter Register)中,Register命名源于CPU中的寄存器,寄存器的工作是存储指令相关的信息,CPU只有把数据装载到寄存器才能运行,运行时数据区中的程序计数寄存器,并不是真实的物理寄存器,JVM中的PC寄存器是对物理PC寄存器的一种抽象模拟

程序计数器是一块很小的内存空间,也是运行速度最快的存储区域,在JVM中,每个进程有若干个线程,每个线程都有自己的程序计数器,是线程私有的,它的生命周期伴随着整个线程

1.1.2 作用

​ 任何时间一个线程某一时刻只有一个方法在执行,也就是当前方法,程序计数器会存储当前线程正在执行的当前方法的JVM指令地址,如果在执行本地方法,则存储未指定值(undefined)

  1. 它是程序控制流的指示器,分支,循环,跳转,异常处理等基础功能 都需要依赖这个程序计数器来完成
  2. 字节码解释器工作时,就是通过读取程序计数器的值 来选择下一条需要执行的字节码指令

在这里插入图片描述

1.2虚拟机栈

http://t.csdn.cn/IOu4H

1.2.1 定义

Java Virtual Machine Stacks (Java 虚拟机栈)

1. Java虚拟机栈(Java Virtual Machine
Stack),早期也叫Java栈。每个线程在创建时
都会创建一个虚拟机栈
,其内部保存一个个的栈帧(Stack Frame),对应着一次次的Java方法调用,栈是线程私有的

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

2. 虚拟机栈的生命周期:

  • 生命周期和线程一致,也就是线程结束了,该虚拟机栈也销毁了

3. 栈的特点:

  • 栈是一种快速有效的分配存储方式,访问速度仅次于程序计数器。JVM直接对Java栈的操作只有两个:
    ①每个方法执行,伴随着进栈(入栈、压栈)
    ②执行结束后的出栈工作
  • 对于栈来说不存在垃圾回收问题栈存在溢出的情况

问题辨析

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

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

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

    • 如果方法内局部变量没有逃离方法的作用访问,它是线程安全的
    • 如果是局部变量引用了对象,并逃离方法的作用范围,需要考虑线程安全
1.2.2 栈内存溢出

栈帧过多导致栈内存溢出

栈帧过大导致栈内存溢出

栈中可能出现的异常

  • Java 虚拟机规范允许Java栈的大小是动态的或者是固定不变的。
  • 如果采用固定大小的Java虚拟机栈,那每一个线程的Java虚拟机栈容量可以在线程创建的时候独立选定。
  • 如果线程请求分配的栈容量超过Java虚拟机栈允许的最大容量,Java虚拟机将会抛出一个StackoverflowError 异常。
  • 如果Java虚拟机栈可以动态扩展,并且在尝试扩展的时候无法申请到足够的内存,或者在创建新的线程时没有足够的内存去创建对应的虚拟机栈,那Java虚拟机将会抛出一个OutofMemoryError 异常。
1.2.3 线程诊断
  1. 案例1: cpu 占用过多

定位

  • 用top定位哪个进程对cpu的占用过高

  • ps H -eo pid,tid,%cpu | grep 进程id (用ps命令进一步定位是哪个线程引起的cpu占用过高)

  • jstack 进程id

    • 可以根据线程id 找到有问题的线程,进一步定位到问题代码的源码行号
  1. 案例2:程序运行很长时间没有结果

    线程死锁:死锁是指两个或两个以上的进程(线程)在运行过程中因争夺资源而造成的一种僵局,若无外力作用,这些进程(线程)都将无法向前推进 ,这时就形成了死锁。处于死锁状态的进程称为死锁进http://t.csdn.cn/YYs53

在这里插入图片描述

在这里插入图片描述

1.3 本地方法栈

本地方法栈本地方法栈

  • Java虚拟机栈用于管理Java方法的调用,而本地方法栈用于管理本地方法的调用。

  • 本地方法栈,也是线程私有的。

  • 允许被实现成固定或者是可动态扩展的内存大小。( 在内存溢出方面是相同的)

    ➢如果线程请求分配的栈容量超过本地方法栈允许的最大容量,Java虚拟机将会抛出一个StackoverflowError 异常。

    ➢如果本地方法栈可以动态扩展,并且在尝试扩展的时候无法申请到足够的内存,或者在创建新的线程时没有足够的内存去创建对应的本地方法栈,那么Java虛拟机将会抛出一个OutOfMemoryError 异常。

  • 本地方法是使用c语言实现的。

  • 它的具体做法是Native Method Stack中登记native方法,在Execution Engine 执行时加载本地方法库。

当某个线程调用一个本地方法时,它就进入了一个全新的并且不再受虚拟机限制的世界。它和虚拟机拥有同样的权限。

➢本地方法可以通过本地方法接来访问虚拟机内部的运行时数据区。

➢它甚至可以直接使用本地处理器中的寄存器

➢直接从本地内存的堆中分配任意数量的内存。

并不是所有的JVM都支持本地方法。因为Java虚拟机规范并没有明确要求本地方法栈的使用语言、具体实现方式、数据结构等。如果JVM产品不打算支持native方法,也可以无需实现本地方法栈。

1.4 堆

1.4.1 定义

Heap 堆

  • 通过 new 关键字,创建对象都会使用堆内存
  1. Java的堆是一个运行时数据区,类的对象从堆中分配空间。这些对象通过new等指令建立,通过垃圾回收器来销毁。
  2. 堆的优势是可以动态地分配内存空间,需要多少内存空间不必事先告诉编译器,因为它是在运行时动态分配的。但缺点是,由于需要在运行时动态分配内存,所以存取速度较慢。

特点

  • 它是线程共享的,堆中对象都需要考虑线程安全的问题

  • 有垃圾回收机制

1.4.1 堆内存溢出

一般来说,绝大部分Java的内存溢出都属于堆溢出。原因是因为大量对象占据了堆空间,这些对象都持有强引用导致无法回收,当对象大小之和大于Xmx参数指定的堆空间时就会发生堆溢出;

解决办法

  • 使用Xmx参数指定一个更大的堆空间;
  • 由于堆空间不可能无限增长,分析找到大量占用对空间的对象,在应用程序上做出优化;

在这里插入图片描述

1.4.3 堆内存诊断
  1. jps 工具

查看当前系统中有哪些 java 进程

  1. jmap 工具

查看堆内存占用情况 jmap - heap 进程id

  1. jconsole 工具

图形界面的,多功能的监测工具,可以连

jconsole :

在这里插入图片描述

1.5 方法区

1.5.1 定义

**Java虚拟机有一个在所有Java虚拟机线程之间共享的方法区域。**方法区域类似于传统语言编译代码的存储区域,或类似于操作系统进程中的“文本”段。它存储每类结构,例如运行时常量池、字段和方法数据,以及方法和构造函数的代码,包括类和实例初始化以及接口初始化中使用的特殊方法(§2.9)。

方法区域在虚拟机启动时创建。虽然方法区域在逻辑上是堆的一部分,但简单的实现可以选择不垃圾收集或压缩它。本规范不强制要求方法区域的位置或用于管理编译代码的策略。方法区域可以是固定大小,也可以根据计算需要进行扩展,如果不需要更大的方法区域,可以收缩。方法区域的内存不需要连续。

Java虚拟机实现可以为程序员或用户提供对方法区域初始大小的控制,以及在大小不同的方法区域的情况下,对最大和最小方法区域大小的控制。

以下例外情况与方法区域有关:

  • 如果方法区域中的内存无法满足分配请求,Java虚拟机会抛出OutOfMemoryError。
1.5.2 方法区组成

在这里插入图片描述

1.5.3 方法区内存溢出

1.8 以前会导致永久代内存溢出

  • 永久代内存溢出 java.lang.OutOfMemoryError: PermGen space
  • -XX:MaxPermSize=8m

1.8 之后会导致元空间内存溢出

  • 元空间内存溢出 java.lang.OutOfMemoryError: Metaspace
  • -XX:MaxMetaspaceSize=8m
1. 永久代和元空间的区别http://t.csdn.cn/THxo7

jdk1.8以前永久代

jdk1.8元空间

永久代和元空间的区别本质只有一个,那就是永久代使用的是jvm内存存储,而元空间使用的是本地内存存储。

元空间与永久代区别是其内存空间直接使用的是本地内存,而metaspace没有了字符串常量池,而在jdk7的时候已经被移动到了堆中,MetaSpace其他存储的东西,包括类文件,在JAVA虚拟机运行时的数据结构,以及class相关的内容,如Method,Field道理上都与永久代一样,只是划分上更趋于合理,比如说类及相关的元数据的生命周期与类加载器一致,每个加载器就是我们常说的classloader,都会分配一个单独的存储空间。

2.永久代、元空间 和方法区的关系

方法区仅仅是一种JVM的规范,规定哪些数据是存储在方法区的,元空间和永久代其实都是方法区的实现,只是实现有所不同,所以说方法区其实只是一种JVM的规范。

3.为什么要废除永久代

1、现实使用中易出问题。

  • 由于永久代内存经常不够用或者发生内存泄露,爆出异常 java.lang.OutOfMemoryError: PermGen 。
  • 类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出。

2、永久代会为GC带来不必要的复杂度,而且回收效率偏低。

1.5.4 运行时常量池

运行时常量池述

二进制字节码文件包含(类基本信息,常量池,类方法定义,包含了虚拟机指令)

常量池与运行时常量池

常量池,就是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量
等信息
运行时常量池,常量池是 *.class 文件中的,当该类被加载,它的常量池信息就会放入运行时常量
池,并把里面的符号地址变为真实地址

常量池:
在这里插入图片描述

1.5.5 StringTable
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); //flase
System.out.println(s3 == s5); //true
System.out.println(s3 == s6); 
String x2 = new String("c") + new String("d"); 
String x1 = "cd"; x2.intern(); // 问,如果调换了【最后两行代码】的位置呢,如果是jdk1.6呢 
System.out.println(x1 == x2);
1.5.5 StringTable特性
  • 常量池中的字符串仅是符号,第一次用到时才变为对象

  • 利用串池的机制,来避免重复创建字符串对象

  • 字符串变量拼接的原理是 StringBuilder (1.8)

  • 字符串常量拼接的原理是编译期优化

  • 可以使用 intern 方法,主动将串池中还没有的字符串对象放入串池

    • 1.7 ,1.8后 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池, 会把串池中的对象返回

      //  ["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
          System.out.println( s == x );//flase
      }
      
    • 1.6 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有会把此对象复制一份,放入串池, 会把串池中的对象返回

          // ["a", "b", "ab"]
          public static void main(String[] args) {
      
      
              String s = new String("a") + new String("b");
      
              // 堆  new String("a")   new String("b")  new String("ab")
              String s2 = s.intern(); // 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池, 会把串池中的对象返回
              // s 拷贝一份,放入串池
      
              String x = "ab";
              System.out.println( s2 == x);
              System.out.println( s == x );
          }
      
常量池练习题
public class 常量池练习题 {
    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
        System.out.println(s3 == s5);//true
        System.out.println(s3 == s6);//true

        String x2 = new String("c") + new String("d"); // new String("cd")
        String x1 = "cd";
        String x3 = x2.intern(); // "cd" 常量池中有cd所有没有放进去,但会返回的返回值会指向常量池

        // 问,如果调换了【最后两行代码】的位置呢,如果是jdk1.6呢
        System.out.println(x1 == x2);//false
        System.out.println(x1 == x3);//true

        String x4 = new String("e") + new String("f"); // new String("cd")
        String x6 = x4.intern(); // 常量池中没有,"cd" 放进常量池去,返回的返回值会指向常量池
        String x5 = "ef"; //常量池已经有了,直接指向

        System.out.println(x5 == x4);//true
        System.out.println(x5 == x6);//true
    }
}
1.5.6 StringTable位置

Jdk1.6及之前:有永久代,常量池1.6在方法区

Jdk1.7:有永久代,但已经逐步“去永久代”,常量池1.7在堆

Jdk1.8及之后:无永久代,常量池1.8在元空间

1.6.7 StringTable垃圾回收

JVM字符串常量池会进行垃圾回收吗?

会,但是对方法区的回收“成绩”比较难以令人满意,Java虚拟机规范中确实说过可以不要求虚拟机在方法区实现垃圾回收,而字符串常量池在方法区中。

假设一个字符串"everor"已经进入了常量池,但是当前系统中没有一个String对象引用常量池中的"everor"常量(即String str = new String(“everor”);),也没有其他地方引用了这个字面量(即String str = “everor”;),如果这个时候发生了内存回收,而且有必要的话,这个"everor"常量就会被系统清理出常量池。

在HotSpot虚拟机中,方法区又被称为永久代,永久代这个概念在其它虚拟机(如BEA JRockit、IBM J9等)中是不存在的。但是使用永久代来实现方法区,并不是一个好主意,因为这样更容易遇到内存溢出问题。从JDK1.7开始,逐步使用元空间(MetaSpace)代替永久代的概念。元空间的内存大小取决于本地内存大小。元空间代替永久代,并不意味着字符串常量池就到了元空间,而是移到了堆中。

1.6.8 StringTable性能调优
  • 调整 -XX:StringTableSize=桶个数

    • stringtable的数据结构为哈希表,如果程序里字符串常量的个数非常多,可以适当修改虚拟机参数-XX:StringTableSize(桶的个数,最小为1009)的大小:原理是增大桶的数量,减小冲突,提升效率
  • 考虑将字符串对象是否入池 (前提是程序中存在大量(几十万,百万个)字符串,且有很多重复的情况)

    • string.intren()方法

1.6 直接内存

1.6.1 定义

Direct Memory

常见于 NIO 操作时,用于数据缓冲区

分配回收成本较高,但读写性能高

不受 JVM 内存回收管理

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

注意:

/**
     * -XX:+DisableExplicitGC 显示的
     */
    private static void method() throws IOException {
        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(_1GB);
        System.out.println("分配完毕");
        System.in.read();
        System.out.println("开始释放");
        byteBuffer = null;
        System.gc(); // 手动 gc 失效
        System.in.read();
    }

一般用 jvm 调优时,会加上下面的参数:

-XX:+DisableExplicitGC  // 禁用显式GC ↑

bleExplicitGC 显示的
*/
private static void method() throws IOException {
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(_1GB);
System.out.println(“分配完毕”);
System.in.read();
System.out.println(“开始释放”);
byteBuffer = null;
System.gc(); // 手动 gc 失效
System.in.read();
}


一般用 jvm 调优时,会加上下面的参数:

```java
-XX:+DisableExplicitGC  // 禁用显式GC ↑

意思就是禁止我们手动的 GC,比如手动 System.gc() 无效,它是一种 full gc,会回收新生代、老年代,会造成程序执行的时间比较长。所以我们就通过 unsafe 对象调用 unfree.freeMemory 的方式释放内存。

好的,以下为B站黑马JavaScript的Ajax案例代码——图书管理案例搜索事件: ```javascript /** * 搜索事件 */ function searchBook() { // 获取搜索框内容 var searchInput = document.querySelector("#searchInput"); var keywords = searchInput.value.trim(); // 如果搜索框内容为空,提示用户 if (keywords == "") { alert("请输入关键字进行搜索!"); return; } // 发送Ajax请求 var xhr = new XMLHttpRequest(); // 创建XmlHttpRequest对象 xhr.open("GET", "/api/book/search?keywords=" + keywords, true); // 配置请求 xhr.onreadystatechange = function () { if (xhr.readyState == 4 && xhr.status == 200) { // 处理响应数据 var books = JSON.parse(xhr.responseText); // 将响应数据转成JavaScript对象 renderBooks(books); // 渲染图书列表 } }; xhr.send(null); // 发送请求 } /** * 渲染图书列表 * @param {*} books 图书列表数据 */ function renderBooks(books) { // 获取图书列表容器 var bookList = document.querySelector("#bookList"); var html = ""; if (books.length > 0) { // 遍历图书列表数据,生成HTML字符串 for (var i = 0; i < books.length; i++) { html += '<tr>'; html += '<td>' + books[i].title + '</td>'; html += '<td>' + books[i].author + '</td>'; html += '<td>' + books[i].category + '</td>'; html += '<td>' + books[i].price + '</td>'; html += '<td>' + books[i].publisher + '</td>'; html += '</tr>'; } } else { // 如果没有搜索到任何图书,提示用户 html = '<tr><td colspan="5">没有搜索到任何图书!</td></tr>'; } // 将生成的HTML字符串添加进图书列表容器 bookList.innerHTML = html; } ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

别来无恙blwy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值