Selector uniquing in the dyld shared cache

SnowLeopard通过dyld共享缓存优化Objective-C运行时的启动开销,将启动时间减少一半,并节省每个应用几百KB的内存。该优化主要通过在dyld共享缓存中实现Objective-C选择器唯一化来达成。

因为在阅读英文版教材时遇到了几个术语,不太理解,然后就转载了下。

Mac OS X Snow Leopard cuts in half the launch-time overhead of starting the Objective-C runtime, and simultaneously saves a few hundred KB of memory per app. This comes for free to every app, courtesy of one of the few pieces of Mac OS X that lives below even the Objective-C runtime: dyld.

一.dyld and the shared cache

dyld is the dynamic loader and linker. When your process starts, dyld loads your executable and its shared libraries into memory, links the cross-library C function and variable references together, and starts execution on its way towards main().

In theory a shared library could be different every time your program is run. In practice, you get the same version of the shared libraries almost every time you run, and so does every other process on the system. The system takes advantage of this by building the dyld shared cache. The shared cache contains a copy of many system libraries, with most of dyld’s linking and loading work done in advance. Every process can then share that shared cache, saving memory and launch time.

(Incidentally, the shared cache beats the pants off the pre-Leopard prebinding system that was supposed to achieve the same optimizations. Remember the post-install “Optimizing System Performance” step that often took longer than the install itself? That was prebinding being updated. Rebuilding the shared cache is so blazingly fast that the installer doesn’t bother to report it anymore.)

二.Objective-C selector uniquing

Leopard’s dyld shared cache is great for C code, but it didn’t do anything to help Objective-C’s startup overhead. The single biggest launch cost for Objective-C is selector uniquing. The app and every shared library contain their own copies of selector names like “alloc” and “init”. The runtime needs to choose a single canonical SEL pointer value for each selector name, and then update the metadata for every call site and method list to use the blessed unique value. This means building a big hash table (memory), calling strcmp() a lot (time), and modifying copy-on-write metadata (more memory).

There are tens of thousands of unique selectors present in a typical process. If you run strings /usr/lib/libobjc.dylib on Leopard you can see the thirty-thousand-line built-in selector table that was a previous attempt to reduce the memory cost. Even so the cost goes up with every new class and method added to Cocoa.framework; left unchecked, an identical app would take longer to launch and use more memory after every OS upgrade.

The obvious solution? Do the work of selector uniquing in the dyld shared cache. Build a selector table into the shared cache itself, and update the selector references in the cached copy of the shared libraries. Then you save memory because every process shares the same selector table, and save time because the runtime does not need to rebuild it during every app launch. The runtime only needs to fix the selector references from the app itself. The catch? Selectors are too dynamic to be implemented as C symbols, so the shared cache construction tool needed to be taught how to read and write Objective-C’s metadata.

三.Optimization WIN

Snow Leopard’s dyld shared cache uniques Objective-C selectors, and Snow Leopard’s Objective-C runtime recognizes when the selectors in a shared library are already uniqued courtesy of the shared cache. About half of the runtime’s initialization time is eliminated, making warm app launch several tenths of a second faster. Typical memory savings is 200-500 KB per process, adding up to a few megabytes system-wide. When this optimization ships on the iPhone OS side, it’s estimated to save 1 MB on a 128 MB device. The iPhone performance team would pay any number of arms and legs for that kind of gain.

You can watch the system in action with various debugging flags.

$ sudo /usr/bin/update_dyld_shared_cache -debug -verify
[…]
update_dyld_shared_cache: for x86_64, uniquing objc selectors
update_dyld_shared_cache: for x86_64, found 68761 unique objc selectors
update_dyld_shared_cache: for x86_64, 541736/590908 bytes (91%) used in libobjc unique selector section
update_dyld_shared_cache: for x86_64, updated 205230 selector references

$ OBJC_PRINT_PREOPTIMIZATION=YES /usr/bin/defaults
objc[424]: PREOPTIMIZATION: selector preoptimization ENABLED (version 3)
objc[424]: PREOPTIMIZATION: honoring preoptimized selectors in /usr/lib/libobjc.A.dylib
objc[424]: PREOPTIMIZATION: honoring preoptimized selectors in /System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation
objc[424]: PREOPTIMIZATION: honoring preoptimized selectors in /System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/Metadata.framework/Versions/A/Metadata
objc[424]: PREOPTIMIZATION: honoring preoptimized selectors in /System/Library/Frameworks/Foundation.framework/Versions/C/Foundation

