OpenJDK类加载实现浅析#2:安全检查

今天来看下类加载过程中的一些安全检查。在那之前得先来了解下Java的Access Control。

Access Control

The access control architecture in the Java platform protects access to sensitive resources (for example, local files) or sensitive application code (for example, methods in a class). All access control decisions are mediated by a security manager, represented by the java.lang.SecurityManager class. A SecurityManager must be installed into the Java runtime in order to activate the access control checks.

访问控制属于Java Security Architecture的一部分。一张图看懂:)

alright,接下来我们还是直接看代码吧。FileInputStream

    public FileInputStream(File file) throws FileNotFoundException {
        String name = (file != null ? file.getPath() : null);
        /////// 进行安全检查
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkRead(name);
        }
        if (name == null) {
            throw new NullPointerException();
        }
        if (file.isInvalid()) {
            throw new FileNotFoundException("Invalid file path");
        }
        fd = new FileDescriptor();
        fd.incrementAndGetUseCount();
        this.path = name;
        open(name);
    }

SecurityManager#checkRead(java.lang.String)

    public void checkRead(String file) {
        checkPermission(new FilePermission(file,
            SecurityConstants.FILE_READ_ACTION));
    }
    public void checkPermission(Permission perm) {
      /////// 直接交由AccessController处理
      java.security.AccessController.checkPermission(perm);
    }

AccessController#checkPermission

    public static void checkPermission(Permission perm)
                 throws AccessControlException
    {
        //System.err.println("checkPermission "+perm);
        //Thread.currentThread().dumpStack();

        if (perm == null) {
            throw new NullPointerException("permission can‘t be null");
        }

        AccessControlContext stack = getStackAccessControlContext();
        // context为null不需要进行检查
        // if context is null, we had privileged system code on the stack.
        if (stack == null) {
            Debug debug = AccessControlContext.getDebug();
            boolean dumpDebug = false;
            if (debug != null) {
                dumpDebug = !Debug.isOn("codebase=");
                dumpDebug &= !Debug.isOn("permission=") ||
                    Debug.isOn("permission=" + perm.getClass().getCanonicalName());
            }

            if (dumpDebug && Debug.isOn("stack")) {
                Thread.currentThread().dumpStack();
            }

            if (dumpDebug && Debug.isOn("domain")) {
                debug.println("domain (context is null)");
            }

            if (dumpDebug) {
                debug.println("access allowed "+perm);
            }
            return;
        }
        /////// 交由AccessControlContext处理
        AccessControlContext acc = stack.optimize();
        acc.checkPermission(perm);
    }

AccessControlContext#checkPermission

        /* if ctxt is null, all we had on the stack were system domains,
           or the first domain was a Privileged system domain. This
           is to make the common case for system code very fast */

        /////// 与classloader为null同一招
        if (context == null)
            return;

        for (int i=0; i< context.length; i++) {
            if (context[i] != null &&  !context[i].implies(perm)) {
                if (dumpDebug) {
                    debug.println("access denied " + perm);
                }

                if (Debug.isOn("failure") && debug != null) {
                    // Want to make sure this is always displayed for failure,
                    // but do not want to display again if already displayed
                    // above.
                    if (!dumpDebug) {
                        debug.println("access denied " + perm);
                    }
                    Thread.currentThread().dumpStack();
                    final ProtectionDomain pd = context[i];
                    final Debug db = debug;
                    AccessController.doPrivileged (new PrivilegedAction<Void>() {
                        public Void run() {
                            db.println("domain that failed "+pd);
                            return null;
                        }
                    });
                }
                throw new AccessControlException("access denied "+perm, perm);
            }
        }

上面的context是一个ProtectionDomain数组,

private ProtectionDomain context[];

所以接下来是交由ProtectionDomain#implies进行检查了,

    public boolean implies(Permission permission) {

        if (hasAllPerm) {
            // internal permission collection already has AllPermission -
            // no need to go to policy
            return true;
        }

        if (!staticPermissions &&
            Policy.getPolicyNoCheck().implies(this, permission))
            return true;
        if (permissions != null)
            return permissions.implies(permission);

        return false;
    }

