文章目录
前言
========================================================================================================
我不能保证写的每个地方都是对的,但是至少保证每一句话、每一行代码都经过了认真的推敲、仔细的斟酌。每一篇文章的背后,希望都能看到自己对于技术、对于生活的态度。
我相信乔布斯说的,只有那些疯狂到认为自己可以改变世界的人才能真正地改变世界。面对压力,我可以挑灯夜战、不眠不休;面对困难,我愿意迎难而上、永不退缩!
其实我想说的是,我只是一个程序员,这就是我现在纯粹人生的全部。
=========================================================================================================
这篇文章主要用来梳理LXF_JVM专题的学习脉络。
深入理解JVM虚拟机开篇:JVM介绍与知识脉络梳理
博主参考了这篇文章:学习Java虚拟机没用? 听听当事人是怎么说的!
深入理解JVM虚拟机1:JVM内存模型 与 永久代的消失
深入理解JVM虚拟机2:JVM垃圾回收基本原理和算法
博主参考了以下四篇文章:
- Java关键术语
- Java HotSpot 虚拟机
- JVM体系结构
- Java堆内存
- 启动Java垃圾回收
- Java垃圾回收过程
- 垃圾回收中实例的终结
- 对象什么时候符合垃圾回收的条件?
- GC Scope 示例程序
- GC OutOfMemoryError 的示例程序
- Java有四种类型的垃圾回收器(Serial Garbage Collector, GC):
- 串行垃圾回收器(Serial GC)
- 并行垃圾回收器(Parallel GC)
- 并发标记扫描垃圾回收器(CMS GC)
- G1垃圾回收器(G1 GC)
- 垃圾回收的JVM配置
- 运行的垃圾回收器类型
- GC的优化配置
- 使用JVM GC参数的例子
- Java GC监视和分析工具
- Java VisualVM
- 启动VisualVM
- 安装可视化GC插件
- 监视GC
1. 各种GC的触发时机(When)
- GC类型
- 触发时机(条件)
2. JVM垃圾判定算法
- 引用计数算法(Reference Counting)
- 可达性分析算法(根搜索算法)
3. 四种引用
- 强引用
- 软引用
- 弱引用
- 虚引用
4. JVM垃圾回收算法
- 标记—清除算法(Mark-Sweep)
- 复制算法(Copying)
- 标记—整理算法(Mark-Compact)
- 分代收集算法(Generational Collection)
import java.text.SimpleDateFormat;
import java.util.Date;
import org.apache.log4j.Logger;
public class HelloWorld {
private static Logger LOGGER = Logger.getLogger(HelloWorld.class.getName());
public void sayHello(String message) {
SimpleDateFormat formatter = new SimpleDateFormat("dd.MM.YYYY");
String today = formatter.format(new Date());
LOGGER.info(today + ": " + message);
}
}
深入理解JVM虚拟机3:垃圾回收器详解
转自 – Javadoop主页
Javadoop主页:在这里,一位优秀的Java程序员写了很多高质量的文章
这篇文章主要是翻译《Memory Management in the Java HotSpot Virtual Machine》白皮书的前四章内容。
一. 垃圾收集概念
- 垃圾收集器的理想特征
- 设计上的权衡
- 什么是stop the world?
- 性能指标
- 分代收集介绍
二. J2SE 5.0 HotSpot JVM中的垃圾收集器
- HotSpot 分代
- 垃圾回收类型
- 快速分配
- 串行收集器
- 并行收集器
- 并行压缩收集器
- CMS收集器
三. G1 垃圾收集器介绍
1. G1 总览
2. G1 工作流程
年轻代收集
Old GC / 并发标记周期
Full GC
1.concurrent mode failure
2.晋升失败
3.疏散失败
4.大对象分配失败
3. G1 参数配置和最佳实践
- 参数介绍
- Java GC监视和分析工具
深入理解JVM虚拟机4:Java class介绍与解析实践
《自己动手写Java虚拟机》,作者利用go语言实现了一个简单的JVM,虽然没有完整实现JVM的所有功能,但是对于一些对JVM稍感兴趣的人来说,可读性还是很高的。作者讲解的很详细,每个过程都分为了一章,其中一部分就是讲解如何解析Class文件。
这本书不太厚,很快就读完了,读完后,收获颇丰。但是纸上得来终觉浅,绝知此事要躬行,我便尝试着自己解析Class文件。go语言虽然很优秀,但是终究不熟练,尤其是不太习惯其把类型放在变量之后的语法,还是老老实实用java吧。
项目地址:https://github.com/HalfStackDeveloper/ClassReader
一. Class文件
1. 什么是Class文件?
2. 基本结构
在原文中这一段看的不清楚,拷贝过来。
是不是一脸懵逼,不过java虚拟机规范中给出了class文件的基本格式,只要按照这个格式去解析就可以了:
ClassFile {
u4 magic;
u2 minor_version;
u2 major_version;
u2 constant_pool_count;
cp_info constant_pool[constant_pool_count-1];
u2 access_flags;
u2 this_class;
u2 super_class;
u2 interfaces_count;
u2 interfaces[interfaces_count];
u2 fields_count;
field_info fields[fields_count];
u2 methods_count;
method_info methods[methods_count];
u2 attributes_count;
attribute_info attributes[attributes_count];
}
3. class 文件的结构
4. 解析
字段类型
原文中代码段不清晰:
public class U1 {
public static short read(InputStream inputStream) {
byte[] bytes = new byte[1];
try {
inputStream.read(bytes);
} catch (IOException e) {
e.printStackTrace();
}
short value = (short) (bytes[0] & 0xFF);
return value;
}
}
public class U2 {
public static int read(InputStream inputStream) {
byte[] bytes = new byte[2];
try {
inputStream.read(bytes);
} catch (IOException e) {
e.printStackTrace();
}
int num = 0;
for (int i= 0; i < bytes.length; i++) {
num <<= 8;
num |= (bytes[i] & 0xff);
}
return num;
}
}
public class U4 {
public static long read(InputStream inputStream) {
byte[] bytes = new byte[4];
try {
inputStream.read(bytes);
} catch (IOException e) {
e.printStackTrace();
}
long num = 0;
for (int i= 0; i < bytes.length; i++) {
num <<= 8;
num |= (bytes[i] & 0xff);
}
return num;
}
}
常量池
首先是读取魔数之类的基本信息:
FileInputStream inputStream = new FileInputStream(file);
ClassFile classFile = new ClassFile();
classFile.magic = U4.read(inputStream);
classFile.minorVersion = U2.read(inputStream);
classFile.majorVersion = U2.read(inputStream);
java虚拟机规范给出了常量池中每一项的格式:
cp_info {
u1 tag;
u1 info[];
}
读取常量池的大小,初始化常量池:
// 解析常量池
int constant_pool_count = U2.read(inputStream);
ConstantPool constantPool = new ConstantPool(constant_pool_count);
constantPool.read(inputStream);
接下来再逐个读取每项内容,并存储到数组cpInfo中,这里需要注意的是,cpInfo[]下标从1开始,0无效,且真正的常量池大小为constant_pool_count-1。
public class ConstantPool {
public int constant_pool_count;
public ConstantInfo[] cpInfo;
public ConstantPool(int count) {
constant_pool_count = count;
cpInfo = new ConstantInfo[constant_pool_count];
}
public void read(InputStream inputStream) {
for (int i = 1; i < constant_pool_count; i++) {
short tag = U1.read(inputStream);
ConstantInfo constantInfo = ConstantInfo.getConstantInfo(tag);
constantInfo.read(inputStream);
cpInfo[i] = constantInfo;
if (tag == ConstantInfo.CONSTANT_Double || tag == ConstantInfo.CONSTANT_Long) {
i++;
}
}
}
}
先来看看CONSTANT_Utf8格式,这一项里面存放的是UTF-8编码的字符串:
CONSTANT_Utf8_info {
u1 tag;
u2 length;
u1 bytes[length];
}
那么如何读取这一项呢?
public class ConstantUtf8 extends ConstantInfo {
public String value;
@Override
public void read(InputStream inputStream) {
int length = U2.read(inputStream);
byte[] bytes = new byte[length];
try {
inputStream.read(bytes);
} catch (IOException e) {
e.printStackTrace();
}
try {
value = readUtf8(bytes);
} catch (UTFDataFormatException e) {
e.printStackTrace();
}
}
private String readUtf8(byte[] bytearr) throws UTFDataFormatException {
//copy from java.io.DataInputStream.readUTF()
}
}
很简单,首先读取这一项的字节数组长度,接着调用readUtf8(),将字节数组转化为String字符串。
再来看看CONSTANT_Class这一项,这一项存储的是类或者接口的符号引用:
CONSTANT_Class_info {
u1 tag;
u2 name_index;
}
注意这里的name_index并不是直接的字符串,而是指向常量池中cpInfo数组的name_index项,且cpInfo[name_index]一定是CONSTANT_Utf8格式。
public class ConstantClass extends ConstantInfo {
public int nameIndex;
@Override
public void read(InputStream inputStream) {
nameIndex = U2.read(inputStream);
}
}
常量池解析完毕后,就可以供后面的数据使用了,比方说ClassFile中的this_class指向的就是常量池中格式为CONSTANT_Class的某一项,那么我们就可以读取出类名:
int classIndex = U2.read(inputStream);
ConstantClass clazz = (ConstantClass) constantPool.cpInfo[classIndex];
ConstantUtf8 className = (ConstantUtf8) constantPool.cpInfo[clazz.nameIndex];
classFile.className = className.value;
System.out.print("classname:" + classFile.className + "\n");
字节码指令
ClassFile中的method_info,其格式如下:
method_info {
u2 access_flags; // 访问标志
u2 name_index; // 方法名索引
u2 descriptor_index; // 方法描述符索引
u2 attributes_count;
attribute_info attributes[attributes_count]; // 属性数组
}
属性的通用格式为:
attribute_info {
u2 attribute_name_index;
u4 attribute_length;
u1 info[attribute_length];
}
Code属性的具体格式为:
Code_attribute {
u2 attribute_name_index; u4 attribute_length;
u2 max_stack;
u2 max_locals;
u4 code_length;
u1 code[code_length];
u2 exception_table_length;
{
u2 start_pc;
u2 end_pc;
u2 handler_pc;
u2 catch_type;
} exception_table[exception_table_length];
u2 attributes_count;
attribute_info attributes[attributes_count];
}
接下来我们就可以解析字节码了:
for (int j = 0; j < methodInfo.attributesCount; j++) {
if (methodInfo.attributes[j] instanceof CodeAttribute) {
CodeAttribute codeAttribute = (CodeAttribute) methodInfo.attributes[j];
for (int m = 0; m < codeAttribute.codeLength; m++) {
short code = codeAttribute.code[m];
System.out.print(InstructionTable.getInstruction(code) + "\n");
}
}
}