You can estimate the memory savings with the allmemory tool. Record post-launch memory usage of an app run with and without environment variable OBJC_DISABLE_PREOPTIMIZATION=YES. Look for the count of dirty pages; each dirty page is 4 KB eaten by that process. With 64-bit TextEdit I see the dirty page count jump from 725 to 1069 after disabling the optimization. This is an overestimate - many of those pages would have been not-dirty in Leopard because of the old built-in selector table - but it does show the magnitude of the win.

The Objective-C runtime does more than just selector uniquing during launch. Future improvements to the dyld shared cache may precompute some of that other work, to further improve launch time, save memory, and reduce the cost of linking to Objective-C code that you don’t actually use. But selector uniquing as seen in Snow Leopard is by far the biggest bang for the buck.

原网页:http://www.sealiesoftware.com/blog

### JVisualVM 中 "Unexpected exception in the selector loop" 错误原因及解决方法 在使用 JVisualVM 进行 Java 应用性能监控或分析时,用户可能会遇到 `Unexpected exception in the selector loop` 这一异常信息。该异常通常出现在 JVM 的 NIO Selector 机制中,特别是在使用 Netty 或其他基于 NIO 的框架时较为常见。错误提示表明在 Selector 的事件循环中发生了意外异常,可能导致应用连接中断或性能下降。 #### 错误原因分析 1. **Selector 被意外关闭**:Selector 在未完成所有任务前被关闭,可能导致正在运行的事件循环抛出异常。例如,某些框架在未正确调用 `EventLoopGroup.shutdownGracefully()` 的情况下关闭了线程池,导致 Selector 提前关闭[^4]。 2. **内存不足(如 PermGen 空间不足)**:当 JVM 遇到内存不足错误(如 `OutOfMemoryError: PermGen space`)时,可能导致 Selector 在运行过程中抛出异常并中断循环[^2]。 3. **文件描述符被占用或损坏**:在某些情况下,系统文件描述符被占用或损坏,导致 Selector 无法正常操作底层资源,从而抛出异常。例如,`.dubbo` 文件被其他进程占用可能导致此类问题[^5]。 4. **JVM Bug 或版本兼容性问题**:某些 JDK 版本中存在与 Selector 相关的已知问题,可能在特定条件下触发该异常。 #### 解决方法 1. **确保正确关闭 EventLoopGroup**:在使用 Netty 等依赖 NIO 的框架时,应确保调用 `EventLoopGroup.shutdownGracefully()` 方法,以安全地关闭事件循环组,避免 Selector 提前关闭导致异常[^4]。 2. **调整 JVM 内存参数**:若异常伴随 `OutOfMemoryError`,应适当增加 PermGen 或 Metaspace 内存大小。例如,在启动参数中添加: ```bash -XX:MaxPermSize=256m ``` 或针对 JDK 8 及以上版本: ```bash -XX:MaxMetaspaceSize=256m ``` 3. **检查并释放被占用的文件描述符**:使用 `lsof` 命令检查是否存在被占用的 `.dubbo` 文件或其他资源文件,若发现占用进程,可尝试使用 `kill -9` 强制终止该进程以释放资源。 4. **升级 JDK 或 Netty 版本**:若怀疑是 JVM 或框架本身的 Bug,建议升级到最新稳定版本,以修复已知的 Selector 相关问题。 5. **使用 JVisualVM 进行线程和内存分析**:通过 JVisualVM 的线程面板查看 Selector 相关线程的状态,结合堆转储分析是否存在内存泄漏或线程阻塞问题。 #### 示例代码:Netty 中正确关闭 EventLoopGroup ```java EventLoopGroup group = new NioEventLoopGroup(); try { // 启动服务或执行任务 } finally { group.shutdownGracefully(); } ``` 该代码确保即使在发生异常的情况下,EventLoopGroup 也会被安全关闭,避免触发 Selector 循环中的异常。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值