learn.lianglianglee.com/文章/Java魔法类:Unsafe应用解析.md.html
2022-05-11 19:04:14 +08:00

966 lines
48 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
<link rel="icon" href="/static/favicon.png">
<title>Java魔法类Unsafe应用解析.md.html</title>
<!-- Spectre.css framework -->
<link rel="stylesheet" href="/static/index.css">
<!-- theme css & js -->
<meta name="generator" content="Hexo 4.2.0">
</head>
<body>
<div class="book-container">
<div class="book-sidebar">
<div class="book-brand">
<a href="/">
<img src="/static/favicon.png">
<span>技术文章摘抄</span>
</a>
</div>
<div class="book-menu uncollapsible">
<ul class="uncollapsible">
<li><a href="/" class="current-tab">首页</a></li>
</ul>
<ul class="uncollapsible">
<li><a href="../">上一级</a></li>
</ul>
<ul class="uncollapsible">
<li>
<a href="/文章/AQS 万字图文全面解析.md.html">AQS 万字图文全面解析.md.html</a>
</li>
<li>
<a href="/文章/Docker 镜像构建原理及源码分析.md.html">Docker 镜像构建原理及源码分析.md.html</a>
</li>
<li>
<a href="/文章/ElasticSearch 小白从入门到精通.md.html">ElasticSearch 小白从入门到精通.md.html</a>
</li>
<li>
<a href="/文章/JVM CPU Profiler技术原理及源码深度解析.md.html">JVM CPU Profiler技术原理及源码深度解析.md.html</a>
</li>
<li>
<a href="/文章/JVM 垃圾收集器.md.html">JVM 垃圾收集器.md.html</a>
</li>
<li>
<a href="/文章/JVM 面试的 30 个知识点.md.html">JVM 面试的 30 个知识点.md.html</a>
</li>
<li>
<a href="/文章/Java IO 体系、线程模型大总结.md.html">Java IO 体系、线程模型大总结.md.html</a>
</li>
<li>
<a href="/文章/Java NIO浅析.md.html">Java NIO浅析.md.html</a>
</li>
<li>
<a href="/文章/Java 面试题集锦(网络篇).md.html">Java 面试题集锦(网络篇).md.html</a>
</li>
<li>
<a href="/文章/Java-直接内存 DirectMemory 详解.md.html">Java-直接内存 DirectMemory 详解.md.html</a>
</li>
<li>
<a href="/文章/Java中9种常见的CMS GC问题分析与解决.md.html">Java中9种常见的CMS GC问题分析与解决.md.html</a>
</li>
<li>
<a href="/文章/Java中9种常见的CMS GC问题分析与解决.md.html">Java中9种常见的CMS GC问题分析与解决.md.html</a>
</li>
<li>
<a href="/文章/Java中的SPI.md.html">Java中的SPI.md.html</a>
</li>
<li>
<a href="/文章/Java中的ThreadLocal.md.html">Java中的ThreadLocal.md.html</a>
</li>
<li>
<a href="/文章/Java线程池实现原理及其在美团业务中的实践.md.html">Java线程池实现原理及其在美团业务中的实践.md.html</a>
</li>
<li>
<a class="current-tab" href="/文章/Java魔法类Unsafe应用解析.md.html">Java魔法类Unsafe应用解析.md.html</a>
</li>
<li>
<a href="/文章/Kafka 源码阅读笔记.md.html">Kafka 源码阅读笔记.md.html</a>
</li>
<li>
<a href="/文章/Kafka、ActiveMQ、RabbitMQ、RocketMQ 区别以及高可用原理.md.html">Kafka、ActiveMQ、RabbitMQ、RocketMQ 区别以及高可用原理.md.html</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB Buffer Pool.md.html">MySQL · 引擎特性 · InnoDB Buffer Pool.md.html</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB IO子系统.md.html">MySQL · 引擎特性 · InnoDB IO子系统.md.html</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB 事务系统.md.html">MySQL · 引擎特性 · InnoDB 事务系统.md.html</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB 同步机制.md.html">MySQL · 引擎特性 · InnoDB 同步机制.md.html</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB 数据页解析.md.html">MySQL · 引擎特性 · InnoDB 数据页解析.md.html</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB崩溃恢复.md.html">MySQL · 引擎特性 · InnoDB崩溃恢复.md.html</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · 临时表那些事儿.md.html">MySQL · 引擎特性 · 临时表那些事儿.md.html</a>
</li>
<li>
<a href="/文章/MySQL 主从复制 半同步复制.md.html">MySQL 主从复制 半同步复制.md.html</a>
</li>
<li>
<a href="/文章/MySQL 主从复制 基于GTID复制.md.html">MySQL 主从复制 基于GTID复制.md.html</a>
</li>
<li>
<a href="/文章/MySQL 主从复制.md.html">MySQL 主从复制.md.html</a>
</li>
<li>
<a href="/文章/MySQL 事务日志(redo log和undo log).md.html">MySQL 事务日志(redo log和undo log).md.html</a>
</li>
<li>
<a href="/文章/MySQL 亿级别数据迁移实战代码分享.md.html">MySQL 亿级别数据迁移实战代码分享.md.html</a>
</li>
<li>
<a href="/文章/MySQL 从一条数据说起-InnoDB行存储数据结构.md.html">MySQL 从一条数据说起-InnoDB行存储数据结构.md.html</a>
</li>
<li>
<a href="/文章/MySQL 地基基础:事务和锁的面纱.md.html">MySQL 地基基础:事务和锁的面纱.md.html</a>
</li>
<li>
<a href="/文章/MySQL 地基基础:数据字典.md.html">MySQL 地基基础:数据字典.md.html</a>
</li>
<li>
<a href="/文章/MySQL 地基基础:数据库字符集.md.html">MySQL 地基基础:数据库字符集.md.html</a>
</li>
<li>
<a href="/文章/MySQL 性能优化:碎片整理.md.html">MySQL 性能优化:碎片整理.md.html</a>
</li>
<li>
<a href="/文章/MySQL 故障诊断:一个 ALTER TALBE 执行了很久,你慌不慌?.md.html">MySQL 故障诊断:一个 ALTER TALBE 执行了很久,你慌不慌?.md.html</a>
</li>
<li>
<a href="/文章/MySQL 故障诊断:如何在日志中轻松定位大事务.md.html">MySQL 故障诊断:如何在日志中轻松定位大事务.md.html</a>
</li>
<li>
<a href="/文章/MySQL 故障诊断:教你快速定位加锁的 SQL.md.html">MySQL 故障诊断:教你快速定位加锁的 SQL.md.html</a>
</li>
<li>
<a href="/文章/MySQL 日志详解.md.html">MySQL 日志详解.md.html</a>
</li>
<li>
<a href="/文章/MySQL 的半同步是什么?.md.html">MySQL 的半同步是什么?.md.html</a>
</li>
<li>
<a href="/文章/MySQL中的事务和MVCC.md.html">MySQL中的事务和MVCC.md.html</a>
</li>
<li>
<a href="/文章/MySQL事务_事务隔离级别详解.md.html">MySQL事务_事务隔离级别详解.md.html</a>
</li>
<li>
<a href="/文章/MySQL优化优化 select count().md.html">MySQL优化优化 select count().md.html</a>
</li>
<li>
<a href="/文章/MySQL共享锁、排他锁、悲观锁、乐观锁.md.html">MySQL共享锁、排他锁、悲观锁、乐观锁.md.html</a>
</li>
<li>
<a href="/文章/MySQL的MVCC多版本并发控制.md.html">MySQL的MVCC多版本并发控制.md.html</a>
</li>
<li>
<a href="/文章/QingStor 对象存储架构设计及最佳实践.md.html">QingStor 对象存储架构设计及最佳实践.md.html</a>
</li>
<li>
<a href="/文章/RocketMQ 面试题集锦.md.html">RocketMQ 面试题集锦.md.html</a>
</li>
<li>
<a href="/文章/SnowFlake 雪花算法生成分布式 ID.md.html">SnowFlake 雪花算法生成分布式 ID.md.html</a>
</li>
<li>
<a href="/文章/Spring Boot 2.x 结合 k8s 实现分布式微服务架构.md.html">Spring Boot 2.x 结合 k8s 实现分布式微服务架构.md.html</a>
</li>
<li>
<a href="/文章/Spring Boot 教程:如何开发一个 starter.md.html">Spring Boot 教程:如何开发一个 starter.md.html</a>
</li>
<li>
<a href="/文章/Spring MVC 原理.md.html">Spring MVC 原理.md.html</a>
</li>
<li>
<a href="/文章/Spring MyBatis和Spring整合的奥秘.md.html">Spring MyBatis和Spring整合的奥秘.md.html</a>
</li>
<li>
<a href="/文章/Spring 帮助你更好的理解Spring循环依赖.md.html">Spring 帮助你更好的理解Spring循环依赖.md.html</a>
</li>
<li>
<a href="/文章/Spring 循环依赖及解决方式.md.html">Spring 循环依赖及解决方式.md.html</a>
</li>
<li>
<a href="/文章/Spring中眼花缭乱的BeanDefinition.md.html">Spring中眼花缭乱的BeanDefinition.md.html</a>
</li>
<li>
<a href="/文章/Vert.x 基础入门.md.html">Vert.x 基础入门.md.html</a>
</li>
<li>
<a href="/文章/eBay 的 Elasticsearch 性能调优实践.md.html">eBay 的 Elasticsearch 性能调优实践.md.html</a>
</li>
<li>
<a href="/文章/不可不说的Java“锁”事.md.html">不可不说的Java“锁”事.md.html</a>
</li>
<li>
<a href="/文章/互联网并发限流实战.md.html">互联网并发限流实战.md.html</a>
</li>
<li>
<a href="/文章/从ReentrantLock的实现看AQS的原理及应用.md.html">从ReentrantLock的实现看AQS的原理及应用.md.html</a>
</li>
<li>
<a href="/文章/从SpringCloud开始聊微服务架构.md.html">从SpringCloud开始聊微服务架构.md.html</a>
</li>
<li>
<a href="/文章/全面了解 JDK 线程池实现原理.md.html">全面了解 JDK 线程池实现原理.md.html</a>
</li>
<li>
<a href="/文章/分布式一致性理论与算法.md.html">分布式一致性理论与算法.md.html</a>
</li>
<li>
<a href="/文章/分布式一致性算法 Raft.md.html">分布式一致性算法 Raft.md.html</a>
</li>
<li>
<a href="/文章/分布式唯一 ID 解析.md.html">分布式唯一 ID 解析.md.html</a>
</li>
<li>
<a href="/文章/分布式链路追踪:集群管理设计.md.html">分布式链路追踪:集群管理设计.md.html</a>
</li>
<li>
<a href="/文章/动态代理种类及原理,你知道多少?.md.html">动态代理种类及原理,你知道多少?.md.html</a>
</li>
<li>
<a href="/文章/响应式架构与 RxJava 在有赞零售的实践.md.html">响应式架构与 RxJava 在有赞零售的实践.md.html</a>
</li>
<li>
<a href="/文章/大数据算法——布隆过滤器.md.html">大数据算法——布隆过滤器.md.html</a>
</li>
<li>
<a href="/文章/如何优雅地记录操作日志?.md.html">如何优雅地记录操作日志?.md.html</a>
</li>
<li>
<a href="/文章/如何设计一个亿级消息量的 IM 系统.md.html">如何设计一个亿级消息量的 IM 系统.md.html</a>
</li>
<li>
<a href="/文章/异步网络模型.md.html">异步网络模型.md.html</a>
</li>
<li>
<a href="/文章/当我们在讨论CQRS时我们在讨论些神马.md.html">当我们在讨论CQRS时我们在讨论些神马.md.html</a>
</li>
<li>
<a href="/文章/彻底理解 MySQL 的索引机制.md.html">彻底理解 MySQL 的索引机制.md.html</a>
</li>
<li>
<a href="/文章/最全的 116 道 Redis 面试题解答.md.html">最全的 116 道 Redis 面试题解答.md.html</a>
</li>
<li>
<a href="/文章/有赞权限系统(SAM).md.html">有赞权限系统(SAM).md.html</a>
</li>
<li>
<a href="/文章/有赞零售中台建设方法的探索与实践.md.html">有赞零售中台建设方法的探索与实践.md.html</a>
</li>
<li>
<a href="/文章/服务注册与发现原理剖析Eureka、Zookeeper、Nacos.md.html">服务注册与发现原理剖析Eureka、Zookeeper、Nacos.md.html</a>
</li>
<li>
<a href="/文章/深入浅出Cache.md.html">深入浅出Cache.md.html</a>
</li>
<li>
<a href="/文章/深入理解 MySQL 底层实现.md.html">深入理解 MySQL 底层实现.md.html</a>
</li>
<li>
<a href="/文章/漫画讲解 git rebase VS git merge.md.html">漫画讲解 git rebase VS git merge.md.html</a>
</li>
<li>
<a href="/文章/生成浏览器唯一稳定 ID 的探索.md.html">生成浏览器唯一稳定 ID 的探索.md.html</a>
</li>
<li>
<a href="/文章/缓存 如何保证缓存与数据库的双写一致性?.md.html">缓存 如何保证缓存与数据库的双写一致性?.md.html</a>
</li>
<li>
<a href="/文章/网易严选怎么做全链路监控的?.md.html">网易严选怎么做全链路监控的?.md.html</a>
</li>
<li>
<a href="/文章/美团万亿级 KV 存储架构与实践.md.html">美团万亿级 KV 存储架构与实践.md.html</a>
</li>
<li>
<a href="/文章/美团点评Kubernetes集群管理实践.md.html">美团点评Kubernetes集群管理实践.md.html</a>
</li>
<li>
<a href="/文章/美团百亿规模API网关服务Shepherd的设计与实现.md.html">美团百亿规模API网关服务Shepherd的设计与实现.md.html</a>
</li>
<li>
<a href="/文章/解读《阿里巴巴 Java 开发手册》背后的思考.md.html">解读《阿里巴巴 Java 开发手册》背后的思考.md.html</a>
</li>
<li>
<a href="/文章/认识 MySQL 和 Redis 的数据一致性问题.md.html">认识 MySQL 和 Redis 的数据一致性问题.md.html</a>
</li>
<li>
<a href="/文章/进阶Dockerfile 高阶使用指南及镜像优化.md.html">进阶Dockerfile 高阶使用指南及镜像优化.md.html</a>
</li>
<li>
<a href="/文章/铁总在用的高性能分布式缓存计算框架 Geode.md.html">铁总在用的高性能分布式缓存计算框架 Geode.md.html</a>
</li>
<li>
<a href="/文章/阿里云PolarDB及其共享存储PolarFS技术实现分析.md.html">阿里云PolarDB及其共享存储PolarFS技术实现分析.md.html</a>
</li>
<li>
<a href="/文章/阿里云PolarDB及其共享存储PolarFS技术实现分析.md.html">阿里云PolarDB及其共享存储PolarFS技术实现分析.md.html</a>
</li>
<li>
<a href="/文章/面试最常被问的 Java 后端题.md.html">面试最常被问的 Java 后端题.md.html</a>
</li>
<li>
<a href="/文章/领域驱动设计在互联网业务开发中的实践.md.html">领域驱动设计在互联网业务开发中的实践.md.html</a>
</li>
<li>
<a href="/文章/领域驱动设计的菱形对称架构.md.html">领域驱动设计的菱形对称架构.md.html</a>
</li>
<li>
<a href="/文章/高效构建 Docker 镜像的最佳实践.md.html">高效构建 Docker 镜像的最佳实践.md.html</a>
</li>
</ul>
</div>
</div>
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
<div class="sidebar-toggle-inner"></div>
</div>
<script>
function add_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.add('show')
}
function remove_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.remove('show')
}
function sidebar_toggle() {
let sidebar_toggle = document.querySelector('.sidebar-toggle')
let sidebar = document.querySelector('.book-sidebar')
let content = document.querySelector('.off-canvas-content')
if (sidebar_toggle.classList.contains('extend')) { // show
sidebar_toggle.classList.remove('extend')
sidebar.classList.remove('hide')
content.classList.remove('extend')
} else { // hide
sidebar_toggle.classList.add('extend')
sidebar.classList.add('hide')
content.classList.add('extend')
}
}
function open_sidebar() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.add('show')
overlay.classList.add('show')
}
function hide_canvas() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.remove('show')
overlay.classList.remove('show')
}
</script>
<div class="off-canvas-content">
<div class="columns">
<div class="column col-12 col-lg-12">
<div class="book-navbar">
<!-- For Responsive Layout -->
<header class="navbar">
<section class="navbar-section">
<a onclick="open_sidebar()">
<i class="icon icon-menu"></i>
</a>
</section>
</header>
</div>
<div class="book-content" style="max-width: 960px; margin: 0 auto;
overflow-x: auto;
overflow-y: hidden;">
<div class="book-post">
<p id="tip" align="center"></p>
<div><h1>Java魔法类Unsafe应用解析</h1>
<h2>前言</h2>
<p>Unsafe是位于sun.misc包下的一个类主要提供一些用于执行低级别、不安全操作的方法如直接访问系统内存资源、自主管理内存资源等这些方法在提升Java运行效率、增强Java语言底层资源操作能力方面起到了很大的作用。但由于Unsafe类使Java语言拥有了类似C语言指针一样操作内存空间的能力这无疑也增加了程序发生相关指针问题的风险。在程序中过度、不正确使用Unsafe类会使得程序出错的概率变大使得Java这种安全的语言变得不再“安全”因此对Unsafe的使用一定要慎重。</p>
<p>本文对sun.misc.Unsafe公共API功能及相关应用场景进行介绍。</p>
<h2>基本介绍</h2>
<p>如下Unsafe源码所示Unsafe类为一单例实现提供静态方法getUnsafe获取Unsafe实例当且仅当调用getUnsafe方法的类为引导类加载器所加载时才合法否则抛出SecurityException异常。</p>
<pre><code class="language-java">public final class Unsafe {
// 单例对象
private static final Unsafe theUnsafe;
private Unsafe() {
}
@CallerSensitive
public static Unsafe getUnsafe() {
Class var0 = Reflection.getCallerClass();
// 仅在引导类加载器`BootstrapClassLoader`加载时才合法
if(!VM.isSystemDomainLoader(var0.getClassLoader())) {
throw new SecurityException(&quot;Unsafe&quot;);
} else {
return theUnsafe;
}
}
}
</code></pre>
<p>那如若想使用这个类,该如何获取其实例?有如下两个可行方案。</p>
<p>其一,从<code>getUnsafe</code>方法的使用限制条件出发通过Java命令行命令<code>-Xbootclasspath/a</code>把调用Unsafe相关方法的类A所在jar包路径追加到默认的bootstrap路径中使得A被引导类加载器加载从而通过<code>Unsafe.getUnsafe</code>方法安全的获取Unsafe实例。</p>
<pre><code class="language-bash">java -Xbootclasspath/a: ${path} // 其中path为调用Unsafe相关方法的类所在jar包路径
</code></pre>
<p>其二通过反射获取单例对象theUnsafe。</p>
<pre><code class="language-java">private static Unsafe reflectGetUnsafe() {
try {
Field field = Unsafe.class.getDeclaredField(&quot;theUnsafe&quot;);
field.setAccessible(true);
return (Unsafe) field.get(null);
} catch (Exception e) {
log.error(e.getMessage(), e);
return null;
}
}
</code></pre>
<h2>功能介绍</h2>
<p><img src="assets/f182555953e29cec76497ebaec526fd1297846.png" alt="img" /></p>
<p>如上图所示Unsafe提供的API大致可分为内存操作、CAS、Class相关、对象操作、线程调度、系统信息获取、内存屏障、数组操作等几类下面将对其相关方法和应用场景进行详细介绍。</p>
<h3>内存操作</h3>
<p>这部分主要包含堆外内存的分配、拷贝、释放、给定地址值操作等方法。</p>
<pre><code class="language-java">//分配内存, 相当于C++的malloc函数
public native long allocateMemory(long bytes);
//扩充内存
public native long reallocateMemory(long address, long bytes);
//释放内存
public native void freeMemory(long address);
//在给定的内存块中设置值
public native void setMemory(Object o, long offset, long bytes, byte value);
//内存拷贝
public native void copyMemory(Object srcBase, long srcOffset, Object destBase, long destOffset, long bytes);
//获取给定地址值,忽略修饰限定符的访问限制。与此类似操作还有: getIntgetDoublegetLonggetChar等
public native Object getObject(Object o, long offset);
//为给定地址设置值,忽略修饰限定符的访问限制,与此类似操作还有: putInt,putDoubleputLongputChar等
public native void putObject(Object o, long offset, Object x);
//获取给定地址的byte类型的值当且仅当该内存地址为allocateMemory分配时此方法结果为确定的
public native byte getByte(long address);
//为给定地址设置byte类型的值当且仅当该内存地址为allocateMemory分配时此方法结果才是确定的
public native void putByte(long address, byte x);
</code></pre>
<p>通常我们在Java中创建的对象都处于堆内内存heap堆内内存是由JVM所管控的Java进程内存并且它们遵循JVM的内存管理机制JVM会采用垃圾回收机制统一管理堆内存。与之相对的是堆外内存存在于JVM管控之外的内存区域Java中对堆外内存的操作依赖于Unsafe提供的操作堆外内存的native方法。</p>
<h4>使用堆外内存的原因</h4>
<ul>
<li>对垃圾回收停顿的改善。由于堆外内存是直接受操作系统管理而不是JVM所以当我们使用堆外内存时即可保持较小的堆内内存规模。从而在GC时减少回收停顿对于应用的影响。</li>
<li>提升程序I/O操作的性能。通常在I/O通信过程中会存在堆内内存到堆外内存的数据拷贝操作对于需要频繁进行内存间数据拷贝且生命周期较短的暂存数据都建议存储到堆外内存。</li>
</ul>
<h4>典型应用</h4>
<p>DirectByteBuffer是Java用于实现堆外内存的一个重要类通常用在通信过程中做缓冲池如在Netty、MINA等NIO框架中应用广泛。DirectByteBuffer对于堆外内存的创建、使用、销毁等逻辑均由Unsafe提供的堆外内存API来实现。</p>
<p>下图为DirectByteBuffer构造函数创建DirectByteBuffer的时候通过Unsafe.allocateMemory分配内存、Unsafe.setMemory进行内存初始化而后构建Cleaner对象用于跟踪DirectByteBuffer对象的垃圾回收以实现当DirectByteBuffer被垃圾回收时分配的堆外内存一起被释放。</p>
<p><img src="assets/5eb082d2e4baf2d993ce75747fc35de6486751.png" alt="img" /></p>
<p>那么如何通过构建垃圾回收追踪对象Cleaner实现堆外内存释放呢</p>
<p>Cleaner继承自Java四大引用类型之一的虚引用PhantomReference众所周知无法通过虚引用获取与之关联的对象实例且当对象仅被虚引用引用时在任何发生GC的时候其均可被回收通常PhantomReference与引用队列ReferenceQueue结合使用可以实现虚引用关联对象被垃圾回收时能够进行系统通知、资源清理等功能。如下图所示当某个被Cleaner引用的对象将被回收时JVM垃圾收集器会将此对象的引用放入到对象引用中的pending链表中等待Reference-Handler进行相关处理。其中Reference-Handler为一个拥有最高优先级的守护线程会循环不断的处理pending链表中的对象引用执行Cleaner的clean方法进行相关清理工作。</p>
<p><img src="assets/9efac865a875c32cf570489332be5d0f131298.png" alt="img" /></p>
<p>所以当DirectByteBuffer仅被Cleaner引用即为虚引用其可以在任意GC时段被回收。当DirectByteBuffer实例对象被回收时在Reference-Handler线程操作中会调用Cleaner的clean方法根据创建Cleaner时传入的Deallocator来进行堆外内存的释放。</p>
<p><img src="assets/66e616c6db18202578c561649facac8d387390.png" alt="img" /></p>
<h3>CAS相关</h3>
<p>如下源代码释义所示这部分主要为CAS相关操作的方法。</p>
<pre><code class="language-java">/**
* CAS
* @param o 包含要修改field的对象
* @param offset 对象中某field的偏移量
* @param expected 期望值
* @param update 更新值
* @return true | false
*/
public final native boolean compareAndSwapObject(Object o, long offset, Object expected, Object update);
public final native boolean compareAndSwapInt(Object o, long offset, int expected,int update);
public final native boolean compareAndSwapLong(Object o, long offset, long expected, long update);
</code></pre>
<p>什么是CAS? 即比较并替换实现并发算法时常用到的一种技术。CAS操作包含三个操作数——内存位置、预期原值及新值。执行CAS操作的时候将内存位置的值与预期原值比较如果相匹配那么处理器会自动将该位置值更新为新值否则处理器不做任何操作。我们都知道CAS是一条CPU的原子指令cmpxchg指令不会造成所谓的数据不一致问题Unsafe提供的CAS方法如compareAndSwapXXX底层实现即为CPU指令cmpxchg。</p>
<h4>典型应用</h4>
<p>CAS在java.util.concurrent.atomic相关类、Java AQS、CurrentHashMap等实现上有非常广泛的应用。如下图所示AtomicInteger的实现中静态字段valueOffset即为字段value的内存偏移地址valueOffset的值在AtomicInteger初始化时在静态代码块中通过Unsafe的objectFieldOffset方法获取。在AtomicInteger中提供的线程安全方法中通过字段valueOffset的值可以定位到AtomicInteger对象中value的内存地址从而可以根据CAS实现对value字段的原子操作。</p>
<p><img src="assets/3bacb938ca6e63d6c79c2bb48d3f608f189412.png" alt="img" /></p>
<p>下图为某个AtomicInteger对象自增操作前后的内存示意图对象的基地址baseAddress=“0x110000”通过baseAddress+valueOffset得到value的内存地址valueAddress=“0x11000c”然后通过CAS进行原子性的更新操作成功则返回否则继续重试直到更新成功为止。</p>
<p><img src="assets/6e8b1fe5d5993d17a4c5b69bb72ac51d89826.png" alt="img" /></p>
<h3>线程调度</h3>
<p>这部分,包括线程挂起、恢复、锁机制等方法。</p>
<pre><code class="language-java">//取消阻塞线程
public native void unpark(Object thread);
//阻塞线程
public native void park(boolean isAbsolute, long time);
//获得对象锁(可重入锁)
@Deprecated
public native void monitorEnter(Object o);
//释放对象锁
@Deprecated
public native void monitorExit(Object o);
//尝试获取对象锁
@Deprecated
public native boolean tryMonitorEnter(Object o);
</code></pre>
<p>如上源码说明中方法park、unpark即可实现线程的挂起与恢复将一个线程进行挂起是通过park方法实现的调用park方法后线程将一直阻塞直到超时或者中断等条件出现unpark可以终止一个挂起的线程使其恢复正常。</p>
<h4>典型应用</h4>
<p>Java锁和同步器框架的核心类AbstractQueuedSynchronizer就是通过调用<code>LockSupport.park()</code><code>LockSupport.unpark()</code>实现线程的阻塞和唤醒的而LockSupport的park、unpark方法实际是调用Unsafe的park、unpark方式来实现。</p>
<h3>Class相关</h3>
<p>此部分主要提供Class和它的静态字段的操作相关方法包含静态字段内存定位、定义类、定义匿名类、检验&amp;确保初始化等。</p>
<pre><code class="language-java">//获取给定静态字段的内存地址偏移量,这个值对于给定的字段是唯一且固定不变的
public native long staticFieldOffset(Field f);
//获取一个静态类中给定字段的对象指针
public native Object staticFieldBase(Field f);
//判断是否需要初始化一个类,通常在获取一个类的静态属性的时候(因为一个类如果没初始化,它的静态属性也不会初始化)使用。 当且仅当ensureClassInitialized方法不生效时返回false。
public native boolean shouldBeInitialized(Class&lt;?&gt; c);
//检测给定的类是否已经初始化。通常在获取一个类的静态属性的时候(因为一个类如果没初始化,它的静态属性也不会初始化)使用。
public native void ensureClassInitialized(Class&lt;?&gt; c);
//定义一个类此方法会跳过JVM的所有安全检查默认情况下ClassLoader类加载器和ProtectionDomain保护域实例来源于调用者
public native Class&lt;?&gt; defineClass(String name, byte[] b, int off, int len, ClassLoader loader, ProtectionDomain protectionDomain);
//定义一个匿名类
public native Class&lt;?&gt; defineAnonymousClass(Class&lt;?&gt; hostClass, byte[] data, Object[] cpPatches);
</code></pre>
<h4>典型应用</h4>
<p>从Java 8开始JDK使用invokedynamic及VM Anonymous Class结合来实现Java语言层面上的Lambda表达式。</p>
<ul>
<li><strong>invokedynamic</strong> invokedynamic是Java 7为了实现在JVM上运行动态语言而引入的一条新的虚拟机指令它可以实现在运行期动态解析出调用点限定符所引用的方法然后再执行该方法invokedynamic指令的分派逻辑是由用户设定的引导方法决定。</li>
<li><strong>VM Anonymous Class</strong>可以看做是一种模板机制针对于程序动态生成很多结构相同、仅若干常量不同的类时可以先创建包含常量占位符的模板类而后通过Unsafe.defineAnonymousClass方法定义具体类时填充模板的占位符生成具体的匿名类。生成的匿名类不显式挂在任何ClassLoader下面只要当该类没有存在的实例对象、且没有强引用来引用该类的Class对象时该类就会被GC回收。故而VM Anonymous Class相比于Java语言层面的匿名内部类无需通过ClassClassLoader进行类加载且更易回收。</li>
</ul>
<p>在Lambda表达式实现中通过invokedynamic指令调用引导方法生成调用点在此过程中会通过ASM动态生成字节码而后利用Unsafe的defineAnonymousClass方法定义实现相应的函数式接口的匿名类然后再实例化此匿名类并返回与此匿名类中函数式方法的方法句柄关联的调用点而后可以通过此调用点实现调用相应Lambda表达式定义逻辑的功能。下面以如下图所示的Test类来举例说明。</p>
<p><img src="assets/7707d035eb5f04314b3684ff91dddb1663516.png" alt="img" /></p>
<p>Test类编译后的class文件反编译后的结果如下图一所示删除了对本文说明无意义的部分我们可以从中看到main方法的指令实现、invokedynamic指令调用的引导方法BootstrapMethods、及静态方法<code>lambda$main$0</code>实现了Lambda表达式中字符串打印逻辑等。在引导方法执行过程中会通过Unsafe.defineAnonymousClass生成如下图二所示的实现Consumer接口的匿名类。其中accept方法通过调用Test类中的静态方法<code>lambda$main$0</code>来实现Lambda表达式中定义的逻辑。而后执行语句<code>consumer.accept&quot;lambda&quot;</code>其实就是调用下图二所示的匿名类的accept方法。</p>
<p><img src="assets/1038d53959701093db6c655e4a342e30456249.png" alt="img" /></p>
<h3>对象操作</h3>
<p>此部分主要包含对象成员属性相关操作及非常规的对象实例化方式等相关方法。</p>
<pre><code class="language-java">//返回对象成员属性在内存地址相对于此对象的内存地址的偏移量
public native long objectFieldOffset(Field f);
//获得给定对象的指定地址偏移量的值与此类似操作还有getIntgetDoublegetLonggetChar等
public native Object getObject(Object o, long offset);
//给定对象的指定地址偏移量设值与此类似操作还有putIntputDoubleputLongputChar等
public native void putObject(Object o, long offset, Object x);
//从对象的指定偏移量处获取变量的引用使用volatile的加载语义
public native Object getObjectVolatile(Object o, long offset);
//存储变量的引用到对象的指定的偏移量处使用volatile的存储语义
public native void putObjectVolatile(Object o, long offset, Object x);
//有序、延迟版本的putObjectVolatile方法不保证值的改变被其他线程立即看到。只有在field被volatile修饰符修饰时有效
public native void putOrderedObject(Object o, long offset, Object x);
//绕过构造方法、初始化代码来创建对象
public native Object allocateInstance(Class&lt;?&gt; cls) throws InstantiationException;
</code></pre>
<h4>典型应用</h4>
<ul>
<li><strong>常规对象实例化方式</strong>我们通常所用到的创建对象的方式从本质上来讲都是通过new机制来实现对象的创建。但是new机制有个特点就是当类只提供有参的构造函数且无显示声明无参构造函数时则必须使用有参构造函数进行对象构造而使用有参构造函数时必须传递相应个数的参数才能完成对象实例化。</li>
<li><strong>非常规的实例化方式</strong>而Unsafe中提供allocateInstance方法仅通过Class对象就可以创建此类的实例对象而且不需要调用其构造函数、初始化代码、JVM安全检查等。它抑制修饰符检测也就是即使构造器是private修饰的也能通过此方法实例化只需提类对象即可创建相应的对象。由于这种特性allocateInstance在java.lang.invoke、Objenesis提供绕过类构造器的对象生成方式、Gson反序列化时用到中都有相应的应用。</li>
</ul>
<p>如下图所示在Gson反序列化时如果类有默认构造函数则通过反射调用默认构造函数创建实例否则通过UnsafeAllocator来实现对象实例的构造UnsafeAllocator通过调用Unsafe的allocateInstance实现对象的实例化保证在目标类无默认构造函数时反序列化不够影响。</p>
<p><img src="assets/b9fe6ab772d03f30cd48009920d56948514676.png" alt="img" /></p>
<h3>数组相关</h3>
<p>这部分主要介绍与数据操作相关的arrayBaseOffset与arrayIndexScale这两个方法两者配合起来使用即可定位数组中每个元素在内存中的位置。</p>
<pre><code class="language-java">//返回数组中第一个元素的偏移地址
public native int arrayBaseOffset(Class&lt;?&gt; arrayClass);
//返回数组中一个元素占用的大小
public native int arrayIndexScale(Class&lt;?&gt; arrayClass);
</code></pre>
<h4>典型应用</h4>
<p>这两个与数据操作相关的方法在java.util.concurrent.atomic 包下的AtomicIntegerArray可以实现对Integer数组中每个元素的原子性操作中有典型的应用如下图AtomicIntegerArray源码所示通过Unsafe的arrayBaseOffset、arrayIndexScale分别获取数组首元素的偏移地址base及单个元素大小因子scale。后续相关原子性操作均依赖于这两个值进行数组中元素的定位如下图二所示的getAndAdd方法即通过checkedByteOffset方法获取某数组元素的偏移地址而后通过CAS实现原子性操作。</p>
<p><img src="assets/160366b0fb2079ad897f6d6b1cb349cd426237.png" alt="img" /></p>
<h3>内存屏障</h3>
<p>在Java 8中引入用于定义内存屏障也称内存栅栏内存栅障屏障指令等是一类同步屏障指令是CPU或编译器在对内存随机访问的操作中的一个同步点使得此点之前的所有读写操作都执行后才可以开始执行此点之后的操作避免代码重排序。</p>
<pre><code class="language-java">//内存屏障禁止load操作重排序。屏障前的load操作不能被重排序到屏障后屏障后的load操作不能被重排序到屏障前
public native void loadFence();
//内存屏障禁止store操作重排序。屏障前的store操作不能被重排序到屏障后屏障后的store操作不能被重排序到屏障前
public native void storeFence();
//内存屏障禁止load、store操作重排序
public native void fullFence();
</code></pre>
<h4>典型应用</h4>
<p>在Java 8中引入了一种锁的新机制——StampedLock它可以看成是读写锁的一个改进版本。StampedLock提供了一种乐观读锁的实现这种乐观读锁类似于无锁的操作完全不会阻塞写线程获取写锁从而缓解读多写少时写线程“饥饿”现象。由于StampedLock提供的乐观读锁不阻塞写线程获取读锁当线程共享变量从主内存load到线程工作内存时会存在数据不一致问题所以当使用StampedLock的乐观读锁时需要遵从如下图用例中使用的模式来确保数据的一致性。</p>
<p><img src="assets/839ad79686d06583296f3abf1bec27e3320222.png" alt="img" /></p>
<p>如上图用例所示计算坐标点Point对象包含点移动方法move及计算此点到原点的距离的方法distanceFromOrigin。在方法distanceFromOrigin中首先通过tryOptimisticRead方法获取乐观读标记然后从主内存中加载点的坐标值 (x,y)而后通过StampedLock的validate方法校验锁状态判断坐标点(x,y)从主内存加载到线程工作内存过程中主内存的值是否已被其他线程通过move方法修改如果validate返回值为true证明(x, y)的值未被修改,可参与后续计算;否则,需加悲观读锁,再次从主内存加载(x,y)的最新值然后再进行距离计算。其中校验锁状态这步操作至关重要需要判断锁状态是否发生改变从而判断之前copy到线程工作内存中的值是否与主内存的值存在不一致。</p>
<p>下图为StampedLock.validate方法的源码实现通过锁标记与相关常量进行位运算、比较来校验锁状态在校验逻辑之前会通过Unsafe的loadFence方法加入一个load内存屏障目的是避免上图用例中步骤②和StampedLock.validate中锁状态校验运算发生重排序导致锁状态校验不准确的问题。</p>
<p><img src="assets/256f54b037d07df53408b5eea9436b34135955.png" alt="img" /></p>
<h3>系统相关</h3>
<p>这部分包含两个获取系统相关信息的方法。</p>
<pre><code class="language-java">//返回系统指针的大小。返回值为432位系统或 864位系统
public native int addressSize();
//内存页的大小此值为2的幂次方。
public native int pageSize();
</code></pre>
<h4>典型应用</h4>
<p>如下图所示的代码片段为java.nio下的工具类Bits中计算待申请内存所需内存页数量的静态方法其依赖于Unsafe中pageSize方法获取系统内存页大小实现后续计算逻辑。</p>
<p><img src="assets/262470b0c3e79b8f4f7b0c0280b1cc5362454.png" alt="img" /></p>
<h2>结语</h2>
<p>本文对Java中的sun.misc.Unsafe的用法及应用场景进行了基本介绍我们可以看到Unsafe提供了很多便捷、有趣的API方法。即便如此由于Unsafe中包含大量自主操作内存的方法如若使用不当会对程序带来许多不可控的灾难。因此对它的使用我们需要慎之又慎。</p>
<h2>参考资料</h2>
<ul>
<li><a href="http://hg.openjdk.java.net/jdk7/jdk7/jdk/file/9b8c96f96a0f/src/share/classes/sun/misc/Unsafe.java">OpenJDK Unsafe source</a></li>
<li><a href="http://mishadoff.com/blog/java-magic-part-4-sun-dot-misc-dot-unsafe">Java Magic. Part 4: sun.misc.Unsafe</a></li>
<li><a href="https://www.zhihu.com/question/51132462">JVM crashes at libjvm.so</a></li>
<li><a href="https://www.cnblogs.com/throwable/p/9139947.html">Java中神奇的双刃剑Unsafe</a></li>
<li><a href="http://lovestblog.cn/blog/2015/05/12/direct-buffer/">JVM源码分析之堆外内存完全解读</a></li>
<li><a href="https://www.jianshu.com/p/007052ee3773">堆外内存 之 DirectByteBuffer 详解</a></li>
<li>《深入理解Java虚拟机第2版</li>
</ul>
</div>
</div>
<div>
<div style="float: left">
<a href="/文章/Java线程池实现原理及其在美团业务中的实践.md.html">上一页</a>
</div>
<div style="float: right">
<a href="/文章/Kafka 源码阅读笔记.md.html">下一页</a>
</div>
</div>
</div>
</div>
</div>
</div>
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
</div>
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"70997fc919ae8b66","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
</body>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'G-NPSEEVD756');
var path = window.location.pathname
var cookie = getCookie("lastPath");
console.log(path)
if (path.replace("/", "") === "") {
if (cookie.replace("/", "") !== "") {
console.log(cookie)
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
}
} else {
setCookie("lastPath", path)
}
function setCookie(cname, cvalue) {
var d = new Date();
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
var expires = "expires=" + d.toGMTString();
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
}
function getCookie(cname) {
var name = cname + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i].trim();
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
}
return "";
}
</script>
</html>