跳至主要内容

Intention Behind ClassLoader Design

Intention Behind ClassLoader Design

We are always talking about the parent delegation model in ClassLoader design (if you are not familiar with this phrase, you may like to refer here), but we seldom ask why such a design? What the intention behind it? In this blog, we will elaborate on this aspect.

Parent Delegation in Detail

The ClassLoader is the abstraction that is responsible to load the core of Java world – class. We give a name of class to ClassLoader and it give us the class instance1.

public Class<?> loadClass(String name) throws ClassNotFoundException {}

The delegation model of Java ClassLoader requires that any request for a class loader to load a given class should be first delegated to its parent class loader before do it by itself.

if (parent != null) {  
    c = parent.loadClass(name, false);  
} else {  
    c = findBootstrapClassOrNull(name);  
}
if (c == null) {  
    // If still not found, then invoke findClass in order  
    // to find the class. 
    c = findClass(name);   
}

The parent class loader, in turn, goes through the same process and ask its parent recursively. This chain of delegation continues through to the bootstrap class loader (also known as the primordial or system class loader). If a class loader’s parent can load a given class, it returns that class. Otherwise, the class loader attempts to load the class itself.

Hierarchy

At the top of the hierarchy is the bootstrap class loader as we have introduced, which is responsible for loading only the classes that are from the core Java API, like String.class, Array. These classes are the most trusted and are used to bootstrap the JVM.

The extensions class loader can load classes that are standard extensions packages in the extensions directory.

The application class loader can load classes from the local file system, and will load files from the CLASSPATH. If we want to customize the class loading process, like getting a class file from network, we can extend application class loader.

Customization & Security

In order to do the customization of class loading, ClassLoader have to leave some space for user. At the same time, Java have to enforce security limit. User shouldn’t be able to load a fake String class and disturb the normal usage. So we have the parent delegation requirement as findClass suggested: ask parent first, then do it by itself.

/**   
 * This method should be overridden by class loader implementations that 
 * follow the delegation model for loading classes, and will be invoked by 
 * the {@link #loadClass <tt>loadClass</tt>} method after checking the  
 * parent class loader for the requested class.
 */
protected Class<?> findClass(String name) throws ClassNotFoundException {  
    throw new ClassNotFoundException(name);  
}

So can malicious user violate this requirement? It seems that we can directly return our class because our loadClass will override the father’s, right?

Yes and no. We indeed override the loadClass method, but we can’t return arbitrary class. It is because that we can’t really init any class and we have no access to its constructor. We can only init by defineClass:

protected final Class<?> defineClass(String name, byte[] b, int off, int len,  
                                     ProtectionDomain protectionDomain)  
    throws ClassFormatError  
{}

What we can customize is the name of class and content (byte[]), that all. Security limit is enforced by father class.

Because class loading is always delegated first to the parent of the class loading hierarchy, the most trusted repository (the core API) is checked first, followed by the standard extensions, then the local files that are on the class path. Finally, classes that are located in any repository that your own class loader can access, are accessible. This system prevents code from less-trusted sources from replacing trusted core API classes by assuming the same name as part of the core API.


PS

Can following code run? Test it and you can understand some of security limit what ClassLoader does.

package java.lang;  
  
/**  
 * @author zzt  
 */
public class ClassloaderCheck {    
  public static void main(String[] args) {  
  }  
}

Written with StackEdit.


  1. We may wonder: the ClassLoader is also a class, how it is loaded? It is kind of chicken or egg problem. It is a very common problem in programming related areas, another example is the process creation, which is usually done by fork an existing process. It is usually solved by introducing some extra element: in Java, the first ClassLoader is loaded by c++, called bootstrap. ↩︎

评论

此博客中的热门博文

Spring Boot: Customize Environment

Spring Boot: Customize Environment Environment variable is a very commonly used feature in daily programming: used in init script used in startup configuration used by logging etc In Spring Boot, all environment variables are a part of properties in Spring context and managed by Environment abstraction. Because Spring Boot can handle the parse of configuration files, when we want to implement a project which uses yml file as a separate config file, we choose the Spring Boot. The following is the problems we met when we implementing the parse of yml file and it is recorded for future reader. Bind to Class Property values can be injected directly into your beans using the @Value annotation, accessed via Spring’s Environment abstraction or bound to structured objects via @ConfigurationProperties. As the document says, there exists three ways to access properties in *.properties or *.yml : @Value : access single value Environment : can access multi

Elasticsearch: Join and SubQuery

Elasticsearch: Join and SubQuery Tony was bothered by the recent change of search engine requirement: they want the functionality of SQL-like join in Elasticsearch! “They are crazy! How can they think like that. Didn’t they understand that Elasticsearch is kind-of NoSQL 1 in which every index should be independent and self-contained? In this way, every index can work independently and scale as they like without considering other indexes, so the performance can boost. Following this design principle, Elasticsearch has little related supports.” Tony thought, after listening their requirements. Leader notice tony’s unwillingness and said, “Maybe it is hard to do, but the requirement is reasonable. We need to search person by his friends, didn’t we? What’s more, the harder to implement, the more you can learn from it, right?” Tony thought leader’s word does make sense so he set out to do the related implementations Application-Side Join “The first implementation

Implement isdigit

It is seems very easy to implement c library function isdigit , but for a library code, performance is very important. So we will try to implement it and make it faster. Function So, first we make it right. int isdigit ( char c) { return c >= '0' && c <= '9' ; } Improvements One – Macro When it comes to performance for c code, macro can always be tried. #define isdigit (c) c >= '0' && c <= '9' Two – Table Upper version use two comparison and one logical operation, but we can do better with more space: # define isdigit(c) table[c] This works and faster, but somewhat wasteful. We need only one bit to represent true or false, but we use a int. So what to do? There are many similar functions like isalpha(), isupper ... in c header file, so we can combine them into one int and get result by table[c]&SOME_BIT , which is what source do. Source code of ctype.h : # define _ISbit(bit) (1 << (