写在文章前:本系列博客是学习黑马程序员JVM完整教程所做笔记,若有错误希望大佬们评论修正


一.概述

JVM的内存结构包括程序计数器(PC Register),虚拟机栈(JVM Stacks),堆内存(heap),方法区(Method Area),本地方法区(Native Method Stacks)

二.程序计数器

  • 定义:Program Counter Register 程序计数器(寄存器)
  • 作用:记录下一条JVM指令的内存地址
  • 特点:① 线程私有的,每个线程都有自己的程序计数器 ②不会存在内存溢出

三.虚拟机栈

定义:Java Virtual Machine Stacks (Java 虚拟机栈)

  • 每个线程运行时所需的内存,称为虚拟机栈
  • 每个栈由多个栈帧组成,一个栈帧对应一个方法调用
  • 栈帧主要记录方法参数,局部变量,返回地址等。
  • 每个栈只有一个活动栈帧,对应着正在执行的那个方法
public class Demo1_1 {
public static void main(String[] args) throws InterruptedException {
method1();
} private static void method1() {
method2(1, 2);
} private static int method2(int a, int b) {
int c = a + b;
return c;
}
}

问题辨析:

1. 垃圾回收是否涉及栈内存?
 
  不涉及,方法调用完后会自动释放内存
 
2. 栈内存分配越大越好吗?
 
不是,一个线程对应一个虚拟机栈。栈内存分配大的话,所能创建的线程数就少了
 
3. 方法内的局部变量是否线程安全?
  • 如果方法内局部变量没有逃离方法的作用范围,则线程安全
  • 如果局部变量引用了对象并逃离了作用范围,则需要考虑线程安全

栈内存溢出问题:

java.lang.StackOverflowError
  • 栈帧过多导致栈内存溢出(例如方法的递归调用而没有终结点),

    -Xss256k 设置JVM栈内存参数
  • 栈帧过大导致栈内存溢出(不常见)

线程运行时诊断:

案例1: cpu 占用过多
  • top定位哪个进程对cpu的占用过高
  • ps H -eo pid,tid,%cpu | grep 进程id (用ps命令进一步定位是哪个线程引起的cpu占用过高)
  • jstack 进程id    可以根据线程id 找到有问题的线程,进一步定位到问题代码的源码行号
案例2:程序运行很长时间没有结果 
     jstack 进程id   检测出一个死锁

四.本地方法栈

  • 本地方法 -- 指那些不是用java编写的方法。JVM会为它们申请一些内存

五.堆

定义:heap 堆

  • 通过new关键字,创建对象都会使用堆内存

特点:

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

堆内存溢出:

java.lang.OutOfMemoryError: Java heap space
  • -Xmx8m 设置JVM堆内存大小

堆内存诊断:

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

jmap 工具  查看堆内存占用情况 jmap - heap 进程id (不能连续监测)

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

jvisualvm工具

 六.方法区

The Java Virtual Machine has a method area that is shared among all Java Virtual Machine threads. The method area is analogous to the storage area for compiled code of a conventional language or analogous to the "text" segment in an operating system process. It stores per-class structures such as the run-time constant pool, field and method data, and the code for methods and constructors, including the special methods  used in class and instance initialization and interface initialization.

The method area is created on virtual machine start-up. Although the method area is logically part of the heap, simple implementations may choose not to either garbage collect or compact it. This specification does not mandate the location of the method area or the policies used to manage compiled code. The method area may be of a fixed size or may be expanded as required by the computation and may be contracted if a larger method area becomes unnecessary. The memory for the method area does not need to be contiguous.

A Java Virtual Machine implementation may provide the programmer or the user control over the initial size of the method area, as well as, in the case of a varying-size method area, control over the maximum and minimum method area size.

The following exceptional condition is associated with the method area:

  • If memory in the method area cannot be made available to satisfy an allocation request, the Java Virtual Machine throws an OutOfMemoryError.

组成:

方法区内存溢出:

  • 1.8 以前会导致永久代内存溢出
* 演示永久代内存溢出 java.lang.OutOfMemoryError: PermGen space
* -XX:MaxPermSize=8m
  • 1.8 之后会导致元空间内存溢出
* 演示元空间内存溢出 java.lang.OutOfMemoryError: Metaspace
* -XX:MaxMetaspaceSize=8m

二进制字节码的的组成:

类的基本信息、常量池、类的方法定义(包含了虚拟机的指令)

通过反编译查看字节码文件:

类的基本信息:

常量池:

public class HelloWorld {
public static void main(String[] args) {
System.out.println("hello world");
}
}

