Java魔法堂:类加载器入了个门
一、前言
《Java魔法堂:类加载机制入了个门》中提及整个类加载流程中只有加载阶段作为码农的我们可以入手干预,其余均由JVM处理。本文将记录加载阶段的核心组件——类加载器的相关信息,以便日后查阅。若有纰漏请大家指正,谢谢。
注意:以下内容基于JDK7和HotSpot VM。
二、类加载器种类及其关系
从上图可知Java主要有4种类加载器
1. Bootstrap ClassLoader(引导类加载器):作为JVM的一部分无法在应用程序中直接引用,由C/C++实现(其他JVM可能通过Java来实现)。负责加载①<JAVA>/jre/lib目录 或 ②-Xbootclasspath参数所指定的目录 或 ③系统属性sun.boot.class.path指定的目录 中特定名称的jar包。在JVM启动时将通过Bootstrap ClassLoader加载rt.jar,并初始化sun.misc.Launcher从而创建Extension ClassLoader和System ClassLoader实例,和将System ClassLoader实例设置为主线程的默认Context ClassLoader(线程上下文加载器)。
注意:Bootstrap ClassLoader只会加载特定名称的类库,如rt.jar等。假如我们自己定义一个jar类库丢进<JAVA_HOME>/jre/lib目录下也不会被加载的!
下面我们看看Bootstrap ClassLoader到底加载了哪些jar包吧!
import java.net.*;
import sun.misc.*; class Main{
public static void main(String[] args){
URL[] urls = Launcher.getBootstrapClassPath().getURLs();
for (URL url : urls)
System.out.println(url.toExternalForm());
}
}
/* vim:!javac % & java Main 后输出
* lib/resources.jar
* lib/rt.jar
* lib/sunrsasign.jar
* lib/jsse.jar
* lib/jce.jar
* lib/charsets.jar
* lib/jfr.jar
* lib/classe
*/
2. Extension ClassLoader(扩展类加载器):仅含一个实例,由 sun.misc.Launcher$ExtClassLoader 实现,负责加载①<JAVA_HOME>/jre/lib/ext目录 或 ②系统属性java.ext.dirs所指定的目录 中的所有类库。
3. App/System ClassLoader(系统类加载器):仅含一个实例,由 sun.misc.Launcher$AppClassLoader 实现,可通过 java.lang.ClassLoader.getSystemClassLoader 获取。负责加载 ①系统环境变量ClassPath 或 ②-cp 或 ③系统属性java.class.path 所指定的目录下的类库。
4. Custom ClassLoader(用户自定义类加载器):可同时存在多个用户自定义的类加载器,具体如何定义请参考后文。
除了上面的4种类加载器外,JDK1.2开始引入了另一个类加载器——Context ClassLoader(线程上下文加载器)。
5. Context ClassLoader(线程上下文加载器):默认为System ClassLoader,可通过Thread.currentThread().setContextClassLoader(ClassLoader)来设置,可通过ClassLoader Thread.currentThread().getContextClassLoader()来获取。每个线程均将Context ClassLoader预先设置为父线程的Context ClassLoader。该类加载器主要用于打破双亲委派模型,容许父类加载器通过子类加载器加载所需的类库。
三、双亲委派模型
在介绍双亲委派模型前先看看以下示例:
/*
* Main.java文件
*/
import java.net.*;
import java.lang.reflect.*; class Main{
public static void main(String[] args)
throws ClassNotFoundException, MalformedURLException, IllegalAccessException, NoSuchMethodException, InstantiationException, InvocationTargetException{
ClassLoader pClassLoader = ClassLoader.getSystemClassLoader(); // 以System ClassLoader作为父类加载器
URL[] baseUrls = {new URL("file:/d:/testLib/")}; // 搜索类库的目录
final String binaryName = "com.fsjohnhuang.HelloWorld"; // 需要加载的类的二进制名称 ClassLoader userClassLoader1 = new URLClassLoader(baseUrls, pClassLoader);
ClassLoader userClassLoader2 = new URLClassLoader(baseUrls, pClassLoader);
Class clazz1 = userClassLoader1.loadClass(binaryName);
Class clazz2 = userClassLoader2.loadClass(binaryName);
Object instance1 = clazz1.newInstance();
Object instance2 = clazz2.newInstance();
// 调用say方法
clazz1.getMethod("say").invoke(instance1);
clazz2.getMethod("say").invoke(instance2);
// 输出类的二进制名称
System.out.println(clazz1.toString());
System.out.println(clazz2.toString()); // 比较两个类的地址是否相同
System.out.println(clazz1 == clazz2);
// 比较两个类是否相同或是否为继承关系
System.out.println(clazz1.isAssignableFrom(clazz2));
// 查看类型转换是否成功
boolean ret = true;
try{
clazz2.cast(instance1);
}
catch(ClassCastException e){
ret = false;
}
System.out.println(ret);
}
}
结果:
Hello World!
Hello World!
class com.fsjohnhuang.HelloWorld
class com.fsjohnhuang.HelloWorld
false
false
false
奇了个怪了,为什么两个类的Class实例不一样呢?这是因为 对于任意一个类,都需要由加载它的类加载器和该类本身一同确立其在JVM中的唯一性。也就是说对于同一个类文件,通过不同的类加载器加载那么在JVM中就生成了不同的类。
那现在问题来了,我们知道由java.lang.*(打包到rt.jar中)是由Bootstrap ClassLoader加载的,现在我闲着蛋疼自定义一个类加载器来加载java.lang.String,按照上面的定义那JVM中就有两个java.lang.String类了,然后出现下列问题:
if (myString.newInstance() instanceof String){
System.out.println(""); // 绝对不会执行这一句
}
else{
System.out.println("");
}
注意:由于类会通过自身对应的类加载器加载其引用的其他类。若myString中还引用了其他类,那么将会通过我自定的类加载器来加载一次哦!
假如会发生上述情况,真实项目中发生的问题就更大了。(注意:上述代码在真实环境绝对无法成立,自定义的类加载器本身就被限制为无法加载java.*的类哦!)
双亲委派模型就是用于解决上述问题,越基础的类由越上层的类加载器进行加载,如Java API类库则有Bootstrap ClassLoader加载。具体如下:
当一个类加载器收到类加载的请求,首先会将请求委派给父类加载器,这样一层一层委派到Bootstrap ClassLoader。然后加载器根据请求尝试搜索和加载类,若搜索失败则向子类加载器反馈信息(抛出ClassNotFoundException),然后子类加载器才尝试自己去加载。JAVA中采用组合的方式实现双亲委派模型,而不是继承的方式。
不难发现Bootstrap、Extension和System三种类加载器默认的加载类的目录路径均是不同的,也可以说 类的来源地与类加载器应该是一一对应。位于同一来源地的类应该由相同的类加载器加载,而不是由其他类加载来加载,或者通过双亲委派模型将加载请求传递给相应的类加载器。因最基础的类库通过Bootstrap加载,其次则由Extension加载,应用程序的则由System来加载,应用程序动态依赖的功能模块则通过用户自定义类加载器加载。
四、非双亲委派模型
双亲委派模型解决了类重复加载的乱象。但现在问题又来了,双亲委派模型仅限于子类加载器将加载请求转发到父类加载器,请求是单向流动的,那如果通过父类加载器加载一个在子类加载器管辖类来源的类,那怎么办呢?再说真的有这样的场景吗?
首先我们将 “通过父类加载器加载一个在子类加载器管辖类来源的类” 具体化为 “在一个由Bootstrap ClassLoader加载的类中动态加载其他目录路径下的类库”,这样我们就轻松地找到JNDI、JAXP等SPI(Service Provider Interface)均符合这种应用场景。以下就以JAXP来介绍吧!
JAXP(Java API for XML Processing),用于处理XML文档的API,接口和默认实现位于rt.jar中,但增强型的具体实现则由各个厂家提供且以第三方jar包的形式部署在项目的CLASSPATH下。其中抽象类 javax.xml.parsers.DocumentBuilderFactory的类方法newInstance(String factoryClassName, ClassLoader classLoader) 可根据二进制名称获取由各厂家具体实现的DocumentBuilderFactory实例。现在以 javax.xml.parsers.DocumentBuilderFactory.newInstance(" org.apache.xerces.jaxp.DocumentBuilderFactoryImpl", null) 的调用形式来深入下去。
首先假设newInstance内部是以以下方式加载类的
Class.forName("org.apache.xerces.jaxp.DocumentBuilderFactoryImpl");
// 或
this.getClass().getClassLoader.loadClass("org.apache.xerces.jaxp.DocumentBuilderFactoryImpl");
由于DocumentBuilderFactory是由Boostrap ClassLoader加载的,因此上述操作结果是通过Bootstrap ClassLoader来加载第三方类库,结果必须是ClassNotFoundException的。也就是说我们需要获取System ClassLoader或它的子类加载器才能成功加载这个类。
首先想到的是通过ClassLoader.getSystemClassLoader()方法来获取System ClassLoader。然而JDK1.2又引入了另一个更灵活的方式,那就是Context ClassLoader(线程上下文类加载器,默认为System ClassLoader),通过Context ClassLoader我们可以获取System ClassLoader或它的子类加载器,从而可以加载CLASSPATH和其他路径下的类库。
newInstance(String, ClassLoader)的实际实现是调用FactoryFinder.newInstance方法,而该方法则调用getProviderClass方法来获取Class实例,getProviderClass方法中则通过SecuritySupport的实例方法getContextClassLoader()来获取类加载器,代码片段如下:
ClassLoader getContextClassLoader()
throws SecurityException
{
return (ClassLoader)AccessController.doPrivileged(new PrivilegedAction()
{
public Object run() {
ClassLoader cl = null; cl = Thread.currentThread().getContextClassLoader(); if (cl == null) {
cl = ClassLoader.getSystemClassLoader();
}
return cl;
}
});
}
注意:Context ClassLoader可是要慎用哦!因为可以通过setContextClassLoader方法动态设置线程上下文类加载器,也就是有可能每次调用时的类加载器均不相同(所管辖的目录路径也不相同),在并发环境下就更容易出问题了。
五、从源码理解
首先我们看看ExtClassLoader和AppClassLoader是如何创建的,目光移到sun/misc/Launcher.java文件中,而ExtClassLoader和AppClassLoader则以Luancher的内部类的形式实现。在Launcher类进入初始化阶段时会创建一个Launcher实例,其构造函数中会实例化ExtClassLoader,然后以ExtClassLoader实例作为父类加载器来实例化AppClassLoader,并将AppClassLoader实例设置为主线程默认的Context ClassLoader。
public Launcher()
{
ExtClassLoader localExtClassLoader;
try
{
// 实例化ExtClassLoader
localExtClassLoader = ExtClassLoader.getExtClassLoader();
} catch (IOException localIOException1) {
throw new InternalError("Could not create extension class loader");
} try
{
// 实例化AppClassLoader
this.loader = AppClassLoader.getAppClassLoader(localExtClassLoader);
} catch (IOException localIOException2) {
throw new InternalError("Could not create application class loader");
}
// 主线程的默认Context ClassLoader
Thread.currentThread().setContextClassLoader(this.loader); String str = System.getProperty("java.security.manager");
if (str != null) {
SecurityManager localSecurityManager = null;
if (("".equals(str)) || ("default".equals(str)))
localSecurityManager = new SecurityManager();
else
try {
localSecurityManager = (SecurityManager)this.loader.loadClass(str).newInstance();
} catch (IllegalAccessException localIllegalAccessException) {
} catch (InstantiationException localInstantiationException) {
} catch (ClassNotFoundException localClassNotFoundException) {
}
catch (ClassCastException localClassCastException) {
}
if (localSecurityManager != null)
System.setSecurityManager(localSecurityManager);
else
throw new InternalError("Could not create SecurityManager: " + str);
}
}
ExtClassLoader和AppClassLoader均继承了java.net.URLClassLoader,并且仅对类的加载、搜索目录路径作修改而已。如AppClassLoader的getAppClassLoader方法:
static class AppClassLoader extends URLClassLoader
{
public static ClassLoader getAppClassLoader(final ClassLoader paramClassLoader)
throws IOException
{
// 获取搜索、加载类的目录路径
String str = System.getProperty("java.class.path");
final File[] arrayOfFile = str == null ? new File[] : Launcher.getClassPath(str); return (ClassLoader)AccessController.doPrivileged(new PrivilegedAction()
{
public Launcher.AppClassLoader run() {
URL[] arrayOfURL = this.val$s == null ? new URL[] : Launcher.pathToURLs(arrayOfFile);
// 设置类加载器的搜索、加载类的目录路径,并创建一个类加载器实例
return new Launcher.AppClassLoader(arrayOfURL, paramClassLoader);
}
});
}
在研究URLClassLoader之前我们先看看java.lang.ClassLoader,除Bootstrap ClassLoader外所有类加载器必须继承ClassLoader。还记得 ClassLoader.getSystemClassLoader().loadClass("org.apache.xerces.jaxp.DocumentBuilderFactoryImpl") 吧,现在我们就从loadClass出发,看看整个类加载机制吧!
protected Class<?> loadClass(String paramString, boolean paramBoolean)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(paramString))
{
// 检查该类是否已加载过,若已加载过则返回缓存中的Class实例
Class localClass = findLoadedClass(paramString);
// 下面是双亲委派模型的具体实现
if (localClass == null) {
long l1 = System.nanoTime();
try {
// 若有父类加载器则将加载请求传递到父类加载器
// 若parent变量为null则表示父类加载器是Bootstrap ClassLoader,同样将加载请求传递到父类加载器
if (this.parent != null)
localClass = this.parent.loadClass(paramString, false);
else {
localClass = findBootstrapClassOrNull(paramString);
}
}
catch (ClassNotFoundException localClassNotFoundException)
{
// 父类加载器无法加载给类时则抛出异常
} if (localClass == null)
{
long l2 = System.nanoTime();
// 开始加载类了!
localClass = findClass(paramString); PerfCounter.getParentDelegationTime().addTime(l2 - l1);
PerfCounter.getFindClassTime().addElapsedTimeFrom(l2);
PerfCounter.getFindClasses().increment();
}
}
// 对类执行解析操作
if (paramBoolean) {
resolveClass(localClass);
}
return localClass;
}
}
可以看到loadClass方法内部主要为双亲委派模型的实现,实际的类加载操作是在findClass方法中实现的。另外由于不允许同一个类加载器重复加载同一个类,因此当对同一个类重复进行加载操作时,则通过findLoadedClass方法来返回已有的Class实例。
ClassLoader中指提供findClass的定义,具体实现由子类提供。而URLClassLoader的findClass则是通过URLClassPath实例来获取类的二进制数据,然后调用defineClass对二进制数据进行初步验证,然后在由ClassLoader的defineClass进行其余的验证后生成Class实例返回。
protected Class<?> findClass(final String paramString)
throws ClassNotFoundException
{
try
{
return (Class)AccessController.doPrivileged(new PrivilegedExceptionAction()
{
public Class run() throws ClassNotFoundException {
// ucp为URLClassPath实例
// 通过URLClassPath实例获取类的二进制数据
String str = paramString.replace('.', '/').concat(".class");
Resource localResource = URLClassLoader.this.ucp.getResource(str, false);
if (localResource != null) {
try {
// 调用URLClassLoader的defineClass方法验证类的二进制数据并返回Class实例
return URLClassLoader.this.defineClass(paramString, localResource);
} catch (IOException localIOException) {
throw new ClassNotFoundException(paramString, localIOException);
}
}
throw new ClassNotFoundException(paramString);
}
}
, this.acc);
}
catch (PrivilegedActionException localPrivilegedActionException)
{
throw ((ClassNotFoundException)localPrivilegedActionException.getException());
}
}
总结一下, 类加载过程为loadClass -> findClass -> defineClass。loadClass为双亲委派的实现,defineClass为类数据验证和生成Class实例,findClass为获取类的二进制数据。
那么我们自定义类加载器时只需重写findClass就可以加载不同路径下的类库了!
六、手动加载类吧,骚年!
手动加载类的形式是多样的,具体如下:
1. 利用现有的类加载器
// 通过当前类的类加载器加载(会执行初始化)
Class.forName("二进制名称");
Class.forName("二进制名称", true, this.getClass().getClassLoader()); // 通过当前类的类加载器加载(不会执行初始化)
Class.forName("二进制名称", false, this.getClass().getClassLoader());
this.getClass().loadClass("二进制名称"); // 通过系统类加载器加载(不会执行初始化)
ClassLoader.getSystemClassLoader().loadClass("二进制名称"); // 通过线程上下文类加载器加载(不会执行初始化)
Thread.currentThread().getContextClassLoader().loadClass("二进制名称");
2. 利用URLClassLoader
URL[] baseUrls = {new URL("file:/d:/testLib/")};
URLClassLoader loader = new URLClassLoader(baseUrl, ClassLoader.getContextClassLoader());
Class clazz = loader.loadClass("com.fsjohnhuang.HelloWorld");
3. 继承ClassLoader自定义类加载器
public class MyClassLoader extends ClassLoader{
private String dir; public MyClassLoader(String dir, ClassLoader parent){
super(parent);
this.dir = dir;
} @Override
protect Class<?> findClass(String binaryName) throws ClassNotFoundException{
String pathSegmentSeperator = System.getProperty("file.separator");
String path = binaryName.replace(".", pathSegmentSeperator ).concat(".class"); FileInputStream fis = new FileInputStream(dir + pathSegmentSeperator + path);
byte[] b = new byte[fis.available()];
fis.read(b, , b.length);
fis.close();
return defineClass(binaryName, b, , b.length);
}
}
七、如何卸载类?
类卸载实质上就是GC对方法区(HotSpot中可称为永久代)的类数据进行垃圾回收。
虚拟机规范:
A class or interface may be unloaded if and only if its class loader is unreachable. The bootstrap class loader is always reachable; as a result , system classes may never be unloaded.
只有当加载该类型的类加载器实例( 非类加载器类型) 为unreachable 状态时,当前被加载的类型才被卸载. 启动类加载器实例永远为reachable 状态,由启动类加载器加载的类型可能永远不会被卸载。
Unreachable状态的解释:
1 、A reachable object is any object that can be accessed in any potential continuing computation from any live thread.
2 、finalizer-reachable: A finalizer-reachable object can be reached from some finalizable object through some chain of references, but not from any live thread. An unreachable object cannot be reached by either means.
也就是说
1. 加载器的类实例已经被回收。
2. 类的实例已经被回收。
3. 类的Class实例没有被任何地方引用,无法在任何地方通过反射访问该类。
对于Bootstrap、Ext和Sys类加载器来说正常情况下是不会被回收的,只有用户自定义类加载器才可以。通过 $ java -verbose:class Main 执行以下代码。
import java.net.*;
import java.io.*; public class Main{
public static class MyURLClassLoader extends URLClassLoader {
public MyURLClassLoader() {
super (getMyURLs());
} private static URL[] getMyURLs() {
try {
return new URL[]{ new File ("d:/").toURL()};
} catch (Exception e) {
e.printStackTrace();
return null ;
}
}
} public static void main(String[] args) throws IOException{
try {
MyURLClassLoader classLoader = new MyURLClassLoader();
Class classLoaded = classLoader.loadClass("RMDIR");
System.out.println(classLoaded.getName()); classLoaded = null ;
classLoader = null ; System.out.println(" 开始GC");
System.gc();
System.out.println("GC 完成");
System.in.read();
} catch (Exception e) {
e.printStackTrace();
}
}
}
八、加载图片、视频等非类资源
ClassLoader除了用于加载类外,还可以用于加载图片、视频等非类资源。同样是采用双亲委派模型将加载资源的请求传递到顶层的Bootstrap ClassLoader,在其管辖的目录下搜索资源,若失败才逐层返回逐层搜索。
相关的实例方法如下:
URL getResource(String name)
InputStream getResourceAsStream(String name)
Enumeration<URL> getResources(String name)
而相关的类方法均是调用系统类加载器的上述方法而已。
九、总结
若有纰漏请大家指正,谢谢!
尊重原创,转载请注明来自:http://www.cnblogs.com/fsjohnhuang/p/4284515.html ^_^肥仔John
十、参考
http://www.ibm.com/developerworks/cn/java/j-lo-classloader/
http://blog.csdn.net/zhoudaxia/article/details/35897057
《深入理解Java虚拟机 JVM高级特性与最佳实践》
Java魔法堂:类加载器入了个门的更多相关文章
- Node魔法堂:NPM入了个门
一.前言 NPM作为Node的模块管理和发布工具,作用与Ruby的gem.Python的pypl或setuptools.PHP的pear和.Net的Nuget一样.在当前前端工程化极速狂奔的年代,即使 ...
- 【转】Java魔法堂:String.format详解
Java魔法堂:String.format详解 目录 一.前言 二.重载方法 三.占位符 四.对字符.字符串进行格式化 五.对整数进行格式化 六. ...
- 黑马程序员——【Java高新技术】——类加载器
---------- android培训.java培训.期待与您交流! ---------- 一.概述 (一)类加载器(class loader) 用来动态加载Java类的工具,它本身也是Java类. ...
- Java魔法堂:打包知识点之jar
一.前言 通过eclipse导出jar包十分方便快捷,但作为码农岂能满足GUI的便捷呢?所以一起来CLI吧! 二.JAR包 JAR包是基于ZIP文件格式,用于将多个.java文件和各种资源文件, ...
- Java中的类加载器
转载:http://blog.csdn.net/zhangjg_blog/article/details/16102131 从java的动态性到类加载机制 我们知道,Java是一种动态语言.那么怎 ...
- Java中的类加载器以及Tomcat的类加载机制
在加载阶段,虚拟机需要完成以下三件事情: 1.通过一个类的全限定名来获取其定义的二进制字节流. 2.将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构. 3.在Java堆中生成一个代表这个类 ...
- 黑马程序员:Java基础总结----类加载器
黑马程序员:Java基础总结 类加载器 ASP.Net+Android+IO开发 . .Net培训 .期待与您交流! 类加载器 Java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,每个 ...
- Java中的类加载器--Class loader
学习一下Java中的类加载器,这个是比较底层的东西,好好学习.理解一下. 一.类加载器的介绍 1.类加载器:就是加载类的工具,在java程序中用到一个类,java虚拟机首先要把这个类的字节码加载到内 ...
- Java基础加强-类加载器
/*类加载器*/ 把.class文件从硬盘上加载出来,将类的字节码(二进制)加载到内存中 /*类加载器及其委托机制*/ Java虚拟机中可以安装多个类加载器(可以自己编写),系统默认三个主要类加载器, ...
随机推荐
- ListView用法总结
前言 列表,它作为一种非常重要的显示形式,不管是在web端还是在移动平台上,都是一种非常友好的,功能强大的展现形式.在Android中,ListView就接管了这一重任.尽管在Android5.X时代 ...
- Redhat Linux /etc/profile 与 /etc/bashrc 的区别
最近学习RHCE,在umask这里,书里说要修改/etc/profile和/etc/bashrc两个文件,却没有说明这两个区别.于是在上网查看之后倒是明白了各是怎么用的./etc/profile是对应 ...
- 【腾讯Bugly干货分享】腾讯验证码的十二年
本文来自于腾讯bugly开发者社区,未经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/581301b146dfb1456904df8d Dev Club 是一个交流移动 ...
- ReentrantLock实现原理深入探究
前言 这篇文章被归到Java基础分类中,其实真的一点都不基础.网上写ReentrantLock的使用.ReentrantLock和synchronized的区别的文章很多,研究ReentrantLoc ...
- Oracle存在修改,不存在插入记录
接触编程以来,在数据存储方面一直用的MS SQL.Oracle这名字对我来说是如此的熟悉,但是对其内容却很陌生,最近公司的一个项目用起了Oracle,所以也开始高调的用起了Oracle.在没有接触Or ...
- 赴美工作常识(Part 4 - 面试)
最近跟同事讨论面试的事情比较多,所以就综合大家所说的列举几条面试建议吧.这些建议是针对中国候选人应聘美国职位而写的,但适用范围可能更广.假若你实际的实力是 X,面试官感知到你的实力是 Y,这些建议既不 ...
- [PCB设计] 2、畸形PCB板子的制作核心——AD14导入dwg格式文件的方法
本文参考园友:The Zone of up.Craftor http://www.cnblogs.com/craftor/archive/2012/06/28/2567259.html 硬件工程师在做 ...
- [ACM_模拟] POJ1068 Parencodings (两种括号编码转化 规律 模拟)
Description Let S = s1 s2...s2n be a well-formed string of parentheses. S can be encoded in two diff ...
- js数组转json
function arrayToJson(o) { var r = []; if (typeof o == "string") return "\"" ...
- mac命令
mac下卸载nodesudo rm -rf /usr/local/{bin/{node,npm},lib/node_modules/npm,lib/node,share/man/*/node.*}xc ...