到这里终于出现Policy了,Policy#getPolicyNoCheck

    static Policy getPolicyNoCheck()
    {
        PolicyInfo pi = policy.get();
        // Use double-check idiom to avoid locking if system-wide policy is
        // already initialized
        if (pi.initialized == false || pi.policy == null) {
            synchronized (Policy.class) {
                PolicyInfo pinfo = policy.get();
                if (pinfo.policy == null) {
                    String policy_class = AccessController.doPrivileged(
                        new PrivilegedAction<String>() {
                        public String run() {
                            /////// 读取{java.home}/lib/security/java.security配置文件
                            /////// policy.provider=sun.security.provider.PolicyFile
                            return Security.getProperty("policy.provider");
                        }
                    });
                    if (policy_class == null) {
                        policy_class = "sun.security.provider.PolicyFile";
                    }

                    try {
                        pinfo = new PolicyInfo(
                            (Policy) Class.forName(policy_class).newInstance(),
                            true);
                    } catch (Exception e) {
                        /*
                         * The policy_class seems to be an extension
                         * so we have to bootstrap loading it via a policy
                         * provider that is on the bootclasspath.
                         * If it loads then shift gears to using the configured
                         * provider.
                         */

                        // install the bootstrap provider to avoid recursion
                        Policy polFile = new sun.security.provider.PolicyFile();
                        pinfo = new PolicyInfo(polFile, false);
                        policy.set(pinfo);

                        final String pc = policy_class;
                        Policy pol = AccessController.doPrivileged(
                            new PrivilegedAction<Policy>() {
                            public Policy run() {
                                try {
                                    ClassLoader cl =
                                            ClassLoader.getSystemClassLoader();
                                    // we want the extension loader
                                    ClassLoader extcl = null;
                                    while (cl != null) {
                                        extcl = cl;
                                        cl = cl.getParent();
                                    }
                                    return (extcl != null ? (Policy)Class.forName(
                                            pc, true, extcl).newInstance() : null);
                                } catch (Exception e) {
                                    if (debug != null) {
                                        debug.println("policy provider " +
                                                    pc +
                                                    " not available");
                                        e.printStackTrace();
                                    }
                                    return null;
                                }
                            }
                        });
                        /*
                         * if it loaded install it as the policy provider. Otherwise
                         * continue to use the system default implementation
                         */
                        if (pol != null) {
                            pinfo = new PolicyInfo(pol, true);
                        } else {
                            if (debug != null) {
                                debug.println("using sun.security.provider.PolicyFile");
                            }
                            pinfo = new PolicyInfo(polFile, true);
                        }
                    }
                    policy.set(pinfo);
                }
                return pinfo.policy;
            }
        }
        return pi.policy;
    }

Policy#implies

    public boolean implies(ProtectionDomain domain, Permission permission) {
        PermissionCollection pc;

        if (pdMapping == null) {
            initPolicy(this);
        }

        synchronized (pdMapping) {
            pc = pdMapping.get(domain.key);
        }

        if (pc != null) {
            return pc.implies(permission);
        }

        pc = getPermissions(domain);
        if (pc == null) {
            return false;
        }

        synchronized (pdMapping) {
            // cache it
            pdMapping.put(domain.key, pc);
        }

        return pc.implies(permission);
    }

所以最终其实都是交由PermissionCollection#implies处理,

    /**
     * Checks to see if the specified permission is implied by
     * the collection of Permission objects held in this PermissionCollection.
     *
     * @param permission the Permission object to compare.
     *
     * @return true if "permission" is implied by the  permissions in
     * the collection, false if not.
     */
    public abstract boolean implies(Permission permission);

总结一下就是,

  1. 通过System.getSecurityManager()拿到SecurityManager
  2. SecurityManager直接交给AccessController处理;
  3. AccessController通过调用getStackAccessControlContext取得AccessControlContext,并交给AccessControlContext处理;
  4. AccessControlContext交给它所持有的一个ProtectionDomain数组处理;
  5. ProtectionDomain交给PermissionCollection处理,有两种方式拿到PermissionCollection,一种是使用自身持有的PermissionCollection(构造函数传入),另一种是使用Policy来获得。使用哪种方式由staticPermissions决定。

那么现在有两个问题,System.getSecurityManager()getStackAccessControlContext分别做了啥?我们一个一个来看下。

SecurityManager

System.getSecurityManager()其实比较简单,直接返回了一个SecurityManager

    public static SecurityManager getSecurityManager() {
        return security;
    }

