JVM(二) --- JVM的内存结构
写在文章前:本系列博客是学习黑马程序员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;
}
}
问题辨析:
- 如果方法内局部变量没有逃离方法的作用范围,则线程安全
- 如果局部变量引用了对象,并逃离了作用范围,则需要考虑线程安全
栈内存溢出问题:
java.lang.StackOverflowError
- 栈帧过多导致栈内存溢出(例如方法的递归调用而没有终结点),
-Xss256k 设置JVM栈内存参数
- 栈帧过大导致栈内存溢出(不常见)
线程运行时诊断:
- 用top定位哪个进程对cpu的占用过高
- ps H -eo pid,tid,%cpu | grep 进程id (用ps命令进一步定位是哪个线程引起的cpu占用过高)
- jstack 进程id 可以根据线程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的内存结构的更多相关文章
- JVM学习01:内存结构
JVM学习01:内存结构 写在前面:本系列分享主要参考资料是 周志明老师的<深入理解Java虚拟机>第二版. 内存结构知识要点Xmind梳理 案例分析 分析1 package com.h ...
- Oracle数据库基础入门《二》Oracle内存结构
Oracle数据库基础入门<二>Oracle内存结构 Oracle 的内存由系统全局区(System Global Area,简称 SGA)和程序全局区(Program Global Ar ...
- jvm比较详尽的内存结构
JVM内存结构 2012-09-17 15:27:59 分类: Java 本文转自:http://www.blogjava.net/nkjava/archive/2012/03/14/371831. ...
- JVM(一)内存结构
今日开篇 什么是JVM 定义 Java Virtual Machine,JAVA程序的运行环境(JAVA二进制字节码的运行环境) 好处 一次编写,到处运行 自动内存管理,垃圾回收机制 数组下标越界检查 ...
- JVM体系结构-----深入理解内存结构
一.概述 内存在计算机中占据着至关重要的地位,任何运行时的程序或者数据都需要依靠内存作为存储介质,否则程序将无法正常运行.与C和C++相比,使用Java语言编写的程序并不需要显示的为每一个对象编写对应 ...
- [二]Java虚拟机 jvm内存结构 运行时数据内存 class文件与jvm内存结构的映射 jvm数据类型 虚拟机栈 方法区 堆 含义
前言简介 class文件是源代码经过编译后的一种平台中立的格式 里面包含了虚拟机运行所需要的所有信息,相当于 JVM的机器语言 JVM全称是Java Virtual Machine ,既然是虚拟机, ...
- JAVA常见错误处理方法 和 JVM内存结构
OutOfMemoryError在开发过程中是司空见惯的,遇到这个错误,新手程序员都知道从两个方面入手来解决:一是排查程序是否有BUG导致内存泄漏:二是调整JVM启动参数增大内存.OutOfMemor ...
- Java中的OutOfMemoryError的各种情况及解决和JVM内存结构
在JVM中内存一共有3种:Heap(堆内存),Non-Heap(非堆内存) [3]和Native(本地内存). [1] 堆内存是运行时分配所有类实例和数组的一块内存区域.非堆内存包含方法区和JVM内部 ...
- 转:JAVA常见错误处理方法 和 JVM内存结构
OutOfMemoryError在开发过程中是司空见惯的,遇到这个错误,新手程序员都知道从两个方面入手来解决:一是排查程序是否有BUG导致内存泄漏:二是调整JVM启动参数增大内存.OutOfMemor ...
- jvm系列二内存结构
二.内存结构 整体架构 1.程序计数器 作用 用于保存JVM中下一条所要执行的指令的地址 特点 线程私有 CPU会为每个线程分配时间片,当当前线程的时间片使用完以后,CPU就会去执行另一个线程中的代码 ...
随机推荐
- MySQL 删除数据 批量删除(大量)数据
在删除数据的时候根据不同的场景使用不同的方法,比如说删除表中部分数据.删除表的结构.删除所有记录并重置自增ID.批量删除大量数据等,可以使用delete.truncate.drop等语句. 一.方法分 ...
- 【学习笔记】XR872 Audio 驱动框架分析
Xradio Sdk 的 Audio 驱动框架和 Linux 的 ASOC 驱动框架非常相似,只不过简化了很多. 驱动和芯片之间的关系图 下面的 SOC 表示的是 XR872 芯片,这里以 AC107 ...
- python3异常打印堆栈信息
import traceback try: a=1/0 except: print(traceback.format_exc())
- python正则查找
a = "#1+#5+#8+#10+#11+#12+#13+#14+#15-#22-#23-#24-#25-#26-#27-#28" b = re.findall("#\ ...
- CC1链详解
前言:这篇文章是对CC1的总结,个人学习,如有不对请多指教.谢谢! 环境:jdk8u71以下,因为在该jdk版本以上这个漏洞已经被修复了 下载链接:https://www.oracle.com/cn/ ...
- FreeBSD 安装 fcitx5的配置
link: Chinese Pinyin Package for typing Chinese sudo pkg install -y zh-CJKUnifonts sudo pkg install ...
- JAVA虚拟机06-垃圾回收及引用类型
Java和C++之间有一堵由内存自动分配和垃圾收集技术围成的高墙 1.了解垃圾收集.内存自动分配的意义 2.JAVA虚拟机各个区域的垃圾回收简介 3.判断对象是否存活 3.1引用计数算法 3.2可达性 ...
- 举例说明postman接口测试
接口测试的本质就是接口的数据和数据库里的数据作对比 接口测试,可以理解为测的是后端的程序,而系统测试的时候,测试的是前端的程序,前端只有在满足条件的时候才会调到接口,所以接口测试可以测得更全面更准确 ...
- 1 .NET Core笔试题
1.说说显示实现接口和隐式实现接口的区别. 2.说说file访问修饰的作用. 3.说说什么是原始字符串. 4.C#10 中struct有什么改进? 5.说说C#10中Lambda表达式的新特点. 6. ...
- 为Jekyll静态网站添加PlantUML插件
前言 突然想起来要好好整理一下自己的博客空间,已经荒废很多年,如果再不捡起来,等到自己知识老化的时候再去写东西就没人看了. 使用Github Pages + Jekyll把博客发布为静态网站,给人感觉 ...