常量池:

  • 就是一张表如上图的(constant pool),虚拟机指令根据这张常量表找到要执行的类名,方法名,参数类型,字面量信息

运行时常量池:

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

常量池与StringTable的关系:

StringTable串池

特性:

  • 常量池中的字符串仅是符号,只有在被用到时才会转化为对象
  • 利用串池的机制避免重复创建字符串对象
  • 字符串变量拼接的原理是StringBuilder
  • 字符串常量拼接的原理是编译器优化
  • 可以使用intern()主动将串池中没有的字符串对象放入串池 。1.8 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池, 会把串池中的对象返回 
// StringTable [ "a", "b" ,"ab" ]  hashtable 结构,不能扩容
public class Demo1_22 {
// 常量池中的信息,都会被加载到运行时常量池中, 这时 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")
String s5 = "a" + "b"; // javac 在编译期间的优化,结果已经在编译期确定为ab System.out.println(s3 == s5); }
}

StringTable垃圾回收

StringTable在内存紧张时会发生内存回收

StringTable调优

  • 因为StringTable是由HashTable实现的,所以可以适当增加HashTable中桶的个数,来减少字符串放入串池的事件 (-XX:StringTableSize=xxxx)
  • 考虑是否需要字符串对象入池 (可以通过intern方法减少重复入池)

 七.直接内存

Direct Memory

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

文件读写流程

使用了ByteBuffer

直接内存是java程序和操作系统都可以访问的一块区域,无需再将数据从系统内存复制到java的堆内存,从而提高了效率

释放原理

直接内存的回收不是JVM的垃圾回收来释放的,而是通过unsafe.freeMemory来手动释放

通过

//通过ByteBuffer申请1M的直接内存
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(_1M);

申请直接内存,但JVM并不能直接回收直接内存中的内容,它是如何实现回收的呢?

查看allocateDirect的实现

public static ByteBuffer allocateDirect(int capacity) {
return new DirectByteBuffer(capacity);
}

DirectByteBuffer类

DirectByteBuffer(int cap) {   // package-private

    super(-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;
}
unsafe.setMemory(base, size, (byte) 0);
if (pa && (base % ps != 0)) {
// Round up to page boundary
address = base + ps - (base & (ps - 1));
} else {
address = base;
}
cleaner = Cleaner.create(this, new Deallocator(base, size, cap)); //通过虚引用,来实现直接内存的释放,this为虚引用的实际对象
att = null;
}

这里调用了Cleaner的create方法,且后台线程还会对虚引用的对象进行监测。如果虚引用的实际对象(这里是ByteBuffer)被回收后,就会调用Cleaner的clean方法,来清除直接内存中占有的内存

public void clean() {
if (remove(this)) {
try {
this.thunk.run(); //调用run方法
} catch (final Throwable var2) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
if (System.err != null) {
(new Error("Cleaner terminated abnormally", var2)).printStackTrace();
} System.exit(1);
return null;
}
});
}

对应对象的run方法

public void run() {
if (address == 0) {
// Paranoia
return;
}
unsafe.freeMemory(address); //释放直接内存中占用的内存
address = 0;
Bits.unreserveMemory(size, capacity);
}

分配和回收原理

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