那么这个security又是什么时候设置的呢?是在虚拟机启动的时候,由launcher来设置的,

    public Launcher() {

        ...

        // Finally, install a security manager if requested
        String s = System.getProperty("java.security.manager");
        if (s != null) {
            SecurityManager sm = null;
            if ("".equals(s) || "default".equals(s)) {
                sm = new java.lang.SecurityManager();
            } else {
                try {
                    sm = (SecurityManager)loader.loadClass(s).newInstance();
                } catch (IllegalAccessException e) {
                } catch (InstantiationException e) {
                } catch (ClassNotFoundException e) {
                } catch (ClassCastException e) {
                }
            }
            if (sm != null) {
                System.setSecurityManager(sm);
            } else {
                throw new InternalError(
                    "Could not create SecurityManager: " + s);
            }
        }
    }

所以我们是可以通过设置java.security.manager这个系统属性来使用我们自己的SecurityManager的。

getStackAccessControlContext

接下来看看比较关键的getStackAccessControlContext方法,

    /**
     * Returns the AccessControl context. i.e., it gets
     * the protection domains of all the callers on the stack,
     * starting at the first class with a non-null
     * ProtectionDomain.
     *
     * @return the access control context based on the current stack or
     *         null if there was only privileged system code.
     */
    private static native AccessControlContext getStackAccessControlContext();

是个本地方法,具体实现我们暂不深究,下面直接贴出这个地方所使用的权限校验算法,具体参考官方文档

Suppose the current thread traversed m callers, in the order of caller 1 to caller 2 to caller m. Then caller m invoked the checkPermission method. The basic algorithm checkPermission uses to determine whether access is granted or denied is the following

  i = m;
  while (i > 0) {
     if (caller i‘s domain does not have the permission)
        throw AccessControlException
     else if (caller i is marked as privileged) {
        if (a context was specified in the call to doPrivileged)
           context.checkPermission(permission);
        return;
     }
     i = i - 1;
  };

  // Next, check the context inherited when
  // the thread was created. Whenever a new thread is created, the
  // AccessControlContext at that time is
  // stored and associated with the new thread, as the "inherited"
  // context.

  inheritedContext.checkPermission(permission);

有一点值得说明下,就是我们平时经常看到的AccessController#doPrivileged方法,

That is, a caller can be marked as being “privileged” when it calls the doPrivileged method. When making access control decisions, the checkPermission method stops checking if it reaches a caller that was marked as “privileged” via a doPrivileged call without a context argument. If that caller’s domain has the specified permission, no further checking is done and checkPermission returns quietly, indicating that the requested access is allowed. If that domain does not have the specified permission, an exception is thrown, as usual.

也就是说遇到privileged的caller,安全检查一定会停止,不管是成功还是失败。应该是出于性能考虑。

Protection Domain

AccessControlContext我们没有深究到底是怎么生成的,那它所持有的ProtectionDomain这货又是个啥?

A domain conceptually encloses a set of classes whose instances are granted the same set of permissions. Protection domains are determined by the policy currently in effect.

还是一张图看懂:)

每个domain所拥有的权限就是我们上面所看到的PermissionCollection

AccessControlContext所持有的ProtectionDomain其实就是在调用栈上面,每个Class所属的ProtectionDomain。那么每个Class所属的ProtectionDomain又是怎么来的呢?妥妥的,就是在类加载的时候塞进去的,这个我们下面会再分析。

再放一个栗子,来看看ProtectionDomain具体长啥样,

    public static void main(String[] args) throws Throwable {
        System.out.println(System.class.getProtectionDomain());
        System.out.println(GroovyClassLoader.class.getProtectionDomain());
        System.out.println(Main.class.getProtectionDomain());
    }
ProtectionDomain  null
 null
 <no principals>
 java.security.Permissions@73c94b51 (
 ("java.security.AllPermission" "<all permissions>" "<all actions>")
)

ProtectionDomain  (file:/C:/Java/jdk1.7.0_51/jre/lib/ext/groovy-2.4.0.jar <no signer certificates>)
 sun.misc.Launcher$ExtClassLoader@d325aef
 <no principals>
 java.security.Permissions@3aeb3f66 (
 ("java.io.FilePermission" "\C:\Java\jdk1.7.0_51\jre\lib\ext\groovy-2.4.0.jar" "read")
)

