第43篇-JNI引用的管理(2)
之前我们已经介绍了JNIHandleBlock,但是没有具体介绍JNIHandleBlock中存储的句柄,这一篇我们将详细介绍对这些句柄的操作。
JNI句柄分为两种,全局和局部对象引用:
(1)大部分对象的引用属于局部对象引用,最终还是调用了JNIHandleBlock来管理,因为JNIHandle没有设计一个JNIHandleMark的机制,所以在创建时需要明确调用JNIHandles::make_local()函数,在回收时也需要明确调用JNIHandles::destory_local()函数;
(2)对于全局对象的引用,比如在编译任务compilerTask中会访问Method实例,这时候就需要把这些实例设置为全局的(否则在GC时可能会被回收)。
下面我们就来详细介绍一下局部对象的引用和全局对象的引用。
1、局部对象引用
当我们编写本地函数时,可能会调用JNI函数获取父类,获取父类的JNI函数jni_GetSuperclass()的实现如下:
JNI_ENTRY(jclass, jni_GetSuperclass(JNIEnv *env, jclass sub))
JNIWrapper("GetSuperclass");
jclass obj = NULL;
// ...
obj = (super == NULL) ? NULL : (jclass) JNIHandles::make_local(super->java_mirror());
return obj;
JNI_END
也就是获取sub类的父类时,如果查询到的父类super不为null,则在返回时需要返回句柄,此时就会调用JNIHandles::make_local()函数。调用的JNIHandles::make_local()函数的实现如下:
jobject JNIHandles::make_local(Thread* thread, oop obj) {
if (obj == NULL) {
return NULL;
} else {
assert(Universe::heap()->is_in_reserved(obj), "sanity check");
return thread->active_handles()->allocate_handle(obj);
}
}
jobject JNIHandles::make_local(JNIEnv* env, oop obj) {
if (obj == NULL) {
return NULL;
} else {
JavaThread* thread = JavaThread::thread_from_jni_environment(env);
// 确保obj是堆上的对象,因为只有堆上的对象才会移动,才会被GC回收
assert(Universe::heap()->is_in_reserved(obj), "sanity check");
return thread->active_handles()->allocate_handle(obj);
}
}
obj是堆中的对象,现在本地函数要通过句柄访问这个对象,此时会调用JNIHandles::make_local()函数创建出对应的句柄,这个句柄的内存空间是从JNIHandleBlock中分配出来的。
调用的JavaThread::thread_from_jni_environment()函数的实现如下 :
static JavaThread* thread_from_jni_environment(JNIEnv* env) {
// 直接通过地址偏移得到JavaThread*,这是因为
// JavaThread类中定义了JNIEnv类型的字段
JavaThread *thread_from_jni_env = (JavaThread*)(
(intptr_t)env - in_bytes(jni_environment_offset())
);
// 最后检查线程是否已经终止状态,没有终止才返回该线程对象
if (thread_from_jni_env->is_terminated()) {
thread_from_jni_env->block_if_vm_exited();
return NULL;
} else {
return thread_from_jni_env;
}
}
在JNIHandles::make_local()函数中调用的JNIHandleBlock::allocate_handle()函数进行句柄分配。
Java线程使用一个对象句柄存储块JNIHandleBlock来为其在本地函数中申请的临时对象创建对应的句柄。具体就是调用JNIHandleBlock::allocate_handle()函数分配句柄,此函数的实现如下:
jobject JNIHandleBlock::allocate_handle(oop obj) {
assert(Universe::heap()->is_in_reserved(obj), "sanity check");
if (_top == 0) {
for (JNIHandleBlock* current = _next;
current != NULL;
current = current->_next
){
assert(current->_last == NULL, "only first block should have _last set");
assert(current->_free_list == NULL,"only first block should have _free_list set");
current->_top = 0;
if (ZapJNIHandleArea)
current->zap();
}
// 重转相关变量的值
_free_list = NULL;
_allocate_before_rebuild = 0;
_last = this;
if (ZapJNIHandleArea)
zap();
}
// 当前的JNIHandleBlock::_handles中能够有空闲的slot分配句柄则分配后直接返回
if (_last->_top < block_size_in_oops) {
oop* handle = &(_last->_handles)[_last->_top++];
*handle = obj;
return (jobject) handle;
}
// 如果有空闲的句柄slot,则从列表中第1个空闲的句柄slot中分配句柄并返回
if (_free_list != NULL) {
oop* handle = _free_list;
_free_list = (oop*) *_free_list;
*handle = obj;
return (jobject) handle;
}
// 如果_last后有空闲的JNIHandleBlock,则从此JNIHandleBlock中分配
if (_last->_next != NULL) {
_last = _last->_next;
return allocate_handle(obj); // 递归调用
}
// 没有空闲的JNIHandleBlock,并且当前的JNIHandleBlock也无法分配句柄需要的内容,则调用rebuild_free_list
if (_allocate_before_rebuild == 0) {
rebuild_free_list();
} else {
Thread* thread = Thread::current();
Handle obj_handle(thread, obj);
// 关于allocate_block()函数在之前已经介绍过
_last->_next = JNIHandleBlock::allocate_block(thread);
_last = _last->_next;
_allocate_before_rebuild--;
obj = obj_handle();
}
// 再次尝试从当前的JNIHandleBlock中分配句柄需要的内存
return allocate_handle(obj); // 递归调用
}
当_allocate_before_rebuild的值为0时,会调用rebuild_free_list()函数重建_free_list列表,_free_list列表中的各个slot都是分布在各个已经使用了的JNIHandleBlock中的空闲slot,所以重建就意味着我们要扫描所有已经使用了的slot,然后找到空闲的slot连接到这个单链表中,最大限度的重用slot。
调用的rebuild_free_list()函数的实现如下:
void JNIHandleBlock::rebuild_free_list() {
assert(_allocate_before_rebuild == 0 && _free_list == NULL, "just checking");
int free = 0;
int blocks = 0;
for (JNIHandleBlock* current = this; current != NULL; current = current->_next) {
for (int index = 0; index < current->_top; index++) {
oop* handle = &(current->_handles)[index];
if (*handle == JNIHandles::deleted_handle()) {
// 找到了空闲的slot,连接到单链表上
*handle = (oop) _free_list;
_free_list = handle;
free++;
}
}
blocks++;
}
// 当已经使用过的slot中有超过一半的slot都是空闲的,那么我们其实偏向于不再重新分配JNIHandleBlock,
// 而是重用这些空闲slot;当空闲的slot少时,会计算出_allocate_before_rebuild值,这样就会偏向于
// 从新的JNIHandleBlock中分配句柄
int total = blocks * block_size_in_oops;
int extra = total - 2*free;
if (extra > 0) {
// Not as many free handles as we would like - compute number of new blocks to append
_allocate_before_rebuild = (extra + block_size_in_oops - 1) / block_size_in_oops;
}
}
我们除了查找所有的空闲slot并连接到_free_list上之外,还要计算_allocate_before_rebuild值。如果计算出的_allocate_before_rebuild值大于0,那么我们查看JNIHandleBlock::allocate_handle()这个函数的逻辑,可以看到当无法分配句柄时,偏向于分配新的JNIHandleBlock,然后从JNIHandleBlock::_handles数组中分配句柄,否则从_free_list中重用空闲句柄,这就很好的避免了过度分配太多的JNIHandleBlock,如果过度分配太多的JNIHandleBlock,不但会加重GC扫描过程中的时间,也会占用更多的内存空间。
释放句柄的函数如下:
inline void JNIHandles::destroy_local(jobject handle) {
if (handle != NULL) {
// 使用JNIHandles::_deleted_handle来初始化,这个值在
// JNIHandles::initialize()函数中会初始化为Object对象,用来
// 表示这个slot为空,没有存储对象
*((oop*)handle) = deleted_handle();
}
}
代码实现非常简单,这里不再详细介绍。
2、全局对象引用
在分配全局变量时,调用如下函数:
jobject JNIHandles::make_global(Handle obj) {
jobject res = NULL;
if (!obj.is_null()) {
MutexLocker ml(JNIGlobalHandle_lock);
assert(Universe::heap()->is_in_reserved(obj()), "sanity check");
res = _global_handles->allocate_handle(obj()); // 同样调用allocate_handle()函数处理
}
return res;
}
在分配全局对象引用时,同样会调用allocate_handle()句柄,实现相对局部对象引用来说比较简单,通过一个全局的_global_handles来保存一个JNIHandleBlock的单链表,这个单链表中的JNIHandleBlock节点只增不减,所以如果某个时间点,全局对象引用足够多时会为GC带来不小负担,尤其是忘记释放全局对象引用会引起内存泄漏。
调用JNIHandles::destroy_global()函数释放全局对象引用:
void JNIHandles::destroy_global(jobject handle) {
if (handle != NULL) {
*((oop*)handle) = deleted_handle();
}
}
函数的实现非常简单,这里不再介绍。
公众号 深入剖析Java虚拟机HotSpot 已经更新虚拟机源代码剖析相关文章到60+,欢迎关注,如果有任何问题,可加作者微信mazhimazh,拉你入虚拟机群交流
第43篇-JNI引用的管理(2)的更多相关文章
- 第42篇-JNI引用的管理(1)
在本地函数中会使用Java服务,这些服务都可以通过调用JNIEnv中封装的函数获取.我们在本地函数中可以访问所传入的引用类型参数,也可以通过JNI函数创建新的 Java 对象.这些 Java 对象显然 ...
- Python 学习 第十篇 CMDB用户权限管理
Python 学习 第十篇 CMDB用户权限管理 2016-10-10 16:29:17 标签: python 版权声明:原创作品,谢绝转载!否则将追究法律责任. 不管是什么系统,用户权限都是至关重要 ...
- Spring Boot 揭秘与实战(二) 数据存储篇 - 声明式事务管理
文章目录 1. 声明式事务 2. Spring Boot默认集成事务 3. 实战演练4. 源代码 3.1. 实体对象 3.2. DAO 相关 3.3. Service 相关 3.4. 测试,测试 本文 ...
- JNI 引用问题梳理(转)
局部引用: JNI 函数内部创建的 jobject 对象及其子类( jclass . jstring . jarray 等) 对象都是局部引用,它们在 JNI 函数返回后无效: 一般情况下,我们应该依 ...
- Java多线程系列目录(共43篇)(转)
Java多线程系列目录(共43篇) http://www.cnblogs.com/skywang12345/p/java_threads_category.html
- Java基础篇 - 强引用、弱引用、软引用和虚引用
Java基础篇 - 强引用.弱引用.软引用和虚引用 原创零壹技术栈 最后发布于2018-09-09 08:58:21 阅读数 4936 收藏展开前言Java执行GC判断对象是否存活有两种方式其中一种是 ...
- 单例模式应用 | Shared_ptr引用计数管理器
在我们模拟设计 shared_ptr 智能指针时发现,不同类型的 Shared_ptr 不能使用同一个引用计数管理器,这显然会造成内存上的浪费.因此我们考虑将其设计为单例模式使其所有的 Shared_ ...
- asp.net微信开发第四篇----已关注用户管理
公众号可通过本接口来获取帐号的关注者列表,关注者列表由一串OpenID(加密后的微信号,每个用户对每个公众号的OpenID是唯一的)组成.一次拉取调用最多拉取10000个关注者的OpenID,可以通过 ...
- Java多线程系列目录(共43篇)
最近,在研究Java多线程的内容目录,将其内容逐步整理并发布. (一) 基础篇 01. Java多线程系列--“基础篇”01之 基本概念 02. Java多线程系列--“基础篇”02之 常用的实现多线 ...
随机推荐
- zookeeper问题在公司可以 回到家用家里网不行 目前还没有解决
Opening socket connection to server 192.168.1.193/192.168.1.193:2181. Will not attempt to authentica ...
- python实现图像梯度
一,定义与作用 图像梯度作用:获取图像边缘信息 二,Sobel 算子与函数的使用 (1)Sobel 算子------来计算变化率 (2)Sobel函数的使用 (3-1)代码实现(分别): (3-2)代 ...
- [hdu7033]Typing Contest
为了避免浮点运算,不妨将$f_{i}$乘上$C=10^{2}$,问题即求$\max_{S\subseteq [1,n]}\frac{\sum_{i\in S}(C^{2}-(\sum_{j ...
- [hdu7013]String Mod
枚举$a$和$b$出现的次数,问题即求$$A_{i,j}=\sum_{p=0}^{L}\sum_{q=0}^{L-p}[n\mid (p-i)][n\mid (q-j)]{L\choo ...
- [bzoj1415]聪聪与可可
直接求出任意两点的距离后记忆化搜索,用f[i][j]表示聪聪在i,可可在j的期望步数,由于i和j的最短路单调递减,所以搜不到环 1 #include<bits/stdc++.h> 2 us ...
- 手动实现一个vue cli
目录 手动实现一个vue cli 1. 思考准备 2. 我们组织源码将会放在名为src的目录,webpack 打包需要一个入口文件,我们取作 main.js 3. 先预想以下会用到哪些基本依赖,第一个 ...
- idea明明设置了utf-8, 但是提交的配置文件到远程中文乱码
IDEA中编辑的.properties配置文件提交到Git后显示乱码 解决方法:
- 联盛德 HLK-W806 (四): 软件SPI和硬件SPI驱动ST7735液晶LCD
目录 联盛德 HLK-W806 (一): Ubuntu20.04下的开发环境配置, 编译和烧录说明 联盛德 HLK-W806 (二): Win10下的开发环境配置, 编译和烧录说明 联盛德 HLK-W ...
- Codeforces 453E - Little Pony and Lord Tirek(二维线段树+ODT)
Codeforces 题目传送门 & 洛谷题目传送门 一道难度 *3100 的 DS,而且被我自己搞出来了! 不过我终究还是技不如人,因为这是一个 \(n\log^2n\) + 大常数的辣鸡做 ...
- P6973 [NEERC2016]List of Primes
题目传送门. 题意简述:将质数集合的所有子集按照子集和为第一关键字,字典序为第二关键字从小到大排序,求最终形成的字符串的第 \(l\sim r\) 个字符. 在 cnblogs 内查看. 又是一道妙妙 ...