JVM(二) --- JVM的内存结构的更多相关文章

  1. JVM学习01:内存结构

    JVM学习01:内存结构 写在前面:本系列分享主要参考资料是  周志明老师的<深入理解Java虚拟机>第二版. 内存结构知识要点Xmind梳理 案例分析 分析1 package com.h ...

  2. Oracle数据库基础入门《二》Oracle内存结构

    Oracle数据库基础入门<二>Oracle内存结构 Oracle 的内存由系统全局区(System Global Area,简称 SGA)和程序全局区(Program Global Ar ...

  3. jvm比较详尽的内存结构

     JVM内存结构 2012-09-17 15:27:59 分类: Java 本文转自:http://www.blogjava.net/nkjava/archive/2012/03/14/371831. ...

  4. JVM(一)内存结构

    今日开篇 什么是JVM 定义 Java Virtual Machine,JAVA程序的运行环境(JAVA二进制字节码的运行环境) 好处 一次编写,到处运行 自动内存管理,垃圾回收机制 数组下标越界检查 ...

  5. JVM体系结构-----深入理解内存结构

    一.概述 内存在计算机中占据着至关重要的地位,任何运行时的程序或者数据都需要依靠内存作为存储介质,否则程序将无法正常运行.与C和C++相比,使用Java语言编写的程序并不需要显示的为每一个对象编写对应 ...

  6. [二]Java虚拟机 jvm内存结构 运行时数据内存 class文件与jvm内存结构的映射 jvm数据类型 虚拟机栈 方法区 堆 含义

    前言简介 class文件是源代码经过编译后的一种平台中立的格式 里面包含了虚拟机运行所需要的所有信息,相当于 JVM的机器语言 JVM全称是Java Virtual Machine  ,既然是虚拟机, ...

  7. JAVA常见错误处理方法 和 JVM内存结构

    OutOfMemoryError在开发过程中是司空见惯的,遇到这个错误,新手程序员都知道从两个方面入手来解决:一是排查程序是否有BUG导致内存泄漏:二是调整JVM启动参数增大内存.OutOfMemor ...

  8. Java中的OutOfMemoryError的各种情况及解决和JVM内存结构

    在JVM中内存一共有3种:Heap(堆内存),Non-Heap(非堆内存) [3]和Native(本地内存). [1] 堆内存是运行时分配所有类实例和数组的一块内存区域.非堆内存包含方法区和JVM内部 ...

  9. 转:JAVA常见错误处理方法 和 JVM内存结构

    OutOfMemoryError在开发过程中是司空见惯的,遇到这个错误,新手程序员都知道从两个方面入手来解决:一是排查程序是否有BUG导致内存泄漏:二是调整JVM启动参数增大内存.OutOfMemor ...

  10. jvm系列二内存结构

    二.内存结构 整体架构 1.程序计数器 作用 用于保存JVM中下一条所要执行的指令的地址 特点 线程私有 CPU会为每个线程分配时间片,当当前线程的时间片使用完以后,CPU就会去执行另一个线程中的代码 ...

随机推荐

  1. Django框架之drf:7、认证组件,权限组件,频率组件,过滤的多种用法,排序,分页,

    Django框架之drf 一.认证组件 简介: ​ 登录认证的限制 ​ 认证组件是drf框架给我们提供的认证接口,它能够在请求进入视图函数/类前进验证(例如:认证用户是否登录),对不符合认证的请求进行 ...

  2. R数据分析:孟德尔随机化中介的原理和实操

    中介本身就是回归,基本上我看到的很多的调查性研究中在中介分析的方法部分都不会去提混杂,都是默认一个三角形画好,中介关系就算过去了,这里面默认的逻辑就是前两步回归中的混杂是一样的,计算中介效应的时候就自 ...

  3. C-05\函数的底层原理

    一.程序运行时内存四大区 wres(内存属性): w:write(可写) r:read(可读) e:execute(可执行) s:share(可共享) 任何操作系统(windows.liunx.安卓. ...

  4. CesiumJS PrimitiveAPI 高级着色入门 - 从参数化几何与 Fabric 材质到着色器 - 上篇

    目录 0. 基础 0.1. 坐标系基础 0.2. 合并批次 1. 参数化几何 1.1. 几何类清单 1.2. 举例 1.3. 纯手搓几何 1.4. *子线程异步生成几何 2. 使用材质 2.1. 外观 ...

  5. Vue37 常用的组件库

    1 移动端 vant ui:https://vant-ui.github.io/vant/#/zh-CN (https://vant-ui.github.io/vant/#/zh-CN) cube u ...

  6. python70 前端框架之vue js的集中循环方式、key值的解释、input事件、v-model双向数据绑定、过滤案例、事件修饰符、按键修饰符、表单控制

    js的几种循环方式 v-for可以循环的变量 可以循环的: 数组.数组带索引 对象.对象带key.value 字符串 字符串带索引 数字.数字带索引 <!DOCTYPE html> < ...

  7. 转载:屎人-->诗人系列--码农之歌

    转贴经常关注的一个博主的文,感觉还挺有趣: https://goofegg.github.io/content.html?id=141 ************************** 这个系列第 ...

  8. JZOJ 3232. 【佛山市选2013】排列

    题目 解析 很神奇的一道题 显然,对于一种排列,相当于给出了数字 \(1..n\) 的对应关系,且不重复不遗漏,刚好把 \(1\) 到 \(n\) 又包含了一遍. 对,连边! 每个数向它对应的数连边, ...

  9. Swiper第一页与最后一页禁止滑动

    resistanceRatio抵抗率.边缘抵抗力的大小比例.值越小抵抗越大越难将slide拖离边缘,0时完全无法拖离. mounted: function() { let _this = this; ...

  10. 使用Shapefile-js读取shp文件并使用WebGL绘制

    1. 引言 坐标数据是空间数据文件的核心,空间数据的数据量往往是很大的.数据可视化是GIS的一个核心应用,绘制海量的坐标数据始终是一个考验设备性能的难题,使用GPU进行绘制可有效减少CPU的负载,提升 ...