ProtectionDomain  (file:/E:/Projects/just4fun/target/classes/ <no signer certificates>)
 sun.misc.Launcher$AppClassLoader@35f784d7
 <no principals>
 java.security.Permissions@2a8f5fc2 (
 ("java.lang.RuntimePermission" "exitVM")
 ("java.io.FilePermission" "\E:\Projects\just4fun\target\classes\-" "read")
)

OK,接下来就来看下类加载过程中的一些安全检查。

findClass

首先是findClass过程中,寻找Resource时会有安全检查,代码在URLClassPath#check

    /*
     * Check whether the resource URL should be returned.
     * Throw exception on failure.
     * Called internally within this file.
     */
    static void check(URL url) throws IOException {
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            URLConnection urlConnection = url.openConnection();
            Permission perm = urlConnection.getPermission();
            if (perm != null) {
                try {
                    security.checkPermission(perm);
                } catch (SecurityException se) {
                    // fallback to checkRead/checkConnect for pre 1.2
                    // security managers
                    if ((perm instanceof java.io.FilePermission) &&
                        perm.getActions().indexOf("read") != -1) {
                        security.checkRead(perm.getName());
                    } else if ((perm instanceof
                        java.net.SocketPermission) &&
                        perm.getActions().indexOf("connect") != -1) {
                        URL locUrl = url;
                        if (urlConnection instanceof JarURLConnection) {
                            locUrl = ((JarURLConnection)urlConnection).getJarFileURL();
                        }
                        security.checkConnect(locUrl.getHost(),
                                              locUrl.getPort());
                    } else {
                        throw se;
                    }
                }
            }
        }
    }

如果是本地资源需要校验文件权限,如果是网络资源需要校验网络权限。

getAndVerifyPackage

接下来在defineClass的时候,首先需要一些关于package的检查,URLClassLoader#getAndVerifyPackage

    private Package getAndVerifyPackage(String pkgname,
                                        Manifest man, URL url) {
        Package pkg = getPackage(pkgname);
        if (pkg != null) {
            // Package found, so check package sealing.
            if (pkg.isSealed()) {
                // Verify that code source URL is the same.
                if (!pkg.isSealed(url)) {
                    throw new SecurityException(
                        "sealing violation: package " + pkgname + " is sealed");
                }
            } else {
                // Make sure we are not attempting to seal the package
                // at this code source URL.
                if ((man != null) && isSealed(pkgname, man)) {
                    throw new SecurityException(
                        "sealing violation: can‘t seal package " + pkgname +
                        ": already loaded");
                }
            }
        }
        return pkg;
    }

类加载的时候会将类的package定义保存下来,其中包括package的一些相关属性,这里需要校验的主要是看看package的Sealed属性。Sealed属性通过Manifest来定义,这其实是一个关于Java中package访问权限的补充。看下面的栗子,

package me.kisimple.just4fun;

public class Bar {
    static String secret = "you know too much";
}
package me.kisimple.just4fun;

public class Foo {
    public static String gotIt() {
        return Bar.secret;
    }
}

Bar#secret是package的访问权限,Foo可以访问到它,这是没有问题的。但假如我不希望Bar#secret被其他jar包访问到,不怀好意的人却可以通过将Foo打进自己jar包的方法来访问secret,就像下面这样,

> jar cvf bar.jar me/kisimple/just4fun/Bar.class
> jar cvf foo.jar me/kisimple/just4fun/Foo.class

然后我同时依赖bar.jarfoo.jar,这样我就可以在自己代码里面通过Foo间接访问Bar#secret了,

    public static void main(String[] args) throws Throwable {
        System.out.println(Foo.gotIt());
    }

这不得不说是Java的package访问权限的一个漏洞,怎么办呢?这时候就可以使用上面我们说到的ManifestSealed属性了,在打Bar的jar包的时候,我们可以指定Manifest并添加Sealed属性,就像下面这样,

Name: me/kisimple/just4fun/
Sealed: true
> jar cvfm bar.jar imanifest me/kisimple/just4fun/Bar.class

OK,这时候再通过Foo来访问Bar#secret的时候将会报错,Foo是无法被类加载器加载的,

Exception in thread "main" java.lang.SecurityException: sealing violation: can‘t seal package me.kisimple.just4fun: already loaded
    at java.net.URLClassLoader.getAndVerifyPackage(URLClassLoader.java:395)
    at java.net.URLClassLoader.defineClass(URLClassLoader.java:417)
    at java.net.URLClassLoader.access$100(URLClassLoader.java:71)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:361)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:355)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:354)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:425)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:358)
    at me.kisimple.just4fun.Foo.gotIt(Foo.java:10)
    at me.kisimple.just4fun.Main.main(Main.java:10)

