Skip to content

Commit

Permalink
Merge pull request #202 from gudzpoz/reflection
Browse files Browse the repository at this point in the history
- fix: try to load class from multiple class-loaders
- perf: cache reflection results
  • Loading branch information
gudzpoz authored Aug 28, 2024
2 parents 1048aac + 6e0dd11 commit 0e4deae
Show file tree
Hide file tree
Showing 12 changed files with 523 additions and 87 deletions.
26 changes: 26 additions & 0 deletions docs/troubleshooting.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,31 @@
# Troubleshooting

## Class (or Resource) Not Found

### Is a wrong classloader used?

By default, LuaJava tries the following classloaders for class loading,
and chooses the first non-null one:

1. `Thread.currentThread().getContextClassLoader()`
2. `party.iroiro.luajava.util.ClassUtils.class.getClassLoader()`
3. `ClassLoader.getSystemClassLoader()`

This might not be optimal if your class loading environment is not set up in this hierarchical way.
You may override `party.iroiro.luajava.util.ClassUtils#DEFAULT_CLASS_LOADER` to use a different class loader.
(It is not documented in the Javadoc, since it is quite internal and subject to changes.)

### Are you (mistakenly) using Java 9 modules?

If you package a fat JAR with, for example, [shadow](https://github.com/GradleUp/shadow),
please note that older versions of the plugins may not prune the `module-info.class` from some of your dependencies,
potentially making the whole JAR a large module.

It should be fine if the fat JAR is the only external JAR you load into the JVM.
But, if you plan to use this JAR as part of another application (e.g., as a plugin),
this can cause problems because the module system can restrict reflective access.
Try moving all `**/*/module-info.class` from your fat JAR.

## JVM Crashed

The crash is often followed by the following error message:
Expand Down
16 changes: 16 additions & 0 deletions example/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ plugins {
id 'application'
id 'jacoco'
id 'com.github.johnrengelman.shadow' version '7.1.2'
id 'me.champeau.jmh' version '0.7.2'
}

repositories {
Expand All @@ -24,6 +25,12 @@ java {
}

dependencies {
jmh project(':luajava')
jmh project(':luaj')
jmh project(':lua54')
jmh project(':luajit')
jmh project(path: ':lua54', configuration: 'desktopNatives')
jmh project(path: ':luajit', configuration: 'desktopNatives')
implementation project(':luajava')
implementation project(':lua51')
implementation project(':lua52')
Expand All @@ -50,6 +57,15 @@ dependencies {
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$jUnitVersion"
}

jmh {
benchmarkMode = ['avgt']
fork = 1
iterations = 3
profilers = ['perfasm']
warmupIterations = 2
timeUnit = 'us'
}

test {
useJUnitPlatform()

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package party.iroiro.luajava.jmh;

import org.openjdk.jmh.annotations.*;
import party.iroiro.luajava.Lua;
import party.iroiro.luajava.lua54.Lua54;
import party.iroiro.luajava.luaj.LuaJ;
import party.iroiro.luajava.luajit.LuaJit;

import java.math.BigInteger;

@State(Scope.Benchmark)
public class MethodCallBenchmark {

@Param({"Lua 5.4", "LuaJIT", "LuaJ"})
public String lua;

private Lua L;

@Setup
public void setup() {
switch (lua) {
case "Lua 5.4":
L = new Lua54();
break;
case "LuaJIT":
L = new LuaJit();
break;
case "LuaJ":
L = new LuaJ();
break;
default:
throw new IllegalStateException();
}
L.set("big_int", BigInteger.valueOf(1024));
L.run("int_value = java.method(big_int, 'intValue', '')");
}

@Benchmark
public void benchmarkObjectMethodCall() {
L.run("assert(big_int:intValue() == 1024)");
}

@Benchmark
public void benchmarkModuleMethodCall() {
L.run("assert(int_value() == 1024)");
}

@TearDown
public void tearDown() {
L.close();
}
}
53 changes: 53 additions & 0 deletions example/src/jmh/java/party/iroiro/luajava/jmh/SimpleBenchmark.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package party.iroiro.luajava.jmh;

import org.openjdk.jmh.annotations.*;
import party.iroiro.luajava.ClassPathLoader;
import party.iroiro.luajava.Lua;
import party.iroiro.luajava.lua54.Lua54;
import party.iroiro.luajava.luaj.LuaJ;
import party.iroiro.luajava.luajit.LuaJit;

@State(Scope.Benchmark)
public class SimpleBenchmark {

private void setupLua(Lua L) {
L.openLibraries();
L.run("io.write = function(s) assert(string.find(s, 'tree', 1, true)) end");
L.setExternalLoader(new ClassPathLoader());
L.loadExternal("binary-trees");
L.setGlobal("benchmark");
}

@Param({"Lua 5.4", "LuaJIT", "LuaJ"})
public String lua;

private Lua L;

@Setup
public void setup() {
switch (lua) {
case "Lua 5.4":
L = new Lua54();
break;
case "LuaJIT":
L = new LuaJit();
break;
case "LuaJ":
L = new LuaJ();
break;
default:
throw new IllegalStateException();
}
setupLua(L);
}

@Benchmark
public void benchmarkBinaryTrees() {
L.run("benchmark()");
}

@TearDown
public void tearDown() {
L.close();
}
}
80 changes: 80 additions & 0 deletions example/src/jmh/resources/binary-trees.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
-- Copyright © 2004-2008 Brent Fulgham, 2005-2024 Isaac Gouy
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions are met:
--
-- 1. Redistributions of source code must retain the above copyright notice,
-- this list of conditions and the following disclaimer.
--
-- 2. Redistributions in binary form must reproduce the above copyright notice,
-- this list of conditions and the following disclaimer in the documentation
-- and/or other materials provided with the distribution.
--
-- 3. Neither the name "The Computer Language Benchmarks Game" nor the name "The
-- Benchmarks Game" nor the name "The Computer Language Shootout Benchmarks" nor
-- the names of its contributors may be used to endorse or promote products
-- derived from this software without specific prior written permission.
--

-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-- AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-- ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
-- LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-- CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-- CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-- POSSIBILITY OF SUCH DAMAGE.

-- The Computer Language Benchmarks Game
-- https://salsa.debian.org/benchmarksgame-team/benchmarksgame/
-- contributed by Mike Pall
-- *reset*

local function BottomUpTree(depth)
if depth > 0 then
depth = depth - 1
local left, right = BottomUpTree(depth), BottomUpTree(depth)
return { left, right }
else
return { }
end
end

local function ItemCheck(tree)
if tree[1] then
return 1 + ItemCheck(tree[1]) + ItemCheck(tree[2])
else
return 1
end
end

local N = tonumber(arg and arg[1]) or 0
local mindepth = 4
local maxdepth = mindepth + 2
if maxdepth < N then maxdepth = N end

do
local stretchdepth = maxdepth + 1
local stretchtree = BottomUpTree(stretchdepth)
io.write(string.format("stretch tree of depth %d\t check: %d\n",
stretchdepth, ItemCheck(stretchtree)))
end

local longlivedtree = BottomUpTree(maxdepth)

for depth=mindepth,maxdepth,2 do
local iterations = 2 ^ (maxdepth - depth + mindepth)
local check = 0
for i=1,iterations do
check = check + ItemCheck(BottomUpTree(depth))
end
io.write(string.format("%d\t trees of depth %d\t check: %d\n",
iterations, depth, check))
end

io.write(string.format("long lived tree of depth %d\t check: %d\n",
maxdepth, ItemCheck(longlivedtree)))
13 changes: 13 additions & 0 deletions example/suite/src/main/resources/suite/apiTest.lua
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,16 @@ assert(currentThread ~= nil) -- Injected by the runner
assertThrows('unable to detach a main state', java.detach, currentThread)
subThread = coroutine.create(function() end)
java.detach(subThread)

--[[
java.method
]]--
-- The following ensures coverage of method caching
BigInteger = java.import('java.math.BigInteger')
Constructor = java.method(BigInteger, 'new', 'java.lang.String')
integer1 = Constructor('100')
integer2 = Constructor('100')
assert(integer1:equals(integer2))
added = java.method(integer1, 'add', 'java.math.BigInteger')(integer2)
added = java.method(added, 'add', 'java.math.BigInteger')(added)
assert(integer:intValue() == 400)
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public final class LuaScriptEngine extends AbstractScriptEngine implements Scrip

private Lua getLua() throws ScriptException {
try {
Lua L = (Lua) ClassUtils.forName(luaClass, null).newInstance();
Lua L = (Lua) ClassUtils.forName(luaClass).newInstance();
L.setExternalLoader(new ClassPathLoader());
L.openLibraries();
return L;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ private static String[] findAvailableEngine() {
}
for (String[] engine : ENGINES) {
try {
ClassUtils.forName(engine[2], null);
ClassUtils.forName(engine[2]);
return engine;
} catch (ClassNotFoundException ignored) {
}
Expand Down
35 changes: 34 additions & 1 deletion luajava/src/main/java/party/iroiro/luajava/ClassPathLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import party.iroiro.luajava.util.ClassUtils;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
Expand All @@ -32,12 +33,37 @@
import java.nio.ByteBuffer;
import java.util.Objects;

/**
* An {@link ExternalLoader} that loads modules from classpath
*
* <p>
* The path to the resource is converted from module name with
* {@link #getPath(String)}. For example, loading a {@code abc.def} module
* will load a Lua file at {@code classpath://abc/def.lua}.
* </p>
*/
public class ClassPathLoader implements ExternalLoader {
protected final ClassLoader classLoader;

/**
* Use {@link ClassUtils#getDefaultClassLoader()} for resource loading
*/
public ClassPathLoader() {
this(ClassUtils.getDefaultClassLoader());
}

/**
* @param classLoader the classloader for resource loading
*/
public ClassPathLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}

@Override
public @Nullable Buffer load(String module, Lua ignored) {
try (InputStream resource = Objects.requireNonNull(
// We use the class loader to load resources support loading from other Java modules.
getClass().getClassLoader().getResourceAsStream(getPath(module))
classLoader.getResourceAsStream(getPath(module))
)) {
ByteArrayOutputStream output = new ByteArrayOutputStream();
byte[] bytes = new byte[4096];
Expand All @@ -57,10 +83,17 @@ public class ClassPathLoader implements ExternalLoader {
}
}

/**
* @param module dot separated module path
* @return module path with {@code .} replaced by {@code /}, appended with {@code .lua}
*/
protected String getPath(String module) {
return module.replace('.', '/') + ".lua";
}

/**
* An output stream used to convert a {@link ByteArrayOutputStream} to a {@link ByteBuffer}
*/
public static class BufferOutputStream extends OutputStream {
private final ByteBuffer buffer;

Expand Down
Loading

0 comments on commit 0e4deae

Please sign in to comment.