报错的地方其实就在getAndVerifyPackage方法,具体可以再回过头去看看代码。

getPermissions

接下来在构建Class所属的ProtectionDomain时,也有安全检查,URLClassLoader#getPermissions

        // make sure the person that created this class loader
        // would have this permission
        if (p != null) {
            final SecurityManager sm = System.getSecurityManager();
            if (sm != null) {
                final Permission fp = p;
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() throws SecurityException {
                        sm.checkPermission(fp);
                        return null;
                    }
                }, acc);
            }
            perms.add(p);
        }

需要确保这个即将grant给该ProtectionDomain的权限,类加载器自己是有的。

preDefineClass

接下来是真正defineClass之前最后的检查了,ClassLoader#preDefineClass

    /* Determine protection domain, and check that:
        - not define java.* class,
        - signer of this class matches signers for the rest of the classes in
          package.
    */
    private ProtectionDomain preDefineClass(String name,
                                            ProtectionDomain pd)
    {
        if (!checkName(name))
            throw new NoClassDefFoundError("IllegalName: " + name);

        if ((name != null) && name.startsWith("java.")) {
            throw new SecurityException
                ("Prohibited package name: " +
                 name.substring(0, name.lastIndexOf(‘.‘)));
        }
        if (pd == null) {
            pd = defaultDomain;
        }

        if (name != null) checkCerts(name, pd.getCodeSource());

        return pd;
    }

注释写得很清楚,有两个校验,确保不是在加载java.*包下的类,确保相同package的签名信息是一致的(类似上面的package访问权限的校验)。

getProtectionDomain

alright,到这里安全检查的话题就结束了。但是上面我们还留了一个坑,就是ProtectionDomain在类加载的时候是怎么整出来的。

我们来看看几个类加载器defineClass方法签名,

/////// java.net.URLClassLoader
defineClass(String name, Resource res)

/////// java.security.SecureClassLoader
defineClass(String name, ByteBuffer b, CodeSource cs)

/////// java.lang.ClassLoader
defineClass(String name, ByteBuffer b, ProtectionDomain pd)

其实就是将Resource转化成了ByteBufferCodeSource,然后再将CodeSource转化成了ProtectionDomain。来看下具体的SecureClassLoader#getProtectionDomain

    private ProtectionDomain getProtectionDomain(CodeSource cs) {
        if (cs == null)
            return null;

        ProtectionDomain pd = null;
        synchronized (pdcache) {
            pd = pdcache.get(cs);
            if (pd == null) {
                PermissionCollection perms = getPermissions(cs);
                pd = new ProtectionDomain(cs, perms, this, null);
                pdcache.put(cs, pd);
                if (debug != null) {
                    debug.println(" getPermissions "+ pd);
                    debug.println("");
                }
            }
        }
        return pd;
    }

getPermissions方法由子类自己来定义,也就是要定义这个Class所属的ProtectionDomain所拥有的权限了。具体可以再去看看Launcher.AppClassLoaderURLClassLoader各自的实现,这样也能更好的理解上面我们栗子中所输出的ProtectionDomain的值。

参考资料

时间: 2024-10-19 23:31:40

OpenJDK类加载实现浅析#2:安全检查的相关文章

OpenJDK类加载实现浅析#3:并行加载

今天来看下OpenJDK类加载中关于并行加载的一些代码.还是一样要分别看看类加载库跟虚拟机,因为二者在这方面仍然是需要配合完成的. 类加载库所做的工作 在JDK7之前,ClassLoader#loadClass方法是synchronized的, protected synchronized Class<?> loadClass(String name, boolean resolve) 也就是说,类加载的时候,直接是要锁住整个classloader的. 到了JDK7,这个地方终于做出了优化,直

Java 命令行编译项目

(初学者的一些总结-高手们勿喷哈-) 原因: 以前一直用Eclispe编程环境运行Java.非常舒服,就像用傻瓜相机照相一般. 有看见许多高手都是直接用vim编辑文件,命令行编译运行,觉得那样不是反而更繁琐? 转折点是在前几天本科毕设题目选定之后.毕设题是一个基于java 字节码的类关系动态分析.需要对.class文件中字节码进行更改(具体的说是在许多指令后加入做标记的新指令,以实现动态跟踪的目的). 我发现,eclipse根本无法如此灵活,他无法直接装载运行一个我修改过的.class文件.它是

groovy脚本导致的FullGC问题

这个是由一个线上问题导致的: 背景: 应用中内嵌了groovy引擎,会动态执行传入的表达式并返回执行结果 线上问题: 发现机器的fullGC从某个时候开始暴涨,并且一直持续: 登到机器上,用jstat -gcutil 命令观察,发现perm区一直是100%,fullGC无法回收: 将这台机器的内存dump出来进行分析: 在类视图中,发现大量的groovy.lang.GroovyClassLoader$InnerLoader; 在类加载器视图里面也看到大量的groovy的InnerLoader:

浅析java类加载器ClassLoader

作为一枚java猿,了解类加载器是有必要的,无论是针对面试还是自我学习. 本文从JDK提供的ClassLoader.委托模型以及如何编写自定义的ClassLoader三方面对ClassLoader做一个简要的总结. JDK中提供的ClassLoader 1. Bootstrap ClassLoader Bootstrap加载器是用C++语言写的,它是在Java虚拟机启动后初始化的,它主要负责加载%JAVA_HOME%/jre/lib以及%JAVA_HOME%/jre/classes中的类,是最顶

[Java类加载器]Java中classLoader浅析.

本文为在公司内部TD上写的一篇小文, 主要讲解java中classLoader基础知识, 现在拿来这里分享一下. 一.问题 请在Eclipse中新建如下类,并运行它: 1 package java.lang; 2 3 public class Long { 4 public static void main(String[] args) { 5 System.out.println("Hi, i am here"); 6 } 7 } 你能猜到它的运行如果吗? 不错,就是如下这个样子!

浅析类加载

前言:总结一下类加载的过程,于静态代码块.静态变量.事例变量等一些构建方式.因为这部分挺绕的,容易出错. 1.在java中可以将对象分为两大体系:字节码对象和实例对象 1.1.字节码对象: 每个类在加载(将类读到内存)时都会创建一个字节码对象,且这个对象在一个JVM内存中是唯一的.此对象中存储的是类的结构信息.所以可以说字节码对象是获取类结构信息的入口. 每个类对应的类字节码文件在jvm中都是唯一的[验证] 1 /** 2 * 测试类的字节码文件是否是唯一的 3 */ 4 private sta

健康,home? [java的内存浅析]

健康,home? [java的内存浅析] 摘要: 原创出处: http://www.cnblogs.com/Alandre/ 泥沙砖瓦浆木匠 希望转载,保留摘要,谢谢! 乐观上上,how can other kno u,u r yourself!I must be strong and carry on. -泥沙砖瓦浆木匠 一.闲谈下 201407月记着那时候身体垮了下来,呵呵.想说,对自己的说,也是对大家的负责吧.那时候胸疼胸闷,然后几乎累垮了,我还坚持了一星期,那一星期真的迷迷糊糊.完全不能

Java内存分配全面浅析

进入正题前首先要知道的是Java程序运行在JVM(Java  Virtual Machine,Java虚拟机)上,可以把JVM理解成Java程序和操作系统之间的桥梁,JVM实现了Java的平台无关性,由此可见JVM的重要性.所以在学习Java内存分配原理的时候一定要牢记这一切都是在JVM中进行的,JVM是内存分配原理的基础与前提.          简单通俗的讲,一个完整的Java程序运行过程会涉及以下内存区域: l  寄存器:JVM内部虚拟寄存器,存取速度非常快,程序不可控制. l  栈:保存

Java 内存分配全面浅析

本文将由浅入深详细介绍Java内存分配的原理,以帮助新手更轻松的学习Java.这类文章网上有很多,但大多比较零碎.本文从认知过程角度出发,将带给读者一个系统的介绍. 进入正题前首先要知道的是Java程序运行在JVM(Java Virtual Machine,Java虚拟机)上,可以把JVM理解成Java程序和操作系统之间的桥梁,JVM实现了Java的平台无关性,由此可见JVM的重要性.所以在学习Java内存分配原理的时候一定要牢记这一切都是在JVM中进行的,JVM是内存分配原理的基础与前提. 简